Skip to content

2.5 do-算子与前门准则

当不可观测混淆存在时,因果效应仍然可以识别

难度方法应用


do-算子:干预的数学化

do-算子定义

Pearl (2000) 的核心贡献:

含义: 如果我们强制取值为,切断所有指向的箭头,Y的分布是什么?

do-算子 vs 条件概率

这是因果推断中最重要的不等式

  • : 观察到后,的条件分布(被动观察)
  • : 强制设定后,的分布(主动干预)

例子: 吸烟与肺癌

DAG:
         基因
        /    \
       ↓      ↓
    吸烟 →  肺癌

观察相关:

可能部分由基因混淆。

因果效应:

通过干预(强制吸烟),切断"基因→吸烟"箭头,得到纯因果效应。


do-算子的图操作

算法

do-算子对应 DAG 上的一个简单操作:

  1. 在DAG中,删除所有指向的箭头
  2. 在修改后的DAG(记为)中计算

直觉: 干预相当于把从一个受其他变量影响的内生变量,变成一个由外部力量决定的外生变量。

公式

中。

其中在原始DAG中的父节点集合。

与后门调整的关系

当后门准则成立时,do-算子可以通过后门调整公式计算:

这意味着:干预分布可以从观察数据中计算出来——不需要真的做实验!


前门准则(Frontdoor Criterion)

为什么需要前门准则?

后门准则要求我们能够观测到足够多的混淆变量来阻断后门路径。但在很多实际情况中,混淆变量是不可观测的。

前门准则: 当存在中介变量时的识别策略——即使存在不可观测混淆!

定义

前门准则成立的条件:

条件1: 截获所有从的有向路径

条件2: 之间没有后门路径

条件3: 所有的后门路径被阻断

前门调整公式

如果满足前门准则:

直觉: 分两步走

  1. 先估计的因果效应: (没有后门路径,直接可估计)
  2. 再估计的因果效应: 通过对边际化,用阻断的后门路径
  3. 两步组合得到的因果效应

经典例子:吸烟与肺癌

DAG

DAG:
         基因(U,不可观测)
        /              \
       ↓                ↓
    吸烟(X) → 焦油(M) → 肺癌(Y)

为什么后门准则不适用?

问题: 基因不可观测,无法用后门准则!

后门路径 无法通过控制来阻断。

为什么前门准则适用?

检查: 焦油满足前门准则!

条件1: (焦油)截获了(吸烟)到(肺癌)的所有有向路径 ✓

  • 唯一的有向路径是 ,在路径上

条件2: 之间没有后门路径 ✓

  • 没有其他变量同时影响吸烟和焦油(基因不直接影响焦油)

条件3: 的后门路径 阻断 ✓

  • 控制后,这条后门路径被阻断

估计步骤

步骤1: 估计:

这可以直接从数据中估计(因为吸烟和焦油之间没有后门路径)。

步骤2: 估计:

步骤3: 边际化:

意义

即使有不可观测混淆,仍可识别因果效应! 这是 Pearl 因果推断理论最令人惊叹的成果之一。


中介分析(Mediation Analysis)

直接效应 vs 间接效应

设定:

    X → M → Y
     \     /
      ↓   ↓

总效应(Total Effect):

直接效应(Direct Effect):

固定中介,X对Y的效应。

间接效应(Indirect Effect):

通过中介的效应。

自然直接/间接效应

Baron & Kenny (1986) 的经典框架:

自然直接效应(NDE):

自然间接效应(NIE):

其中是当的值。

分解:

识别条件

需要假设:

1. 无混淆: 外生

2. 无中介-结果混淆: 没有影响的共同原因

3. 无处理-中介混淆


Python实现:中介分析

python
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression

def mediation_analysis(X, M, Y):
    """
    中介分析

    参数:
    ------
    X: (N,) 处理
    M: (N,) 中介变量
    Y: (N,) 结果

    返回:
    ------
    effects: dict, 包含总效应、直接效应、间接效应
    """
    N = len(X)

    # 步骤1:总效应 (Y ~ X)
    model_total = LinearRegression()
    model_total.fit(X.reshape(-1, 1), Y)
    total_effect = model_total.coef_[0]

    # 步骤2:X对M的效应 (M ~ X)
    model_mediator = LinearRegression()
    model_mediator.fit(X.reshape(-1, 1), M)
    a = model_mediator.coef_[0]  # X → M

    # 步骤3:控制M后,X对Y的直接效应 (Y ~ X + M)
    model_direct = LinearRegression()
    model_direct.fit(np.column_stack([X, M]), Y)
    c_prime = model_direct.coef_[0]  # 直接效应
    b = model_direct.coef_[1]  # M → Y

    # 间接效应
    indirect_effect = a * b

    # Sobel检验
    se_indirect = np.sqrt(b**2 * np.var(M) + a**2 * np.var(Y))
    z_score = indirect_effect / se_indirect
    p_value = 2 * (1 - stats.norm.cdf(abs(z_score)))

    results = {
        'Total Effect': total_effect,
        'Direct Effect': c_prime,
        'Indirect Effect': indirect_effect,
        'Proportion Mediated': indirect_effect / total_effect if total_effect != 0 else 0,
        'Sobel z': z_score,
        'p-value': p_value
    }

    return results

# 示例
np.random.seed(42)
N = 1000
X = np.random.binomial(1, 0.5, N)
M = 0.5*X + np.random.normal(0, 0.5, N)  # X → M
Y = 0.3*X + 0.4*M + np.random.normal(0, 0.5, N)  # X → Y, M → Y

results = mediation_analysis(X, M, Y)
for key, value in results.items():
    print(f"{key}: {value:.4f}")

️ DoWhy库:因果推断统一框架

DoWhy的四步框架

Microsoft开发的DoWhy统一了Pearl和Rubin:

步骤1: 建模(Model) - 构造因果图

步骤2: 识别(Identify) - 使用图准则识别因果效应

步骤3: 估计(Estimate) - 选择估计方法

步骤4: 反驳(Refute) - 敏感性分析

完整示例

python
import numpy as np
import pandas as pd
import dowhy
from dowhy import CausalModel
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']

# =================================================================
# 第1步:生成数据
# =================================================================

np.random.seed(123)
N = 2000

# 混淆变量
Z1 = np.random.normal(0, 1, N)
Z2 = np.random.binomial(1, 0.5, N)

# 处理(受Z影响)
logit_D = -0.5 + 0.8*Z1 + 0.5*Z2
p_D = 1 / (1 + np.exp(-logit_D))
D = np.random.binomial(1, p_D, N)

# 结果(受D和Z影响)
Y = 2*D + 1.5*Z1 + 0.8*Z2 + np.random.normal(0, 1, N)

# 创建DataFrame
data = pd.DataFrame({
    'D': D,
    'Y': Y,
    'Z1': Z1,
    'Z2': Z2
})

print(f"数据概览:")
print(data.describe())
print(f"\n简单均值差(有偏): {data[data['D']==1]['Y'].mean() - data[data['D']==0]['Y'].mean():.3f}")
print(f"真实ATE: 2.0")

# =================================================================
# 第2步:构造因果模型
# =================================================================

# 方法1:指定DAG
model = CausalModel(
    data=data,
    treatment='D',
    outcome='Y',
    graph="""
    digraph {
        Z1 -> D;
        Z2 -> D;
        Z1 -> Y;
        Z2 -> Y;
        D -> Y;
    }
    """
)

# 可视化DAG
model.view_model()
plt.title('因果图', fontsize=14, fontweight='bold')
plt.show()

# 方法2:自动识别混淆
model_auto = CausalModel(
    data=data,
    treatment='D',
    outcome='Y',
    common_causes=['Z1', 'Z2']
)

# =================================================================
# 第3步:识别因果效应
# =================================================================

identified_estimand = model.identify_effect(proceed_when_unidentifiable=True)
print("\n识别的因果效应:")
print(identified_estimand)

# =================================================================
# 第4步:估计因果效应(多种方法)
# =================================================================

# 4a. 后门调整(线性回归)
estimate_backdoor = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.linear_regression"
)
print(f"\n后门调整(回归): ATE = {estimate_backdoor.value:.3f}")

# 4b. 倾向得分匹配
estimate_psm = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.propensity_score_matching"
)
print(f"倾向得分匹配: ATE = {estimate_psm.value:.3f}")

# 4c. 倾向得分加权
estimate_ipw = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.propensity_score_weighting"
)
print(f"倾向得分加权: ATE = {estimate_ipw.value:.3f}")

# =================================================================
# 第5步:反驳/稳健性检验
# =================================================================

print("\n稳健性检验:")

# 5a. 随机共同原因
refute_random = model.refute_estimate(
    identified_estimand,
    estimate_backdoor,
    method_name="random_common_cause"
)
print(f"随机共同原因检验: p = {refute_random.refutation_result['p_value']:.3f}")

# 5b. 安慰剂处理
refute_placebo = model.refute_estimate(
    identified_estimand,
    estimate_backdoor,
    method_name="placebo_treatment_refuter"
)
print(f"安慰剂处理检验: 新估计 = {refute_placebo.estimated_effect:.3f}")

# 5c. 数据子集验证
refute_subset = model.refute_estimate(
    identified_estimand,
    estimate_backdoor,
    method_name="data_subset_refuter",
    subset_fraction=0.8
)
print(f"子集验证: 新估计 = {refute_subset.estimated_effect:.3f}")

# =================================================================
# 第6步:CATE估计(可选)
# =================================================================

from dowhy.causal_estimators.econml import Econml

# 使用EconML的DML估计CATE
estimate_dml = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.econml.dml.DML",
    method_params={
        "init_params": {
            'model_y': GradientBoostingRegressor(),
            'model_t': GradientBoostingRegressor()
        },
        "fit_params": {}
    }
)
print(f"\nDML估计: ATE = {estimate_dml.value:.3f}")

# 异质性分析
if hasattr(estimate_dml, 'estimator'):
    cate = estimate_dml.estimator.effect(data[['Z1', 'Z2']].values)
    print(f"CATE范围: [{cate.min():.3f}, {cate.max():.3f}]")

# =================================================================
# 第7步:可视化
# =================================================================

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 子图1:不同方法的估计
methods = ['后门调整', 'PSM', 'IPW', 'DML']
estimates = [
    estimate_backdoor.value,
    estimate_psm.value,
    estimate_ipw.value,
    estimate_dml.value
]

ax1 = axes[0]
ax1.barh(methods, estimates, color='steelblue', alpha=0.7)
ax1.axvline(x=2.0, color='red', linestyle='--', linewidth=2, label='真实ATE')
ax1.set_xlabel('估计的ATE', fontsize=12)
ax1.set_title('不同方法比较', fontsize=14, fontweight='bold')
ax1.legend()
ax1.grid(axis='x', alpha=0.3)

# 子图2:CATE分布
if 'cate' in locals():
    ax2 = axes[1]
    ax2.hist(cate, bins=50, alpha=0.7, color='green', edgecolor='black')
    ax2.axvline(x=cate.mean(), color='red', linestyle='--', linewidth=2,
                label=f'平均CATE = {cate.mean():.3f}')
    ax2.set_xlabel('CATE', fontsize=12)
    ax2.set_ylabel('频数', fontsize=12)
    ax2.set_title('异质性处理效应分布', fontsize=14, fontweight='bold')
    ax2.legend()
    ax2.grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()

print("\nDoWhy分析完成!")

DoWhy的优势

1. 统一框架: 融合Pearl(图)和Rubin(潜在结果)

2. 明确假设: 通过DAG清晰展示识别假设

3. 多种方法: 支持后门、前门、IV等

4. 自动化: 自动识别调整集

5. 稳健性: 内置多种反驳检验


️ 常见陷阱

陷阱1:错误的DAG

问题: 如果DAG画错,所有推断都错!

缓解:

  • 领域知识
  • 敏感性分析
  • 比较多个DAG

陷阱2:忽略时间顺序

问题: DAG必须反映时间因果顺序

错误:

收入 → 教育  (不合理!)

正确:

教育 → 收入

陷阱3:前门准则的适用范围

注意: 前门准则需要严格的条件,在实际应用中相对少见。大多数情况下,后门准则是更常用的识别策略。


本章总结

核心要点

1. 因果阶梯:

  • Level 1: 关联
  • Level 2: 干预
  • Level 3: 反事实

2. DAG的三大工具:

  • d-分离: 识别独立性
  • 后门准则: 通过调整识别因果效应
  • 前门准则: 利用中介识别

3. do-算子: 形式化干预

4. 中介分析: 分解总效应 = 直接效应 + 间接效应

5. DoWhy: 统一Pearl和Rubin的实用工具

关键公式

do-算子:

后门调整:

前门调整:

中介分解:

Python工具

  • dowhy: 因果推断统一框架
  • causalgraphicalmodels: DAG操作
  • networkx: 图论算法
  • pgmpy: 概率图模型

上一节: 2.4 碰撞偏差与控制变量选择 | 下一节: Module 3 Python与回归基础


因果图:从图形到因果的认知桥梁!

基于 MIT 许可证发布。内容版权归作者所有。