简介:这份资源是斯坦福大学Stefan Wager于2024年9月更新的《Causal Inference: A Statistical Learning Approach》教科书草稿,面向数据科学、统计学、经济学与公共政策领域的学习者和研究者,帮助其系统掌握基于统计学习方法的因果推断理论与应用框架。压缩包内仅含1个PDF文件,大小约1.66MB,轻量便携,适合在电脑或平板上随时查阅。全书共十二章,从随机对照试验中的差异均值估计与回归调整讲起,依次覆盖无混淆性与倾向得分、双重稳健方法与双重机器学习、异质处理效应估计、政策学习与经验福利最大化、自适应实验、平衡估计器、回归断点设计,并延伸至内生处理、局部平均处理效应、溢出与干扰等进阶主题,每章附有文献注记便于延伸阅读。目前已有1996人学习下载,适合希望从统计学习视角深入理解因果关系的中高级读者作为教材或参考书使用。
1. 从一份 2024 年 9 月的草稿说起:这本因果推断教材到底能解决什么
如果你做过 AB 实验,大概率遇到过这种场景:实验组转化率涨了 3%,老板问“这 3% 里有多少是真实因果效应,多少是用户本来就打算买”,你盯着 p 值答不上来。差异均值能告诉你两组有区别,但回答不了“为什么”和“对谁有效”。这份 2024 年 9 月 6 日的草稿版教材,作者是 Stefan Wager,斯坦福大学统计学习方向的教授,全书 16 章加习题,从随机对照试验一路铺到回归断点、干扰效应和动态策略评估。它不是那种只讲潜在结果框架的入门读物,而是把因果推断当成统计学习问题来写——双重机器学习、半参数效率界、策略学习、自适应实验,这些在工业界真正落地时绕不开的工具,书里都有独立章节。适合谁?做实验平台的数据科学家、需要从观测数据里挖因果效应的分析师、以及想把因果推断和机器学习接起来的研究生。如果你只想知道“AB 实验怎么做 t 检验”,这本书前两章够用;如果你想搞清楚“为什么双重稳健估计比 IPW 更稳”“异质处理效应怎么建模”,那它值得你花时间啃。
2. 随机对照试验的估计量:从差异均值到回归调整
2.1 差异均值估计的无偏性到底依赖什么
书里第 1 章开篇就给了 Neyman-Rubin 潜在结果框架的定义:每个个体 i 有两个潜在结果 Y_i(1) 和 Y_i(0),个体因果效应 Δ_i = Y_i(1) - Y_i(0)。根本问题是只能观测到一个,所以 Δ_i 永远不可知。但随机化让我们能估计样本平均处理效应 SATE 和总体平均处理效应 ATE。
差异均值估计量的形式很简单:
import numpy as np def diff_in_means(Y, W): """ Y: 观测结果数组, shape (n,) W: 处理分配数组, 0 或 1, shape (n,) 返回: 差异均值估计量 """ n1 = np.sum(W == 1) n0 = np.sum(W == 0) if n1 == 0 or n0 == 0: raise ValueError("处理组或对照组为空,无法估计") tau_dm = np.mean(Y[W == 1]) - np.mean(Y[W == 0]) return tau_dm逻辑说明:这个估计量就是处理组均值减对照组均值。参数上唯一需要注意的是 n1 和 n0 都不能为 0,否则分母炸掉。书里定理 1.1 证明了在 SUTVA 和随机分配条件下,它是 SATE 的有限样本无偏估计。SUTVA 的意思是“个体 i 的潜在结果只取决于自己接受的处理,不受其他人处理分配影响”——医学试验里通常成立,但社交网络、市场平台这类有溢出效应的场景就不成立,书里第 11、12 章专门处理这个问题。
我一般会提醒团队:差异均值无偏,但不代表它是最优的。书里明确说了,它没有利用协变量信息,方差可能很大。如果你有基线协变量,回归调整能显著降方差。
2.2 回归调整:为什么加了协变量反而更稳
书里 1.2 节讲回归调整。核心思路是在差异均值的基础上,用协变量 X 做调整,估计量变成:
import numpy as np from sklearn.linear_model import LinearRegression def regression_adjustment(Y, W, X): """ Y: 结果, shape (n,) W: 处理, shape (n,) X: 协变量矩阵, shape (n, p) 返回: 回归调整估计量 """ # 对处理组和对照组分别拟合,或者用交互项模型 # 这里用最简单的:Y ~ W + X,W 的系数即估计量 X_aug = np.column_stack([W, X]) model = LinearRegression(fit_intercept=True) model.fit(X_aug, Y) tau_adj = model.coef_[0] # W 的系数 return tau_adj逻辑说明:把处理指示变量 W 和协变量 X 一起放进线性回归,W 的系数就是调整后的处理效应估计。参数上要注意,如果 X 和处理效应有交互(即处理效应随 X 变化),这个模型设定就不够,需要用 W×X 交互项。书里指出,回归调整在随机试验中是无偏的,而且当协变量与结果相关时,方差比差异均值小。但有个坑:如果模型设定错误(比如真实关系是非线性的,你用了线性),估计量可能有偏。常见做法是至少把 X 中心化,或者用更灵活的模型。
提示:回归调整的降方差效果取决于协变量与结果的相关强度。如果 X 和 Y 几乎不相关,加了反而增加自由度消耗,方差可能不降反升。
2.3 有限样本 vs 总体推断:你到底在推断什么
书里区分了有限样本分析和总体渐近分析。定理 1.1 是无偏性,不依赖任何分布假设,条件在潜在结果上。但要做置信区间,书里引入了总体抽样假设:把 n 个研究对象的潜在结果看作从某个总体 P 中独立抽取。在这个假设下,定理 1.2 给出了中心极限定理:
√n(τ̂_DM - τ) ⇒ N(0, V_DM),其中 V_DM = Var[Y_i(0)]/(1-π) + Var[Y_i(1)]/π。
对应的方差估计量是:
def variance_dm(Y, W): """ 差异均值估计量的方差估计 """ n = len(Y) n1 = np.sum(W == 1) n0 = np.sum(W == 0) var1 = np.var(Y[W == 1], ddof=1) var0 = np.var(Y[W == 0], ddof=1) # 书里公式 (1.10) 的 plug-in 版本 V_hat = n / n0 * var0 + n / n1 * var1 return V_hat参数说明:ddof=1 用样本方差的无偏估计。n/n0 和 n/n1 是权重,反映两组样本量的倒数。这个方差估计量是一致的,可以用来构造渐近置信区间:τ̂ ± Φ^{-1}(1-α/2) * sqrt(V̂/n)。
这里有个容易翻车的地方:如果你做的是完全随机化试验(固定 n1 个处理),而不是 Bernoulli 试验(每个个体独立以概率 π 接受处理),方差公式会略有不同。书里在脚注里专门提了这一点。我见过有人混用公式,置信区间覆盖率不对,还以为是数据问题。
3. 无混淆性与倾向得分:观测研究的第一道防线
3.1 分层估计:把混杂变量切成块
书里第 2 章转向观测研究。核心假设是无混淆性:给定协变量 X,处理分配 W 与潜在结果独立。用数学写就是 {Y_i(0), Y_i(1)} ⊥ W_i | X_i。这个假设没法用数据检验,只能靠领域知识论证。
分层估计的思路很直白:把 X 的取值空间切成若干层,每层内近似随机,然后在层内做差异均值,最后按层大小加权平均。
import numpy as np import pandas as pd def stratified_estimation(Y, W, X, n_strata=5): """ Y: 结果, W: 处理, X: 协变量(单变量,用于分层) 返回: 分层估计量 """ # 按 X 的分位数分层 strata = pd.qcut(X, q=n_strata, labels=False, duplicates='drop') tau_strat = 0 total = len(Y) for s in np.unique(strata): mask = strata == s n_s = np.sum(mask) if n_s == 0: continue Y_s = Y[mask] W_s = W[mask] if np.sum(W_s == 1) == 0 or np.sum(W_s == 0) == 0: continue # 该层只有一种处理,跳过 tau_s = np.mean(Y_s[W_s == 1]) - np.mean(Y_s[W_s == 0]) tau_strat += (n_s / total) * tau_s return tau_strat逻辑说明:qcut 按分位数切,保证每层样本量大致均衡。参数 n_strata 控制层数,太少则层内混杂没消干净,太多则每层样本不足。书里建议每层至少要有几个处理组和对照组样本,否则该层估计不稳定。常见做法是层数在 5 到 10 之间,或者用交叉拟合的方式自动选层。
坑在哪:如果 X 是多维的,分层会遭遇维数灾难——每个格子里的样本稀疏到无法估计。这时候倾向得分就派上用场了。
3.2 逆倾向加权:用概率倒数重新加权样本
倾向得分 e(X) = P(W=1|X) 是把多维 X 压成一维的降维工具。逆倾向加权(IPW)估计量:
from sklearn.linear_model import LogisticRegression import numpy as np def ipw_estimation(Y, W, X): """ 逆倾向加权估计 """ # 第一步:估计倾向得分 model = LogisticRegression(max_iter=1000) model.fit(X, W) e_hat = model.predict_proba(X)[:, 1] # 截断极端倾向得分,防止方差爆炸 e_hat = np.clip(e_hat, 0.05, 0.95) # 第二步:加权 n = len(Y) tau_ipw = np.mean(W * Y / e_hat) - np.mean((1 - W) * Y / (1 - e_hat)) return tau_ipw逻辑说明:处理组样本用 1/e(X) 加权,对照组用 1/(1-e(X)) 加权,模拟出一个“伪总体”,其中处理分配与 X 独立。参数上最关键的是截断阈值——如果某个样本的倾向得分接近 0 或 1,它的权重会爆炸,导致估计量方差极大。常见做法是截断在 0.05 到 0.95 之间,或者用稳定化权重。
书里 2.2 节还讨论了 IPW 的渐近性质:在倾向得分模型正确设定时,IPW 是无偏且渐近正态的。但模型错了就有偏。而且 IPW 对极端倾向得分非常敏感,这是它最大的软肋。
注意:倾向得分模型的选择很关键。如果真实倾向得分是非线性的,你用逻辑回归可能拟合不好。常见做法是加入样条项或使用梯度提升树,但后者需要交叉拟合来避免过拟合偏差。
3.3 倾向得分的实操边界
书里在文献注记里提到,倾向得分方法在经济学和流行病学中广泛使用,但它的有效性高度依赖无混淆性假设。这个假设在观测研究中往往不可验证。我一般会建议团队做敏感性分析:如果存在一个未观测混杂变量,它需要多强才能推翻结论?书里没有展开 Rosenbaum 边界,但这是实操中必须补的一课。
另一个边界是重叠性:如果某些 X 取值下只有处理组或只有对照组,倾向得分接近 0 或 1,IPW 和分层都失效。这时候只能做局部估计,或者重新定义目标人群。
4. 双重稳健与双重机器学习:当模型可能错时怎么办
4.1 双重稳健估计的基本形式
书里第 3 章讲双重稳健方法。核心思想:同时建模结果回归 μ(X, W) = E[Y|X, W] 和倾向得分 e(X),构造一个估计量,只要其中一个模型正确,估计就是一致的。
AIPW(增广逆倾向加权)估计量:
import numpy as np from sklearn.linear_model import LinearRegression, LogisticRegression def aipw_estimation(Y, W, X): """ 增广逆倾向加权估计 """ # 结果模型 model_y = LinearRegression() model_y.fit(np.column_stack([W, X]), Y) mu1 = model_y.predict(np.column_stack([np.ones(len(Y)), X])) mu0 = model_y.predict(np.column_stack([np.zeros(len(Y)), X])) # 倾向模型 model_w = LogisticRegression(max_iter=1000) model_w.fit(X, W) e_hat = np.clip(model_w.predict_proba(X)[:, 1], 0.05, 0.95) # AIPW n = len(Y) tau_aipw = np.mean( mu1 - mu0 + W * (Y - mu1) / e_hat - (1 - W) * (Y - mu0) / (1 - e_hat) ) return tau_aipw逻辑说明:mu1 - mu0 是结果模型的直接估计,后面两项是加权残差修正。如果结果模型对,残差项期望为零,估计量退化为结果模型估计;如果倾向模型对,修正项把结果模型的偏差纠回来。参数上,两个模型都需要交叉拟合来避免过拟合——书里 3.1 节专门讲了这一点。
4.2 双重机器学习:交叉拟合与 Neyman 正交性
书里 3.1 节的双重机器学习(DML)是 AIPW 的升级版。关键创新是交叉拟合:把数据分成 K 折,每折用其他折训练模型,再对当前折做预测。这样避免了过拟合导致的偏差。
from sklearn.model_selection import KFold import numpy as np def dml_estimation(Y, W, X, n_folds=5): """ 双重机器学习估计(部分线性模型) """ n = len(Y) mu_hat = np.zeros(n) e_hat = np.zeros(n) kf = KFold(n_splits=n_folds, shuffle=True, random_state=42) for train_idx, test_idx in kf.split(X): # 结果模型 model_y = LinearRegression() model_y.fit(np.column_stack([W[train_idx], X[train_idx]]), Y[train_idx]) mu_hat[test_idx] = model_y.predict( np.column_stack([W[test_idx], X[test_idx]]) ) # 倾向模型 model_w = LogisticRegression(max_iter=1000) model_w.fit(X[train_idx], W[train_idx]) e_hat[test_idx] = model_w.predict_proba(X[test_idx])[:, 1] e_hat = np.clip(e_hat, 0.05, 0.95) # 残差 Y_res = Y - mu_hat W_res = W - e_hat # 部分线性模型的 DML 估计 tau_dml = np.mean(W_res * Y_res) / np.mean(W_res * W) return tau_dml逻辑说明:交叉拟合保证 mu_hat 和 e_hat 是“样本外”预测,避免过拟合偏差。Neyman 正交性意味着估计量对两个模型的局部扰动不敏感,即使模型有轻微错误,估计量的偏差也是二阶小量。参数上 n_folds 通常取 5 或 10,折数越多计算越贵但偏差越小。
书里 3.2 节还讨论了无混淆下的有效估计——在给定假设下,能达到半参数效率界的估计量。这部分理论性较强,但实操意义在于:如果你用了 DML,你的估计量在渐近意义上是最优的(方差最小)。
4.3 双重稳健的实操坑
第一个坑:两个模型都错。双重稳健只在“至少一个正确”时保证一致性。如果两个都错,估计量可能有偏。我一般会做交叉验证检查两个模型的预测性能,如果都不好,结论要谨慎。
第二个坑:倾向得分截断。DML 里同样需要截断,否则权重爆炸。书里建议截断阈值随样本量调整,但实操中 0.05 到 0.95 是常见起点。
第三个坑:交叉拟合的随机性。不同的折划分会给出略微不同的估计。常见做法是重复多次交叉拟合,取平均,同时报告标准差。
5. 异质处理效应与策略学习:从“平均有效”到“对谁有效”
5.1 半参数建模与 CATE 估计
书里第 4 章讲异质处理效应。条件平均处理效应 CATE:τ(x) = E[Y(1) - Y(0)|X=x]。估计 CATE 比 ATE 难得多,因为每个 x 处的样本更少。
书里介绍的方法包括半参数建模和针对处理异质性的损失函数。一个常见做法是用因果森林或 BART,但书里更强调半参数效率理论。实操中,我一般会用以下策略:
from sklearn.ensemble import GradientBoostingRegressor import numpy as np def cate_estimation(Y, W, X, n_folds=5): """ 用 T-learner 估计 CATE """ from sklearn.model_selection import KFold n = len(Y) tau_hat = np.zeros(n) kf = KFold(n_splits=n_folds, shuffle=True, random_state=42) for train_idx, test_idx in kf.split(X): # 分别拟合处理组和对照组 model1 = GradientBoostingRegressor(n_estimators=100, max_depth=3) model0 = GradientBoostingRegressor(n_estimators=100, max_depth=3) model1.fit(X[train_idx][W[train_idx] == 1], Y[train_idx][W[train_idx] == 1]) model0.fit(X[train_idx][W[train_idx] == 0], Y[train_idx][W[train_idx] == 0]) tau_hat[test_idx] = model1.predict(X[test_idx]) - model0.predict(X[test_idx]) return tau_hat逻辑说明:T-learner 分别对处理组和对照组拟合模型,预测值之差就是 CATE 估计。参数上 max_depth 控制模型复杂度,太深会过拟合,太浅会欠拟合。书里指出,T-learner 在样本量不均衡时表现差——如果处理组很少,model1 拟合不好,CATE 估计就有偏。
5.2 策略学习:从 CATE 到最优决策规则
书里第 5 章讲策略学习。给定 CATE 估计,最优策略是:如果 τ(x) > 0,则处理;否则不处理。但实操中要考虑成本——如果处理有成本 c,则最优策略是 τ(x) > c 时处理。
书里 5.2 节的经验福利最大化框架把这个决策问题形式化了。策略价值 V(π) = E[Y(π(X))],目标是找到最大化 V 的策略。书里给出了策略评估和策略学习的方法,包括 AIPW 版本的策略价值估计。
def policy_value(Y, W, X, policy, e_hat, mu_hat): """ 用 AIPW 估计策略价值 policy: 函数,输入 X 返回 0/1 决策 """ pi = policy(X) n = len(Y) value = np.mean( mu_hat[range(n), pi] + (W == pi) * (Y - mu_hat[range(n), W]) / np.where(pi == 1, e_hat, 1 - e_hat) ) return value逻辑说明:这个估计量是策略价值的双重稳健估计。参数上 mu_hat 是结果模型的预测矩阵,两列分别对应处理和不处理。e_hat 是倾向得分。策略 π 可以是任何从 X 到 {0,1} 的映射。
坑在哪:策略学习容易过拟合——在训练集上找到的最优策略,在测试集上可能表现差。书里建议用交叉验证或样本分割来评估策略价值。
6. 避坑与排查:因果推断落地时最容易翻车的五件事
6.1 现象:置信区间覆盖率远低于名义水平
原因:倾向得分极端值未截断,或者方差估计公式用错(完全随机化 vs Bernoulli 试验混用)。
解决:检查倾向得分分布,截断在 0.05 到 0.95;确认试验设计类型,用对应的方差公式。书里定理 1.2 的方差公式适用于 Bernoulli 试验,完全随机化试验需要调整。
6.2 现象:DML 估计和 AIPW 估计差异很大
原因:两个模型的拟合质量差异大,或者交叉拟合的折数太少导致预测不稳定。
解决:检查两个模型的交叉验证误差;增加折数到 10;重复多次交叉拟合取平均。如果差异持续存在,说明至少一个模型设定有问题。
6.3 现象:CATE 估计在某些子群体上方差极大
原因:该子群体样本量太少,或者处理组/对照组不均衡。
解决:检查子群体的样本量和处理分配比例;如果样本太少,考虑合并相似子群体,或者用正则化方法(如因果森林的 honesty 分裂)来稳定估计。
6.4 现象:策略在训练集上表现好,测试集上崩了
原因:策略过拟合,或者策略价值估计用了同样的数据来选择和评估。
解决:用样本分割——一半数据学策略,另一半评估;或者用交叉验证的策略价值估计。书里 5.1 节的策略评估方法要求评估数据独立于策略学习数据。
6.5 现象:无混淆性假设不成立,但不知道偏多少
原因:存在未观测混杂变量。
解决:做敏感性分析。常见做法是计算 Rosenbaum 边界或 E-value,量化需要多强的未观测混杂才能推翻结论。书里没有展开这部分,但这是观测研究发表的标配。
7. 回归断点与干扰效应:两个容易被低估的章节
7.1 回归断点设计的局部线性回归
书里第 8 章讲回归断点。核心场景:处理分配由一个连续变量 X 是否超过阈值 c 决定。在阈值附近,处理组和对照组近似随机。
局部线性回归的实操:
import numpy as np from sklearn.linear_model import LinearRegression def rd_local_linear(Y, X, c, bandwidth): """ Y: 结果, X: 驱动变量, c: 阈值, bandwidth: 带宽 """ mask = np.abs(X - c) <= bandwidth X_centered = X[mask] - c W = (X[mask] >= c).astype(int) # 分别拟合两侧 model1 = LinearRegression() model0 = LinearRegression() model1.fit(X_centered[W == 1].reshape(-1, 1), Y[mask][W == 1]) model0.fit(X_centered[W == 0].reshape(-1, 1), Y[mask][W == 0]) # 阈值处的截距差 tau_rd = model1.intercept_ - model0.intercept_ return tau_rd逻辑说明:带宽 bandwidth 是关键参数——太小则样本不足,太大则线性近似不成立。书里 8.2 节讲了最优带宽选择和偏差感知推断。常见做法是用 IK 或 CCT 方法自动选带宽。
7.2 干扰效应:当 SUTVA 不成立时
书里第 11、12 章讲干扰效应。社交网络、市场平台、传染病干预——这些场景下,一个人的处理会影响另一个人的结果。书里介绍了暴露映射和置换检验,以及有限总体方法。
这部分实操门槛较高,但书里给了清晰的框架。如果你做的是平台实验,用户之间存在互动,SUTVA 不成立,差异均值估计有偏。书里建议用暴露映射重新定义处理,或者用聚类随机化。
7.3 一个具体技巧:用模拟数据验证你的估计流程
在真实数据上跑之前,我习惯先用模拟数据验证整个流程。构造已知真实效应的数据,跑一遍估计量,看置信区间是否覆盖真值。
import numpy as np def simulate_and_verify(n=2000, true_tau=1.0, seed=42): """ 模拟数据验证因果估计流程 """ rng = np.random.default_rng(seed) X = rng.normal(0, 1, (n, 3)) e = 1 / (1 + np.exp(-X[:, 0])) W = rng.binomial(1, e) Y0 = X[:, 0] + X[:, 1] + rng.normal(0, 1, n) Y1 = Y0 + true_tau + 0.5 * X[:, 2] Y = W * Y1 + (1 - W) * Y0 # 跑你的估计量 tau_hat = aipw_estimation(Y, W, X) print(f"真实 ATE: {true_tau + 0.5 * np.mean(X[:, 2]):.3f}") print(f"估计 ATE: {tau_hat:.3f}") return tau_hat逻辑说明:这个模拟里,处理效应是异质的(随 X[:,2] 变化),倾向得分是非线性的。如果你的估计量能接近真实值,说明流程基本正确。参数上 n 越大越稳定,true_tau 是基准效应。
从那以后我每次上线新的因果推断流程前,都强制走一遍模拟验证——先确认代码在已知真值的数据上能跑对,再碰真实数据。这个习惯帮我省了至少三次“估计量符号反了”的翻车。希望帮到你。
本文还有配套的精品资源,点击获取