# Testing Conditional Indirect Effects/Mediation in R

This post builds on a previous post on Testing Indirect Effects/Mediation in R.

## What is mediation?

There are many ways to define mediation and mediators. Here’s one way: Mediation is the process by which one variable transmits an effect onto another through one or more mediating variables. For example, as room temperature increases, people get thirstier, and then they drink more water. In this case, thirst transmits the effect of room temperature on water drinking.

## What is an indirect effect?

The indirect effect quantifies a mediation effect, if such an effect exists. Referring to the thirst example above, in statistical terms, the indirect effect quantifies the extent to which room temperature is associated with water drinking indirectly through thirstiness. If you’re familiar with interpreting regression coefficients and the idea of controlling for other variables, then you might find it intuitive to think of the indirect effect as the decrease in the relationship between room temperature and water drinking after you’ve partialed out the association between room temperature and thirtiness. In other words, how much does the coefficient for room temperature decrease when you control for thirstiness?

## What is moderation?

Moderation refers to how some variable modifies the direction or the strength of the association between two variables. In other words, a moderator variable qualifies the relation between two variables. A moderator is not a part of some proposed causal process; instead, it interacts with the relation between two variables in such a way that their relation is stronger, weaker, or opposite in direction—depending on values of the moderator. For example, as room temperature increases, people may report feeling thirstier. But that may depend on how physically fit people are. Maybe physically fit people don’t report feeling thirsty as room temperature increases, or maybe physically fit people—compared to less physically fit people—have a higher room temperature threshold at which they start feeling thirstier. In this example, the product of one predictor variables and the moderator—their interaction—quantifies the moderator’s effect. Statistically, the product term accounts for variability in thirst or water drinking independently of either predictor variable by itself.

## What is a conditional indirect effect (i.e., moderated mediation)?

The conditional indirect concept combines moderation and mediation. Think back to the idea behind a simple indirect effect: It quantifies the extent to which two variables are related through a third variable, the mediator. Coneptually, the conditional indirect effect quantifies the indirect effect at different values of a moderator. In this sense, an indirect effect may be stronger, weaker, or opposite in sign, depending on values of a moderator. Importantly, a moderator may qualify any relation that’s a part of some proposed mediation model. For example, physical fitness might qualify the association between room temperature and thirstiness, between thirstiness and water drinking, or both.

## What is the Index of Moderated Mediation?

Much like the product or interaction term in a linear regression analysis quantifies the relation between a predictor and a moderator, the index of moderated mediation quantifies the relationship between the indirect effect and a moderator. An index of moderated mediation that is significantly different from zero implies that any two conditional indirect effects are smaller, larger, or opposite in sign at different levels of the moderator.

## Model and Conceptual Assumptions

• Correct functional form. Your model variables share linear relationships and don’t interact with eachother.
• No omitted influences. This one is hard: Your model accounts for all relevant influences on the variables included. All models are wrong, but how wrong is yours?
• Accurate measurement. Your measurements are valid and reliable. Note that unreliable measures can’t be valid, and reliable measures don’t necessairly measure just one construct or even your construct.
• Well-behaved residuals. Residuals (i.e., prediction errors) aren’t correlated with predictor variables or eachother, and residuals have constant variance across values of your predictor variables. Also, residual error terms aren’t correlated across regression equations. This could happen if, for example, some omitted variable causes both thirst and water drinking.

## Libraries

# install.packages("tidyverse")
# install.packages("knitr")
# install.packages("lavaan")
# install.packages("psych")

library(tidyverse)
library(knitr)
library(lavaan)
library(psych)

## Data

I combined the data from Table 3.1 in Mackinnon (2008, p. 56) [mackinnon_2008_t3.1.csv] with those from Table 10.1 in Mackinnon (2008, p. 291) [mackinnon_2008_t10.1.csv.csv]

thirst.normal <- read_csv("data/mackinnon_2008_t3.1.csv")
thirst.fit <- read_csv("data/mackinnon_2008_t10.1.csv")

thirst.fit$id <- 51:100 ## Add column in both datasets that identifies fitness group Unfit = -0.5 and Fit = 0.5 thirst.normal$phys_fit <- -0.5
thirst.fit\$phys_fit <- 0.5

## Bind unfit and fit data by rows

Imagine stacking these datasets on top of eachother

thirst.data <- bind_rows(thirst.normal, thirst.fit)

## Mean-center predictors

i.e., mean-center everything but the consume variable

thirst.data <- thirst.data %>%
mutate(id = factor(id),
room_temp_c = room_temp - mean(room_temp),
thirst_c = thirst - mean(thirst))

## Compute interaction terms

thirst.data <- thirst.data %>%
mutate(tmp_fit = room_temp_c * phys_fit,
thrst_fit = thirst_c * phys_fit)

### Save to data folder

thirst.data %>%
write_csv(path = "data/thirst.data.csv")

## Visualize relationships

It’s always a good idea to look at your data. Check some assumptions. See help(pairs.panels) for details.

thirst.data %>%
select(room_temp, room_temp_c, thirst, thirst_c, consume, phys_fit, tmp_fit, thrst_fit) %>%
pairs.panels(scale = FALSE, pch = ".")

## Write model to test conditional indirect effect using sem() from lavaan

• ~ = Regress onto …
• Within the regression models, I label coefficients with the astrix.
• := = Define a new parameter. Note when you define a new parameter with :=, you can use the astrix to multiply values
• For more details about lavaan syntax, see the tutorials tab at the lavaan website (linked in Resources below)
mod1 <- "# a path
thirst_c ~ 1 + a1 * room_temp_c
thirst_c ~ a2 * phys_fit
thirst_c ~ a3 * tmp_fit

# b paths
consume ~ b1 * thirst_c

# c prime path
consume ~ 1 + cp * room_temp_c

# index of moderated mediation and conditional indirect effects
b1a3 := b1 * a3
normss := a1 + a3 * -0.5
fitss := a1 + a3 * 0.5
norm := a1 * b1 + b1a3 * -0.5
fit := a1 * b1 + b1a3 * 0.5"

## Set random seed so results can be reproduced

set.seed(1234)

## Fit model

You must specify bootstrapping in the sem() function

sem.fit1 <- sem(mod1, data = thirst.data, se = "bootstrap", bootstrap = 10000, likelihood = "wishart")

## Summarize model

standardized = TRUE adds standardized estimate to the model output. Also, see help("standardizedsolution")

summary(sem.fit1, standardized = TRUE, fit.measures = TRUE)
## lavaan 0.6-3 ended normally after 18 iterations
##
##   Optimization method                           NLMINB
##   Number of free parameters                          9
##
##   Number of observations                           100
##
##   Estimator                                         ML
##   Model Fit Test Statistic                       2.800
##   Degrees of freedom                                 2
##   P-value (Chi-square)                           0.247
##
## Model test baseline model:
##
##   Minimum Function Test Statistic               52.141
##   Degrees of freedom                                 7
##   P-value                                        0.000
##
## User model versus baseline model:
##
##   Comparative Fit Index (CFI)                    0.982
##   Tucker-Lewis Index (TLI)                       0.938
##
## Loglikelihood and Information Criteria:
##
##   Loglikelihood user model (H0)               -284.018
##   Loglikelihood unrestricted model (H1)       -282.604
##
##   Number of free parameters                          9
##   Akaike (AIC)                                 586.037
##   Bayesian (BIC)                               609.483
##   Sample-size adjusted Bayesian (BIC)          581.059
##
## Root Mean Square Error of Approximation:
##
##   RMSEA                                          0.064
##   90 Percent Confidence Interval          0.000  0.220
##   P-value RMSEA <= 0.05                          0.329
##
## Standardized Root Mean Square Residual:
##
##   SRMR                                           0.032
##
## Parameter Estimates:
##
##   Standard Errors                            Bootstrap
##   Number of requested bootstrap draws            10000
##   Number of successful bootstrap draws           10000
##
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   thirst_c ~
##     rom_tmp_c (a1)    0.550    0.091    6.062    0.000    0.550    0.495
##     phys_fit  (a2)    0.195    0.199    0.979    0.328    0.195    0.086
##     tmp_fit   (a3)    0.422    0.182    2.322    0.020    0.422    0.190
##   consume ~
##     thirst_c  (b1)    0.361    0.102    3.536    0.000    0.361    0.361
##     rom_tmp_c (cp)    0.162    0.111    1.459    0.145    0.162    0.146
##
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .thirst_c          0.011    0.100    0.105    0.916    0.011    0.009
##    .consume           3.130    0.103   30.507    0.000    3.130    2.760
##
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .thirst_c          0.975    0.111    8.826    0.000    0.975    0.759
##    .consume           1.030    0.139    7.391    0.000    1.030    0.801
##
## Defined Parameters:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##     b1a3              0.153    0.084    1.824    0.068    0.153    0.069
##     normss            0.339    0.103    3.294    0.001    0.339    0.400
##     fitss             0.761    0.149    5.118    0.000    0.761    0.590
##     norm              0.122    0.050    2.458    0.014    0.122    0.145
##     fit               0.275    0.098    2.814    0.005    0.275    0.213

## Interpretation

There was no sufficient evidence that physically fit people were thirstier than normal people, a2 = 0.19 (S.E. = 0.2). Every 1°F increase in room temperature was associated with an a1 = 0.55 (S.E. = 0.09) increase in thirstiness units. However, this association was different between physically fit people and normal people, a3 = 0.42 (S.E. = 0.18). Adjusting for room temperature, every 1-unit increase in thirstiness was associated with drinking b1 = 0.36 (S.E. = 0.1) more deciliters of water. Increases in room temperature were associated with increases in water drinking indirectly through increases in thirstiness, but there was no sufficient evidence that this indirect effect was different between physically fit and normal people, b1a3 = 0.15 (S.E. = 0.08); a bias-corrected bootstrapped confidence interval with 10,000 samples for the index of moderated mediation captured zero, 95% CI []. Among normal people, for every a1 = 0.55 unit increase in the association between room temperature and thirstiness, there was an a1b1 + a1b3 = 0.12 (S.E. = 0.05) increase in deciliters of water people drank. Among physically fit people, for every a1 = 0.55 unit increase in the association between room temperature and thirstiness, there was an a1b1 + a1b3 = 0.27 (S.E. = 0.1) increase in deciliters of water people drank. Last, there was no sufficient evidence that room temperature was associated with how many deciliters of water people drank independent of its association with thirstiness, c’ = 0.16 (S.E. = 0.11).

## Write model to test conditional indirect effect using sem() from lavaan

• ~ = Regress onto …
• Within the regression models, I label coefficients with the astrix.
• := = Define a new parameter. Note when you define a new parameter with :=, you can use the astrix to multiply values
• For more details about lavaan syntax, see the tutorials tab at the lavaan website (linked in Resources below)
mod2 <- "# a path
thirst_c ~ 1 + a1 * room_temp_c

# b paths
consume ~ 1 + b1 * thirst_c
consume ~ b2 * phys_fit
consume ~ b3 * thrst_fit

# c prime path
consume ~ cp * room_temp_c

# index of moderated mediation and conditional indirect effects
a1b3 := a1 * b3
normie := a1 * b1 + a1b3 * -0.5
fitie := a1 * b1 + a1b3 * 0.5"

## Fit model

You must specify bootstrapping in the sem() function

sem.fit2 <- sem(mod2, data = thirst.data, se = "bootstrap", bootstrap = 10000)

## Summarize model

standardized = TRUE adds standardized estimate to the model output. Also, see help("standardizedsolution")

