想象你是一名医生,面对两种新药A和B的临床试验数据。数据显示服用A药的患者康复率更高,但当你仔细分析时发现:年轻患者普遍选择A药且康复率更高,老年患者则相反。这时候如果直接得出"A药更有效"的结论,可能就掉进了辛普森悖论的陷阱。这正是因果推断要解决的核心问题——如何从观察数据中识别真正的因果关系。
在医疗、教育、经济等领域,我们常常需要回答"如果...会怎样"的问题。比如:
要回答这些问题,必须理解因果推断的三大基石假设:
稳定单位处理值假设(Stable Unit Treatment Value Assumption)就像要求实验室里的培养皿互不干扰。具体包含两层含义:
无干扰性:一个患者的治疗结果不会影响其他患者。比如在疫苗试验中,接种者是否产生抗体不应受其他接种者影响。现实中这个假设可能被打破——如果试验中存在群体免疫效应,接种者实际上降低了未接种者的感染风险。
单一版本处理:每种治疗方式只有唯一标准版本。例如在研究"吸烟对肺癌的影响"时,如果"吸烟"包含每天1支和每天20支的不同情况,就违反了这一假设。我曾参与的一个药物研究项目中,发现不同医院对"标准治疗方案"的执行存在差异,导致分析结果出现偏差。
这个假设又称无混杂假设,相当于要求治疗分配像抛硬币一样完全随机。数学上表示为:(Y(0),Y(1)) ⊥ W | X,即在给定协变量X后,治疗分配W与潜在结果独立。
举个例子:在研究教育水平对收入的影响时,如果聪明的人更可能接受高等教育(W)同时本身收入更高(Y),就存在智力这个混杂因子。只有当所有影响教育和收入的变量都被测量并控制时,这个假设才成立。
实际应用中,我们常用倾向得分匹配来近似满足这一假设。具体操作是:
python复制from sklearn.linear_model import LogisticRegression
# 计算倾向得分(接受治疗的概率)
ps_model = LogisticRegression().fit(X, W)
propensity_scores = ps_model.predict_proba(X)[:,1]
# 然后对每个处理组样本寻找对照组中倾向得分最接近的样本
这个假设要求对于任何X的取值,都有机会观察到所有处理状态:0 < P(W=w|X=x) < 1。就像临床试验不能只给年轻人用新药,而完全不给老年人使用机会。
违反这个假设的典型案例是:某医院规定BMI>30的患者必须接受手术,那么我们就无法研究手术对肥胖人群的真实效果,因为缺乏对照组数据。在实践中,可以通过数据修剪(trimming)删除倾向得分接近0或1的样本。
某医院统计了两种肾结石治疗方案的数据:
| 结石大小 | 治疗方案A成功率 | 治疗方案B成功率 | 患者数量 |
|---|---|---|---|
| 小型结石 | 93% (81/87) | 87% (234/270) | 357 |
| 大型结石 | 73% (192/263) | 69% (55/80) | 343 |
| 合计 | 78% (273/350) | 83% (289/350) | 700 |
乍看之下方案B更优,但分层后却发现方案A在各组都更好!这是因为:
这个案例同时违反了可忽略性(治疗分配与病情相关)和积极性假设(某些病情只用特定方案)。
在COVID-19疫苗研究中,如果社区接种率达到70%,未接种者感染风险也会降低。这意味着:
某研究用历史数据分析降压药效果,发现:
实际原因是:
这种情况需要工具变量等更高级方法处理。
用DAG(有向无环图)可以清晰表达我们的因果假设。例如:
code复制[年龄] → [治疗选择]
[年龄] → [康复概率]
[治疗选择] → [康复概率]
这个图明确显示年龄是混杂变量,指导后续分析策略。
结合回归模型和倾向得分模型的优势:
python复制from econml.dr import DRLearner
# 既指定结果模型Y~X,又使用倾向得分模型
est = DRLearner(model_propensity=LogisticRegression(),
model_regression=RandomForestRegressor())
est.fit(Y, W, X=X)
treatment_effects = est.effect(X_test)
这种方法只要其中一个模型正确,就能得到无偏估计。
通过R值量化未观测混杂因子的影响程度:
code复制假设存在未测量的混杂因子U
使处理组和对照组的风险比变化R倍
当R>2时结论是否仍然成立?
我在分析某医疗设备效果时,发现需要R>3.5才能逆转结论,这增强了结果可信度。
r复制# 使用R的cobalt包
library(cobalt)
bal.tab(treatment ~ age + gender + disease_stage, data=df)
python复制import seaborn as sns
sns.kdeplot(data=df, x='propensity_score', hue='treatment')
在实际项目中,我发现最常出现的问题是:
这时需要结合领域知识调整方法,比如: