10.5 When the moderator is multicategorical

10.5.1 An example.

Just as a refresher, here’s the print() output for model2.

print(model2, digits = 3)
##  Family: gaussian 
##   Links: mu = identity; sigma = identity 
## Formula: liking ~ D1 + D2 + sexism + D1:sexism + D2:sexism 
##    Data: protest (Number of observations: 129) 
## Samples: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
##          total post-warmup samples = 4000
## 
## Population-Level Effects: 
##           Estimate Est.Error l-95% CI u-95% CI Eff.Sample  Rhat
## Intercept    7.709     1.055    5.721    9.828       1502 1.001
## D1          -4.110     1.522   -7.057   -1.093       1551 1.000
## D2          -3.498     1.429   -6.246   -0.722       1415 1.001
## sexism      -0.473     0.206   -0.889   -0.077       1485 1.001
## D1:sexism    0.897     0.291    0.319    1.462       1524 1.001
## D2:sexism    0.778     0.279    0.239    1.318       1406 1.001
## 
## Family Specific Parameters: 
##       Estimate Est.Error l-95% CI u-95% CI Eff.Sample  Rhat
## sigma    1.006     0.064    0.890    1.143       2865 1.000
## 
## Samples were drawn using sampling(NUTS). For each parameter, Eff.Sample 
## is a crude measure of effective sample size, and Rhat is the potential 
## scale reduction factor on split chains (at convergence, Rhat = 1).

The Bayesian \(R^2\):

bayes_R2(model2) %>% round(digits = 3)
##    Estimate Est.Error  Q2.5 Q97.5
## R2    0.155     0.049 0.064 0.256

And the \(R^2\) difference between this and the model excluding the interaction terms:

bayes_R2(model1, summary = F) %>% 
  as_tibble() %>% 
  rename(`Model 1` = R2) %>% 
  bind_cols(
    bayes_R2(model2, summary = F) %>% 
      as_tibble() %>% 
      rename(`Model 2` = R2)
  ) %>% 
  transmute(difference = `Model 2` - `Model 1`) %>% 
  summarize(mean = mean(difference),
            ll = quantile(difference, probs = .025),
            ul = quantile(difference,  probs = .975)) %>% 
  mutate_if(is.double, round, digits = 3)
## # A tibble: 1 x 3
##    mean     ll    ul
##   <dbl>  <dbl> <dbl>
## 1 0.084 -0.039 0.202

Much like in the text, our Figure 10.7 is just a little different from what we did with Figure 10.3.

# This will help us with the `geom_text()` annotation
slopes <-
  tibble(slope = c(fixef(model2)["sexism", "Estimate"] + fixef(model2)["D1:sexism", "Estimate"],
                   fixef(model2)["sexism", "Estimate"] + fixef(model2)["D2:sexism", "Estimate"],
                   fixef(model2)["sexism", "Estimate"]),
         x = c(4.8, 4.6, 5),
         y = c(6.37, 6.25, 4.5),
         condition = c("Individual Protest", "Collective Protest", "No Protest")) %>% 
  mutate(label = str_c("This slope is about ", slope %>% round(digits = 3)),
         condition = factor(condition, levels = c("No Protest", "Individual Protest", "Collective Protest")))

# Here we plot
model2_fitted %>% 
  ggplot(aes(x = sexism)) +
  geom_ribbon(aes(ymin = Q2.5, ymax = Q97.5),
              alpha = 1/3) +
  geom_ribbon(aes(ymin = Q25, ymax = Q75),
              alpha = 1/3) +
  geom_line(aes(y = Estimate)) +
  geom_text(data = slopes,
            aes(x = x,
                y = y,
                label = label)) +
  coord_cartesian(xlim = 4:6) +
  labs(x = expression(paste("Perceived Pervasiveness of Sex Discrimination in Society (", italic(X), ")")),
       y = "Evaluation of the Attorney") +
  facet_wrap(~condition) +
  theme_minimal()

10.5.2 Probing the interaction and interpreting the regression coefficients.

We computed the posterior means for the slopes when prepping for the figure, above. Here’s how we might get more complete posterior summaries. Much like in the text, our Figure 10.7 is just a little different from what we did with Figure 10.3.

post <- 
  posterior_samples(model2) %>% 
  transmute(`No Protest` = b_sexism + `b_D1:sexism`*0 + `b_D2:sexism`*0,
            `Individual Protest` = b_sexism + `b_D1:sexism`*1 + `b_D2:sexism`*0,
            `Collective Protest` = b_sexism + `b_D1:sexism`*0 + `b_D2:sexism`*1)

post %>% 
  gather() %>% 
  mutate(key = factor(key, levels = c("No Protest", "Individual Protest", "Collective Protest"))) %>% 
  group_by(key) %>% 
  summarise(mean = mean(value),
            sd = sd(value),
            ll = quantile(value, probs = .025),
            ul = quantile(value, probs = .975)) %>% 
  mutate_if(is.double, round, digits = 3)
## # A tibble: 3 x 5
##   key                  mean    sd     ll     ul
##   <fct>               <dbl> <dbl>  <dbl>  <dbl>
## 1 No Protest         -0.473 0.206 -0.889 -0.077
## 2 Individual Protest  0.424 0.204  0.007  0.824
## 3 Collective Protest  0.306 0.182 -0.063  0.655

Here are the differences among the three protest groups.

post %>% 
  transmute(`Individual Protest - No Protest` = `Individual Protest` - `No Protest`,
            `Collective Protest - No Protest` = `Collective Protest` - `No Protest`,
            `Individual Protest - Collective Protest` = `Individual Protest` - `Collective Protest`) %>% 
  gather() %>% 
  # again, not necessary, but useful for reordering the summaries
  mutate(key = factor(key, levels = c("Individual Protest - No Protest", "Collective Protest - No Protest", "Individual Protest - Collective Protest"))) %>% 
  group_by(key) %>% 
  summarise(mean = mean(value),
            sd = sd(value),
            ll = quantile(value, probs = .025),
            ul = quantile(value, probs = .975)) %>% 
  mutate_if(is.double, round, digits = 3)
## # A tibble: 3 x 5
##   key                                      mean    sd     ll    ul
##   <fct>                                   <dbl> <dbl>  <dbl> <dbl>
## 1 Individual Protest - No Protest         0.897 0.291  0.319 1.46 
## 2 Collective Protest - No Protest         0.778 0.279  0.239 1.32 
## 3 Individual Protest - Collective Protest 0.119 0.277 -0.431 0.643