Show the code
aragon <- import(here("data", "aragon.csv")) Seasonal mortality is associated with varying temperatures. To explore this relationship from a time series perspective, mortality and temperature data from Aragón, an Autonomous Community in Spain, will be employed. For this exercise, you are tasked with understanding how temperatures affect mortality, and asses whether this effect is the same across the entire year or seasonal effects can be isolated.
By the end of this session, participants should be able to:
Assess and interpret associations with external variables
Identify and interpret effect modification between external variables and the outcome variable in surveillance data
Open the dataset aragon.csv. There you will find mean maximum temperature and mortality data for the autonomous community of Aragon by week.
aragon <- import(here("data", "aragon.csv")) Convert the data to a time series object (aragon_ts) and plot both temperature and mortality variables using ggplot and patchwork to join both plots together
# Create ts dataframe with yearweek index
aragon_ts <- aragon %>%
mutate(
year_week = make_yearweek(year = year, week = week),
index = seq.int(from = 1, to = nrow(aragon))
) %>%
as_tsibble(index = index)
# Temperature plot
aragon_ts_tmax_plot <- ggplot(data = aragon_ts) +
geom_point(mapping = aes(x = year_week, y = tmax), colour = "blue", alpha = 0.5) +
scale_y_continuous(limits = c(0, NA)) +
scale_x_yearweek(date_labels = "%Y-%W",
date_breaks = "1 year") +
labs(x = "Week",
y = "Maximum Temperature") +
tsa_theme
# Mortality plot
aragon_ts_cases_plot <- ggplot(data = aragon_ts) +
geom_point(mapping = aes(x = year_week, y = cases), colour = "green", alpha = 0.5) +
scale_y_continuous(limits = c(0, NA)) +
scale_x_yearweek(date_labels = "%Y-%W",
date_breaks = "1 year") +
labs(x = "Week",
y = "Weekly case counts") +
tsa_theme
# Using patchwork to join the two plots
# The "/" determines an above/below display
aragon_ts_two_plot <- (aragon_ts_tmax_plot / aragon_ts_cases_plot)
aragon_ts_two_plotGenerate variables for sine and cosine for annual oscillation.
aragon_ts <- aragon_ts %>%
mutate(sin52 = sin(2 * pi * date / 52),
cos52 = cos(2 * pi * date / 52))Fit a poisson regression model with a simple trend to the weekly number of deaths, accounting for seasonality.
aragmodel1 <- glm(cases ~ index + sin52 + cos52,
family = "poisson",
data = aragon_ts)Plot the predicted weekly number of deaths.
ggplot(data = aragon_ts) +
geom_point(mapping = aes(x = year_week, y = cases), alpha = 0.2) +
geom_line(mapping = aes(x = year_week, y = aragmodel1$fitted.values),
colour = "darkorange",
alpha = 0.7,
lwd = 1.5) +
scale_y_continuous(limits = c(0, NA)) +
scale_x_yearweek(date_labels = "%Y-%W",
date_breaks = "1 year") +
labs(x = "Week",
y = "Weekly case counts",
title = "Cases with fitted trend") +
tsa_themeDiscuss how your base model fits the original data.
When you plotted the weekly number of deaths in Aragon, perhaps you noticed that mortality peaks in winter; however, there are smaller peaks during the summer, too. You can account for that in two different ways:
Include two sine/cosine functions in the model (one for 52-week cycles and one for 26-week cycles)
Explore temperature as an explanatory variable for these two peaks. We can also take into account the fact that temperature might be behaving differently as a risk factor in winter than it does in summer (sign of effect modification?)
First, you need to create a new winter variable, defined as the time between weeks 49 and 8:
aragon_ts <- aragon_ts %>%
mutate(winter = (week >= 49 | week <= 8))Now you will run two models: one including temperature as a main effect (aragmodel2), and other including an interaction term with winter (aragmodel3). Compare them and decide which is better
# Model with only main effect
aragmodel2 <- glm(cases ~ index + sin52 + cos52 + tmax,
family = "poisson",
data = aragon_ts)
# Model with temperature interaction
aragmodel3 <- glm(cases ~ index + sin52 + cos52 + winter * tmax,
family = "poisson",
data = aragon_ts)We compare both model’s AIC below.
# Easiest, most direct way
AIC(aragmodel2); AIC(aragmodel3)We can also create a fancier table using the library gtsummary like in practical 4. We use again exponentiate = T to exponentiate coefficients and style_number() to specify the number of digits. In addition, we use one extra argument: add_glance_table() allows to add key parameters at the bottom of the table. And table_merge() allows to take several tables built with tbl_regression and put them in one big table. This makes it easy to compare models.
tbl_m2 <- aragmodel2 %>%
tbl_regression(exponentiate = TRUE,
estimate_fun = ~style_number(.x, digits = 4)) %>%
add_glance_table(include = c(AIC, BIC, deviance))
tbl_m3 <- aragmodel3 %>%
tbl_regression(exponentiate = TRUE,
estimate_fun = ~style_number(.x, digits = 4)) %>%
add_glance_table(include = c(AIC, BIC, deviance))
tbl_m2m3 <-
tbl_merge(
tbls = list(tbl_m2, tbl_m3),
tab_spanner = c("aragmodel2", "aragmodel3")
)
tbl_m2m3| Characteristic |
aragmodel2
|
aragmodel3
|
||||
|---|---|---|---|---|---|---|
| IRR | 95% CI | p-value | IRR | 95% CI | p-value | |
| index | 1.0001 | 1.0001, 1.0002 | <0.001 | 1.0001 | 1.0001, 1.0002 | <0.001 |
| sin52 | 1.0539 | 1.0435, 1.0644 | <0.001 | 1.0621 | 1.0508, 1.0735 | <0.001 |
| cos52 | 1.1521 | 1.1245, 1.1804 | <0.001 | 1.1721 | 1.1385, 1.2066 | <0.001 |
| tmax | 1.0035 | 1.0014, 1.0056 | 0.001 | 1.0078 | 1.0054, 1.0103 | <0.001 |
| AIC | 4,748 | 4,666 | ||||
| BIC | 4,769 | 4,696 | ||||
| Deviance | 1,036 | 950 | ||||
| winter | ||||||
| FALSE | — | — | ||||
| TRUE | 1.2688 | 1.2020, 1.3392 | <0.001 | |||
| winter * tmax | ||||||
| TRUE * tmax | 0.9849 | 0.9806, 0.9893 | <0.001 | |||
| Abbreviations: CI = Confidence Interval, IRR = Incidence Rate Ratio | ||||||
Note that winter * tmax tells R to include terms in the model for winter, tmax and for their interaction. If we just wanted to include the interaction term without its corresponding “main effects”, i.e. without winter or tmax, we would use winter:tmax instead.
Discuss the output of the two models. Does the interpretation change when winter is taken into account as an interaction term along with temperature?
It has been argued by experts that low temperatures in winter might be “slow killers”; that is, very low temperatures in winter do not result in a peak in mortality on the same day/week as they are observed, but rather after some time has elapsed. On the other hand, very high temperatures in summer are “fast killers”; they are associated with peaks in mortality very fast, i.e. heat waves kill people fast. Can you find evidence for this in Aragón?
stats::lag is the default base R function to compute lags from the stats package. In the example below, we prefer to use one of the tidyverse package alternatives,namely dplyr::lag. We specify the package name here to avoid possible conflicts with other packages which have a lag function.
For a given week, the value of tmax is lagged by 1 in respect to the previous week’s value.
# Create lag variable
aragon_ts <- aragon_ts %>%
mutate(L1.tmax = dplyr::lag(x = tmax, n = 1))Create a new model named aragmodel4 including the lag variable, and compare it with the previous model aragmodel3. What can you conclude from it?
aragmodel4 <- glm(cases ~ index + sin52 + cos52 + winter * tmax + L1.tmax,
family = "poisson",
data = aragon_ts)
tbl_m4 <- aragmodel4 %>%
tbl_regression(exponentiate = TRUE,
estimate_fun = ~style_number(.x, digits = 4)) %>%
add_glance_table(include = c(AIC, BIC, deviance))
tbl_m3m4 <-
tbl_merge(
tbls = list(tbl_m3, tbl_m4),
tab_spanner = c("aragmodel3", "aragmodel4")
)
tbl_m3m4| Characteristic |
aragmodel3
|
aragmodel4
|
||||
|---|---|---|---|---|---|---|
| IRR | 95% CI | p-value | IRR | 95% CI | p-value | |
| index | 1.0001 | 1.0001, 1.0002 | <0.001 | 1.0001 | 1.0001, 1.0002 | <0.001 |
| sin52 | 1.0621 | 1.0508, 1.0735 | <0.001 | 1.0584 | 1.0454, 1.0717 | <0.001 |
| cos52 | 1.1721 | 1.1385, 1.2066 | <0.001 | 1.1623 | 1.1250, 1.2008 | <0.001 |
| winter | ||||||
| FALSE | — | — | — | — | ||
| TRUE | 1.2688 | 1.2020, 1.3392 | <0.001 | 1.2546 | 1.1876, 1.3252 | <0.001 |
| tmax | 1.0078 | 1.0054, 1.0103 | <0.001 | 1.0082 | 1.0057, 1.0108 | <0.001 |
| winter * tmax | ||||||
| TRUE * tmax | 0.9849 | 0.9806, 0.9893 | <0.001 | 0.9858 | 0.9814, 0.9902 | <0.001 |
| AIC | 4,666 | 4,654 | ||||
| BIC | 4,696 | 4,688 | ||||
| Deviance | 950 | 944 | ||||
| L1.tmax | 0.9988 | 0.9965, 1.0010 | 0.3 | |||
| Abbreviations: CI = Confidence Interval, IRR = Incidence Rate Ratio | ||||||
The lagged effect of temperature was not signifcant in aragmodel4 but you are still curious about the differential effect of temperatures on mortality in winter and the other seasons. Instead of running the model with another interaction term, you could run the analyses for winter and the rest of your dataset separately.
First, create aragon_ts.winter and aragon_ts.notwinter
## winter subset
aragon_ts.winter <-
aragon_ts %>%
filter(winter == TRUE)
## not winter subset
aragon_ts.notwinter <-
aragon_ts %>%
filter(winter == FALSE)Now, run two new models aragmodel5 for the winter data and aragmodel6 for non-winter data. Think carefully about how you will compare them: is the AIC/BIC criteria still valid for this task?
# Winter model
aragmodel5 <- glm(cases ~ index + sin52 + cos52 + L1.tmax,
family = "poisson",
data = aragon_ts.winter)
# Not winter model
aragmodel6 <- glm(cases ~ index + sin52 + cos52 + L1.tmax,
family = "poisson",
data = aragon_ts.notwinter)
# Table summaries
tbl_m5 <- aragmodel5 %>%
tbl_regression(exponentiate = TRUE,
estimate_fun = ~style_number(.x, digits = 4))
tbl_m6 <- aragmodel6 %>%
tbl_regression(exponentiate = TRUE,
estimate_fun = ~style_number(.x, digits = 4))
# Comparison table
tbl_m5m6 <-
tbl_merge(
tbls = list(tbl_m5, tbl_m6),
tab_spanner = c("Winter model", "Not winter model")
)
tbl_m5m6| Characteristic |
Winter model
|
Not winter model
|
||||
|---|---|---|---|---|---|---|
| IRR | 95% CI | p-value | IRR | 95% CI | p-value | |
| index | 1.0002 | 1.0001, 1.0003 | <0.001 | 1.0001 | 1.0001, 1.0002 | <0.001 |
| sin52 | 1.1944 | 1.1428, 1.2486 | <0.001 | 1.0485 | 1.0348, 1.0624 | <0.001 |
| cos52 | 1.7229 | 1.4361, 2.0675 | <0.001 | 1.1094 | 1.0785, 1.1412 | <0.001 |
| L1.tmax | 0.9923 | 0.9879, 0.9967 | <0.001 | 1.0031 | 1.0006, 1.0055 | 0.015 |
| Abbreviations: CI = Confidence Interval, IRR = Incidence Rate Ratio | ||||||
Discuss the output of the different models. Which one would you go for?