Skip to content

Module 9 双重差分法(Difference-in-Differences, DID)

因果推断的黄金标准:从自然实验到政策评估

难度重要性


本章目标

完成本章后,你将能够:

  • 理解双重差分法的核心思想和识别逻辑
  • 掌握DID的基本假设(平行趋势假设)及其检验方法
  • 实施标准DID回归和事件研究设计
  • 进行安慰剂检验和稳健性检验
  • 处理违反平行趋势假设的情况
  • 使用Python实现DID分析(statsmodels, linearmodels)
  • 复现经典的DID研究(Card & Krueger 1994等)

为什么 DID 是社会科学的核心方法?

DID:准实验设计的利器

在社会科学研究中,我们很少有机会进行随机对照试验(RCT)。政策制定者不会为了学术研究而随机分配政策。但是,自然实验(Natural Experiments)为我们提供了"准随机"的机会:

经典场景:某些地区实施了新政策,而另些地区没有

  • 处理组(Treatment Group):受到政策影响的地区/个体
  • 对照组(Control Group):未受政策影响的地区/个体

核心问题:如何从观察数据中识别政策的因果效应

DID vs 简单比较

假设我们想评估最低工资提高就业率的影响。

** 错误方法1:简单对比(事后 vs 事前)**

问题:可能存在时间趋势!即使没有政策,就业率也可能自然增长或下降。

** 错误方法2:横截面对比(处理组 vs 对照组)**

问题:处理组和对照组可能本来就不同(选择性偏差)!

** 正确方法:双重差分(DID)**

直觉

  1. 第一次差分:消除截面差异(处理组和对照组的固有差异)
  2. 第二次差分:消除时间趋势(共同的时间效应)
  3. 剩余部分:政策的因果效应

DID 的核心直觉

图示:理想的 DID

python
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']  # macOS
plt.rcParams['axes.unicode_minus'] = False
sns.set_style("whitegrid")

# 模拟数据
np.random.seed(42)
time = np.arange(0, 20)
treatment_time = 10

# 对照组:稳定增长
control = 50 + 2 * time + np.random.normal(0, 2, len(time))

# 处理组:政策前平行,政策后跳升
treatment = 55 + 2 * time + np.random.normal(0, 2, len(time))
treatment[treatment_time:] += 15  # 政策效应

# 反事实(如果没有政策,处理组会怎样)
counterfactual = 55 + 2 * time + np.random.normal(0, 2, len(time))

fig, ax = plt.subplots(figsize=(14, 8))

# 绘制实际观测
ax.plot(time[:treatment_time], control[:treatment_time],
        'o-', color='blue', linewidth=2, label='对照组(Control)', markersize=6)
ax.plot(time[treatment_time:], control[treatment_time:],
        'o-', color='blue', linewidth=2, markersize=6)

ax.plot(time[:treatment_time], treatment[:treatment_time],
        's-', color='red', linewidth=2, label='处理组(Treatment)', markersize=6)
ax.plot(time[treatment_time:], treatment[treatment_time:],
        's-', color='red', linewidth=2, markersize=6)

# 绘制反事实(虚线)
ax.plot(time[treatment_time:], counterfactual[treatment_time:],
        '--', color='red', linewidth=2, alpha=0.6, label='反事实(Counterfactual)')

# 标注政策时点
ax.axvline(x=treatment_time, color='green', linestyle='--', linewidth=2, alpha=0.7)
ax.text(treatment_time + 0.3, 45, '政策实施时点', fontsize=12, color='green', fontweight='bold')

# 标注DID效应
did_effect_y = treatment[treatment_time + 5]
counterfactual_y = counterfactual[treatment_time + 5]
ax.annotate('', xy=(treatment_time + 5, did_effect_y),
            xytext=(treatment_time + 5, counterfactual_y),
            arrowprops=dict(arrowstyle='<->', color='purple', lw=3))
ax.text(treatment_time + 5.5, (did_effect_y + counterfactual_y) / 2,
        'DID 效应\n(因果效应)', fontsize=12, color='purple', fontweight='bold',
        bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.3))

ax.set_xlabel('时间', fontsize=14, fontweight='bold')
ax.set_ylabel('结果变量(如就业率)', fontsize=14, fontweight='bold')
ax.set_title('双重差分法(DID)的核心逻辑', fontsize=16, fontweight='bold')
ax.legend(loc='upper left', fontsize=12)
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

