Chapter 8 Conditional manatees
8.1 Building an interaction
\(R\) = ruggedness, \(G\) = GDP, \(C\) = continent, \(U\) = unobserved variables.
library(dagitty)
library(rethinking)
.1 <- dagitty("dag{
dag_8 R -> G
C -> G
U -> G
U -> R
}")
coordinates(dag_8.1) <- list(y = c(R = 0, G = 0, C = 0, U = 1),
x = c(R = 0, G = 1, U = 1, C = 2))
drawdag(dag_8.1)
\(G = f(R,C)\)
8.1.1 Making a rugged model
library(rethinking)
data(rugged)
<- rugged
d
#log transform GDP
$log_gdp <- log(d$rgdppc_2000)
d
#only include countries with GDP data
<- d[complete.cases(d$rgdppc_2000),]
dd
#rescale variables
$log_gdp_std <- (dd$log_gdp) / mean(dd$log_gdp) # values of 1 is average
dd$rugged_std <- (dd$rugged) / max(dd$rugged) # values range from 0 to max ruggedness (1) dd
Basic model \[\text{log}(y_{i}) \sim \text{Normal}(\mu_{i}, \sigma)\\ \mu_{i} = \alpha + \beta(r_{i} - \overline{r})\\ \alpha \sim \text{Normal}|(1,1)\\ \beta \sim \text{Normal}(0, 1)\\ \sigma \sim \text{Exponential}(1)\]
In R:
.1 <- quap(
m8alist(
~ dnorm(mu, sigma),
log_gdp_std <- a + b*(rugged_std - 0.215),
mu ~ dnorm(1, 1),
a ~ dnorm(0, 1),
b ~ dexp(1)
sigma data = dd
), )
Sample priors:
set.seed(11)
<- extract.prior(m8.1)
prior
#set plot
plot(NULL, xlim=c(0,1), ylim=c(0.5, 1.5), xlab = "ruggedness", ylab = "log GDP")
abline(h=min(dd$log_gdp_std), lty = 2)
abline(h=max(dd$log_gdp_std), lty = 2)
#draw lines from prior
<- seq(from = -0.1, to = 1.1, length.out=30)
rugged_seq <- link(m8.1, post = prior, data = data.frame(rugged_std=rugged_seq))
mu for(i in 1:50){
lines(rugged_seq, mu[i,], col=col.alpha('black',0.3))
}
\(\alpha\) is too wild. intercept should be somewhere around where the mean of ruggedness hits 1 on the log GDP scale so adjust to Normal(1, 0.1).
\(\beta\) is also out of control. we need something (positive or negative) that spans the difference between the dashed lines
Slope should be \(\pm 0.6\) which is the differece between the maximum and minimum values of GDP
max(dd$log_gdp_std) - min(dd$log_gdp_std)
## [1] 0.5658058
#proportion of slopes greater than 0.6
sum(abs(prior$b) > 0.6) / length(prior$b)
## [1] 0.54
Let’s fix the model
.1 <- quap(
m8alist(
~dnorm(mu,sigma),
log_gdp_std <-a+b*(rugged_std-0.215),
mu ~dnorm(1,0.1),
a ~dnorm(0,0.3),
b ~dexp(1)
sigma data = dd
), )
precis(m8.1)
## mean sd 5.5% 94.5%
## a 0.999998578 0.010412457 0.98335746 1.01663970
## b 0.001994904 0.054795958 -0.08557962 0.08956943
## sigma 0.136503830 0.007397023 0.12468196 0.14832570
No association seen yet
8.1.2 Adding an indicator isn’t enough
Update \(\mu\)
\[\mu_{i} = \alpha_{CID[i]} + \beta(r_{i} - \overline{r})\]
#make an index variable for Africa (1) and other continents (2)
$cid <- ifelse(dd$cont_africa == 1, 1, 2) dd
Now update the model
.2 <- quap(
m8alist(
~ dnorm(mu, sigma),
log_gdp_std <-a[cid] + b * (rugged_std - 0.215),
mu ~ dnorm(1, 0.1),
a[cid] ~ dnorm(0, 0.3),
b ~dexp(1)
sigma data = dd
), )
compare(m8.1, m8.2)
## WAIC SE dWAIC dSE pWAIC weight
## m8.2 -252.1508 15.25508 0.00000 NA 4.291725 1.000000e+00
## m8.1 -188.9664 13.28913 63.18439 15.11018 2.574284 1.904075e-14
precis(m8.2, depth = 2)
## mean sd 5.5% 94.5%
## a[1] 0.88040848 0.015938408 0.8549358 0.90588113
## a[2] 1.04916209 0.010186473 1.0328821 1.06544204
## b -0.04651791 0.045690786 -0.1195406 0.02650479
## sigma 0.11239762 0.006092464 0.1026607 0.12213455
<- extract.samples(m8.2)
post <- post$a[,1] - post$a[,2]
diff_a1_a2 PI(diff_a1_a2)
## 5% 94%
## -0.1988472 -0.1384092
<- seq(from = -0.1, to = 1.1, length.out = 30)
rugged.seq <- link(m8.2, data = data.frame(cid=2, rugged_std=rugged.seq))
mu.NotAfrica <- link(m8.2, data = data.frame(cid = 1, rugged_std = rugged.seq))
mu.Africa <- apply(mu.NotAfrica, 2, mean)
mu.NotAfrica_mu <- apply(mu.NotAfrica, 2, PI, prob = 0.97)
mu.NotAfrica_ci <- apply(mu.Africa, 2, mean)
mu.Africa_mu <- apply(mu.Africa, 2, PI)
mu.Africa_ci
plot(NULL, xlim=c(0,1), ylim=c(0.5, 1.5), xlab = "ruggedness", ylab = "log GDP")
points(dd$rugged_std, dd$log_gdp_std, col = dd$cid, pch = 16)
lines(rugged.seq, mu.Africa_mu, lwd = 2, col = 1)
shade(mu.Africa_ci, rugged.seq)
lines(rugged.seq, mu.NotAfrica_mu, lwd = 2, col = 2)
shade(mu.NotAfrica_ci, rugged.seq, col = col.alpha(2, 0.3))
8.1.3 Adding an interaction does work
\[\mu_{i} = \alpha_{CID[i]} + \beta_{CID[i]}(r_{i} - \overline{r})\]
.3 <- quap(
m8alist(
~ dnorm(mu, sigma),
log_gdp_std <-a[cid] + b[cid] * (rugged_std - 0.215),
mu ~ dnorm(1, 0.1),
a[cid] ~ dnorm(0, 0.3),
b[cid] ~dexp(1)
sigma data = dd
),
)
precis(m8.3, depth = 2)
## mean sd 5.5% 94.5%
## a[1] 0.8865632 0.015675727 0.86151037 0.91161605
## a[2] 1.0505709 0.009936627 1.03469025 1.06645155
## b[1] 0.1325019 0.074204597 0.01390861 0.25109517
## b[2] -0.1425818 0.054749512 -0.23008206 -0.05508147
## sigma 0.1094944 0.005935331 0.10000855 0.11898016
compare(m8.1, m8.2, m8.3, func=PSIS)
## Some Pareto k values are high (>0.5). Set pointwise=TRUE to inspect individual points.
## PSIS SE dPSIS dSE pPSIS weight
## m8.3 -258.8749 15.35435 0.000000 NA 5.314384 9.688047e-01
## m8.2 -252.0033 15.30167 6.871589 6.936621 4.354277 3.119533e-02
## m8.1 -188.4136 13.40307 70.461243 15.674795 2.856543 4.850334e-16
plot(PSIS(m8.3, pointwise = TRUE)$k)
## Some Pareto k values are high (>0.5). Set pointwise=TRUE to inspect individual points.
8.1.4 Plotting the interaction
par(mfrow=c(1,2))
# plot Africa - cid = 1
<-dd[dd$cid == 1,]
d.A1 plot(d.A1$rugged_std, d.A1$log_gdp_std, pch=16, col=rangi2,
xlab="ruggedness (standardized)",ylab="log GDP (as proportion of mean)",
xlim=c(0,1) )
<-link(m8.3,data=data.frame(cid=1,rugged_std=rugged_seq))
mu <-apply(mu,2,mean)
mu_mean <-apply(mu,2,PI,prob=0.97)
mu_ci lines( rugged_seq,mu_mean,lwd=2)
shade( mu_ci,rugged_seq,col=col.alpha(rangi2,0.3))
mtext("African nations")
# plotnon-Africa-cid=2
<-dd[dd$cid==2,]
d.A0 plot( d.A0$rugged_std,d.A0$log_gdp_std,pch=1,col="black",
xlab="ruggedness (standardized)",ylab="logGDP(asproportionofmean)",
xlim=c(0,1) )
<-link(m8.3,data=data.frame(cid=2,rugged_std=rugged_seq))
mu <-apply(mu,2,mean)
mu_mean <-apply(mu,2,PI,prob=0.97)
mu_ci lines( rugged_seq,mu_mean,lwd=2)
shade( mu_ci,rugged_seq)
mtext("Non-African nations")
8.2 Symmetry of interactions
You can break an interaction into 2 identical phrasings
1. GDP ~ ruggedness depending on Africa
2. Africa ~ GDP depending on rugedness
\[\mu_{i} = (2 - CID_{i})(\alpha_{1} + \beta_{1}(r_{i} - \overline{r})) + (CID_{i} - 1)(\alpha_{2} + \beta_{2}(r_{i} - \overline{r}))\]
<- seq(from = -0.2, to = 1.2, length.out = 30)
rugged_seq <- link(m8.3, data=data.frame(cid=1, rugged_std=rugged_seq))
muA <- link(m8.3, data=data.frame(cid=2, rugged_std=rugged_seq))
muN <- muA - muN
delta
<- apply(delta, 2, mean)
mu.delta <- apply(delta, 2, PI)
PI.delta
plot(x=rugged_seq, type = 'n', xlim = c(0,1), ylim = c(-0.3, 0.2),
xlab = 'ruggedness (std)', ylab = 'expected difference log GDP')
shade(PI.delta, rugged_seq, col='grey')
abline(h = 0, lty = 2)
text(x = 0.2, y = 0, label = "Africa higher GDP\nAfrica lower GDP")
lines(rugged_seq, mu.delta)
At high ruggedness, being in Africa gives higher than expected GDP.
8.3 Continuous interactions
8.3.1 A winter flower
data(tulips)
<- tulips
d str(d)
## 'data.frame': 27 obs. of 4 variables:
## $ bed : Factor w/ 3 levels "a","b","c": 1 1 1 1 1 1 1 1 1 2 ...
## $ water : int 1 1 1 2 2 2 3 3 3 1 ...
## $ shade : int 1 2 3 1 2 3 1 2 3 1 ...
## $ blooms: num 0 0 111 183.5 59.2 ...
8.3.2 the models
Water and Shade work together to create Blooms; \(W \rightarrow B \leftarrow S ; B = f(W,S)\)
- water
\[\beta_{i} \sim \text{Normal}(\mu_{i}, \sigma)\\ \mu_{i} = \alpha + \beta_{W}(W_{i} - \overline{W})\\ \alpha \sim \text{Normal}(0.5,1)\\ \beta_{W} \sim \text{Normal}(0,1)\\ \sigma \sim \text{Exponential}(1)\] - shade
\[\beta_{i} \sim \text{Normal}(\mu_{i}, \sigma)\\ \mu_{i} = \alpha + \beta_{S}(S_{i} - \overline{S})\\ \alpha \sim \text{Normal}(0.5,1)\\ \beta_{S} \sim \text{Normal}(0,1)\\ \sigma \sim \text{Exponential}(1)\] - water + shade
\[\beta_{i} \sim \text{Normal}(\mu_{i}, \sigma)\\ \mu_{i} = \alpha + \beta_{W}(W_{i} - \overline{W}) + \beta_{S}(S_{i} - \overline{S})\\ \alpha \sim \text{Normal}(0.5,1)\\ \beta_{W} \sim \text{Normal}(0,1)\\ \beta_{S} \sim \text{Normal}(0,1)\\ \sigma \sim \text{Exponential}(1)\] - water * shade
$$$$
#center predictors and scale outcome
$blooms_std <- d$blooms / max(d$blooms)
d$water_cent <- d$water - mean(d$water)
d$shade_cent <- d$shade - mean(d$shade) d
The \(\alpha\) prior is likely too broad. We need it to be between 0 and 1. How much is outside that?
<- rnorm(1e4, 0.5, 1); sum(a < 0 | a > 1) / length(a) a
## [1] 0.623
Let’s tighten it
<- rnorm(1e4, 0.5, 0.25); sum(a < 0 | a > 1) / length(a) a
## [1] 0.047
range of water and shade are each 2 units. range of blooms is one unit. max slopes = 2/1 (0.5) so we can set the prior to 0 with 0.25 sd to get values ranging from -0.5 to 0.5.
#water
.4a <- quap(
m8alist(
~ dnorm(mu, sigma),
blooms_std <- a + bw*water_cent,
mu ~ dnorm(0.5,0.25),
a ~ dnorm(0, 0.25),
bw ~ dexp(1)
sigma data = d
),
)
#shade
.4b <- quap(
m8alist(
~ dnorm(mu, sigma),
blooms_std <- a + bs*shade_cent,
mu ~ dnorm(0.5,0.25),
a ~ dnorm(0, 0.25),
bs ~ dexp(1)
sigma data = d
),
)
# water + shade
.4c <- quap(
m8alist(
~ dnorm(mu, sigma),
blooms_std <- a + bw*water_cent + bs*shade_cent,
mu ~ dnorm(0.5,0.25),
a ~ dnorm(0, 0.25),
bw ~ dnorm(0, 0.25),
bs ~ dexp(1)
sigma data = d
),
)
#water * shade
.4d <- quap(
m8alist(
~ dnorm(mu, sigma),
blooms_std <- a + bw*water_cent + bs*shade_cent + bws*water_cent*shade_cent,
mu ~ dnorm(0.5,0.25),
a ~ dnorm(0, 0.25),
bw ~ dnorm(0, 0.25),
bs ~ dnorm(0, 0.25),
bws ~ dexp(1)
sigma data = d
), )
prior simulations
set.seed(11)
<- extract.prior(m8.4a) #water
prior_a <- extract.prior(m8.4b) #shade
prior_b <- extract.prior(m8.4c) #water + shade
prior_c <- extract.prior(m8.4d) #water * shade
prior_d
#set plot
plot(NULL, xlim=c(0,2), ylim=c(0, 1.25), xlab = "Water / shade", ylab = "blooms")
abline(h=min(d$blooms_std), lty = 2)
abline(h=max(d$blooms_std), lty = 2)
#draw lines from prior
<- seq(from = -1.1, to = 1.1, length.out=30)
water_seq <- seq(from = -1.1, to = 1.1, length.out=30)
shade_seq
<- link(m8.4a, post = prior_a, data = data.frame(water_cent=water_seq))
mu_a <- link(m8.4b, post = prior_b, data = data.frame(shade_cent=shade_seq))
mu_b <- link(m8.4c, post = prior_c, data = data.frame(water_cent=water_seq, shade_cent=shade_seq))
mu_c <- link(m8.4d, post = prior_d, data = data.frame(water_cent=water_seq, shade_cent=shade_seq))
mu_d
#set plot
plot(NULL, xlim=c(-1,1), ylim=c(0, 1.25), xlab = "Water / shade (centered)", ylab = "blooms (std)")
abline(h=min(d$blooms_std), lty = 2)
abline(h=max(d$blooms_std), lty = 2)
for(i in 1:50){
lines(water_seq, mu_a[i,], col=col.alpha('blue',0.3))
}
for(i in 1:50){
lines(water_seq, mu_b[i,], col=col.alpha('black',0.3))
}for(i in 1:50){
lines(water_seq, mu_c[i,], col=col.alpha('green',0.3))
}
for(i in 1:50){
lines(water_seq, mu_d[i,], col=col.alpha('red',0.3))
}
text(x = -0.75, y = 1.2, label = "Water", col = "blue")
text(x = -0.45, y = 1.2, label = "Shade", col = "black")
text(x = 0, y = 1.2, label = "Water + Shade", col = "green")
text(x = 0.5, y = 1.2, label = "Water * Shade", col = 'red')
precis(m8.4a)
## mean sd 5.5% 94.5%
## a 0.3594883 0.03502089 0.3035181 0.4154584
## bw 0.2034854 0.04270338 0.1352371 0.2717336
## sigma 0.1837433 0.02489828 0.1439510 0.2235355
precis(m8.4b)
## mean sd 5.5% 94.5%
## a 0.3611171 0.04403635 0.2907385 0.43149566
## bs -0.1097643 0.05351857 -0.1952973 -0.02423127
## sigma 0.2323701 0.03143809 0.1821260 0.28261427
precis(m8.4c)
## mean sd 5.5% 94.5%
## a 0.3587452 0.03022116 0.3104459 0.40704441
## bw 0.2050352 0.03689240 0.1460741 0.26399641
## bs -0.1125324 0.03687853 -0.1714714 -0.05359337
## sigma 0.1581668 0.02144796 0.1238888 0.19244481
precis(m8.4d)
## mean sd 5.5% 94.5%
## a 0.3579980 0.02391747 0.31977332 0.39622278
## bw 0.2067288 0.02923282 0.16000910 0.25344849
## bs -0.1134595 0.02922579 -0.16016795 -0.06675103
## bws -0.1431791 0.03567746 -0.20019860 -0.08615967
## sigma 0.1248375 0.01693790 0.09776742 0.15190750
8.3.3 Plotting posterior predictions
par(mfrow = c(1,3))
for (s in -1:1){
<- d[d$shade_cent == s,]
idx plot(x = idx$water_cent, y = idx$blooms_std, xlim = c(-1,1), ylim = c(0,1),
xlab = "water", ylab = "blooms", pch = 16, col = rangi2)
<- link(m8.4c, data = data.frame(shade_cent=s, water_cent = -1:1))
mu for(i in 1:20){
lines(-1:1, mu[i,], col = col.alpha('black',0.3))
}mtext(concat("m8.4c post: shade = ", s))
}
par(mfrow = c(1,3))
for (s in -1:1){
<- d[d$shade_cent == s,]
idx plot(x = idx$water_cent, y = idx$blooms_std, xlim = c(-1,1), ylim = c(0,1),
xlab = "water", ylab = "blooms", pch = 16, col = rangi2)
<- link(m8.4d, data = data.frame(shade_cent=s, water_cent = -1:1))
mu for(i in 1:20){
lines(-1:1, mu[i,], col = col.alpha('black',0.3))
}mtext(concat("m8.4d post: shade = ", s))
}
par(mfrow = c(1,3))
for (s in -1:1){
<- d[d$water_cent == s,]
idx plot(x = idx$shade_cent, y = idx$blooms_std, xlim = c(-1,1), ylim = c(0,1),
xlab = "shade", ylab = "blooms", pch = 16, col = rangi2)
<- link(m8.4c, data = data.frame(water_cent=s, shade_cent = -1:1))
mu for(i in 1:20){
lines(-1:1, mu[i,], col = col.alpha('black',0.3))
}mtext(concat("m8.4c post: water = ", s))
}
par(mfrow = c(1,3))
for (s in -1:1){
<- d[d$water_cent == s,]
idx plot(x = idx$shade_cent, y = idx$blooms_std, xlim = c(-1,1), ylim = c(0,1),
xlab = "shade", ylab = "blooms", pch = 16, col = rangi2)
<- link(m8.4d, data = data.frame(water_cent=s, shade_cent = -1:1))
mu for(i in 1:20){
lines(-1:1, mu[i,], col = col.alpha('black',0.3))
}mtext(concat("m8.4c post: water = ", s))
}
8.3.4 Plotting prior predictions
par(mfrow = c(1,3))
for (s in -1:1){
<- d[d$shade_cent == s,]
idx plot(x = idx$water_cent, y = idx$blooms_std, type = 'n', xlim = c(-1,1), ylim = c(-0.5,1.5),
xlab = "water", ylab = "blooms")
<- link(m8.4c, post = prior_c, data = data.frame(shade_cent=s, water_cent = -1:1))
mu for(i in 1:20){
lines(-1:1, mu[i,], col = col.alpha('black',0.3))
}lines(-1:1, mu[11,], lwd = 2, col = rangi2)
abline(h = 0, lty = 2)
abline(h = 1, lty = 2)
mtext(concat("m8.4c post: shade = ", s))
}
par(mfrow = c(1,3))
for (s in -1:1){
<- d[d$shade_cent == s,]
idx plot(x = idx$water_cent, y = idx$blooms_std, type = 'n', xlim = c(-1,1), ylim = c(-0.5,1.5),
xlab = "water", ylab = "blooms")
<- link(m8.4d, post = prior_d, data = data.frame(shade_cent=s, water_cent = -1:1))
mu for(i in 1:20){
lines(-1:1, mu[i,], col = col.alpha('black',0.3))
}lines(-1:1, mu[11,], lwd = 2, col = rangi2)
abline(h = 0, lty = 2)
abline(h = 1, lty = 2)
mtext(concat("m8.4c post: shade = ", s))
}
par(mfrow = c(1,3))
for (s in -1:1){
<- d[d$water_cent == s,]
idx plot(x = idx$shade_cent, y = idx$blooms_std, type = 'n', xlim = c(-1,1), ylim = c(-0.5,1.5),
xlab = "shade", ylab = "blooms")
<- link(m8.4c, post = prior_c, data = data.frame(water_cent=s, shade_cent = -1:1))
mu for(i in 1:20){
lines(-1:1, mu[i,], col = col.alpha('black',0.3))
}lines(-1:1, mu[11,], lwd = 2, col = rangi2)
abline(h = 0, lty = 2)
abline(h = 1, lty = 2)
mtext(concat("m8.4c post: water = ", s))
}
par(mfrow = c(1,3))
for (s in -1:1){
<- d[d$water_cent == s,]
idx plot(x = idx$shade_cent, y = idx$blooms_std, type = 'n', xlim = c(-1,1), ylim = c(-0.5,1.5),
xlab = "shade", ylab = "blooms")
<- link(m8.4d, post = prior_d, data = data.frame(water_cent=s, shade_cent = -1:1))
mu for(i in 1:20){
lines(-1:1, mu[i,], col = col.alpha('black',0.3))
}lines(-1:1, mu[11,], lwd = 2, col = rangi2)
abline(h = 0, lty = 2)
abline(h = 1, lty = 2)
mtext(concat("m8.4c post: water = ", s))
}