Skip to content

Commit 16a49d6

Browse files
adds to chapter 8
1 parent e441413 commit 16a49d6

1 file changed

Lines changed: 246 additions & 2 deletions

File tree

ch8_conditional.qmd

Lines changed: 246 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ library(GGally)
2222
library(dagitty)
2323
library(ggdag)
2424
library(ggrepel)
25+
library(ggthemes)
2526
2627
library(rethinking)
28+
data(rugged)
29+
2730
detach(package:rethinking, unload = T)
28-
data(milk)
31+
2932
at <- c(-3, -2, -1, 0, 1, 2, 3)
3033
3134
@@ -43,4 +46,245 @@ Simple linear models frequently fail to provide enough conditioning, however. Ev
4346

4447
To model deeper conditionality—where the importance of one predictor depends upon another predictor—we need interaction (also known as moderation). Interaction is a kind of conditioning, a way of allowing parameters (really their posterior distributions) to be conditional on further aspects of the data.
4548

46-
More generally, interactions are central to most statistical models beyond the cozy world of Gaussian outcomes and linear models of the mean. In generalized linear models (GLMs), even when one does not explicitly define variables as interacting, they will always interact to some degree. Multilevel models induce similar effects. Common sorts of multilevel models are essentially massive interaction models, in which estimates (intercepts and slopes) are conditional on clusters (person, genus, village, city, galaxy) in the data. Multilevel interaction effects are complex. They’re not just allowing the impact of a predictor variable to change depending upon some other variable, but they are also estimat- ing aspects of the distribution of those changes. This may sound like genius, or madness, or both. Regardless, you can’t have the power of multilevel modeling without it.
49+
More generally, interactions are central to most statistical models beyond the cozy world of Gaussian outcomes and linear models of the mean. In generalized linear models (GLMs), even when one does not explicitly define variables as interacting, they will always interact to some degree. Multilevel models induce similar effects. Common sorts of multilevel models are essentially massive interaction models, in which estimates (intercepts and slopes) are conditional on clusters (person, genus, village, city, galaxy) in the data. Multilevel interaction effects are complex. They’re not just allowing the impact of a predictor variable to change depending upon some other variable, but they are also estimat- ing aspects of the distribution of those changes. This may sound like genius, or madness, or both. Regardless, you can’t have the power of multilevel modeling without it.
50+
51+
52+
## Continuous with Discrete Interactions
53+
54+
55+
### Ruggedness vs GDP in Africa 🌍
56+
57+
In this vignette we look at the Log gdp per capita of different countries. The more rugged the terrain the lower the gdp is on average, which fits a nice causal story of the harder it is to cross the land the less trade and wealth is created. This story is reversed though when we look at countries in Africa, as seen below in the figure.
58+
59+
```{r}
60+
rugged_clean <-
61+
rugged %>%
62+
mutate(log_gdp = log(rgdppc_2000)) %>%
63+
filter(complete.cases(rgdppc_2000)) %>% # countries with GDP data
64+
# re-scale variables
65+
mutate(log_gdp_std = log_gdp / mean(log_gdp),
66+
rugged_std = rugged / max(rugged),
67+
rugged_std_centered = rugged_std - mean(rugged_std))
68+
```
69+
70+
71+
```{r}
72+
p1 <- rugged_clean %>%
73+
filter(cont_africa == 1) %>%
74+
ggplot(aes(x = rugged_std, y = log_gdp_std)) +
75+
geom_smooth(method = "lm", formula = y ~ x,
76+
fill = palette_pander(n = 2)[1],
77+
color = palette_pander(n = 2)[1]) +
78+
geom_point(color = palette_pander(n = 2)[1]) +
79+
geom_text_repel(data = . %>%
80+
filter(country %in% c("Lesotho", "Seychelles")),
81+
aes(label = country),
82+
size = 3, family = "Times", seed = 8) +
83+
labs(subtitle = "African nations",
84+
x = "ruggedness (standardized)",
85+
y = "log GDP (as proportion of mean)")+
86+
theme_minimal()
87+
88+
p2 <-
89+
rugged_clean %>%
90+
filter(cont_africa == 0) %>%
91+
ggplot(aes(x = rugged_std, y = log_gdp_std)) +
92+
geom_smooth(method = "lm", formula = y ~ x,
93+
fill = palette_pander(n = 2)[2],
94+
color = palette_pander(n = 2)[2]) +
95+
geom_point(color = palette_pander(n = 2)[2]) +
96+
geom_text_repel(data = . %>%
97+
filter(country %in% c("Switzerland", "Tajikistan")),
98+
aes(label = country),
99+
size = 3, family = "Times", seed = 8) +
100+
xlim(0, 1) +
101+
labs(subtitle = "Non-African nations",
102+
x = "ruggedness (standardized)",
103+
y = "log GDP (as proportion of mean)")+
104+
theme_minimal()
105+
106+
# combine
107+
p1 + p2 + plot_annotation(title = "Figure 8.2. Separate linear regressions inside and outside of Africa")
108+
```
109+
110+
What's to make of the story? Maybe historical slavery has a lasting effect on the economies of african countries, the flatter more accessible regions of africa had slaving?
111+
112+
```{r}
113+
#| fig-width: 4
114+
#| fig-height: 3
115+
dag_coords <- tibble(name = c("R", "G", "U", "C"),
116+
x = c(1, 2, 2, 2.5),
117+
y = c(2, 2, 1, 2))
118+
119+
m6 <- dagify("G" ~ "R" + "U" + "C",
120+
"R" ~ "U",
121+
coords = dag_coords) %>%
122+
ggplot(aes(x = x, y = y, xend = xend, yend = yend))+
123+
geom_dag_point(color = "firebrick", alpha = 1/4, size = 10)+
124+
geom_dag_point(data = . %>% filter(name == "U"),
125+
color = "firebrick4", fill = NA, size = 10, shape = 21,
126+
stroke = 2, linetype = "dashed")+
127+
geom_dag_text( color = "firebrick", parse = TRUE) +
128+
geom_dag_edges(edge_color = "firebrick") +
129+
scale_x_continuous(NULL, breaks = NULL, expand = c(0.1, 0.1)) +
130+
scale_y_continuous(NULL, breaks = NULL, expand = c(0.2, 0.2)) +
131+
theme_bw() +
132+
theme(panel.grid = element_blank())+
133+
labs(caption = "G = GDP, R = Ruggedness, C = Continent, U = Unobserved Effect")
134+
135+
m6
136+
```
137+
138+
In the graph above we formalize this idea by saying that ruggedness $R$ influences the current GDP $G$. Both $R$ & $G$ are influenced by some set of unknown confounders $U$ like proximity to coastline, which we'll ignore for the moment. Finally $C$ the continent effects $G$ as well, crucially $R$ and $C$ could be independent or interact on their influence on $G$. The DAG does not display an interaction, instead we declare outside of the graph like this: $G = f(R, C)$.
139+
140+
How do we estimate that function $f$, we could split up the data and make separate models 1 for africa and 1 for all the other continents countries. But this lead to a poor estimate of other parameters like $\sigma$. Additionally if we wanted to compare models with an information criteria we'd need to use the same data, so splitting the data also hurts that process.
141+
142+
Our first model is:
143+
144+
$$ log(y_i) \ sim Normal(\mu_i, \sigma)$$
145+
146+
$$ \mu_i = \alpha + \beta(\text{rugged}_i - \overline{rugged})$$
147+
```{r}
148+
b8.0 <- brm(data = rugged_clean,
149+
family = gaussian(),
150+
log_gdp_std ~ 1 + rugged_std_centered,
151+
prior = c(
152+
prior(normal(1, 0.1), class = Intercept),
153+
prior(normal(0, 0.3), class = b),
154+
prior(exponential(1), class = sigma)
155+
),
156+
iter = 2000, warmup = 500, cores = 4, seed = 5,
157+
backend = "cmdstanr", silent = 2, file = "fits/b08.0.1")
158+
```
159+
160+
::: panel-tabset
161+
162+
##### 📈 μ heatmap
163+
164+
```{r}
165+
166+
min_rugged = min(rugged_clean$rugged_std_centered)
167+
max_rugged = max(rugged_clean$rugged_std_centered)
168+
169+
simModel <- as_tibble(b8.0) %>%
170+
mutate(simRuggedness = seq(from = min_rugged, to = max_rugged, length.out = n()),
171+
simGDPEst= Intercept + (b_rugged_std_centered * simRuggedness),
172+
simGDP = rnorm(n(), simGDPEst, sd = sigma))
173+
174+
# ymin <- min(c(simModel$simGDP, simModel$simGDPEst, rugged_clean$log_gdp_std))
175+
# ymax <- max(c(simModel$simGDP, simModel$simGDPEst, rugged_clean$log_gdp_std))
176+
177+
at <- seq(from = min_rugged, to = max_rugged, length.out = 5)
178+
179+
modelEst_plot <- ggplot() +
180+
stat_density_2d(data = simModel,
181+
aes(x = simRuggedness, y = simGDPEst, fill = after_stat(ndensity)),
182+
geom = "raster", contour = FALSE) +
183+
scale_fill_viridis_c(option = "magma") +
184+
geom_point(data = rugged_clean,
185+
aes(x = rugged_std_centered, y = log_gdp_std),
186+
shape = 21, color = "white", fill = "black", lwd = 3, alpha = .8)+
187+
labs(y = "Log GDP (1.0 = average country)",
188+
title = "Log GDP ~ Ruggedness", subtitle = "Mu estimate")+
189+
theme_minimal()+
190+
scale_x_continuous("Ruggedness (0 = minimum ruggedness, 100 = maximum ruggedness)",
191+
breaks = at,
192+
labels = round(at + mean(rugged_clean$rugged_std), 1) * 100) +
193+
guides(fill = "none")
194+
195+
modelEst_plot
196+
```
197+
##### 🎛 Parameters
198+
199+
```{r}
200+
#| fig-width: 10
201+
#| fig-height: 2
202+
203+
as_tibble(b8.0) %>%
204+
rename("Ruggedness" = b_rugged_std_centered) %>%
205+
dplyr::select(c(`Ruggedness`, `Intercept`)) %>%
206+
pivot_longer(cols = everything(),
207+
names_to = "Covariate",
208+
values_to = "Effect") %>%
209+
ggplot(aes(x = `Effect`, y = reorder(Covariate, `Effect`))) +
210+
stat_halfeye(point_interval = median_qi, .width = .95,
211+
fill = "firebrick4") +
212+
labs(x = "Effect on Log GDP per capita",
213+
y = NULL) +
214+
theme_bw() +
215+
theme(axis.text.y = element_text(hjust = 0),
216+
axis.ticks.y = element_blank(),
217+
panel.grid = element_blank())
218+
219+
```
220+
221+
222+
223+
:::
224+
225+
#### Indicator Variable Solution
226+
227+
The first thing to realize is that just including an indicator variable for African nations, won't reveal the reversed slope.
228+
229+
To build a model that alls nations inside nd outside Africa to have different intercepts, we need to modify the model for $\mu_i$ so that the mean is conditional on continent. The conventional way to do this would be to just add another term to the linear model:
230+
231+
$$ \mu_i = \alpha + \beta_!(\text{rugged}_i - \overline{rugged}) - \beta_2 \mathbf{I} (\text{Africa}_i)$$
232+
233+
Where $\text{Africa}_i$ is a 0/1 indicator variable for Africa or not Africa. But this model assumes that african countries have more uncertainty inherently built into their $mu$ estimate, which makes no sense.
234+
235+
Our simple solution is to create separate intercepts for the different categories, like so:
236+
237+
$$ \mu_i = \alpha_\text{Africa [i]} + \beta_!(\text{rugged}_i - \overline{rugged})$$
238+
Where $\text{Africa [i]}$ is an index variable which takes the value 1 for African nations and 2 for all other nations.
239+
240+
```{r}
241+
rugged_clean <- rugged_clean %>%
242+
mutate(african_status = ifelse(cont_africa == 1, "african", "not african"))
243+
244+
b8.1 <- brm(data = rugged_clean,
245+
family = gaussian(),
246+
log_gdp_std ~ 0 + african_status + rugged_std_centered,
247+
prior = c(
248+
prior(normal(0.9, 0.1), class = b, coef = "african_statusafrican"),
249+
prior(normal(1.1, 0.1), class = b, coef = "african_statusnotafrican"),
250+
prior(normal(0, 0.3), class = b, coef = "rugged_std_centered"),
251+
prior(exponential(1), class = sigma)
252+
),
253+
iter = 2000, warmup = 500, cores = 4, seed = 5,
254+
backend = "cmdstanr", silent = 2, file = "fits/b08.1.0")
255+
256+
# get_prior(data = rugged_clean,
257+
# family = gaussian(),
258+
# log_gdp_std ~ 0 + african_status + rugged_std_centered)
259+
```
260+
261+
262+
263+
264+
265+
266+
##### 🎛 Parameters
267+
268+
```{r}
269+
#| fig-width: 10
270+
#| fig-height: 2
271+
272+
as_tibble(b8.1) %>%
273+
rename("Ruggedness" = b_rugged_std_centered,
274+
"African" = b_african_statusafrican,
275+
"Non-African" = b_african_statusnotafrican) %>%
276+
dplyr::select(c(`Ruggedness`, `African`, `Non-African`)) %>%
277+
pivot_longer(cols = everything(),
278+
names_to = "Covariate",
279+
values_to = "Effect") %>%
280+
ggplot(aes(x = `Effect`, y = reorder(Covariate, `Effect`))) +
281+
stat_halfeye(point_interval = median_qi, .width = .95,
282+
fill = "firebrick4") +
283+
labs(x = "Effect on Log GDP per capita",
284+
y = NULL) +
285+
theme_bw() +
286+
theme(axis.text.y = element_text(hjust = 0),
287+
axis.ticks.y = element_blank(),
288+
panel.grid = element_blank())
289+
290+
```

0 commit comments

Comments
 (0)