summary(sem.fit2, standardized = TRUE, fit.measures = TRUE)
## lavaan 0.6-3 ended normally after 17 iterations
##
##   Optimization method                           NLMINB
##   Number of free parameters                          9
##
##   Number of observations                           100
##
##   Estimator                                         ML
##   Model Fit Test Statistic                       3.132
##   Degrees of freedom                                 2
##   P-value (Chi-square)                           0.209
##
## Model test baseline model:
##
##   Minimum Function Test Statistic               50.217
##   Degrees of freedom                                 7
##   P-value                                        0.000
##
## User model versus baseline model:
##
##   Comparative Fit Index (CFI)                    0.974
##   Tucker-Lewis Index (TLI)                       0.908
##
## Loglikelihood and Information Criteria:
##
##   Loglikelihood user model (H0)               -284.391
##   Loglikelihood unrestricted model (H1)       -282.825
##
##   Number of free parameters                          9
##   Akaike (AIC)                                 586.782
##   Bayesian (BIC)                               610.228
##   Sample-size adjusted Bayesian (BIC)          581.804
##
## Root Mean Square Error of Approximation:
##
##   RMSEA                                          0.075
##   90 Percent Confidence Interval          0.000  0.226
##   P-value RMSEA <= 0.05                          0.288
##
## Standardized Root Mean Square Residual:
##
##   SRMR                                           0.038
##
## Parameter Estimates:
##
##   Standard Errors                            Bootstrap
##   Number of requested bootstrap draws            10000
##   Number of successful bootstrap draws           10000
##
## Regressions:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##   thirst_c ~
##     rom_tmp_c (a1)    0.497    0.086    5.765    0.000    0.497    0.447
##   consume ~
##     thirst_c  (b1)    0.388    0.100    3.879    0.000    0.388    0.384
##     phys_fit  (b2)   -0.259    0.201   -1.291    0.197   -0.259   -0.114
##     thrst_fit (b3)   -0.174    0.181   -0.956    0.339   -0.174   -0.086
##     rom_tmp_c (cp)    0.150    0.109    1.382    0.167    0.150    0.134
##
## Intercepts:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .thirst_c         -0.000    0.100   -0.000    1.000   -0.000   -0.000
##    .consume           3.136    0.103   30.342    0.000    3.136    2.757
##
## Variances:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##    .thirst_c          1.018    0.114    8.963    0.000    1.018    0.800
##    .consume           0.994    0.127    7.857    0.000    0.994    0.768
##
## Defined Parameters:
##                    Estimate  Std.Err  z-value  P(>|z|)   Std.lv  Std.all
##     a1b3             -0.086    0.090   -0.960    0.337   -0.086   -0.038
##     normie            0.236    0.080    2.938    0.003    0.236    0.191
##     fitie             0.149    0.069    2.170    0.030    0.149    0.153

## Interpretation

Every 1°F increase in room temperature was associated with an a1 = 0.5 (S.E. = 0.09) increase in thirstiness units. Adjusting for all other predictors, every 1-unit increase in thirstiness was associated with drinking b1 = 0.39 (S.E. = 0.1) more deciliters of water. Also, adjusting for all other predictors, there was no sufficient evidence that physically fit people drank less water than normal people, b2 = -0.26 (S.E. = 0.2). There was also no sufficient evidence that the relation between thirstiness and water drinking depended on physical fitness group, b3 = -0.17 (S.E. = 0.18). Increases in room temperature were associated with increases in water drinking indirectly through increases in thirstiness, but there was no sufficient evidence that this indirect effect was different between physically fit and normal people, a1b3 = -0.09 (S.E. = 0.09); a bias-corrected bootstrapped confidence interval with 10,000 samples for the index of moderated mediation captured zero, 95% CI []. Among normal people, for every a1 = 0.5 unit increase in the association between room temperature and thirstiness, there was an a1b1 + a1b3 = 0.24 (S.E. = 0.08) increase in deciliters of water people drank. Among physically fit people, for every a1 = 0.5 unit increase in the association between room temperature and thirstiness, there was an a1b1 + a1b3 = 0.15 (S.E. = 0.07) increase in deciliters of water people drank. Last, there was no sufficient evidence that room temperature was associated with how many deciliters of water people drank independent of its association with thirstiness, c’ = 0.15 (S.E. = 0.11).

## Resources

• Hayes, A. F. (2015). An index and test of linear moderated mediation. Multivariate Behavioral Research, 50(1), 1-22.
• MacKinnon, D. P. (2008). Introduction to statistical mediation analysis. New York, NY: Lawrence Erlbaum Associates.
• Revelle, W. (2017) How to use the psych package for mediation/moderation/regression analysis. [.pdf]
• Rosseel, Y. (2012). Lavaan: An R package for structural equation modeling and more. Version 0.5–12 (BETA). Journal of statistical software, 48(2), 1-36. [website]
• Rucker, D. D., Preacher, K. J., Tormala, Z. L., & Petty, R. E. (2011). Mediation analysis in social psychology: Current practices and new recommendations. Social and Personality Psychology Compass, 5(6), 359-371. [.pdf]

## General word of caution

Above, I listed resources prepared by experts on these and related topics. Although I generally do my best to write accurate posts, don’t assume my posts are 100% accurate or that they apply to your data or research questions. Trust statistics and methodology experts, not blog posts.

##### Nicholas M. Michalak
###### Data Scientist | Ph.D. Social Psychology

I study how both modern and evolutionarily-relevant threats affect how people perceive themselves and others.