关键观察

  1. 政策前:处理组和对照组平行(趋势相同,水平不同)
  2. 政策后:处理组跳升,对照组继续原趋势
  3. DID效应:处理组实际值 - 反事实值(虚线)

DID 的数学表达

2×2 DID:最简单的情况

数据结构

政策前(Before)政策后(After)差值(Δ)
处理组(Treated)
对照组(Control)

DID 估计量

直觉

  • 第一种表达:先比时间(前后差),再比组间差
  • 第二种表达:先比组间(处理vs对照),再比时间差

2×2 DID 的回归形式

等价的回归模型:

变量定义

  • 如果个体 在处理组
  • 如果时间 在政策后
  • 交互项,政策效应的关键!

参数解释

  • :对照组在政策前的基准水平
  • :处理组和对照组在政策前的固定差异(截面异质性)
  • :对照组政策前后的时间趋势(共同的时间效应)
  • DID 效应(政策的因果效应)

为什么 就是因果效应?

让我们推导四组均值:

组合交互项预测值
对照组-前000
对照组-后010
处理组-前100
处理组-后111

计算 DID

结论:回归中的交互项系数 就是我们要的 DID 效应!


DID 的核心假设

核心假设:如果没有政策干预,处理组和对照组会遵循相同的时间趋势

数学表达

白话

  • 在没有政策的反事实世界中,处理组和对照组之间的差距保持不变
  • 换句话说,两组的趋势相同(平行),但水平可以不同

为什么重要?

  • 如果违反平行趋势,DID 估计会有偏差
  • 这是 DID 最关键、也是最容易受质疑的假设

如何检验?(第3节详细讨论)

  1. 事前趋势检验:查看政策前的趋势是否平行
  2. 事件研究图(Event Study):绘制动态效应
  3. 安慰剂检验(Placebo Test):使用假的政策时点

假设 2:外生性假设(Exogeneity)

假设:政策的实施时间和地点与潜在结果无关(条件独立)。

数学表达

白话

  • 哪些地区/个体被政策影响,不能由未来的结果决定
  • 例如:如果政府只在就业率即将上升的地区提高最低工资,DID 会高估效应

实践中的威胁

  • 预期效应:企业提前知道政策,提前调整
  • 反向因果:政府根据结果变量选择政策目标(内生性)

假设 3:稳定单位处理值假设(SUTVA)

假设:一个单位的处理状态不影响其他单位的结果(无溢出效应)。

威胁

  • 地理溢出:政策地区的企业迁移到非政策地区
  • 一般均衡效应:政策改变市场价格、工资等

DID 的经典应用

案例 1:Card & Krueger (1994) - 最低工资与就业

研究问题:最低工资提高会降低就业吗?

背景

  • 1992年4月,美国新泽西州将最低工资从 $4.25 提高到 $5.05
  • 邻近的宾夕法尼亚州没有提高

研究设计

  • 处理组:新泽西州的快餐店
  • 对照组:宾夕法尼亚州的快餐店
  • 结果变量:全职等效就业人数(FTE)

数据

  • 政策前:1992年2月(政策前2个月)
  • 政策后:1992年11月(政策后7个月)

发现

结论:最低工资提高后,新泽西州的就业增加了2.76人(相对于宾州)!

影响

  • 挑战了传统经济学理论
  • 引发了关于最低工资的长期争论
  • 成为DID方法的经典案例

案例 2:Bertrand et al. (2004) - DID 的统计推断问题

发现的问题

  • 许多DID研究低估了标准误
  • 原因:序列相关(Serial Correlation)

解决方案

  1. 聚类标准误(Clustered Standard Errors):在政策单位层面聚类
  2. Block Bootstrap:重抽样方法
  3. Randomization Inference:随机化推断

教训:DID 不仅要关注点估计,更要关注统计推断


️ Python 实现:2×2 DID 示例

简单示例:模拟数据

python
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
from statsmodels.iolib.summary2 import summary_col

# 设置随机种子
np.random.seed(123)

# 模拟数据
n_units = 50  # 每组单位数
n_periods = 2  # 前后两期

# 创建数据框
data = []
for unit in range(n_units * 2):
    treated = 1 if unit >= n_units else 0
    for period in range(n_periods):
        post = 1 if period == 1 else 0

        # 数据生成过程
        # Y = 50 + 10*treated + 5*post + 15*(treated*post) + noise
        y = 50 + 10 * treated + 5 * post + 15 * (treated * post) + np.random.normal(0, 5)

        data.append({
            'unit': unit,
            'period': period,
            'treated': treated,
            'post': post,
            'y': y
        })

