Mediation and Moderation Analysis
Statistics
Understanding How and When Effects Occur
Mediation reveals the mechanism through which X affects Y, while moderation identifies boundary conditions. Together they transform simple causal questions into nuanced explanations of process and context.
-
Psychology β Identify psychological mechanisms that explain therapeutic interventions
-
Management β Determine whether leadership style effects depend on team culture
-
Public Health β Understand how education affects health through behavioral pathways
Mediation answers how; moderation answers when β both are essential for complete understanding.
Mediation explains how an effect occurs (the mechanism), while moderation explains when or for whom an effect occurs (the boundary condition).
Mediation Model
Types of Effects
| Effect | Definition | Formula |
|--------|-----------|---------|
| Total effect | Overall effect of X on Y | |
| Direct effect | Effect of X on Y not through M | |
| Indirect effect | Effect of X on Y through M | |
Testing the Indirect Effect
Sobel Test
Bootstrapping (Preferred)
Generate bootstrap samples, compute for each, and construct a confidence interval for the indirect effect. If the CI excludes zero, mediation is significant.
Moderation (Interaction)
Mediated Moderation vs Moderated Mediation
| Concept | Definition |
|---------|-----------|
| Mediated moderation | The interaction effect on Y is explained through a mediator |
| Moderated mediation | The indirect effect (mediation) varies across levels of a moderator |
Python Implementation
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.formula.api import ols
import matplotlib.pyplot as plt
np.random.seed(42)
# Simulate mediation data
n = 500
X = np.random.randn(n)
M = 0.6 * X + np.random.randn(n)
Y = 0.4 * M + 0.2 * X + np.random.randn(n)
# Path analysis
# Path a: X -> M
model_m = ols('M ~ X', data=pd.DataFrame({'X': X, 'M': M})).fit()
a = model_m.params['X']
se_a = model_m.bse['X']
# Path b: M -> Y (controlling for X)
model_y = ols('Y ~ X + M', data=pd.DataFrame({'X': X, 'M': M, 'Y': Y})).fit()
b = model_y.params['M']
se_b = model_y.bse['M']
c_prime = model_y.params['X']
c_total = ols('Y ~ X', data=pd.DataFrame({'X': X, 'Y': Y})).fit().params['X']
indirect = a * b
direct = c_prime
total = c_total
print(f"Path a: {a:.3f} (SE: {se_a:.3f})")
print(f"Path b: {b:.3f} (SE: {se_b:.3f})")
print(f"Direct effect: {direct:.3f}")
print(f"Indirect effect: {indirect:.3f}")
print(f"Total effect: {total:.3f}")
print(f"Proportion mediated: {indirect/total:.1%}")
# Bootstrap CI for indirect effect
n_boot = 5000
indirect_boots = []
for _ in range(n_boot):
idx = np.random.choice(n, n, replace=True)
X_b, M_b, Y_b = X[idx], M[idx], Y[idx]
a_b = sm.OLS(M_b, sm.add_constant(X_b)).fit().params[1]
b_b = sm.OLS(Y_b, np.column_stack([X_b, M_b])).fit().params[1]
indirect_boots.append(a_b * b_b)
ci_lower = np.percentile(indirect_boots, 2.5)
ci_upper = np.percentile(indirect_boots, 97.5)
print(f"\nBootstrap 95% CI for indirect effect: [{ci_lower:.3f}, {ci_upper:.3f}]")
print(f"Significant: {'Yes' if ci_lower > 0 or ci_upper < 0 else 'No'}")
# Moderation
W = np.random.randn(n)
Y_mod = 0.3 * X + 0.4 * W + 0.25 * X * W + np.random.randn(n)
model_mod = ols('Y ~ X * W', data=pd.DataFrame({'X': X, 'W': W, 'Y': Y_mod})).fit()
print(f"\nModeration model:")
print(model_mod.summary().tables[1])
Worked Example
Key Takeaways
Related Topics
-
See Causal Inference for establishing causation
-
See Regression Discontinuity for quasi-experimental designs
-
See Propensity Score Matching for observational studies