Skip to content

Commit 0734aa6

Browse files
committed
change model to have a parameter of beta vectors
1 parent 152535a commit 0734aa6

File tree

2 files changed

+119
-143
lines changed

2 files changed

+119
-143
lines changed

examples/causal_inference/moderation_analysis.ipynb

Lines changed: 103 additions & 129 deletions
Large diffs are not rendered by default.

examples/causal_inference/moderation_analysis.myst.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ kernelspec:
1313
(moderation_analysis)=
1414
# Bayesian moderation analysis
1515

16-
:::{post} May, 2024
16+
:::{post} June, 2024
1717
:tags: moderation, path analysis, causal inference
1818
:category: beginner
1919
:author: Benjamin T. Vincent
@@ -86,7 +86,7 @@ def posterior_prediction_plot(result, x, moderator, m_quantiles, ax=None):
8686
m_levels = result.constant_data["m"].quantile(m_quantiles).rename({"quantile": "m_level"})
8787
8888
for p, m in zip(m_quantiles, m_levels):
89-
y = post.β0 + post.β1 * xi + post.β2 * xi * m + post.β3 * m
89+
y = post.β[0] + post.β[1] * xi + post.β[2] * xi * m + post.β[3] * m
9090
region = y.quantile([0.025, 0.5, 0.975], dim="sample")
9191
ax.fill_between(
9292
xi,
@@ -119,7 +119,7 @@ def plot_moderation_effect(result, m, m_quantiles, ax=None):
119119
120120
# calculate 95% CI region and median
121121
xi = xr.DataArray(np.linspace(np.min(m), np.max(m), 20), dims=["x_plot"])
122-
rate = post.β1 + post.β2 * xi
122+
rate = post.β[1] + post.β[2] * xi
123123
region = rate.quantile([0.025, 0.5, 0.975], dim="sample")
124124
125125
ax.fill_between(
@@ -139,7 +139,7 @@ def plot_moderation_effect(result, m, m_quantiles, ax=None):
139139
for p, m in zip(percentile_list, m_levels):
140140
ax.plot(
141141
m,
142-
np.mean(post.β1) + np.mean(post.β2) * m,
142+
np.mean(post.β[1]) + np.mean(post.β[2]) * m,
143143
"o",
144144
c=scalarMap.to_rgba(m),
145145
markersize=10,
@@ -202,7 +202,7 @@ $$
202202
+++
203203

204204
### Conceptual or path diagram
205-
We can also draw moderation in a mode conceptual manner. This is perhaps visually simpler and easier to parse, but is less explicit. The moderation is shown by an arrow from the moderating variable to the line between a predictor and an outcome variable.
205+
We can also draw moderation in a more conceptual manner. This is perhaps visually simpler and easier to parse, but is less explicit. The moderation is shown by an arrow from the moderating variable to the line between a predictor and an outcome variable.
206206

207207
But the diagram would represent the exact same equation as shown above.
208208

@@ -344,13 +344,10 @@ def model_factory(x, m, y):
344344
x = pm.Data("x", x)
345345
m = pm.Data("m", m)
346346
# priors
347-
β0 = pm.Normal("β0", mu=0, sigma=10)
348-
β1 = pm.Normal("β1", mu=0, sigma=10)
349-
β2 = pm.Normal("β2", mu=0, sigma=10)
350-
β3 = pm.Normal("β3", mu=0, sigma=10)
347+
β = pm.Normal("β", mu=0, sigma=10, size=4)
351348
σ = pm.HalfCauchy("σ", 1)
352349
# likelihood
353-
y = pm.Normal("y", mu=β0 + (β1 * x) + (β2 * x * m) + (β3 * m), sigma=σ, observed=y)
350+
y = pm.Normal("y", mu=β[0] + (β[1] * x) + (β[2] * x * m) + (β[3] * m), sigma=σ, observed=y)
354351
355352
return model
356353
```
@@ -373,7 +370,7 @@ with model:
373370
Visualise the trace to check for convergence.
374371

375372
```{code-cell} ipython3
376-
az.plot_trace(result);
373+
az.plot_trace(result, compact=False);
377374
```
378375

379376
We have good chain mixing and the posteriors for each chain look very similar, so no problems in that regard.
@@ -397,7 +394,7 @@ az.plot_pair(
397394
And just for the sake of completeness, we can plot the posterior distributions for each of the $\beta$ parameters and use this to arrive at research conclusions.
398395

399396
```{code-cell} ipython3
400-
az.plot_posterior(result, var_names=["β1", "β2", "β3"], figsize=(14, 4));
397+
az.plot_posterior(result, var_names=["β"], figsize=(14, 4));
401398
```
402399

403400
For example, from an estimation (in contrast to a hypothesis testing) perspective, we could look at the posterior over $\beta_2$ and claim a credibly less than zero moderation effect.
@@ -424,10 +421,15 @@ ax.set_title("Data and posterior prediction");
424421
### Spotlight graph
425422
We can also visualise the moderation effect by plotting $\beta_1 + \beta_2 \cdot m$ as a function of the $m$. This was named a spotlight graph, see {cite:t}`spiller2013spotlights` and {cite:t}`mcclelland2017multicollinearity`.
426423

424+
```{code-cell} ipython3
425+
# result.posterior["β"].isel(β_dim_0=2)
426+
```
427+
427428
```{code-cell} ipython3
428429
fig, ax = plt.subplots(1, 2, figsize=(10, 5))
429430
plot_moderation_effect(result, m, m_quantiles, ax[0])
430-
az.plot_posterior(result, var_names="β2", ax=ax[1]);
431+
az.plot_posterior(result.posterior["β"].isel(β_dim_0=2), ax=ax[1])
432+
ax[1].set(title="Posterior distribution of $\\beta_2$");
431433
```
432434

433435
The expression $\beta_1 + \beta_2 \cdot \text{moderator}$ defines the rate of change of the outcome (muscle percentage) per unit of $x$ (training hours/week). We can see that as age (the moderator) increases, this effect of training hours/week on muscle percentage decreases.
@@ -473,7 +475,7 @@ But readers are strongly encouraged to read {cite:t}`mcclelland2017multicollinea
473475
- Updated by Benjamin T. Vincent in March 2022
474476
- Updated by Benjamin T. Vincent in February 2023 to run on PyMC v5
475477
- Updated to use `az.extract` by [Benjamin T. Vincent](https://github.com/drbenvincent) in February 2023 ([pymc-examples#522](https://github.com/pymc-devs/pymc-examples/pull/522))
476-
- Updated by [Benjamin T. Vincent](https://github.com/drbenvincent) in May 2024 to incorporate causal concepts
478+
- Updated by [Benjamin T. Vincent](https://github.com/drbenvincent) in June 2024 to incorporate causal concepts
477479

478480
+++
479481

0 commit comments

Comments
 (0)