Skip to content

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实现:完整分析

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库

python
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库

python
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 本章小结

基于 MIT 许可证发布。内容版权归作者所有。