13.5 因果图与结构因果模型
"Causal inference is not a statistical problem—it is a problem of translating causal assumptions into statistical estimands.""因果推断不是统计问题——它是将因果假设转化为统计估计量的问题。"— Judea Pearl, 2011 Turing Award Winner (2011年图灵奖得主)
Judea Pearl的因果革命:从相关到因果的认知飞跃
本节目标
- 理解有向无环图(DAG)与因果关系
- 掌握do-算子、后门准则、前门准则
- 学习中介分析(Mediation Analysis)
- 用图形理解IV、DID等方法
- 使用DoWhy库进行因果推断
- 理解Pearl vs Rubin的统一框架
引言:因果推断的三个层次
Judea Pearl的因果阶梯
《The Book of Why》(2018) 提出人类认知的三个层次:
Level 1: 关联(Association) - "看到"
- 问题: "如果我看到,关于我能推断什么?"
- 数学: - 条件概率
- 工具: 统计学、回归分析
- 例子: "吸烟者的肺癌率更高"
Level 2: 干预(Intervention) - "做"
- 问题: "如果我做,会发生什么?"
- 数学: - 干预分布
- 工具: RCT、IV、DID、RDD
- 例子: "如果强制戒烟,肺癌率会如何变化?"
Level 3: 反事实(Counterfactual) - "如果当初"
- 问题: "如果我当初做了,会怎样?"
- 数学: - 反事实概率
- 工具: 结构因果模型(SCM)
- 例子: "如果这个已患癌的吸烟者当初没吸烟,他还会得癌症吗?"
关键差异:
直觉:
- : 被动观察
- : 主动干预
- : 时光倒流
有向无环图(DAG)
定义
DAG (Directed Acyclic Graph):
- 节点(Node): 变量
- 有向边(Directed Edge): 因果关系
- 无环(Acyclic): 没有这样的循环
例子:
简单DAG:
X → Y
混淆DAG:
Z
/ \
↓ ↓
X → Y
中介DAG:
X → M → Y
对撞DAG:
X → Z ← Y图形元素
1. 路径(Path): 节点序列,忽略箭头方向
2. 有向路径(Directed Path): 沿箭头方向的路径
3. 父节点(Parent): ,则是的父节点
4. 子节点(Child): 是的子节点
5. 祖先(Ancestor): 沿有向路径可达
6. 后代(Descendant): 反向
三种基本结构
链(Chain):
- 是中介变量
- 控制 → 阻断路径
分叉(Fork):
- 是混淆变量
- 控制 → 阻断路径
对撞(Collider):
- 是对撞变量
- 不控制 → 路径阻断
- 控制 → 路径打开(️ 危险!)
d-分离与独立性
d-分离定义
d-separation (d-分离):
给定条件集,如果和之间的所有路径都被"阻断",则称和 d-分离。
阻断规则:
路径被阻断,当且仅当路径上存在节点满足:
链或分叉: 在条件集中
X → M → Y (控制M → 阻断) X ← M → Y (控制M → 阻断)对撞: 及其所有后代都不在中
X → M ← Y (不控制M → 阻断) X → M ← Y (控制M → 打开!)
d-分离的含义:
直觉: 图结构蕴含统计独立性!
例子:对撞偏误
场景: 招聘
才能 → 录用 ← 关系数据: 只观察被录用的人
问题: 在被录用者中,"才能"和"关系"负相关!
原因: 控制"对撞变量"(录用)打开了路径
教训: 永远不要无意中控制对撞变量!
do-算子:干预的数学化
do-算子定义
Pearl (2000) 的核心贡献:
含义: 如果我们强制取值为,切断所有指向的箭头,Y的分布是什么?
vs 条件概率:
例子: 吸烟与肺癌
基因
/ \
↓ ↓
吸烟 → 肺癌观察相关:
可能部分由基因混淆。
因果效应:
通过干预(强制吸烟),切断"基因→吸烟"箭头,得到纯因果效应。
do-算子的图操作
算法:
- 在DAG中,删除所有指向的箭头
- 在修改后的DAG(记为)中计算
公式:
在中。
后门准则(Backdoor Criterion)
定义
后门准则 (Pearl 1993):
给定DAG ,变量集满足后门准则相对于,如果:
条件1: 不包含的任何后代
条件2: 阻断和之间的所有后门路径
后门路径: 从到的路径,箭头指向
后门调整公式
如果满足后门准则:
含义: 通过条件调整,可以识别因果效应!
这就是传统回归的理论基础!
例子
Z
/ \
↓ ↓
X → Y后门路径:
满足后门准则:
因果效应:
Python:
# 通过分层估计因果效应
ate = 0
for z in z_values:
ate += P_Y_given_X_Z(Y, X, z) * P_Z(z)前门准则(Frontdoor Criterion)
定义
前门准则: 当存在中介变量时的识别策略
条件:
条件1: 截获所有从到的有向路径
条件2: 和之间没有后门路径
条件3: 所有到的后门路径被阻断
前门调整公式
如果满足前门准则:
经典例子:吸烟与肺癌
基因(U,不可观测)
/ \
↓ ↓
吸烟(X) → 焦油(M) → 肺癌(Y)问题: 基因不可观测,无法用后门准则!
解决: 焦油满足前门准则!
步骤:
1. 估计:
2. 估计:
3. 边际化:
意义: 即使有不可观测混淆,仍可识别因果效应!
中介分析(Mediation Analysis)
直接效应vs间接效应
设定:
X → M → Y
\ /
↓ ↓总效应(Total Effect):
直接效应(Direct Effect):
固定中介,X对Y的效应。
间接效应(Indirect Effect):
通过中介的效应。
自然直接/间接效应
Baron & Kenny (1986) 的经典框架:
自然直接效应(NDE):
自然间接效应(NIE):
其中是当时的值。
分解:
识别条件
需要假设:
1. 无混淆: 外生
2. 无中介-结果混淆: 没有影响和的共同原因
3. 无处理-中介混淆
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}")工具变量(IV)的图形理解
IV的DAG表示
传统IV设定:
Z → D → Y
↑ ↑
└─U─┘- : 工具变量
- : 内生处理
- : 结果
- : 不可观测混淆
IV假设的图形表述:
1. 相关性: (有边)
2. 排除性约束: 到的唯一路径是
3. 外生性: 和之间没有路径
用后门准则理解IV
问题: 估计
直接:后门路径无法阻断(不可观测)
间接: 利用!
步骤:
- 可识别(无后门路径)
- 可识别(无后门路径)
- IV估计:
️ DoWhy库:因果推断统一框架
DoWhy的四步框架
Microsoft开发的DoWhy统一了Pearl和Rubin:
步骤1: 建模(Model) - 构造因果图
步骤2: 识别(Identify) - 使用图准则识别因果效应
步骤3: 估计(Estimate) - 选择估计方法
步骤4: 反驳(Refute) - 敏感性分析
完整示例
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}")
# 4d. 工具变量(如果有)
# estimate_iv = model.estimate_effect(
# identified_estimand,
# method_name="iv.instrumental_variable"
# )
# =================================================================
# 第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. 稳健性: 内置多种反驳检验
DAG vs 潜在结果框架
两大流派对比
| 维度 | Pearl的DAG | Rubin的潜在结果 |
|---|---|---|
| 核心工具 | 图、do-算子 | |
| 识别 | 图准则(后门/前门) | 假设(CIA, SUTVA) |
| 优势 | 直观、非参数 | 统计推断严谨 |
| 劣势 | 图可能错误 | 假设难验证 |
| 适用 | 复杂因果结构 | 简单处理效应 |
统一
Imbens (2020): "The Two Approaches to Causal Inference: Structural and Reduced Form"
核心: 两者等价!
例子:
DAG: 满足后门准则相对于
Rubin:
都导出:
️ 常见陷阱
陷阱1:错误的DAG
问题: 如果DAG画错,所有推断都错!
缓解:
- 领域知识
- 敏感性分析
- 比较多个DAG
陷阱2:控制对撞变量
经典错误: 无意中控制了对撞变量
例子: Berkson's Paradox
病因A → 住院 ← 病因B在住院患者中,A和B负相关(虽然本无关)!
陷阱3:忽略时间顺序
问题: DAG必须反映时间因果顺序
错误:
收入 → 教育 (不合理!)正确:
教育 → 收入本节小结
核心要点
1. 因果阶梯:
- Level 1: 关联
- Level 2: 干预
- Level 3: 反事实
2. DAG的三大工具:
- d-分离: 识别独立性
- 后门准则: 通过调整识别因果效应
- 前门准则: 利用中介识别
3. do-算子: 形式化干预
4. 中介分析: 分解总效应 = 直接效应 + 间接效应
5. DoWhy: 统一Pearl和Rubin的实用工具
关键公式
do-算子:
后门调整:
前门调整:
中介分解:
Python工具
- dowhy: 因果推断统一框架
- causalgraphicalmodels: DAG操作
- networkx: 图论算法
- pgmpy: 概率图模型
下一节: 13.6 前沿专题与综合应用 - Staggered DID与综合案例
因果图:从图形到因果的认知桥梁!