Skip to content

9.5 经典案例和 Python 实现(Case Studies & Python)

从真实研究到可复现代码


本节目标

  • 了解 DID 的经典应用场景
  • 能用合成数据复现 DID 的完整流程
  • 掌握可迁移到真实数据集的代码模板

一、经典案例速览(概念性梳理)

  • 最低工资政策:以政策生效前后、受影响与未受影响地区的就业/工资为结果变量。
  • 环境管制/税收政策:对产能、排放、投资等的影响。
  • 教育/医疗改革:对学生成绩、健康指标的影响。

提示:在任何案例中,都要回到“平行趋势是否合理”这一核心问题(见 9.3)。


二、合成数据演示:从 0 到 1 的 DID(可运行)

python
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf

# 参数
n_units = 60      # 个体数量
n_periods = 10    # 时间期数
policy_time = 6   # 政策实施期(从 1 开始计数)

rng = np.random.default_rng(42)
ids = np.arange(n_units)
periods = np.arange(1, n_periods + 1)

data = []
for i in ids:
    treated = 1 if i >= n_units // 2 else 0
    unit_fe = rng.normal(0, 3)
    for t in periods:
        time_fe = 0.5 * t
        post = 1 if t >= policy_time else 0
        tau = 4.0  # 政策真实效应
        y = 20 + unit_fe + time_fe + tau * treated * post + rng.normal(0, 2)
        data.append({
            'id': i,
            'time': t,
            'treated': treated,
            'post': post,
            'y': y
        })

df = pd.DataFrame(data)

# DID 回归(含实体/时间固定效应,按实体聚类标准误)
model = smf.ols('y ~ treated*post + C(id) + C(time)', data=df) \
            .fit(cov_type='cluster', cov_kwds={'groups': df['id']})
print(model.summary().tables[1])
print('\nATE (treated:post) =', model.params['treated:post'])

扩展:

  • 事件研究(Event Study)以检查前趋势与动态效应(参见 9.3)。
  • 在多期/错位处理下,建议使用近期多期 DID 估计器(如 Sun & Abraham, Callaway & Sant'Anna 等实现)。

三、现实数据示例(操作模板)

  1. 整理数据到长表:一行=一个体×时期(含 id、time、treated、post、y)
  2. 做 9.3 的“前趋势与事件研究”以评估识别假设
  3. 估计基本 DID + 稳健性(聚类/双向聚类、控制趋势)
  4. 做 9.4 的安慰剂检验:假时点/假对照组/Leave-one-out/置换检验/安慰剂结果
  5. 报告:主结果 + 前趋势/事件研究图 + 安慰剂检验 + 解释

本节小结

  • DID 的经典案例很多,但识别逻辑始终围绕“平行趋势”。
  • 先用可复现的合成数据打通一遍流程,再迁移到真实数据。
  • 模板化思维:数据结构、回归式、聚类方式、可视化与稳健性。

附:事件研究图(与 9.3 风格一致)

使用上面构造的 df 数据,估计并绘制事件研究(Event Study)动态效应。

python
import matplotlib.pyplot as plt
from linearmodels.panel import PanelOLS

# 构造相对时间(以政策实施期为 0,政策前为负,后为正)
df['rel_time'] = df['time'] - policy_time
df['rel_time_treated'] = df['rel_time'] * df['treated']

# 生成 leads/lags(以 -1 期为基准)
min_lead, max_lag = - (policy_time - 1), (n_periods - policy_time)
lead_lag_vars = []
for k in range(min_lead, max_lag + 1):
    if k == -1:
        continue  # 基准期不建虚拟变量
    col = f'LL_{k}'
    df[col] = (df['rel_time_treated'] == k).astype(int)
    lead_lag_vars.append(col)

# 面板索引
panel = df.set_index(['id', 'time'])

# 回归(个体与时间固定效应)
model_es = PanelOLS(
    dependent=panel['y'],
    exog=panel[lead_lag_vars],
    entity_effects=True,
    time_effects=True
).fit(cov_type='clustered', cluster_entity=True)

print(model_es.summary)

# 取系数与置信区间,拼出包含基准期(-1)的序列
rows = []
for k in range(min_lead, max_lag + 1):
    if k == -1:
        rows.append({'rel_time': k, 'coef': 0.0, 'low': 0.0, 'high': 0.0})
    else:
        name = f'LL_{k}'
        coef = float(model_es.params.get(name, 0.0))
        ci = model_es.conf_int().loc[name]
        rows.append({'rel_time': k, 'coef': coef, 'low': float(ci[0]), 'high': float(ci[1])})

es = pd.DataFrame(rows).sort_values('rel_time')

# 绘图
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(es['rel_time'], es['coef'], 'o-', color='navy', label='DID 系数')
ax.fill_between(es['rel_time'], es['low'], es['high'], color='navy', alpha=0.25, label='95% CI')
ax.axhline(0, color='black', linestyle='--', linewidth=1)
ax.axvline(0, color='red', linestyle='--', linewidth=1.5, label='政策实施期')
ax.set_xlabel('相对时间 (政策前为负,后为正)')
ax.set_ylabel('效应')
ax.set_title('事件研究:动态处理效应')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()

Bacon 分解与交错处理时间

在许多现实应用中,不同地区或个体并非在同一时间点接受政策干预,而是在不同时间先后进入处理状态——这就是所谓的"交错处理"(staggered treatment)。传统的双向固定效应(TWFE)回归在这种场景下可能产生严重的估计偏差。

TWFE 在交错处理下做了什么?

Goodman-Bacon (2021) 的分解定理揭示了一个深刻的事实:当处理时间存在差异时,TWFE 估计的 实际上是所有可能的 2x2 DID 比较的方差加权平均。这些比较可以分为三类:

  1. 已处理组 vs 从未处理组:这是我们期望的"干净"比较,用从未接受处理的个体作为对照组
  2. 早期处理组 vs 晚期处理组(以晚期组为对照):在晚期组尚未接受处理时,用它们作为早期组的对照
  3. 晚期处理组 vs 早期处理组(以早期组为对照):在晚期组开始接受处理后,用已经处理了更长时间的早期组作为"对照"

问题的根源:第三类比较是危险的。当早期处理组已经处于处理状态,并且处理效应随时间变化(例如效应逐渐增强或减弱)时,将它们作为"对照组"会引入偏差。在极端情况下,即使所有个体的真实处理效应都是正的,TWFE 的加权平均也可能得到负的估计值——因为某些权重可以是负数。

数学表达:TWFE 估计量可以分解为

其中 是每组 2x2 比较的权重(由组别大小和处理时间的方差决定), 是对应的子比较估计量。关键在于:权重 可以为负,这意味着 TWFE 估计的并非简单的 ATT,而是一个可能被扭曲的混合量。

实践建议:如果你的数据存在交错处理时间,应优先考虑以下稳健估计方法:

  • Callaway & Sant'Anna (2021):先估计每个"组别-时期"(group-time)的 ATT,再进行灵活的汇总
  • Sun & Abraham (2021):通过构造"交互加权"(interaction-weighted)估计器修正 TWFE 的偏差
  • de Chaisemartin & D'Haultfoeuille (2020):提出对异质性处理效应稳健的 DID 估计器

这些方法的共同思路是:避免使用已处理单位作为对照组,从而确保每一组比较都具有因果解释力。

参考 Goodman-Bacon (2021) 的分解定理,详见 Cunningham (2021), Causal Inference: The Mixtape, Chapter 9


上一节: 9.4 安慰剂检验 | 下一节: 9.6 本章小结

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