A friend asked me how to test simple slopes from interaction effects in multilevel models when you have a continuous predictor, since the only packages that seem to compute them are for one-level/fixed effects models. Here I’ll show you how to (1) test whether simple slopes are different from 0, and (2) test whether simple slopes are different from each other.
A big thing to note up front—the random intercepts or slopes in your model have no impact on how you compute the simple slopes for your fixed effects. You can use this method for normal regression too.
tl;dr
You can recode your predictors so that the slopes in your model are the simple slopes you want to test.
Motivating example
I want to test whether people who are more Agreeable empathize more. But I also think the effect of Agreeableness on empathy is moderated by how strongly the target of empathy expresses their emotions (are they crying or just a little bummed?) and how serious the target’s situation is (did their mother die or did they just spill their coffee?).
I run an experiment with a 2 (Expression: strong vs. weak) x 2 (Situation: serious vs. trivial) mixed design, with Situation as the within-subjects factor. Every research subject sees two targets—one in a serious situation, one in a trivial situation—but both targets express the same amount of emotion—strong or weak. I measure subjects’ empathy for the targets and their Agreeableness.
Here are my sample data.
# load the packages we need
library(dplyr)
library(car)
library(lme4)
set.seed(123) # set the seed so you can get the same results
SubjectID <- rep(1:140, each = 2)
expression <- rep(c('strong', 'weak'), each = 140)
situation <- rep(c('serious', 'trivial'), times= 140)
agree <- rep(sample(1:7, 140, replace = TRUE), each = 2)
empathy <- sample(1:7, 280, replace = TRUE)
emp.data <- data.frame(SubjectID, expression, situation, agree, empathy)
Here’s a sample of what the data look like
emp.data[c(1:4, 141:144), ]
## SubjectID expression situation agree empathy ## 1 1 strong serious 3 3 ## 2 1 strong trivial 3 3 ## 3 2 strong serious 6 1 ## 4 2 strong trivial 6 2 ## 141 71 weak serious 6 6 ## 142 71 weak trivial 6 1 ## 143 72 weak serious 5 3 ## 144 72 weak trivial 5 2
Testing Main Effects and Interactions
First, I want to test the main effects and interaction effects. I could do this with repeated-measures ANOVA, but for this example I’ll do it with multilevel modeling, with a random intercept nested within Subjects.
To test this model, I need to mean-center Agreeableness and I need to compute contrasts for the two experimental conditions.
emp.data$agree.cent <- emp.data$agree - mean(emp.data$agree)
emp.data$expr.cont <- recode(emp.data$expression, as.factor.result = FALSE, "'strong' = 1; 'weak' = -1")
emp.data$sit.cont <- recode(emp.data$situation, as.factor.result = FALSE, "'serious' = 1; 'trivial' = -1")
Now I can run my multilevel model.
m <- lmer(empathy ~ expr.cont*sit.cont*agree.cent + (1|SubjectID), data = emp.data) # I'm using %>% from dplyr to make this example easier to read
summary(m) %>% # get the model summary
coef() %>% # get the coefficients from the model summary
round(digits = 2) # round the coefficients to two digits
## Estimate Std. Error t value ## (Intercept) 3.89 0.12 33.19 ## expr.cont 0.09 0.12 0.77 ## sit.cont -0.05 0.12 -0.42 ## agree.cent 0.01 0.06 0.16 ## expr.cont:sit.cont -0.09 0.12 -0.74 ## expr.cont:agree.cent -0.08 0.06 -1.34 ## sit.cont:agree.cent 0.07 0.06 1.19 ## expr.cont:sit.cont:agree.cent -0.07 0.06 -1.15
In this model, the slope for expr.cont is the main effect of Expression, the slope for sit.cont is the main effect of Situation, the slope for expr.cont:agree.cent is the interaction of Expression and Agreeableness, and so on.
Testing Whether Simple Slopes are Different from 0
Perhaps I’d like to test whether there is an effect of Agreeableness on empathy in the trivial Situation/strong Expression condition. How can I do this?
We can find the full effect of Agreeableness by taking all the slopes from the model that include Agreeableness and multiplying them by the variables that contribute to those slopes. I’m going to use the letter B to represent these slopes instead of using the actual values from our model, so that the final lesson will make more sense.
BAgree * Agreeableness + BAgree*Exp * Agreeableness * Expression + BAgree*Sit * Agreeableness * Situation + BAgree*Exp*Sit * Agreeableness * Expression * Situation
When we factor out Agreeableness we get
Agreeableness * (BAgree + BAgree*Exp * Expression + BAgree*Sit * Situation + BAgree*Exp*Sit * Expression * Situation)
What’s the effect of Agreeableness in the strong/trivial condition? We can find this by plugging in the proper values for that condition. Currently, the Expression variable is coded so 1 = strong and -1 = weak, and the Situation variable is coded so 1 = serious and -1 = trivial. In the strong/trivial condition we have Expression = 1 and Situation = -1
When we plug these into our equation we get
Agreeableness * (BAgree + BAgree*Exp * 1 + BAgree*Sit * -1 + BAgree*Exp*Sit * 1 * -1)
= Agreeableness * (BAgree + BAgree*Exp – BAgree*Sit – BAgree*Exp*Sit)
Great, we can compute the effect of Agreeableness in the strong/trivial condition. But is it significantly different from 0? We don’t know, because we don’t have significance tests for sums and differences of slopes. To get past this problem, we’re going to replace the current Expression and Situation variables with dummy codes that have 0’s for the strong and trivial conditions and 1’s for the weak and serious conditions. This will change the meaning of our slopes in a useful way.
emp.data$expr.dum <- recode(emp.data$expression, as.factor.result = FALSE, "'strong' = 0; 'weak' = 1")
emp.data$sit.dum <- recode(emp.data$situation, as.factor.result = FALSE, "'serious' = 1; 'trivial' = 0")
Now let’s run our model again with these dummy variables.
m.dummy <- lmer(empathy ~ expr.dum*sit.dum*agree.cent + (1|SubjectID), data = emp.data) summary(m.dummy) %>%
coef() %>%
round(digits = 2)
## Estimate Std. Error t value ## (Intercept) 4.11 0.23 17.56 ## expr.dum -0.35 0.33 -1.06 ## sit.dum -0.27 0.33 -0.82 ## agree.cent -0.07 0.12 -0.62 ## expr.dum:sit.dum 0.35 0.47 0.74 ## expr.dum:agree.cent 0.02 0.17 0.13 ## sit.dum:agree.cent 0.00 0.16 0.03 ## expr.dum:sit.dum:agree.cent 0.27 0.23 1.15
Now again, the full effect of Agreeableness is
Agreeableness * (BAgree + BAgree*Exp * Expression + BAgree*Sit * Situation + BAgree*Exp*Sit * Expression * Situation)
But when we plug in the new values for the strong Expression/trivial Situation condition, we get
Agreeableness * (BAgree + BAgree*Exp * 0 + BAgree*Sit * 0 + BAgree*Exp*Sit * 0 * 0) \
= Agreeableness * (BAgree)
In this model, the slope for Agreeableness is no longer the main effect of Agreeableness; instead, it’s the effect of Agreeableness in the strong Expression/trivial Situation condition, and we can get the significance test of the simple slope.
Testing Whether Simple Slopes are Different from Each Other
Now, perhaps I’d like to know whether the effect of Agreeableness on empathy in the trivial Situation/strong Expression condition is different from the effect of Agreeableness on empathy in the serious Situation/strong Expression condition. How can I do this?
In our original model, our Situation and Expression variables represent three orthogonal contrasts:
| C1 | C2 | C3 | |
|---|---|---|---|
| Strong Expression/Trivial Situation | 1 | 1 | 1 |
| Strong Expression/Serious Situation | 1 | -1 | -1 |
| Weak Expression/Trivial Situation | -1 | 1 | -1 |
| Weak Expression/Serious Situation | -1 | -1 | 1 |
C1 is the main effect of Expression, C2 is the main effect of Situation, and C3 is the Expression x Situation interaction. We know these are orthogonal contrasts because if we multiple any two columns row-wise, the sum of the product will equal 0.
Now let’s say we wanted to test the difference in empathy between the strong/trivial condition and the strong/serious condition. We could replace those three contrasts with a different set of orthogonal contrasts.
| C1 | C2 | C3 | |
|---|---|---|---|
| Strong Expression/Trivial Situation | 1 | 0 | 1 |
| Strong Expression/Serious Situation | -1 | 0 | 1 |
| Weak Expression/Trivial Situation | 0 | 1 | -1 |
| Weak Expression/Serious Situation | 0 | -1 | -1 |
Now C1 tests whether there’s a difference between the strong/trivial and strong/serious conditions, C2 tests whether there’s a difference between the weak/trivial and weak/serious conditions, and C3 tests the main effect of Expression.
We can test whether there’s a difference in the effect of Agreeableness on empathy between the strong/trivial and strong/serious conditions (and between the weak/trivial and weak/serious conditions) by entering the sum of these contrasts in the model and multiplying by Agreeableness.
emp.data$C1 <- with(emp.data, ifelse(expression == 'strong' & situation == 'trivial', 1,
ifelse(expression == 'strong' & situation == 'serious', -1,
0)))
emp.data$C2 <- with(emp.data, ifelse(expression == 'weak' & situation == 'trivial', 1,
ifelse(expression == 'weak' & situation == 'serious', -1,
0)))
emp.data$C3 <- with(emp.data, ifelse(expression == 'strong', 1, -1))
m <- lmer(empathy ~ agree.cent*(C1 + C2 + C3) + (1 | SubjectID), data = emp.data) summary(m) %>%
coef() %>%
round(digits = 2)
## Estimate Std. Error t value ## (Intercept) 3.89 0.12 33.19 ## agree.cent 0.01 0.06 0.16 ## C1 0.14 0.17 0.82 ## C2 -0.04 0.17 -0.22 ## C3 0.09 0.12 0.77 ## agree.cent:C1 0.00 0.08 -0.03 ## agree.cent:C2 -0.14 0.08 -1.63 ## agree.cent:C3 -0.08 0.06 -1.34
The agree.cent:C1 interaction tests whether the effect of Agreeableness is different in the strong/trivial and strong/serious condition.










