7.5 经典案例与Python实现
LaLonde (1986): 非实验方法的终极挑战
经典评估: Lalonde (1986) 与非实验方法的挑战
倾向得分方法的发展史上,没有比 Lalonde (1986) 和 Dehejia & Wahba (1999) 的对话更具教育意义的案例了。
实验基准: NSW 随机实验
1970年代,美国实施了一项大规模就业培训随机对照试验——全国支持性工作示范项目(National Supported Work Demonstration, NSW)。该项目将求职困难者(长期失业者、前吸毒者、前罪犯等)随机分入培训组和对照组。由于随机化保证了内部有效性,实验估计的平均处理效应约为 $1,794 的年收入增长,这一数字成为后续所有非实验方法的"金标准"参照。
Lalonde 的破坏性实验
Lalonde (1986) 做了一件极具创意的事:他保留了NSW的处理组(185名培训参与者),但将实验对照组替换为来自 CPS(当前人口调查)和 PSID(收入动态面板调查)的观察性样本,然后用当时主流的计量方法(OLS、固定效应、DID等)重新估计培训效应。
结果是灾难性的:绝大多数估计值不仅远离真实效应,而且方向都是错的——呈现显著的负效应,仿佛培训反而降低了收入。
为什么失败?
根源在于处理组与观察性对照组之间存在极端的协变量不平衡。NSW处理组的典型特征是:平均年龄约24.6岁、约80%为黑人、多数无高中文凭、培训前收入极低。而CPS对照样本的特征完全不同:平均年龄约33.2岁、仅约7%为黑人、收入远高于处理组。这种程度的不平衡意味着两组人群几乎来自完全不同的总体,简单的回归调整根本无力纠正如此巨大的选择偏差。
Dehejia & Wahba (1999) 的修复
十多年后,Dehejia & Wahba (1999) 证明倾向得分方法可以"拯救"非实验估计——但前提是进行严格的共同支撑修剪。他们首先估计倾向得分,发现绝大多数CPS对照个体的倾向得分接近零(几乎不可能参加培训),与处理组的得分分布毫无重叠。因此,他们将15,992名CPS对照中的12,611名剔除,仅保留倾向得分与处理组有重叠的3,381名对照。
修剪后,倾向得分匹配估计的效应为 $1,473 至 $1,691,与实验基准 $1,794 非常接近。
核心教训
共同支撑条件绝非技术细节,而是倾向得分方法有效运作的先决条件。 当处理组和对照组的倾向得分分布严重不重叠时,说明两组之间存在根本性的不可比性,任何统计调整都难以弥补。在应用倾向得分方法之前,务必绘制两组的倾向得分分布图,识别并处理不重叠区域。
参见 Lalonde (1986); Dehejia & Wahba (1999); Cunningham (2021), Chapter 5
数据背景
研究信息
Robert LaLonde (1986): "Evaluating the Econometric Evaluations of Training Programs"
研究问题: 非实验方法能复现实验结果吗?
NSW (National Supported Work Demonstration): 就业培训项目RCT
- 处理组: 185人接受培训
- 对照组: 260人(实验对照)
LaLonde的贡献: 用非实验数据(PSID, CPS)替换实验对照组,检验PSM等方法。
变量
结果变量: 1978年收入(培训后)
协变量:
age: 年龄education: 受教育年限black: 是否黑人hispanic: 是否西裔married: 是否已婚nodegree: 是否无高中文凭re74: 1974年收入(培训前2年)re75: 1975年收入(培训前1年)
真实效应(来自RCT):
Python实现:完整分析
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import NearestNeighbors
from sklearn.ensemble import RandomForestRegressor
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt
# =================================================================
# 加载数据(模拟)
# =================================================================
np.random.seed(123)
n_treated = 185
n_control = 2490 # PSID对照
# 处理组(类似NSW参与者)
X_t = pd.DataFrame({
'age': np.random.normal(25, 7, n_treated),
'education': np.random.normal(10, 2, n_treated),
'black': np.random.binomial(1, 0.8, n_treated),
'hispanic': np.random.binomial(1, 0.1, n_treated),
'married': np.random.binomial(1, 0.2, n_treated),
'nodegree': np.random.binomial(1, 0.7, n_treated),
're74': np.random.gamma(2, 1500, n_treated),
're75': np.random.gamma(2, 1500, n_treated)
})
# 对照组(来自PSID,更高收入/教育)
X_c = pd.DataFrame({
'age': np.random.normal(35, 10, n_control),
'education': np.random.normal(12, 3, n_control),
'black': np.random.binomial(1, 0.25, n_control),
'hispanic': np.random.binomial(1, 0.05, n_control),
'married': np.random.binomial(1, 0.7, n_control),
'nodegree': np.random.binomial(1, 0.3, n_control),
're74': np.random.gamma(5, 3000, n_control),
're75': np.random.gamma(5, 3000, n_control)
})
X = pd.concat([X_t, X_c], ignore_index=True)
D = np.array([1]*n_treated + [0]*n_control)
# 生成结果(模拟真实效应$1794)
true_effect = 1794
Y_c = X['re75'] * 1.1 + X['education'] * 500 + np.random.normal(0, 3000, len(X))
Y_t = Y_c + true_effect
Y = D * Y_t + (1-D) * Y_c
# =================================================================
# 分析1:简单均值差(有偏!)
# =================================================================
naive = Y[D==1].mean() - Y[D==0].mean()
print(f"简单均值差(Naive): ${naive:.0f}")
# =================================================================
# 分析2:线性回归
# =================================================================
data = X.copy()
data['D'] = D
data['Y'] = Y
ols = smf.ols('Y ~ D + age + education + black + hispanic + married + nodegree + re74 + re75', data=data).fit()
print(f"\n线性回归估计: ${ols.params['D']:.0f}")
# =================================================================
# 分析3:PSM
# =================================================================
ATT_psm, matched_data, e_hat = estimate_psm(X.values, D, Y, n_neighbors=1, caliper=0.1)
print(f"\nPSM估计(1:1匹配): ${ATT_psm:.0f}")
# =================================================================
# 分析4:IPW
# =================================================================
ATE_ipw, weights = estimate_ipw(X.values, D, Y, stabilized=True, trim=(0.1, 0.9))
print(f"IPW估计(稳定权重): ${ATE_ipw:.0f}")
# =================================================================
# 分析5:Doubly Robust
# =================================================================
ATE_dr, _ = estimate_doubly_robust(X.values, D, Y, trim=(0.1, 0.9))
print(f"双重稳健估计: ${ATE_dr:.0f}")
print(f"\n真实效应(RCT): ${true_effect:.0f}")
# =================================================================
# 平衡性检验
# =================================================================
balance_before = balance_table(X.values, D, w=None, varnames=X.columns)
balance_after_ipw = balance_table(X.values, D, w=weights, varnames=X.columns)
print("\n平衡性检验:")
print(balance_before[['Variable', 'SMD']])
print("\nIPW后:")
print(balance_after_ipw[['Variable', 'SMD']])结果汇总
方法比较表
| 方法 | 估计值 | 与RCT的偏差 | 备注 |
|---|---|---|---|
| RCT基准 | $1,794 | — | 金标准 |
| 简单均值差 | 大幅偏离 | 极大 | 选择偏差 |
| OLS回归 | 部分修正 | 中等 | 函数形式敏感 |
| PSM | ~$1,500-1,700 | 小 | 需要共同支撑修剪 |
| IPW | ~$1,600-1,800 | 小 | 需要稳定权重 |
| Doubly Robust | ~$1,700-1,900 | 很小 | 最稳健 |
高级工具: CausalML与EconML
使用CausalML库
from causalml.propensity import ElasticNetPropensityModel
from causalml.match import NearestNeighborMatch
# 估计倾向得分
pm = ElasticNetPropensityModel()
pm.fit(X.values, D)
e_hat_causal = pm.predict(X.values)
# 匹配
matcher = NearestNeighborMatch(caliper=0.1, replace=False)
matched = matcher.match(data={'X': X.values, 'treatment': D, 'outcome': Y})
print(f"CausalML PSM ATT: ${matched['ATT']:.0f}")使用EconML库
from econml.metalearners import XLearner
from econml.dr import DRLearner
# X-learner
xl = XLearner(models=RandomForestRegressor())
xl.fit(Y, D, X=X.values)
ate_xl = xl.ate(X.values)
print(f"EconML X-Learner ATE: ${ate_xl:.0f}")
# DR-learner
dr = DRLearner(model_propensity=LogisticRegression(),
model_regression=RandomForestRegressor())
dr.fit(Y, D, X=X.values)
ate_dr_econml = dr.effect(X.values).mean()
print(f"EconML DR-Learner ATE: ${ate_dr_econml:.0f}")本节小结
核心教训
1. LaLonde (1986) 的警示: 非实验方法在极端不平衡数据上可能完全失败
2. Dehejia & Wahba (1999) 的修复: 严格的共同支撑修剪是关键
3. 方法比较: Doubly Robust 通常最稳健,但所有方法都需要:
- 共同支撑检查
- 平衡性检验
- 敏感性分析
Python工具
- sklearn:
LogisticRegression,NearestNeighbors - causalml:
ElasticNetPropensityModel,NearestNeighborMatch - econml:
DRLearner, Meta-learners - statsmodels: 回归基准
上一节: 7.4 IPW与双重稳健估计 | 下一节: 7.6 本章小结