使用do-operator的干预分布和图突变#

PyMC 是开源贝叶斯统计生态系统中的一个关键组件。它每天都在帮助解决跨多个行业和学术研究领域的实际问题。并且它通过易于使用、功能强大且在解决贝叶斯统计推断问题方面具有实际用途而达到了这一实用水平。

但时代在变化。一场因果革命正在进行,越来越多的人认识到,要回答一些最有趣和最具挑战性的问题,我们需要将因果推理融入我们的努力中。

PyMC 正在迎接这一挑战!虽然有许多新颖的因果概念需要学习,但贝叶斯学者会发现他们并不是从头开始。他们已经对 有向无环图 (DAGs) 相当熟悉,因此这为他们进入 贝叶斯因果推断 的世界提供了一个良好的起点,相对容易上手。

本笔记本将涵盖因果推理的基础组成部分之一,这一部分最近已加入到 PyMC 生态系统中,即 \(\operatorname{do}\) 操作符。实际上,根据您想要使用的定义,将 \(\operatorname{do}\) 操作符添加到 PyMC 用户每天构建的贝叶斯 DAG 中,使我们达到了构建 因果贝叶斯网络 的状态。

如果这听起来很酷,让我们深入了解…

设置笔记本#

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

from packaging import version
WARNING (pytensor.configdefaults): g++ not available, if using conda: `conda install m2w64-toolchain`
WARNING (pytensor.configdefaults): g++ not detected!  PyTensor will be unable to compile C-implementations and will default to Python. Performance may be severely degraded. To remove this warning, set PyTensor flags cxx to an empty string.
WARNING (pytensor.tensor.blas): Using NumPy C-API based implementation for BLAS functions.
# import the new functionality
from pymc.model.transform.conditioning import do
RANDOM_SEED = 123
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-darkgrid")
%config InlineBackend.figure_format = 'retina'

我们可以用贝叶斯推断做什么?#

无论是构建描述性模型还是试图模拟潜在过程的模型,贝叶斯方法都非常擅长构建白盒(即与黑盒相反)、可解释的数据生成过程模型。虽然我们使用代码构建PyMC模型,但在幕后,这被表示为一个DAG,我们可以使用graphviz进行可视化。让我们看看这在文档中的示例中是如何工作的:

J = 8
y = np.array([28, 8, -3, 7, -1, 1, 18, 12])
sigma = np.array([15, 10, 16, 11, 9, 11, 10, 18])

with pm.Model() as schools:
    eta = pm.Normal("eta", 0, 1, shape=J)
    mu = pm.Normal("mu", 0, sigma=1e6)
    tau = pm.HalfCauchy("tau", 25)
    theta = mu + tau * eta
    obs = pm.Normal("obs", theta, sigma=sigma, observed=y)

pm.model_to_graphviz(schools)
../_images/e806743b0f56d604960981028daf1a965d648b05d3638438842820a085dcd323.svg

无论我们使用的是特定的模型还是多个模型,我们都可以进行一系列的统计程序:

  • 我们可以检查先验预测分布,以了解在给定的DAG中基于我们陈述的先验信念所期望看到的内容,使用pymc.sample_prior_predictive。一个示例用例是当我们想要基于模型的结构(例如,国家和国际经济)以及我们对潜在变量的先验信念来理解我们对通货膨胀未来发展的预测时。

  • 我们可以通过从后验分布中采样来进行贝叶斯推断,使用pymc.sample。这将更新我们的信念,以在给定我们观察到的数据的情况下,对潜在变量的不同值赋予可信度。例如,也许我们的数据集中又增加了一个通货膨胀数据点,我们希望更新我们对经济模型中潜在变量的信念。

  • 我们可以使用pymc.sample_posterior_predictive来检查后验预测分布。这与先验预测分布密切相关,但在我们的运行示例中,它将允许我们在观察到另一个数据点后创建一组关于未来通货膨胀率的修订预测。

  • 如果我们愿意,我们可以通过GLM: 模型选择来比较不同的模型(数据生成过程)。这可能特别有用,因为我们可能并不完全相信我们了解经济的“真实”模型,即使在粗略的抽象层面上。因此,我们可以构建多个模型(DAGs)并评估每个模型生成观测数据的相对可信度。

  • 如果我们有多个候选数据生成过程,我们可以通过模型平均来纳入我们对数据生成过程的不确定性。

如果我们已经掌握了所有这些步骤,我们可以理所当然地对自己感到非常满意。通过这些统计和预测程序,我们可以完成很多事情。

