数据分析中最经典的警告莫过于“相关不等于因果”。辛普森悖论就是典型例子:某药物分组数据显示有效,合并数据后却显示有害,原因在于病情轻重这一混杂因素在两组中分布不均。要破解这类问题,需要引入因果推断的核心工具——干预与反事实推理。本文将从因果混淆的本质讲起,介绍结构因果模型、do演算和潜在结果框架,并给出可运行的代码示例。

因果混淆的本质:混杂变量与虚假相关
因果混淆指的是观察数据中的统计关联被第三方因素污染,导致我们无法判断A是否真的导致B。以经典的“冰淇淋销量与溺水人数正相关”为例,两者其实都由气温驱动,气温就是混杂变量。如果直接用观察数据回归,得到的系数反映的是“相关性路径”的总和,其中包含了一条虚假的后门路径:气温影响冰淇淋销量,同时气温影响溺水人数。
用因果图来描述更直观。设冰淇淋销量为X,溺水人数为Y,气温为Z,则有向边为Z→X和Z→Y。这条Z→X←…→Y结构的路径中,X和Y之间存在一条经过共同原因Z的通路,统计上体现为相关,但因果上X对Y没有任何直接作用。混杂的本质就是:存在一条从X到Y的后门路径,且路径上的变量未被控制。
识别混杂变量有一个经验法则:混杂变量必须同时是处理变量的原因和结果变量的原因。需要注意的是,只控制混杂变量还不够,有些变量反而是“对撞变量”(collider),控制它反而会引入新的虚假相关。例如“才华→录取←人脉”,录取是对撞变量,若只在录取者中分析才华与人脉的关系,会看到虚假的负相关。这说明盲目加入更多控制变量是不可靠的,必须依靠因果图明确变量间的结构关系。
干预与do演算:切断后门路径的数学工具
区分相关与因果的关键在于两个记号:P(Y|X)与P(Y|do(X))。前者是观察条件分布,包含所有自然路径的信息;后者是干预分布,表示外部强制设定X取某值后Y的分布。干预操作会切断所有指向X的边,相当于在因果图中删除Z→X,从而消除后门路径的干扰。只有当P(Y|do(X))与P(Y|X)相等时,观察性关联才能直接解读为因果效应。
do演算提供了一套规则来在干预分布与观察分布之间转换,其中最常用的是后门调整公式。当混杂变量集合Z满足后门准则(Z中不含X的后代,且Z阻断所有X到Y的后门路径)时,因果效应可以通过观察数据计算:
P(Y | do(X=x)) = Σ_z P(Y | X=x, Z=z) * P(Z=z)
这个公式的含义是:先在混杂变量的每个分层内估计条件概率,再按混杂变量的分布加权求和,相当于对混杂因素的分布做了标准化,抹平了干预前后的分布差异。这正是辛普森悖论的解药——按病情分层计算药效,再按病人病情构成加权,就能得到正确的因果结论。
在Python中可以用一个简单模拟验证后门调整的效果:
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 20000
# Z 是混杂变量(病情严重程度)
Z = rng.binomial(1, 0.5, n)
# 处理变量 X 受 Z 影响(重症更可能用药)
X = rng.binomial(1, 0.3 + 0.5 * Z)
# 结果 Y 同时受 X 和 Z 影响
Y = rng.binomial(1, 1 / (1 + np.exp(-(0.8 * X + 2.0 * Z - 1.5))))
df = pd.DataFrame({"Z": Z, "X": X, "Y": Y})
# 朴素估计:直接比较用药与不用药的康复率(有偏差)
naive = df[df.X == 1].Y.mean() - df[df.X == 0].Y.mean()
# 后门调整:按 Z 分层估计,再按 Z 的分布加权
effect = 0.0
for z in [0, 1]:
sub = df[df.Z == z]
p_z = sub.shape[0] / n
diff = sub[sub.X == 1].Y.mean() - sub[sub.X == 0].Y.mean()
effect += p_z * diff
print("朴素估计:", round(naive, 3))
print("后门调整:", round(effect, 3))
运行后会发现朴素估计被混杂因素严重扭曲(可能为负),而后门调整的结果接近真实效应0.8对应的概率提升。这段代码揭示了因果推断的实践逻辑:结构先于统计,只有明确了因果图,才能知道该调整哪些变量。
反事实推理:回答“如果当初”的问题
干预回答的是“如果我强制施加处理,结果会怎样”,而反事实回答的是更精细的问题:“这个已经接受处理的个体,如果当初没接受处理,结果会怎样”。反事实是因果阶梯的第三层,比干预更高一层,个体层面的反事实原则上不可观测——同一个人不可能同时吃药又不吃药,这被称为因果推断的根本问题。
潜在结果框架为反事实提供了形式化语言。每个个体有两个潜在结果Y(1)和Y(0),实际观察到的只是其中一个,缺失的那个就是反事实。个体处理效应为Y(1)-Y(0),虽然不可识别,但平均处理效应(ATE)在满足可忽略性假设(给定协变量,处理分配与潜在结果独立)下可以估计。匹配、回归调整、倾向得分加权都是填补反事实的手段。其中倾向得分加权相当于为每个个体构造一个“伪总体”,在该总体中处理分配近似随机。
下面用倾向得分逆概率加权估计ATE:
from sklearn.linear_model import LogisticRegression
# 用协变量 Z 预测接受处理的倾向得分
ps_model = LogisticRegression().fit(df[["Z"]], df["X"])
ps = ps_model.predict_proba(df[["Z"]])[:, 1]
# 逆概率加权:为每个个体加权,模拟随机分组
w1 = df.X / ps # 处理组权重
w0 = (1 - df.X) / (1 - ps) # 对照组权重
ate_ipw = (w1 * df.Y).sum() / w1.sum() - (w0 * df.Y).sum() / w0.sum()
print("IPW估计的ATE:", round(ate_ipw, 3))
IPW的核心思想是:接受处理但倾向得分低的个体(本不太可能被处理却处理了)在对照人群中代表性强,应赋予更高权重,反之亦然。加权之后,两组的协变量分布趋于一致,混杂效应被抵消,剩余差异即可归因于处理本身。实际应用中还可以用双重稳健估计(Doubly Robust)同时建模结果和倾向得分,只要其中一个模型正确,估计就一致,这是当前观察性因果推断的主流方法之一。
实践建议:从因果图开始,用实验数据验证
因果推断的第一步永远是画出因果图。哪怕只是粗略地列出变量间的假设关系,也能帮助你判断哪些变量该控制、哪些不该控制。常用的工具包括DAGitty和Python的dowhy库,dowhy提供了因果图定义、识别(自动寻找调整集)、估计和反驳检验的完整流程。
当观察数据无法满足识别假设时,随机对照实验(A/B测试)是最强的因果证据来源,因为随机分配天然切断了所有后门路径。但要注意实验中的常见陷阱:样本比例不匹配、网络溢出效应、以及 novelty effect 等都会污染因果结论。将观察性方法与实验结合使用——用观察数据提出假设,用实验验证——是业务场景中最稳健的工作流。
最后提醒两点:第一,所有因果方法都依赖无法从数据检验的假设,结论应表述为“在给定假设下成立”;第二,异质性处理效应往往比平均效应更有业务价值,因果森林(causal forest)等工具可以估计不同子群体的效应差异。掌握干预与反事实这两把钥匙,你就能在观察数据中看清真正的因果结构。