df = pd.DataFrame(data)
df['treated_post'] = df['treated'] * df['post']

print("=" * 60)
print("数据预览")
print("=" * 60)
print(df.head(10))
print("\n")

# 计算均值
means = df.groupby(['treated', 'post'])['y'].mean().unstack()
print("=" * 60)
print("2×2 均值表")
print("=" * 60)
print(means)
print("\n")

# 手动计算 DID
treated_diff = means.loc[1, 1] - means.loc[1, 0]
control_diff = means.loc[0, 1] - means.loc[0, 0]
did_manual = treated_diff - control_diff

print("=" * 60)
print("手动计算 DID")
print("=" * 60)
print(f"处理组差值: {treated_diff:.3f}")
print(f"对照组差值: {control_diff:.3f}")
print(f"DID 效应: {did_manual:.3f}")
print("\n")

# 回归估计 DID
model = smf.ols('y ~ treated + post + treated_post', data=df).fit(cov_type='HC1')

print("=" * 60)
print("回归估计 DID")
print("=" * 60)
print(model.summary())

输出解读

  • treated 系数 ≈ 10:处理组和对照组的固有差异
  • post 系数 ≈ 5:时间趋势(对所有组相同)
  • treated_post 系数 ≈ 15:DID 效应(政策因果效应)

本章结构

第 1 节:本章介绍(当前)

  • DID 的核心思想和直觉
  • 2×2 DID 的数学表达和回归形式
  • 核心假设:平行趋势、外生性、SUTVA
  • 经典应用案例
  • Python 基础实现

第 2 节:DID 原理

  • DID 的识别逻辑和因果图
  • 多期DID(Staggered DID)
  • 控制变量和协变量调整
  • 标准误的计算(聚类、稳健性)
  • 面板数据的DID实现

第 3 节:平行趋势假设

  • 事前趋势检验(Pre-trend Test)
  • 事件研究设计(Event Study)
  • 动态效应图(Dynamic Effects)
  • 违反平行趋势的处理方法

第 4 节:安慰剂检验

  • 安慰剂检验(Placebo Test)
  • 假政策时点检验
  • 样本排除检验
  • 随机化推断

第 5 节:经典案例和Python实现

  • Card & Krueger (1994) 完整复现
  • 环境政策的DID评估
  • 面板数据处理技巧
  • 可视化最佳实践

第 6 节:本章小结

  • DID 方法总结
  • 常见陷阱和注意事项
  • 练习题
  • 文献推荐

️ Python 工具包

核心库

主要功能安装
pandas数据处理pip install pandas
statsmodelsOLS回归pip install statsmodels
linearmodels面板DIDpip install linearmodels
matplotlib可视化pip install matplotlib
seaborn高级可视化pip install seaborn

基础设置

python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.iolib.summary2 import summary_col
from linearmodels.panel import PanelOLS

# 中文字体设置
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']  # macOS
plt.rcParams['axes.unicode_minus'] = False

# 设置样式
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (12, 6)
pd.set_option('display.float_format', '{:.4f}'.format)

必读文献

奠基性论文

  1. Card, D., & Krueger, A. B. (1994). "Minimum Wages and Employment: A Case Study of the Fast-Food Industry in New Jersey and Pennsylvania." American Economic Review, 84(4), 772-793.

  2. Bertrand, M., Duflo, E., & Mullainathan, S. (2004). "How Much Should We Trust Differences-In-Differences Estimates?" Quarterly Journal of Economics, 119(1), 249-275.

  3. Callaway, B., & Sant'Anna, P. H. (2021). "Difference-in-Differences with Multiple Time Periods." Journal of Econometrics, 225(2), 200-230.

推荐教材

  1. Angrist & Pischke (2009). Mostly Harmless Econometrics, Chapter 5
  2. Cunningham (2021). Causal Inference: The Mixtape, Chapter 9
  3. Huntington-Klein (2022). The Effect, Chapter 18

准备好了吗?

DID 是现代因果推断的基石方法之一。掌握它,你将能够:

  • 评估真实世界的政策效应
  • 在顶级期刊发表研究
  • 为政策制定提供科学证据

记住核心思想

"The beauty of DID is its simplicity: it differences out fixed differences and common trends, leaving only the treatment effect."

让我们开始学习 第2节:DID 原理


因果推断从DID开始!

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