Quick Mediation Recap
1-Mediator Model
2-Mediator Model
Mediation occurs when the relationship between a predictor and an outcome, can be explained by their relationship to a third variable: the mediator
We're looking at how variables are related, we're attempting to further our understanding by explaining the relationship between the predictor and the outcome
With mediation, we're interested in pathways between variables:
We then partition this Total Effect into:
An Indirect Effect which is the effect of the predictor on the outcome, through the mediator
& a Direct Effect which is the effect of the predictor on the outcome, adjusting for the mediator
The Total Effect is comprised of the Indirect Effect AND the Direct Effect
Even though we call it the Total Effect, it's still just the simple relationship between predictor and outcome - we haven't accounted for any other variables
By adding in a mediator, we can see how much of this Total Effect is a Direct Effect of our predictor, and how much can be explained by our mediator (i.e., the Indirect Effect)
Relatively straightforward to do in R with a 3-step process:
Define model
Fit model
Summarise model
We can then interpret our results and build our model!
We define our model by writing out the paths as a string of text
We then save it into an object ('my_mod') that we can call on when we fit our model in step 2...
my_mod <- 'outcome ~ c*predictor + b*mediator mediator ~ a*predictor indirect_effect := a*b total_effect := c + (a*b) '
Top Tip! The weird symbols in the model definition code mean different things: '~' means 'is predicted from' and ':=' means 'is created from'
my_mod <- 'maths_att ~ c*ses + b*parent_inv parent_inv ~ a*ses indirect_effect := a*b total_effect := c + (a*b) '
Top Tip! We don't need to change the second part of the code because we create the a, b, and c labels in the first 2 lines!
To fit our mediation model we use the sem()
function from the lavaan
package
Here we specify the name of the model we just defined in step 1, the name of our data, how we want to handle missing data, and the estimation method we want to use...
my_fit <- lavaan::sem(my_mod, data = my_data, missing = "something", estimator = "something")
By default, the sem()
function will use listwise deletion to deal with any missing data
Listwise deletion this is where an entire participant is excluded from analysis if any single variable is missing
A better method to use that's available in the sem()
function is full information maximum likelihood estimation (aka FIML - no, not FML...)
FIML produces unbiased parameter estimates and standard errors where there are missing data 🥳
missing = "FIML"
in the previous code to change this option! Don't Do It!!! Listwise deletion is a bad method of dealing with missing data, much better ones include FIML and multiple imputation!
We can only use FIML when we use maximum likelihood estimators
Maximum likelihood estimators work by choosing the parameters that make the data most likely to have happened
We would use estimator = "ML"
in the previous code to change this option!
We can also use estimator = "MLR"
instead to get robust standard errors (always a good idea!)
So our code becomes:
my_fit <- lavaan::sem(my_mod, data = my_data, missing = "FIML", estimator = "MLR")
Once we've fit our model using appropriate methods for handling missing data, we can summarise it with a few different functions():
broom::glance()
allows us to look at how good our model is - the model fit statistics:
broom::glance(my_fit)
broom::tidy()
allows us to look at our actual effects - the model parameters: broom::tidy(my_fit, conf.int = TRUE)
broom::tidy()
is the part we're most interested in! term | op | label | estimate | std.error | statistic | p.value | conf.low | conf.high | std.lv | std.all | std.nox |
---|---|---|---|---|---|---|---|---|---|---|---|
maths_att ~ ses | ~ | c | 4.07 | 0.45 | 8.95 | 0 | 3.18 | 4.96 | 4.07 | 0.35 | 0.01 |
maths_att ~ parent_inv | ~ | b | 71.91 | 6.39 | 11.25 | 0 | 59.37 | 84.44 | 71.91 | 0.49 | 0.49 |
parent_inv ~ ses | ~ | a | 0.02 | 0.00 | 6.73 | 0 | 0.02 | 0.03 | 0.02 | 0.30 | 0.00 |
maths_att ~~ maths_att | ~~ | 343190.48 | 23717.97 | 14.47 | 0 | 296704.12 | 389676.84 | 343190.48 | 0.54 | 0.54 | |
parent_inv ~~ parent_inv | ~~ | 27.00 | 1.88 | 14.33 | 0 | 23.31 | 30.69 | 27.00 | 0.91 | 0.91 | |
ses ~~ ses | ~~ | 4713.36 | 0.00 | NA | NA | 4713.36 | 4713.36 | 4713.36 | 1.00 | 4713.36 | |
maths_att ~1 | ~1 | 2078.00 | 325.09 | 6.39 | 0 | 1440.83 | 2715.16 | 2078.00 | 2.59 | 2.59 | |
parent_inv ~1 | ~1 | 50.03 | 0.85 | 58.99 | 0 | 48.36 | 51.69 | 50.03 | 9.18 | 9.18 | |
ses ~1 | ~1 | -254.13 | 0.00 | NA | NA | -254.13 | -254.13 | -254.13 | -3.70 | -254.13 | |
indirect_effect := a*b | := | indirect_effect | 1.73 | 0.33 | 5.24 | 0 | 1.08 | 2.37 | 1.73 | 0.15 | 0.00 |
total_effect := c+(a*b) | := | total_effect | 5.80 | 0.47 | 12.28 | 0 | 4.87 | 6.72 | 5.80 | 0.50 | 0.01 |
term | op | label | estimate | std.error | statistic | p.value | conf.low | conf.high | std.lv | std.all | std.nox |
---|---|---|---|---|---|---|---|---|---|---|---|
maths_att ~ ses | ~ | c | 4.07 | 0.45 | 8.95 | 0 | 3.18 | 4.96 | 4.07 | 0.35 | 0.01 |
maths_att ~ parent_inv | ~ | b | 71.91 | 6.39 | 11.25 | 0 | 59.37 | 84.44 | 71.91 | 0.49 | 0.49 |
parent_inv ~ ses | ~ | a | 0.02 | 0.00 | 6.73 | 0 | 0.02 | 0.03 | 0.02 | 0.30 | 0.00 |
maths_att ~~ maths_att | ~~ | 343190.48 | 23717.97 | 14.47 | 0 | 296704.12 | 389676.84 | 343190.48 | 0.54 | 0.54 | |
parent_inv ~~ parent_inv | ~~ | 27.00 | 1.88 | 14.33 | 0 | 23.31 | 30.69 | 27.00 | 0.91 | 0.91 | |
ses ~~ ses | ~~ | 4713.36 | 0.00 | NA | NA | 4713.36 | 4713.36 | 4713.36 | 1.00 | 4713.36 | |
maths_att ~1 | ~1 | 2078.00 | 325.09 | 6.39 | 0 | 1440.83 | 2715.16 | 2078.00 | 2.59 | 2.59 | |
parent_inv ~1 | ~1 | 50.03 | 0.85 | 58.99 | 0 | 48.36 | 51.69 | 50.03 | 9.18 | 9.18 | |
ses ~1 | ~1 | -254.13 | 0.00 | NA | NA | -254.13 | -254.13 | -254.13 | -3.70 | -254.13 | |
indirect_effect := a*b | := | indirect_effect | 1.73 | 0.33 | 5.24 | 0 | 1.08 | 2.37 | 1.73 | 0.15 | 0.00 |
total_effect := c+(a*b) | := | total_effect | 5.80 | 0.47 | 12.28 | 0 | 4.87 | 6.72 | 5.80 | 0.50 | 0.01 |
We can use semPlot::semPaths()
to build a diagram of our model, but making it look pretty is quite tricky
Instead we can use any other software to create it (Word, PowerPoint etc.)
We can take each of the estimates from our broom::tidy(my_fit, conf.int = TRUE)
summary, putting them into the correct paths:
Here our predictor is maths anxiety, our outcome is maths attainment, our first mediator is general anxiety, and our second mediator is test anxiety
With this model, we're saying that the relationship between maths anxiety and maths attainment can be explained by general and test anxiety
my_mod_2 <- "outcome ~ c*predictor + b1*med_1 + b2*med_2 med_1 ~ a1*predictor med_2 ~ a2*predictor indirect_med_1 := a1*b1 indirect_med_2 := a2*b2 total_effect := c + (a1*b1) + (a2*b2) med_1 ~~ med_2 "
my_mod_2 <- "outcome ~ c*predictor + b1*med_1 + b2*med_2 med_1 ~ a1*predictor med_2 ~ a2*predictor indirect_med_1 := a1*b1 indirect_med_2 := a2*b2 total_effect := c + (a1*b1) + (a2*b2) med_1 ~~ med_2 "
Demo! 2-Mediator Model in R
"maths_att ~ c*maths_anx + b1*gen_anx + b2*test_anx gen_anx ~ a1*maths_anx test_anx ~ a2*maths_anx indirect_gen := a1*b1 indirect_test := a2*b2 total_effect := c + (a1*b1) + (a2*b2) gen_anx ~~ test_anx"
The interpretation of our mediation is the same as before:
The interpretation of our mediation is the same as before:
*all results are fictional 😉
Quick Mediation Recap
1-Mediator Model
2-Mediator Model
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |