贝叶斯中介分析#

本笔记本涵盖了贝叶斯中介分析。当我们想要探索预测变量与结果变量之间可能的中介路径时,这是非常有用的。

需要注意的是,中介分析的方法随着时间的推移已经发生了演变。本笔记本深受Hayes [2017]在其教科书《中介、调节和条件过程分析导论》中所提出的方法的影响。

读者应注意,中介分析通常与调节分析混淆,对此我们有一个单独的示例(贝叶斯调节分析)。

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pymc as pm
import seaborn as sns

from pandas import DataFrame
%config InlineBackend.figure_format = 'retina'
plt.rcParams.update({"font.size": 14})
seed = 42
rng = np.random.default_rng(seed);

中介模型#

简单的媒介模型非常简单,其中 \(m\)\(x\) 的线性函数,而 \(y\)\(x\)\(m\) 的线性函数:

\[ m_i \sim \mathrm{Normal}(i_M + a \cdot x_i, \sigma_M) \]
\[ y_i \sim \mathrm{Normal}(i_Y + c' \cdot x_i + b \cdot m_i, \sigma_Y) \]

其中 \(i\) 表示每个观测值(数据集中的行),而 \(i_M\)\(i_Y\) 是截距参数。注意,\(x_i\)\(m_i\)\(y_i\) 是观测数据。

使用来自 Hayes [2017] 的定义,我们可以定义一些感兴趣的效果:

  • 直接效应:\(c'\)给出。在\(x\)上相差一个单位但在\(m\)上相等的两种情况,估计在\(y\)上相差\(c'\)个单位。

  • 间接效应:\(a \cdot b\) 给出。两个相差一个单位 \(x\) 的情况估计在 \(y\) 上相差 \(a \cdot b\) 个单位,这是由于 \(x \rightarrow m\)\(m \rightarrow y\) 的影响。

  • 总效应:\(c = c' + a \cdot b\),即直接效应和间接效应的总和。这可以理解为:在 \(x\) 上相差一个单位的两个案例,由于间接路径 \(x \rightarrow m \rightarrow y\),估计在 \(y\) 上相差 \(a \cdot b\) 个单位,由于直接路径 \(x \rightarrow y\),估计相差 \(c'\) 个单位。总效应也可以通过评估替代模型 \(y_i \sim \mathrm{Normal}(i_{Y*} + c \cdot x_i, \sigma_{Y*})\) 来估计。

生成模拟数据#

def make_data():
    N = 75
    a, b, cprime = 0.5, 0.6, 0.3
    im, iy, σm, σy = 2.0, 0.0, 0.5, 0.5
    x = rng.normal(loc=0, scale=1, size=N)
    m = im + rng.normal(loc=a * x, scale=σm, size=N)
    y = iy + (cprime * x) + rng.normal(loc=b * m, scale=σy, size=N)
    print(f"True direct effect = {cprime}")
    print(f"True indirect effect = {a*b}")
    print(f"True total effect = {cprime+a*b}")
    return x, m, y


x, m, y = make_data()

sns.pairplot(DataFrame({"x": x, "m": m, "y": y}));
True direct effect = 0.3
True indirect effect = 0.3
True total effect = 0.6
../_images/b8423726013c43fe09cb7ebdddd0bf7b3f51c0749cdc183173b4dc6711013b72.png

定义 PyMC 模型并进行推理#

def mediation_model(x, m, y):
    with pm.Model() as model:
        x = pm.ConstantData("x", x, dims="obs_id")
        y = pm.ConstantData("y", y, dims="obs_id")
        m = pm.ConstantData("m", m, dims="obs_id")

        # intercept priors
        im = pm.Normal("im", mu=0, sigma=10)
        iy = pm.Normal("iy", mu=0, sigma=10)
        # slope priors
        a = pm.Normal("a", mu=0, sigma=10)
        b = pm.Normal("b", mu=0, sigma=10)
        cprime = pm.Normal("cprime", mu=0, sigma=10)
        # noise priors
        σm = pm.HalfCauchy("σm", 1)
        σy = pm.HalfCauchy("σy", 1)

        # likelihood
        pm.Normal("m_likelihood", mu=im + a * x, sigma=σm, observed=m, dims="obs_id")
        pm.Normal("y_likelihood", mu=iy + b * m + cprime * x, sigma=σy, observed=y, dims="obs_id")

        # calculate quantities of interest
        indirect_effect = pm.Deterministic("indirect effect", a * b)
        total_effect = pm.Deterministic("total effect", a * b + cprime)

    return model


model = mediation_model(x, m, y)
pm.model_to_graphviz(model)
../_images/816f371c50025ede38dac94d42a45426d1756ad88e359b39841d448f095ed3af.svg
with model:
    result = pm.sample(tune=4000, target_accept=0.9, random_seed=42)
Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [im, iy, a, b, cprime, σm, σy]
100.00% [20000/20000 00:09<00:00 Sampling 4 chains, 0 divergences]
Sampling 4 chains for 4_000 tune and 1_000 draw iterations (16_000 + 4_000 draws total) took 9 seconds.

可视化轨迹以检查收敛性。

我们拥有良好的链混合,每个链的后验分布看起来非常相似,因此在这方面没有问题。

可视化重要参数#

首先我们将使用配对图来查看联合后验分布。

az.plot_pair(
    result,
    marginals=True,
    point_estimate="median",
    figsize=(12, 12),
    scatter_kwargs={"alpha": 0.05},
    var_names=["a", "b", "cprime", "indirect effect", "total effect"],
);
../_images/8412f00588078d7ece107c6d7f1a7962d2e6bf1a0f8d0308d5afbeac9a008ef0.png

解释结果#

我们可以更仔细地查看间接、总体和直接效应:

ax = az.plot_posterior(
    result,
    var_names=["cprime", "indirect effect", "total effect"],
    ref_val=0,
    hdi_prob=0.95,
    figsize=(14, 4),
)
ax[0].set(title="direct effect");
../_images/2a4466f8eae2f01cc0a4618fddb31613803b591dcfbb983b38743a7c7ac3e45e.png
  • 后验均值直接效应为0.29,意味着每增加1单位\(x\)\(y\)由于直接效应\(x \rightarrow y\)增加0.29。

  • 后验均值间接效应为0.49,意味着对于\(x\)每增加1个单位,\(y\)通过路径\(x \rightarrow m \rightarrow y\)增加0.49。间接效应为零的概率是微乎其微的。

  • 后验均值总效应为0.77,意味着每增加1单位\(x\)\(y\)通过直接和间接路径增加0.77。

仅使用总效应模型进行双重检查#

上面,我们提到总效应也可以通过评估替代模型 \(y_i \sim \mathrm{Normal}(i_{Y*} + c \cdot x_i, \sigma_{Y*})\) 来估计。这里我们将通过比较中介模型中 \(c'\) 的后验分布与替代模型中 \(c\) 的后验分布来验证这一点。

with pm.Model() as total_effect_model:
    _x = pm.ConstantData("_x", x, dims="obs_id")
    iy = pm.Normal("iy", mu=0, sigma=1)
    c = pm.Normal("c", mu=0, sigma=1)
    σy = pm.HalfCauchy("σy", 1)
    μy = iy + c * _x
    pm.Normal("yy", mu=μy, sigma=σy, observed=y, dims="obs_id")
with total_effect_model:
    total_effect_result = pm.sample(tune=4000, target_accept=0.9, random_seed=42)
Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [iy, c, σy]
100.00% [20000/20000 00:01<00:00 Sampling 4 chains, 0 divergences]
Sampling 4 chains for 4_000 tune and 1_000 draw iterations (16_000 + 4_000 draws total) took 2 seconds.
fig, ax = plt.subplots(figsize=(14, 4))
az.plot_posterior(
    total_effect_result, var_names=["c"], point_estimate=None, hdi_prob="hide", c="r", lw=4, ax=ax
)
az.plot_posterior(
    result, var_names=["total effect"], point_estimate=None, hdi_prob="hide", c="k", lw=4, ax=ax
);
../_images/ec60a6ad7ca29ef0bc321b356960a36b4245f31d6bf462115d1234017d41bcd1.png

正如我们所见,中介模型(黑色曲线)和直接模型(红色曲线)的直接效应的后验分布几乎是相同的。

参数估计与假设检验#

本笔记本重点介绍了贝叶斯参数估计的方法。在许多情况下,这种方法是完全足够的,更多信息可以在Yuan和MacKinnon [2009]中找到。它将告诉我们,除其他事项外,我们对直接效应、间接效应和总效应的后验信念是什么。我们可以使用这些后验信念来进行后验预测检查,以直观地检查模型对数据的解释程度。

然而,根据使用情况,可能更倾向于测试关于间接效应存在或不存在的假设(例如\(x \rightarrow m \rightarrow y\))。在这种情况下,采用更明确的假设检验方法来检查与简单直接效应模型(即\(y_i = \mathrm{Normal}(i_{Y*} + c \cdot x_i, \sigma_{Y*})\))相比,中介模型的相对可信度可能更为合适。读者可参考Nuijten 等人 [2015]了解贝叶斯中介模型的假设检验方法,并参考Kruschke [2011]获取更多关于参数估计与假设检验的信息。

概述#

如开头所述,调解分析中使用的方法已经随着时间的推移而发展。因此,有很多人不一定了解现代的最佳实践。本笔记本中的方法遵循了Hayes [2017]所概述的内容,但了解一些历史背景以避免混淆是相关的——这在同行评审中为自己的方法辩护时尤为重要。

作者#

  • 由Benjamin T. Vincent于2021年8月撰写

  • 由Benjamin T. Vincent于2022年2月更新

参考资料#

[1] (1,2,3)

安德鲁·F·海斯。中介、调节和条件过程分析导论:基于回归的方法。吉尔福德出版社,2017年。

[2]

Ying Yuan 和 David P MacKinnon. 贝叶斯中介分析. 心理方法, 14(4):301, 2009.

[3]

米歇尔·B·努伊滕, 鲁德·韦策尔斯, 多拉·马茨克, 康纳·V·多兰, 和埃里克-扬·瓦格纳马克。一种用于中介分析的默认贝叶斯假设检验。行为研究方法, 47(1):85–97, 2015.

[4]

约翰·K·克鲁施克. 通过参数估计和模型比较的贝叶斯评估零值. 心理科学视角, 6(3):299–312, 2011.

水印#

%load_ext watermark
%watermark -n -u -v -iv -w -p pytensor,aeppl,xarray
Last updated: Wed Feb 01 2023

Python implementation: CPython
Python version       : 3.11.0
IPython version      : 8.9.0

pytensor: 2.8.11
aeppl   : not installed
xarray  : 2023.1.0

arviz     : 0.14.0
pymc      : 5.0.1
matplotlib: 3.6.3
numpy     : 1.24.1
seaborn   : 0.12.2

Watermark: 2.3.1

许可证声明#

本示例库中的所有笔记本均在MIT许可证下提供,该许可证允许修改和重新分发,前提是保留版权和许可证声明。

引用 PyMC 示例#

要引用此笔记本,请使用Zenodo为pymc-examples仓库提供的DOI。

重要

许多笔记本是从其他来源改编的:博客、书籍……在这种情况下,您应该引用原始来源。

同时记得引用代码中使用的相关库。

这是一个BibTeX的引用模板:

@incollection{citekey,
  author    = "<notebook authors, see above>",
  title     = "<notebook title>",
  editor    = "PyMC Team",
  booktitle = "PyMC examples",
  doi       = "10.5281/zenodo.5654871"
}

渲染后可能看起来像: