Causal Identification / DID
DID 双重差分与事件研究
DID 的核心不是“两次相减”这个算术动作,而是用未处理组的趋势构造处理组在未处理状态下的反事实路径。
Mechanism Lab
动画:DID 如何构造处理组反事实
动画把处理组实际路径、对照组趋势和虚线反事实放在同一张图中,政策效果就是政策后实际路径与反事实路径之间的垂直距离。
Step 1 / 5
Pre trends
先观察政策前处理组和对照组是否沿相近斜率移动。
pre-trend checkAnimation Control
Reduced-motion users receive the same step states without continuous motion.
01 / 直觉
核心直觉
DID 估计的是处理组在政策之后的反事实缺口:实际结果减去“如果没有政策,本来会怎样”。
对照组不是为了比较水平,而是为了提供未处理潜在结果 Y(0) 的时间趋势。
平行趋势假设说:如果没有处理,处理组和对照组的平均变化应当相同。事件研究把这个假设放到多个相对时间点上检查。
02 / 数学
从潜在结果到 DID 估计量
01 / 目标参数
设 G=1 表示处理组,Post=1 表示政策后。目标通常是处理组政策后平均处理效应 ATT。
ATT = E[Y(1) - Y(0) | G=1, Post=1]
= E[Y | G=1, Post=1] - E[Y(0) | G=1, Post=1]02 / 不可观察反事实
困难在于 E[Y(0)|G=1,Post=1] 不可观察。DID 用处理组政策前水平加上对照组的未处理趋势来构造它。
E[Y(0)|G=1,Post=1]
= E[Y|G=1,Post=0]
+ {E[Y|G=0,Post=1] - E[Y|G=0,Post=0]}03 / 平行趋势
上式成立依赖于未处理潜在结果的平均变化在两组之间相同。这是识别假设,不是回归自动保证的事实。
E[Y(0)_post - Y(0)_pre | G=1]
= E[Y(0)_post - Y(0)_pre | G=0]04 / DID 估计量
把反事实代回 ATT,就得到双重差分:处理组前后变化减去对照组前后变化。
tau_DID = {E[Y|G=1,Post=1] - E[Y|G=1,Post=0]}
- {E[Y|G=0,Post=1] - E[Y|G=0,Post=0]}05 / 回归形式
两组两期 DID 可写成带交互项的线性回归;交互项系数就是 DID 估计量。
Y_it = alpha + gamma G_i + lambda Post_t + tau(G_i * Post_t) + epsilon_it03 / 代码
Python 代码:DID、聚类标准误与事件研究
下面代码演示两组两期 DID,以及面板事件研究的基本结构。真实论文中还要加入固定效应、聚类标准误、样本限制和稳健性检验。
import pandas as pd
import statsmodels.formula.api as smf
# df columns:
# unit_id, year, outcome, treated, post, first_treat_year
df = pd.read_csv("policy_panel.csv")
df["did"] = df["treated"] * df["post"]
did_model = smf.ols(
"outcome ~ treated + post + did",
data=df,
).fit(
cov_type="cluster",
cov_kwds={"groups": df["unit_id"]},
)
print(did_model.params["did"])
print(did_model.conf_int().loc["did"])
# Event-study skeleton with unit and year fixed effects.
df["event_time"] = df["year"] - df["first_treat_year"]
event_terms = []
for k in range(-4, 5):
if k == -1:
continue # omitted baseline period
col = f"event_{k:+d}".replace("+", "p").replace("-", "m")
df[col] = ((df["event_time"] == k) & (df["treated"] == 1)).astype(int)
event_terms.append(col)
formula = "outcome ~ " + " + ".join(event_terms) + " + C(unit_id) + C(year)"
event_model = smf.ols(formula, data=df).fit(
cov_type="cluster",
cov_kwds={"groups": df["unit_id"]},
)
print(event_model.params[event_terms])04 / 案例
案例:教育补贴政策的 DID 评估
- 研究问题:某地区 2020 年开始提供教育补贴,是否提高了学生考试成绩?
- 处理组是受到补贴覆盖的学校,对照组是同一时期未覆盖但制度环境相近的学校。
- 关键检查不是政策后处理组是否更高,而是政策前两组趋势是否接近,以及政策后缺口是否超出对照组趋势。
- 输出应同时报告主 DID 系数、事件研究图、样本流、聚类层级、替代窗口和安慰剂政策时间。
05 / 风险
常见误区
参考资料
- Card and Krueger (1994), Minimum Wages and Employmenthttps://www.jstor.org/stable/2118030
- Callaway and Sant’Anna (2021), Difference-in-Differences with Multiple Time Periodshttps://doi.org/10.1016/j.jeconom.2020.12.001
- Goodman-Bacon (2021), Difference-in-Differences with Variation in Treatment Timinghttps://doi.org/10.1016/j.jeconom.2021.03.014