为什么因果关系很重要#

但现在是我们自以为是的贝叶斯脸上被打一巴掌的时候了。正如其他人所指出的(例如 Pearl 和 Mackenzie [2018], Pearl [2000]),完全有可能构建一个相当不错的预测模型,但一旦你(或任何人)干预系统,它可能会灾难性地失败。这种干预可以彻底摧毁预测建模方法,并让你迅速意识到将因果推理加入我们的技能集的必要性。

在我们的运行示例中,这可能对应于当中央银行从对通货膨胀进行预测转变为现在采取行动干预系统,例如通过改变利率。突然之间,你可能会面临经济对你的干预没有按照你预测的那样做出反应的情况。

让我们考虑一个看似微不足道的例子,有3个节点,看看我们是如何被愚弄的。下图显示了两个不同的因果DAG。在左边,我们感兴趣的是\(X\)如何因果地影响\(Y\),无论是直接还是通过中介变量\(M\)间接影响。如果我们采用纯统计的方法(例如贝叶斯中介分析),我们可能会发现数据非常可能是由这个DAG生成的。这可能会让我们有信心对\(M\)进行干预,以影响我们的目标结果\(Y\)。但当我们在现实世界中进行这种干预并改变\(M\)时,我们实际上发现\(Y\)完全没有变化。这里发生了什么?

Hide code cell source
g = gr.Digraph()
# Wrong data generating process
g.node(name="x2", label="X")
g.node(name="y2", label="Y")
g.node(name="m2", label="M")
g.edge(tail_name="x2", head_name="y2")
g.edge(tail_name="x2", head_name="m2")
g.edge(tail_name="m2", head_name="y2")
# Actual causal DAG
g.node(name="x", label="X")
g.node(name="y", label="Y")
g.node(name="m", label="M")
g.node(name="u", label="U", color="lightgrey", style="filled")
g.edge(tail_name="x", head_name="y")
g.edge(tail_name="x", head_name="m")
g.edge(tail_name="m", head_name="y", style="dashed", dir="none")
g.edge(tail_name="u", head_name="m", color="lightgrey")
g.edge(tail_name="u", head_name="y", color="lightgrey")
# Render
g
../_images/9638b14b4bf42c43e4cadbdfb41fac3194fee21873f216397f28a85f4a70ea06.svg

我们并不知道,但实际的数据生成过程是由右边的DAG捕捉的。这表明\(X\)确实对\(M\)\(Y\)都有因果影响,然而\(M\)实际上并不对\(Y\)产生因果影响。相反,存在一个未观察到的变量\(U\),它对\(M\)\(Y\)都有因果影响。这个未观察到的混杂因素在路径\(X \rightarrow M \rightarrow U \rightarrow Y\)中创建了一个后门路径,其中统计关联可能在这条路径中流动。所有这些导致\(M\)\(Y\)之间存在统计关联,这使得我们纯粹的统计方法误导我们以为\(M\)确实对\(Y\)有因果影响,而实际上并没有。难怪我们的干预没有产生任何效果。

我们的错误在于将统计模型因果性地解释了。

统计分布与干预分布#

到目前为止,这已经相当高层次了,但让我们试着稍微具体化一些。在我们的例子中,如果我们采取纯粹的统计方法,我们可以问“当利率为2%时发生了什么?”这是一个统计问题,因为我们基本上是在回顾我们的数据集,并在利率处于(或非常接近)2%的时间点上进行过滤(或条件处理)。所以让我们标记一下 - 条件分布是纯粹的统计量

尽管我们可能想要回答的真正问题是“如果我们过去将利率设定为2%,会发生什么?”或者“如果我们将来将利率设定为2%,会发生什么?”尽管措辞略有变化,但这现在彻底改变了我们回答问题所需做的事情。因此,这里的一个关键点是干预性分布需要因果(而非统计)方法

干预分布非常酷,因为它们允许我们提出假设性(或反事实)问题。例如,通过因果DAG,我们可以提出以下形式的问题:“如果我做了X,我认为未来会发生什么?”或“如果X发生了,我认为过去会发生什么?”请注意,这些问题与纯粹的统计问题有很大的不同,后者更像是“鉴于我所看到的,我认为会发生什么。”请注意,这更侧重于被动观察。

从这里开始,本笔记本的主要目的是提供一些关于条件分布和干预分布之间差异的理解和直觉,以及如何使用PyMC估计干预分布。正如我们上面所说,我们可以使用\(\operatorname{do}\)操作符来估计干预分布。那么让我们深入了解它是如何工作的。

干预措施和\(\operatorname{do}\)操作符#

我们将考虑来自Pearl [2000]的一个例子,其中我们检查了一个DAG,该DAG是一个假设的因果解释,说明了各种因素如何相互影响,导致草地变得湿滑。左边展示了我们的因果DAG,右边展示了如果我们考虑打开洒水器的干预(假设或实际)时,DAG如何变化。\(\operatorname{do}\)操作符实现了一个我们想要进行的干预。它由两个简单的步骤组成:

  1. 它将图中的给定节点设置为所需值。

  2. 它消除了其他节点对该节点的任何因果影响。这是通过删除所有进入该节点的入边来实现的。

在图的左侧,我们有一个描述季节、洒水器是否开启、是否下雨、草地是否湿润以及草地是否滑的因果关系的因果DAG。

联合分布可以分解为:

\[ P(x_1, x_2, x_3, x_4, x_5) = P(x_1) P(x_3|x_1) P(x_2|x_1) P(x_4|x_3, x_2) P(x_5|x_4) \]
Factorizing joint distributions

对于一个DAG,一个复杂的联合分布可以分解为条件分布的乘积:

\[ P(x_1, x_2, \ldots, x_n) = \prod_i P(x_i|pa_i) \]

其中 \(pa_i\) 是节点 \(x_i\) 的父节点,且 \(i = \{ 1, \ldots, n \}\)

在图的右侧,我们应用了\(\operatorname{do}\)操作符来检查如果我们设置洒水器为开启状态会发生什么。你可以看到,我们现在设置了该节点的值,\(x_3=1\),并且我们移除了季节的入边(影响),这意味着一旦我们手动打开洒水器,它就不再受季节的影响了。

为了描述这种新的干预分布,我们需要截断因子分解:

Truncated factorization

Pearl [2000] 描述了截断因式分解如下。如果我们有一个概率分布 \(P(v)\)\(V\) 变量集上,那么 \(P_x(v)\) 是从 \(\operatorname{do}(X=x)\) 得到的干预分布,它将 \(X\) 变量的一个子集设置为常数 \(x\)。然后我们可以用截断因式分解来描述干预分布:

\[ P_x(v) = \prod_{ \{ i | V_i \notin X \} } P(v_i|pa_i) \]

这实际上非常简单。可以认为它与联合分布的常规分解完全相同,但我们只包括那些不会影响任何被干预变量的项。

感兴趣的读者请参阅Pearl [2000]关于因果贝叶斯网络的第1.3节。

将此应用于洒水器示例,我们可以将干预分布定义为:

\[ P(x_1, x_2, \operatorname{do}(x_3=1), x_4, x_5) = P(x_1) P(x_2|x_1) P(x_4|x_3=1, x_2) P(x_5|x_4) \]

这里有两个重要的变化:

  1. 请注意,\(x_3\) 之前是一个随机变量,但由于我们的干预,它现在已被“锁定”在特定值 \(x_3=1\)

  2. 注意缺少了 \(P(x_3|x_1)\) 项,因为 \(x_1\) 不再对 \(x_3\) 有任何因果影响。

所以总结来说,这非常酷。我们可以使用\(\operatorname{do}\)操作符在我们的世界模型中进行干预。然后我们可以观察这种干预的后果,并做出更好的预测,预测当我们主动干预(实际或假设)世界时会发生什么。当然,准确性取决于我们的因果DAG在多大程度上反映了现实世界中的真实过程。

对于那些想要从不同角度进一步了解\(\operatorname{do}\)操作符背景信息的读者,应该查看这篇图解丰富且解释详尽的博客文章 Causal Effects via the Do-operator [Talebi, 2022],由Pearl 和 Mackenzie [2018]撰写的科普书籍,或由Molak [2023]编写的教科书。

三种不同的因果DAG#

注意

本节大量借鉴了文章 因果推断 2:通过一个玩具示例说明干预 [Huszár, 2019]。模仿是最真诚的赞美形式。

如果我们思考两个变量,\(x\)\(y\),之间的关系,我们可以提出许多不同的因果DAG。下面我们只考虑3种可能性,我们将它们标记为DAG 1、2和3。

  1. \(x\) 因果影响 \(y\)

  2. \(y\) 因果影响 \(x\)

  3. \(z\)\(x\)\(y\) 都有因果影响

我们可以更图形化地绘制这些内容如下:

Hide code cell source
g = gr.Digraph()

# DAG 1
g.node(name="x1", label="x")
g.node(name="y1", label="y")
g.edge(tail_name="x1", head_name="y1")

# DAG 2
g.node(name="y2", label="y")
g.node(name="x2", label="x")
g.edge(tail_name="y2", head_name="x2")

# DAG 3
g.node(name="z", label="z")
g.node(name="x", label="x")
g.node(name="y", label="y")
g.edge(tail_name="z", head_name="x")
g.edge(tail_name="z", head_name="y")

g
../_images/769f3655a31935b59de2f72085477145da207f3c2a83e8897de1faf52fdc4a8d.svg

我们也可以想象在Python代码中实现这样的因果DAG,以生成N个随机数。每一个随机数都会产生特定的联合分布,\(P(x, y)\),事实上,因为Ferenc Huszár在他的博客文章中很聪明,我们稍后会看到,这些都会产生相同的联合分布。

DAG 1

x = rng.normal(loc=0, scale=1, size=N)
y = x + 1 + np.sqrt(3) * rng.normal(size=N)

DAG 2

y = 1 + 2 * rng.normal(size=N)
x = (y - 1) / 4 + np.sqrt(3) * rng.normal(size=N) / 2

DAG 3

z = rng.normal(size=N)
y = z + 1 + np.sqrt(3) * rng.normal(size=N)
x = z

注意

这些代码片段很重要,因为它们定义了相同的联合分布 \(P(x,y)\),但它们具有不同的DAG结构。因此,当涉及到使用 \(\operatorname{do}\) 操作符进行干预时,它们可能会有不同的响应。值得回顾这些代码片段,以确保你理解它们与上述DAG结构的关系,并思考对变量进行干预将如何影响每个变量 \(x, y, z\) 的值(如果有影响的话)。

然而,我们将使用带有PyMC的贝叶斯因果DAG来实现这些。让我们看看如何做到这一点,然后使用pm.sample_prior_predictive从它们生成样本。随着我们逐步进行每个DAG,我们将提取样本用于稍后的绘图,并绘制PyMC模型的graphviz表示。你会发现,尽管这些在视觉上稍微复杂一些,但它们实际上与我们上面指定的因果DAG相匹配。

# number of samples to generate
N = 1_000_000
with pm.Model() as model1:
    x = pm.Normal("x")
    temp = pm.Normal("temp")
    y = pm.Deterministic("y", x + 1 + np.sqrt(3) * temp)
    idata1 = pm.sample_prior_predictive(samples=N, random_seed=rng)

ds1 = az.extract(idata1.prior, var_names=["x", "y"])

pm.model_to_graphviz(model1)
Sampling: [temp, x]
../_images/f87a884bce08cf9915c3a9b498eab08500f7320da118f662a39417c95ab0fe17.svg
with pm.Model() as model2:
    y = pm.Normal("y", mu=1, sigma=2)
    temp = pm.Normal("temp")
    x = pm.Deterministic("x", (y - 1) / 4 + np.sqrt(3) * temp / 2)
    idata2 = pm.sample_prior_predictive(samples=N, random_seed=rng)

ds2 = az.extract(idata2.prior, var_names=["x", "y"])

pm.model_to_graphviz(model2)
Sampling: [temp, y]
../_images/bae4df519f8d5e6a6dc49573d6ce2adf5e756b49b0afcaee526fda5c4086d95a.svg
with pm.Model() as model3:
    z = pm.Normal("z")
    temp = pm.Normal("temp")
    y = pm.Deterministic("y", z + 1 + np.sqrt(3) * temp)
    x = pm.Deterministic("x", z)
    idata3 = pm.sample_prior_predictive(samples=N)

ds3 = az.extract(idata3.prior, var_names=["x", "y"])

pm.model_to_graphviz(model3)
Sampling: [temp, z]
../_images/b75b897f8e469ea35d45abd54686de865c706ffd7e14bb465fb6759a0b7ebfa1.svg

联合分布, \(P(x,y)\)#

首先,让我们看一下每个DAG的联合分布,以说服自己这些实际上是相同的。

Hide code cell source
fig, ax = plt.subplots(1, 3, figsize=(12, 8), sharex=True, sharey=True)

for i, ds in enumerate([ds1, ds2, ds3]):
    az.plot_kde(
        ds["x"],
        ds["y"],
        hdi_probs=[0.25, 0.5, 0.75, 0.9, 0.95],
        contour_kwargs={"colors": None},
        contourf_kwargs={"alpha": 0.5},
        ax=ax[i],
    )
    ax[i].set(
        title=f"$P(x, y)$, DAG {i+1}",
        xlim=[-4, 4],
        xticks=np.arange(-4, 4 + 1, step=2),
        ylim=[-6, 8],
        yticks=np.arange(-6, 8 + 1, step=2),
        aspect="equal",
    )
    ax[i].axvline(x=2, ls="--", c="k")
../_images/b8c71a7245ccc4ef95c7ab4fb383cdef8f4d0204ea79d6a38f8ec9e144ef010d.png

在这一点上,我们已经遇到了3种不同的数据生成过程(及其对应的DAGs)。我们从先验分布中抽取了许多MCMC样本,并可视化了每个模型的联合分布\(P(x,y)\)。我们现在可以回顾条件分布(例如\(P(y|x=2\),见下一节)以及它们如何与干预分布\(P(y|\operatorname{do}=2)\)在接下来的部分中进行比较。

条件分布, \(P(y|x=2)\)#

在MCMC的精神中,通过样本表示概率分布,现在让我们计算条件分布。如果我们选择了所有\(x\)恰好为2的值,那么我们可能根本得不到任何样本,因此我们将做的是在2附近取一个非常窄的样本切片。因此,这些将是近似值——随着样本数量的增加和切片宽度的减小,我们的近似值将变得更加准确。

# Extract samples from P(y|x≈2)
conditional1 = ds1.query(sample="1.99 < x < 2.01")["y"]
conditional2 = ds2.query(sample="1.99 < x < 2.01")["y"]
conditional3 = ds3.query(sample="1.99 < x < 2.01")["y"]

所以现在我们已经得到了所有DAG的\(P(y|x=2)\)的MCMC估计。但在我们绘制它们之前,你还得再等一会儿。让我们继续计算\(P(y|\operatorname{do}(x=2))\),然后一次性绘制它们,以便我们可以进行比较。

干预分布, \(P(y|\operatorname{do}(x=2))\)#

对于每个3个DAG,让我们使用\(\operatorname{do}\)操作符,设置\(x=2\)。这将给我们一个新的DAG,我们将绘制graphviz表示,然后取样以表示干预分布。

重要

让我们花点时间回顾一下我们在这里所做的事情!我们采用了一个模型(model1),然后使用了\(\operatorname{do}\)函数并指定了一个我们想要进行的干预。在这种情况下,我们设置\(x=2\)。然后,我们得到了一个新模型,其中原始DAG按照我们上述设定的方式发生了变化。也就是说,我们定义了\(x=2\) 并且移除了来自输入节点到\(x\)的边。在这个第一个DAG中,没有输入边,但在下面的DAG2和DAG3中,情况并非如此。

因此,我们可以看到在DAG 1中,变量\(x\)仍然对\(y\)有因果影响。然而,在DAGs 2和3中,\(y\)不再受到\(x\)的因果影响。所以在DAGs 2和3中,我们的干预\(\operatorname{do}(x=2)\)\(y\)没有影响。

接下来我们将从这些干预分布中进行采样。请注意,我们使用的是受损模型,model1_domodel2_domodel3_do

with model1_do:
    idata1_do = pm.sample_prior_predictive(samples=N, random_seed=rng)

with model2_do:
    idata2_do = pm.sample_prior_predictive(samples=N, random_seed=rng)

with model3_do:
    idata3_do = pm.sample_prior_predictive(samples=N, random_seed=rng)
Sampling: [temp]
Sampling: [temp, y]
Sampling: [temp, z]

所以让我们比较所有3个DAG的条件分布和干预分布。

Hide code cell source
fig, ax = plt.subplots(1, 2, figsize=(10, 4), sharex=True, sharey=True)

az.plot_density(
    [conditional1, conditional2, conditional3],
    data_labels=["DAG 1", "DAG 2", "DAG 3"],
    combine_dims={"sample"},
    ax=ax[0],
    hdi_prob=1.0,
)
ax[0].set(xlabel="y", title="Conditional distributions\n$P(y|x=2)$")

az.plot_density(
    [idata1_do, idata2_do, idata3_do],
    data_labels=["DAG 1", "DAG 2", "DAG 3"],
    group="prior",
    var_names="y",
    ax=ax[1],
    hdi_prob=1.0,
)
ax[1].set(xlabel="y", title="Interventional distributions\n$P(y|\\operatorname{do}(x=2))$");
../_images/b0cfab4ac4fbece8b6357ac74333037612713756304cfe10212418b5fc783522.png

正如预期的那样,我们可以看到所有3个DAG的条件分布是相同的。请注意,这些分布不是估计参数的后验分布——我们在这里没有进行任何参数估计。

然而,对于干预分布来说,情况就不同了。在这里,DAG 1 有所不同,因为它是唯一一个我们的 \(\operatorname{do}(x=2)\) 干预对 \(y\) 产生因果效应的图。如果我们进一步思考,因为 \(\operatorname{do}\) 没有影响 这个 DAG 的结构,在这个例子中 \(P(y|\operatorname{do}(x=2)) = P(y|x=2)\)。然而,这并不是一个可以普遍化的结论,它只是这个特定简单 DAG 的一个特殊情况。

干预切断了\(x\)在DAGs 2和3中对\(y\)的任何因果影响。让我们回顾一下突变的DAG是什么样子的;突变的DAG 2如下所示,我们可以看到\(P(y|\operatorname{do}(x=2)) = P(y)\)

Hide code cell source
g = gr.Digraph()
g.node(name="y2", label="y")
g.node(name="x2", label="x")
g
../_images/2a4b7d64129ff49fb0df0d14e52b09d48d85bf2cc4fee45fedfadb9621e58fe0.svg

变异后的DAG 3如下所示。我们可以看到,对于这个DAG,\(P(y|\operatorname{do}(x=2)) = P(y|z)\)

Hide code cell source
g = gr.Digraph()
g.node(name="z", label="z")
g.node(name="x", label="x")
g.node(name="y", label="y")
g.edge(tail_name="z", head_name="y")
g
../_images/928c93b616da86ef4804e3b0c0551a572f93fe823863e8a3538895c83a901d5d.svg

\(P(y|\operatorname{do}(x=2))\) 对于DAG 2和DAG 3在这个人为设计的例子中实际上是相同的,因为细节被安排得使得所有DAG的边际分布 \(P(y)\) 相同。

概述#

希望我已经充分说明了为什么我们需要将技能扩展到贝叶斯统计之外。虽然这些方法仍然是,并将永远是PyMC的核心,但生态系统正在拥抱因果推理。

特别是,我们已经看到如何使用新的\(\operatorname{do}\)算子在因果模型的世界中实现现实或假设的干预,以获得干预分布。理解潜在的因果DAG以及干预如何改变这个DAG是构建我们对因果推理理解的关键组成部分。

令人兴奋的是,还有许多更多的因果推理思想和概念需要学习。而PyMC正在根据需要进行调整,以支持您所有的贝叶斯因果推断需求。

希望了解更多内容的读者建议查看引用的博客文章以及教科书,Pearl [2000], Glymour et al. [2016], McElreath [2018], Molak [2023].

作者#

参考资料#

[1] (1,2)

朱迪亚·珀尔和达纳·麦肯齐。为什么之书:因果关系的新科学。基础图书出版社,2018年。

[2] (1,2,3,4,5)

朱迪亚·珀尔。因果关系:模型、推理与推断。剑桥大学出版社,2000年。ISBN 978-0521895606。

[3]

Shawhin Talebi. 通过do-operator的因果效应。2022年。URL: https://towardsdatascience.com/causal-effects-via-the-do-operator-5415aefc834a (访问于2023-07-01)。

[4] (1,2)

亚历山大·莫拉克。Python中的因果推断与发现。Packt出版社,2023年。

[5]

Ferenc Huszár. 因果推断 2:通过一个玩具示例说明干预。2019. URL: https://www.inference.vc/causal-inference-2-illustrating-interventions-in-a-toy-example/ (访问于 2023-07-01).

[6]

Madelyn Glymour、Judea Pearl 和 Nicholas P Jewell。《统计学中的因果推断:入门》。John Wiley & Sons,2016年。

[7]

理查德·麦克埃尔雷思。统计重构:一个带有R和Stan示例的贝叶斯课程。查普曼和霍尔/CRC,2018年。

水印#

%load_ext watermark
%watermark -n -u -v -iv -w -p pytensor,xarray
Last updated: Mon Feb 19 2024

Python implementation: CPython
Python version       : 3.9.13
IPython version      : 8.18.1

pytensor: 2.18.6
xarray  : 2024.1.1

numpy     : 1.26.4
packaging : 23.2
graphviz  : 0.20.1
seaborn   : 0.13.2
arviz     : 0.17.0
pymc      : 5.10.4
matplotlib: 3.8.3

Watermark: 2.4.3