前置条件与学习依赖 / PREREQUISITES
① 数学 / 统计基础
因果推断潜在结果框架(Rubin Causal Model:Y(1) / Y(0)、ATE / ATT)、固定效应估计、面板差分、平行趋势假设的统计表述。
② 经济学理论前置
政策评估 / 项目评估方法(program evaluation);理解交叠 DID 的异质性处理效应。
③ 软件 / 计算前置
Stata 17+(外部命令:reghdfecsdideventddcoefplot;新估计量 did_multipgtdid_imputation)。
④ 站内前置页面
先学 04 基准回归(掌握双向固定效应与聚类标准误)。
⑤ 难度分级
进阶

01 DID 的核心思想与 2×2 设定

Difference-in-Differences (DID) 的直觉极其直观:想象一个政策在某一时点只对一部分人(处理组 treated)实施,另一部分人(对照组 control)从未受影响。简单地比较"处理组政策前后之差"会把政策效应和时间趋势混在一起;简单地比较"处理组 vs 对照组政策后之差"会把政策效应和两组本来就存在的差异混在一起。DID 的巧思是做两次差分:先在每个组内做"政策后 − 政策前",再把两个组的这个差再相减——时间趋势被差分掉,组间固有差异也被差分掉,剩下的就是政策的因果效应。

经典 2×2 DID 模型写成双向固定效应(two-way FE)形式:

Eq. 1.1 — 经典 TWFE DID 模型
$$ y_{it} = \beta_0 + \beta_1 \, Treat_i \times Post_t + \alpha_i + \gamma_t + \mathbf{Z}_{it}'\boldsymbol{\delta} + \varepsilon_{it} $$

其中 $Treat_i$ 是组别虚拟变量(处理组=1,对照组=0),$Post_t$ 是时期虚拟变量(政策后=1,政策前=0)。我们真正关心的系数是交互项 $Treat_i \times Post_t$ 的系数 $\beta_1$——它就是 DID 估计量:

Eq. 1.2 — DID 估计量的二阶差分形式
$$ \hat{\beta}_{DID} = \big(\bar{y}_{T,Post} - \bar{y}_{T,Pre}\big) - \big(\bar{y}_{C,Post} - \bar{y}_{C,Pre}\big) $$
关键识别假设:Parallel Trends 平行趋势

DID 成立的唯一关键假设是:如果没有政策,处理组和对照组的结果变量会以相同的趋势演变(parallel trends assumption)。这个假设无法直接检验,但可以用政策前的数据做"伪政策时点"检验——如果政策前处理组和对照组的趋势已经分叉,平行趋势就值得怀疑。

02 平行趋势检验与事件研究图

论文里几乎一定会画一张事件研究图(event study plot)。它把 DID 展开成动态形式:不再假设政策效应一次性出现并保持不变,而是允许政策效应随时间变化:

Eq. 2.1 — Event Study 事件研究模型
$$ y_{it} = \sum_{k \neq -1} \beta_k \, D_{it}^k + \alpha_i + \gamma_t + \mathbf{Z}_{it}'\boldsymbol{\delta} + \varepsilon_{it} $$

这里 $D_{it}^k$ 是"相对政策时点 $k$ 年"的虚拟变量:$k<0$ 表示政策前 $|k|$ 年,$k=0$ 表示政策当年,$k>0$ 表示政策后 $k$ 年。我们省略 $k=-1$ 作为基准期(省略的那一期系数归一化为 0)。事件研究图横轴是 $k$,纵轴是 $\hat{\beta}_k$ 及其置信区间:

  • 政策前($k<-1$):$\hat{\beta}_k$ 应该贴着 0、不显著异于 0——这就是平行趋势的图形证据。
  • 政策后($k \ge 0$):$\hat{\beta}_k$ 应该显著偏离 0,且可以展示动态轨迹(政策效应是立即出现还是逐渐累积?)。
画图命令

Stata 中最常用的是 eventdd(即 ssc install eventdd)或 coefplot 配合手工构造的滞后/超前项。两期及以上的 leads 必须联合不显著(pre-trend F 检验),否则审稿人会攻击平行趋势。

2.1 pre-trend 联合 F 检验:不能只看单期系数

单看事件研究图里每一根 whisker 是否跨 0 是不够的——如果有 5 个 leads,每个单独看不显著,5 个联合起来却显著异于 0 的情况完全可能发生。规范做法是在事件研究回归后,把所有政策前 leads 联合做 Wald 检验:

stata · s2_pretrend_test.do
* s2.1 事件研究 + pre-trend 联合 F 检验
* 沿用 s7 模拟数据:D095=k=-5 ... D098=k=-2, D100=k=0...
reghdfe y D095 D096 D097 D098 D100 D101 D102 D103 D104 D105, ///
    absorb(id year) vce(cluster id)

* 联合检验所有政策前 leads(D095..D098,省略 k=-1 基准)
test D095 D096 D097 D098
* H0: 所有 pre 期系数联合 = 0
* 若 p<0.05 → 拒绝平行趋势假设

* 也可以用更现代的写法:先构造事件时间变量再写因子
* testparm i.rel_time#i.treat if rel_time<0
⚠ Roth (2022) 关键警告:pre-trend 检验功效低,不能把"不显著"当"平行趋势成立"

这是 2020 年后顶刊审稿人最常追问的一点。Roth (2022, JoE) 用仿真证明:在绝大多数实务样本里,pre-trend 联合检验的功效常常只有 30%–50%——也就是说,即使真实存在一条足以让你 ATT 偏误 20% 的 pre-trend 斜率,你也有一半以上概率"抓不到"。把 test D095..D098 的 p=0.21 直接写成"平行趋势成立",是统计上的过度解读。正确写法必须同时交代三件事:

  • ① 联合检验 p 值("我们不能拒绝平行趋势");
  • pretrends 命令估计的检验功效与"可检测的最小 pre-trend 斜率";
  • ③ 若功效低,做 Rambachan & Roth 式敏感性分析(允许 pre 期存在一定幅度偏离时,post 效应 CI 还跨不跨 0)。

2.2 pretrends(Roth 2022):量化 pre-trend 检验的功效

stata · s22_pretrends_roth.do
* s2.2 Roth (2022) pretrends:评估 pre-trend 检验有多大功效
* ssc install pretrends, replace

* 前置:已跑事件研究回归(见 s2.1)
* pretrends 会自动识别事件研究中的 pre 期虚拟变量,
* 回答:"如果真实存在线性 pre-trend,本检验有多大概率抓住?"
*
* 语法(在 reghdfe 事件研究回归后):
* pretrends, leads(4) lag(0) post(5)
*   leads(4)  : 政策前有 4 期 leads
*   lag(0)    : 政策当年作为基准
*   post(5)   : 政策后估计窗口
*
* 关键输出解读:
*   Power of pre-trend test at 5% level: ~0.35
*   Detectable bias (in fraction of ATT): ~0.20
*
* 论文写法模板:
*   "The joint pre-trend test does not reject the null
*    (p = 0.21), but pretrends (Roth 2022) indicates the
*    test has only ~35% power to detect a linear pre-trend
*    that would bias the ATT by 20%. We therefore report
*    Rambachan-Roth sensitivity bounds (see sX.X) showing
*    the ATT remains positive under modest pre-trend violations."

* 重要:pretrends 命令在 Stata 中需要事件研究回归保留
* e(b) 和 e(V);如果用 reghdfe,先 estimates store 再调用。

2.3 观测量合并检验:当某事件期样本太少

当你展开到 k=−6 甚至 k=−8 时,远端 leads 往往只有少数几个处理组观测,单独系数 SE 巨大、置信带宽到没意义。此时合法做法是把观测太少的相邻事件期合并(如把 k=−6 与 k=−5 合并成一个 "early-leads" 系数),而不是把远端 leads 从图上删掉。

stata · s23_pooled_leads.do
* s2.3 远端 leads 观测太少时合并相邻期
* 前提:该合并是由"远端期观测数过少"这一客观事实决定的,
*       不能是"合并后刚好平行"才合并。

* 假设 k=-6/-5 两期处理组观测都 <30,合并:
gen D_early = (rel_time<=-5) if g!=0
replace D_early = 0 if g==0

* 其余 leads/lags 照常
reghdfe y D_early D096 D097 D098 D100 D101 D102 D103 D104 D105, ///
    absorb(id year) vce(cluster id)
test D_early D096 D097 D098        // 合并后 pre 期联合检验

* 红线:发现 k=-6 单独显著(不平行),把它和 k=-5 合并
*       合并后"看起来不显著"——这是事后缝合趋势。

03 动态效应与 PSM-DID

如果担心处理组和对照组在可观测特征上差异很大(covariate imbalance),可以用倾向得分匹配 DID(Propensity Score Matching DID, PSM-DID):先用 logit/probit 估计每个个体被处理的概率 $p(X)$(倾向得分),然后按 $p(X)$ 在处理组和对照组之间做匹配(nearest neighbor / kernel / caliper),只保留共同支撑(common support)范围内的观测,再在匹配后的样本上做 DID。

PSM-DID 的逻辑是:PSM 解决"可观测特征上的自选择",DID 解决"时不变不可观测特征"。两者结合比单独 PSM 或单独 DID 都更稳健。但要注意:PSM 只能处理可观测的混淆,不可观测的自选择依然要靠 DID 的平行趋势假设兜住。

04 交叠 DID 的负权重问题(重点)

2018 年以来,计量经济学界发生了一场"DID 革命"。Goodman-Bacon (2021)、de Chaisemartin & D'Haultfœuille (2020)、Sun & Abraham (2021)、Callaway & Sant'Anna (2021) 一系列论文指出:当政策在不同时点分批实施(staggered DID / 交叠 DID)时,传统 TWFE DID 估计量会出现严重偏误。

偏误从哪里来?

TWFE DID 把所有可能的"对比组 × 时期"配对加权平均成一个 ATT。问题在于:当政策效应存在异质性(处理效应随时间或组别变化,即 $\tau_{it} \neq \tau$),TWFE 在做配对时会把已经接受处理的组当作"对照组"去和新处理组比较——这就是所谓"forbidden comparison"。例如:A 组在 2010 年接受处理,B 组在 2015 年接受处理。TWFE 在估计 2015 年的效应时,会把 A 组(已经被处理 5 年)当作 B 组的"对照组"。如果 A 组的动态效应还没回到稳态,这种比较就把 A 组的动态效应"反向"加权进来,导致 TWFE 系数出现负权重(negative weights),严重时甚至符号都能翻转。

Bacon 分解:TWFE 究竟在加权什么?

Goodman-Bacon (2021) 的贡献是把上面这个直觉变成了一个分解定理:交叠 DID 中的 TWFE 估计量,等于所有可能的 2×2 DID 估计量的加权平均,权重与每一对比较所动用的样本量、两组处理暴露程度成正比。分解结果天然分成三类比较:"先处理组 vs 未处理组""后处理组 vs 未处理组"这两类是干净的好比较;而"后处理组 vs 先处理组"这类把已经接受处理的组当作对照,就是上节说的 forbidden comparison——当处理效应随时间或队列异质时,这类坏比较的权重可以是负的。de Chaisemartin & D'Haultfœuille (2020, AER) 把这一结论推广到更一般的 TWFE 设定,证明只要处理变量是异质性的、权重和不为 1,负权重就可能出现,进而让 TWFE 系数的符号都不可信。

此后的新估计量本质上都是"扔掉坏比较、只在干净比较上做加权":Callaway & Sant'Anna (2021) 逐"组×期"估计 $ATT(g,t)$ 再汇总;Sun & Abraham (2021) 在事件研究框架内对 leads/lags 做交互加权;Roth et al. (2023) 的综述则把这场"DID 革命"的方法谱系、假设与实务取舍做了系统总结。

Bacon 分解:TWFE 权重公式的正式推导

Goodman-Bacon (2021, JoE) 的核心定理可以写成如下形式。设样本中有若干个处理队列(cohort),$g$ 表示该队列首次接受处理的年份,从未处理的队列为 $g=0$。对任意两个队列 $e<l$($e$ 早于 $l$ 接受处理)以及任意一对时期 $t>t'$,可以构造一个经典的 2×2 DID 对比:

Eq. 4.1 — 单个 2×2 DID 对比分量
$$ \hat{\beta}^{e,l}_{DID}(t,t') = \big(\bar{y}_{e,t} - \bar{y}_{e,t'}\big) - \big(\bar{y}_{l,t} - \bar{y}_{l,t'}\big) $$

Bacon 定理证明:交叠 DID 中的 TWFE 估计量 $\hat{\beta}_{TWFE}$ 恰好等于所有这些 2×2 分量的一个加权平均,权重之和严格等于 1:

Eq. 4.2 — Goodman-Bacon (2021) 分解定理
$$ \hat{\beta}_{TWFE} = \sum_{e

其中权重 $w_{e,l}$ 与两个队列的样本份额以及它们各自的处理变量方差成正比:

Eq. 4.3 — Bacon 权重(比例形式)
$$ w_{e,l} \;\propto\; \frac{N_e}{N}\cdot\frac{N_l}{N}\cdot\Big[s_e(1-s_e) + s_l(1-s_l)\Big], \qquad s_g = \frac{\#\{t\ge g\}}{T} $$

这里 $s_g$ 是队列 $g$ 在样本期内处于"已处理"状态的时间份额。直观地说:队列越大、处理暴露越分散,它在 TWFE 里占的权重就越大。

三类对比与"负权重"从何而来

Bacon 分解把所有 2×2 对比分成三类:

  1. Early-treated vs Never-treated(干净对比):早处理组与从未处理组比较,两组的反事实平行趋势都可以用未处理观测识别,是"好比较"。
  2. Late-treated vs Never-treated(干净对比):晚处理组与从未处理组比较,同样是"好比较"。
  3. Late-treated vs Early-treated(forbidden comparison,坏比较):晚处理组 vs 已经被处理的早处理组。此时早处理组的结果里已经包含了它自己的处理效应 $\tau_{e}$,把它当作"干净对照"等于把 $\tau_e$ 反向代入 DID 差分。

当处理效应是同质性的($\tau_{it}=\tau$ 常数)时,坏比较的权重虽然存在,但因为 $\tau$ 处处相同,加权平均仍能无偏估计 $\tau$——这就是为什么传统 TWFE 在"常数效应"假设下看起来没问题。问题出在异质性处理效应上:如果处理效应随队列或随处理时长变化(动态效应 $\tau^k$),坏比较里早处理组的动态效应会以负号进入 DID 差分,对应的权重在分解中可以为负,最终让 TWFE 系数的符号都可能被翻转。

de Chaisemartin & D'Haultfœuille (2020, AER) 的负权重定理

de Chaisemartin & D'Haultfœuille 把 Bacon 的结论推广到一般 TWFE 设定(不限于 DID),证明:当处理效应在个体×时间上异质时,TWFE 估计量是 $\hat{\beta}^{TWFE}=\frac{1}{N}\sum_{i,t}\omega_{it}\hat{\beta}_{it}$ 的加权和,权重 $\omega_{it}$ 满足 $\sum_{i,t}\omega_{it}=N$(不是 1),并且允许出现 $\omega_{it}<0$。这意味着:即使每个真实的 $\hat{\beta}_{it}$ 都为正,只要负权重存在,$\hat{\beta}^{TWFE}$ 仍可能为负。他们据此提出 $DID_M$ 估计量(用 $\Delta D_{it}=D_{it}-D_{i,t-1}$ 作为处理变量),并给出 Stata 命令 did_multiplegt

实务含义

只要你的处理时点不统一("分批试点""分步推开""政策在不同地区不同年份落地"),传统 reghdfe y TreatPost, absorb(id year) 的结果就可能不可信。审稿人会要求你报告新估计量。

05 四大新估计量

目前主流的、能处理交叠 DID 异质性处理效应的估计量有四个,论文中至少要报告其中一个:

估计量作者Stata 命令核心思路
CSDIDCallaway & Sant'Anna (2021, JoE)csdid估计每个"组×期"的 $ATT(g,t)$,再按队列大小加权汇总;用 never-treated 或 not-yet-treated 作为对照。
Sun-AbrahamSun & Abraham (2021, JoE)eventstudyinteract把事件研究系数按组×期加权,剔除负权重。
dCDHde Chaisemartin & D'Haultfœuille (2020, AER)did_multiplegt用 $\sqrt{D_{it}-D_{i,t-1}}$ 作为处理变量,构造 $DID_M$ 估计量。
BJSBorusyak, Jaravel & Spiess (2024, ReStud)did_imputation在组-期层面做 imputation,效率最高,适合大样本。

这四个估计量在平行趋势假设下都能一致估计平均处理效应,并且都不再产生负权重。实务中,csdid 因为输出友好(自动按 event-time 汇总、画 event study 图)、对 never-treated 对照组要求灵活,已经成为中文顶刊的"新默认"。

5.1 Bacon 分解诊断:bacondecomp

在写论文的稳健性部分,第一步永远是先做 Bacon 分解——它本身就是一个"诊断工具",告诉审稿人你的 TWFE 估计量里有多少权重来自干净对比、多少来自坏对比。如果坏对比权重接近 0,TWFE 仍然可信;如果坏对比权重很大且估计符号可疑,就必须上新估计量。Stata 命令 bacondecomp(Goodman-Bacon, Goldring & Nichols 编写)直接报告每一类对比的权重和对应 DID 估计值:

stata · bacon_decomp.do
*--------------------------------------------------------------*
* Bacon 分解:诊断 TWFE 里的"坏对比"权重
* 文档: Goodman-Bacon (2021, JoE)
*--------------------------------------------------------------*
ssc install bacondecomp, replace      // 首次运行安装

* 数据要求:
*   1) 必须 xtset
*   2) 处理变量只能从 0 跳到 1(不能反复开关)
*   3) 最好是平衡面板
xtset id year

* 跑经典 TWFE(作为对照)
reghdfe y treat, absorb(id year) vce(cluster id)

* Bacon 分解主命令:
*   outcome  = y
*   treatment = treat(0/1)
*   ddetail  = 列出每一对 2×2 对比的权重
*   stub(bacon_) = 把所有对比分量存成 bacon_1, bacon_2, ... 供画图
bacondecomp y treat, ddetail stub(bacon_)

* 输出会自动分三类报告:
*   "Earlier T vs Later C"        : 早处理 vs 晚处理(not-yet)  —— 干净
*   "Later T vs Earlier C"        : 晚处理 vs 早处理(forbidden) —— 坏对比
*   "T vs Never-treated Control"  : 处理组 vs 从未处理组       —— 干净
* 关注: bad-comparison 的权重占比是否过大? 它的 DID 估计符号是否与主回归相反?

* 用 coefplot 画权重散点图(横轴=各 2×2 DID 估计, 纵轴=权重)
* 坏对比的点若落在与主回归相反的一侧, 就是 TWFE 偏误的图形证据
怎么读 Bacon 分解输出

Stata 输出一张表,每行是一类对比,列出该类的"加权平均 DID"和"权重"。例如:"T vs Never-treated = 0.823 (权重 0.71)" 意味着 71% 的权重来自干净对比;"Later T vs Earlier C = −0.410 (权重 0.29)" 意味着 29% 的权重来自坏对比,且其效应符号相反——这种情况下 TWFE 系数必然被污染。

5.2 Callaway–Sant'Anna:csdid 与 $ATT(g,t)$

Callaway & Sant'Anna (2021, JoE) 的思路是逐"队列×时期"估计处理效应,再按透明的权重汇总。定义 $ATT(g,t)$ 为:队列 $g$(在年份 $g$ 首次接受处理)在时期 $t$ 相对于"未处理时反事实"的平均处理效应:

Eq. 5.1 — 组×期平均处理效应 ATT(g,t)
$$ ATT(g,t) \equiv \mathbb{E}\big[Y_t(g) - Y_t(0) \mid G=g\big] $$

识别 $ATT(g,t)$ 需要两条假设:(i) 条件平行趋势 $E[\Delta Y_t(0)\mid G=g,X]=E[\Delta Y_t(0)\mid G=0,X]$;(ii) 无预期效应(no anticipation)。对照组可以选 never-treated($g=0$)或 not-yet-treated(还没被处理的队列)。估计时用逆概率加权(IPW / 双重稳健 DR)。

估计完所有 $ATT(g,t)$ 后,CS 提供四种聚合(aggregation)方式,论文里通常全部报告:

  • estat simple:简单平均 ATT(所有 $g,t$ 等权),最接近"政策总效应"。
  • estat group:按队列 $g$ 聚合,看哪一批处理效应最大。
  • estat event:按相对处理时长 $k=t-g$ 聚合,即动态事件研究效应,可画事件研究图。
  • estat calendar:按日历年 $t$ 聚合,看政策效应的时间趋势。
stata · csdid_demo.do
*--------------------------------------------------------------*
* Callaway-Sant'Anna (2021, JoE) 估计量
* 命令: csdid (需 drdid 包)
*--------------------------------------------------------------*
ssc install csdid,  replace
ssc install drdid,  replace

* 关键变量准备:
*   gvar = 每个体首次处理年份; 从未处理 = 0
*   必须有: 个体 id, 年份 year, gvar
gen gvar = g                      // 沿用本页模拟数据: g=0 表示 never-treated

* 主命令:
*   csdid  outcome  [covariates],
*          ivar(id) time(year) gvar(gvar)
*          method(dripw)          // 双重稳健 IPW (推荐)
*          [notyet]               // 若没有 never-treated 组, 用 not-yet-treated 作对照
csdid y alpha_i gamma_t, ivar(id) time(year) gvar(gvar) method(dripw)

* --- 四种聚合 ---
estat simple      // 总体 ATT (所有 ATT(g,t) 的简单平均)
estat group       // 按队列 g 聚合
estat event       // 按事件时间 k=t-g 聚合 (动态效应)
estat calendar    // 按日历年聚合

* --- 事件研究图 ---
* csdid_plot 会读取 estat event 的结果并画 95% CI 的系数图
csdid_plot, title("Callaway-Sant'Anna 事件研究图") ///
    ytitle("ATT(g,t)") xtitle("相对处理时点 k")

* 也可以分队列画:
* csdid_plot, group(2005) title("2005 队列")

5.3 Sun–Abraham:eventstudyinteract 交互加权

Sun & Abraham (2021, JoE) 指出:传统事件研究回归 $y_{it}=\sum_{k\neq -1}\beta_k D_{it}^k+\alpha_i+\gamma_t+\varepsilon_{it}$ 中的 $\beta_k$,即使在平行趋势下也会被其他队列的处理效应污染——因为不同队列在同一个 event-time $k$ 上的处理时长不一样。他们提出"交互加权估计量":把每个 lead/lag 虚拟变量与队列虚拟变量交互,先估计"队列×event-time"层面的干净效应 $\hat{\beta}_{g,k}$,再按队列大小加权汇总成不被污染的 $\hat{\beta}_k$。

stata · sun_abraham.do
*--------------------------------------------------------------*
* Sun-Abraham (2021, JoE) 交互加权事件研究
*--------------------------------------------------------------*
ssc install eventstudyinteract, replace

* 1) 构造 event-time 虚拟变量 Dk (与 s7 节代码一致, 省略 k=-1 为基准)
gen rel_time = year - g if g!=0
forvalues k=-5/5 {
    local kk = `k' + 100
    gen D`kk' = (rel_time==`k') if g!=0
    replace D`kk' = 0 if g==0
}

* 2) 构造队列虚拟变量 (每个处理队列一个哑变量)
gen C2005 = (g==2005)
gen C2008 = (g==2008)
gen C0    = (g==0)        // never-treated 对照组

* 3) 主命令:
*   eventstudyinteract  outcome  lead_list lag_list,
*       cohort(队列变量或队列哑变量列表)
*       control_cohort(对照组哑变量)
*       absorb(i.id i.year)
*       vce(cluster id)
eventstudyinteract y D095 D096 D097 D098 D100 D101 D102 D103 D104 D105, ///
    cohort(C2005 C2008) control_cohort(C0) ///
    absorb(id year) vce(cluster id)

* 4) 画图: 该命令返回的系数矩阵未命名, 用 r(table) 配合 coefplot 画图
* 或直接用 event_plot (ssc install event_plot) 读取 e(estimates)

5.4 Stacked DID:Cengiz et al. (2019) 与 Wooldridge (2021)

当上面"干净对照"难以定义(例如所有单位最终都被处理,没有 never-treated 组)时,Stacked DID(堆叠 DID)是另一条干净路径。核心思想是:为每一个处理队列 $g$ 单独切一个"小数据集"——只包含该队列 $g$ 加上"在 $g$ 前后窗口期内还没被处理或永远不被处理"的单位,然后把所有这些小数据集纵向堆叠(stack)成一个新面板,在堆叠数据上做带 stack 固定效应的 TWFE。这样每个小数据集内部都是干净的 2×2 DID,不会把已经处理的单位当对照。

Eq. 5.2 — Stacked DID 估计方程
$$ y_{i,t,s} = \beta_s\, D_{it}\cdot Post_{t\ge g_s} + \alpha_{i,s} + \gamma_{t,s} + \varepsilon_{it,s} $$

其中 $s$ 是 stack 索引(每个队列一个 stack),$\alpha_{i,s}$ 是"个体×stack"固定效应,$\gamma_{t,s}$ 是"时间×stack"固定效应。Cengiz, Dube, Lindner & Zipperer (2019, QJE) 用这套方法研究美国最低工资对低工资岗位的影响,发现就业反应接近于零。Wooldridge (2021) 从计量理论上证明了堆叠回归等价于一种扩展的 TWFE Mundlak 回归。Stata 上可以用 stackedev 命令一键实现:

stata · stacked_did.do
*--------------------------------------------------------------*
* Stacked DID: Cengiz, Dube, Lindner, Zipperer (2019, QJE)
*--------------------------------------------------------------*
ssc install stackedev, replace

* 语法:
*   stackedev  outcome,
*       cohort(first_treat_year)     // 每个体首次处理年份 (从未处理=0)
*       time(year) unit(id)
*       never_treat(no_treat)        // 从未处理组哑变量
*       window(-4 5)                 // 处理前后各取 4/5 期
*       event(rel_time)              // 相对 event-time 变量
*       covariates(x1 x2)
*       cluster(id)
*
* stackedev 会自动为每个队列 g 生成一个 stack, 只保留:
*   - 队列 g 的处理组
*   - 尚未处理 (t < g) 或永远不处理的对照组
* 然后把所有 stack 纵向合并, 跑带 stack×unit / stack×time FE 的 TWFE

* 在本页模拟数据上的示意:
gen no_treat = (g==0)
stackedev y, cohort(g) time(year) unit(id) ///
    never_treat(no_treat) window(-3 5) event(rel_time) ///
    cluster(id)

5.5 新估计量对比表

面对交叠 DID,到底选哪个?下表把 TWFE、Bacon 分解(诊断)、CS、SA、Stacked 五个方法放在一起对比:

方法核心假设估计量形式对照组要求适用场景 / 中文论文推荐
TWFE(传统) 平行趋势 + 同质性处理效应($\tau_{it}=\tau$) 所有 2×2 DID 加权平均,权重可负 隐含使用已处理组作对照 仅当处理时点统一或效应同质时可信;现在应作对照而非主回归。
Bacon 分解(诊断) 同上,但只是诊断工具 把 TWFE 拆成三类对比并报告权重 不需要新的对照假设 论文必做第一步:判断 TWFE 是否被污染。不单独报告结论。
CS(Callaway–Sant'Anna 2021) 条件平行趋势 + 无预期效应 $ATT(g,t)$ 双重稳健估计后简单/事件时间/日历时间聚合 需要 never-treated 或 not-yet-treated 中文顶刊新默认;输出友好、可画 event study;政策分批次推开时首选。
SA(Sun–Abraham 2021) 平行趋势 + 队列均匀分布 队列×event-time 交互加权,剔除污染 需要 never-treated 或最后处理队列 主做动态效应 / 事件研究图;与 csdid 的 event 聚合互为稳健性。
Stacked DID(Cengiz et al. 2019 / Wooldridge 2021) 平行趋势 + 局部窗口内干净对照 每队列切窗 → 堆叠 → TWFE with stack FE 可没有 never-treated,用 not-yet-treated 所有单位最终都被处理(无 never-treated)时的备选;也适合政策扩散型案例。

5.6 交叠 DID 专用 pre-trend 诊断(关键补充)

注意:在交叠 DID 设定下,不能直接用传统 TWFE 事件研究图的 leads 做 pre-trend 检验。原因见 s4:TWFE 的 leads 系数本身会被异质性处理效应与负权重污染,即使图上看起来平,也可能是坏比较相互抵消的假象。规范做法是:

  1. 主回归用 csdideventstudyinteract,输出"干净"的事件期 ATT;
  2. estat event 得到按事件时间聚合的 $\hat{\beta}_k$,对 pre 期 $k<0$ 的系数做联合检验;
  3. pretrends(Roth 2022)评估该 pre-trend 检验的功效;
  4. 若功效低,补充 Rambachan–Roth 敏感性界。
stata · s56_staggered_pretrend.do
* s5.6 交叠 DID 的 pre-trend 诊断流程
* 1) csdid 主估计(见 s5.2)
csdid y alpha_i gamma_t, ivar(id) time(year) gvar(gvar) ///
    method(dripw)

* 2) 事件时间聚合:得到干净的 leads/lags ATT
estat event
csdid_plot, title("csdid 干净事件研究图(无负权重污染)")

* 3) 对 estat event 输出的 pre 期 leads 做联合检验
*    estat event 返回的系数向量按事件时间排列;
*    在 Stata 中可通过 `e(event_results)` 或返回到矩阵后 test。
*
* 4) Roth (2022) 功效分析(在 csdid 之后)
*    pretrends 命令支持 csdid 输出的事件研究系数;
*    报告 "detectable pre-trend bias" 与检验功效。
*
* 关键写作要点(Roth et al. 2023, JoE 综述清单第 5 条):
*   "We report the pre-trend joint test on the
*    callaway-sant'Anna event estimates: leads k=-4..-1
*    are jointly not different from zero (Wald p=0.31).
*    pretrends indicates power of approximately 45%
*    to detect a linear pre-trend that would bias the
*    ATT by 20%, so we additionally report
*    Rambachan-Roth sensitivity bounds."
*
* 红线:交叠 DID 论文只贴一张 TWFE 事件研究图就宣称
*       "平行趋势成立"——这在 2024 年后的顶刊审稿中
*       几乎一定会被要求补做 csdid pre-trend 诊断。
实务建议(Roth et al. 2023 清单)

① 先跑 Bacon 分解,报告坏对比权重;② 主回归用 csdidestat event 出事件研究图);③ 稳健性表同时报告 eventstudyinteract(SA)与 did_multiplegt(dCDH);④ 无 never-treated 组时改用 stackedevcsdid, notyet;⑤ 政策前 leads 必须联合做 F 检验。

06 安慰剂检验 Placebo Test

为了证明你估计到的效应真的来自政策,而不是随机噪声或某种隐藏趋势,论文通常做安慰剂检验。规范做法是三种安慰剂同时报告,而不是只挑一种:

  • ① 随机化处理时点(placebo time):在样本中随机抽取"伪处理时点"(placebo time),重新跑 DID,重复 500/1000 次,画出估计系数的分布。真实的 DID 估计量应该落在 placebo 分布的尾部(小于 5% 的位置)。
  • ② 随机分配处理组(placebo group):随机抽取"伪处理组",重新估计,真实处理组的系数应显著偏离 placebo 分布。
  • ③ 伪结果变量(placebo outcome):用一个理论上不受政策影响的结果变量(如性别、年龄、企业成立年份)重复回归,应得到不显著系数。
stata · s6_placebo.do
* s6 安慰剂检验:三种做法
* 沿用 s7 模拟数据

* (a) 伪处理时点:把政策时点提前到 2007(真实政策 2012)
gen treat_fake = (g!=0 & year>=2007)
reghdfe y treat_fake, absorb(id year) vce(cluster id)
* 期望:treat_fake 不显著;若显著 → pre-trend 可疑

* (b) 随机分配伪处理组:500 次蒙特卡洛
permute treat, reps(500) seed(2026) ///
    saving("placebo_dist.dta", replace): ///
    reghdfe y treat, absorb(id year) vce(cluster id)
* 真实系数 0.8 应落在 placebo 分布 95% 分位之外

use "placebo_dist.dta", clear
sum _b_treat, detail
* 报告:真实系数在 placebo 分布中的分位(应 < p5 或 > p95)

* (c) 伪结果变量:用理论上不受政策影响的变量
gen placebo_y = rnormal(0,1)         // 纯噪声
reghdfe placebo_y treat, absorb(id year) vce(cluster id)
* 期望:treat 系数不显著

* 红线:从 1998..2010 扫伪时点,挑一个"刚好不显著"的写论文
*      合法:伪时点事前选定(如政策前最早一期)+ 500次随机分布
⚠ 安慰剂检验的常见误用

① 只做 (a) 伪时点,不做 (b) 随机化分布;② 500 次随机化分布图里真实系数其实落在 placebo 分布中部,却只报告"均值≈0"不提分位数;③ 伪时点选择事后挑选。规范做法是三种 placebo 互相印证,并报告真实系数在 placebo 分布中的分位数。

06b 平行趋势不通过时:诊断原因与合法补救

pre-trend 联合检验拒绝(或图形上明显漂移)后,不能硬说"通过了",也不能"事后改窗口凑通过"。正确做法是按下面的顺序诊断原因、选择合法补救方案。

6b.1 先诊断:不通过的可能原因

可能原因诊断方法合法补救
处理时机内生:政策挑了本来就在变化的地区先试点事件研究图 pre 期系数单调漂移;试点批次的事前趋势差异大csdid 加协变量做条件平行趋势;Rambachan–Roth 敏感性界
时变混杂:处理组与对照组面临不同宏观冲击加行业×年 FE 或地区×年 FE 后 pre-trend 改善加入更高维交互固定效应;用 not-yet-treated 替代 never-treated
交叠 DID 的负权重污染Bacon 分解坏对比权重 >0.3换 csdid / eventstudyinteract / stackedev;不要硬用 TWFE
预期效应(anticipation):政策宣布前市场已反应政策前 1 期系数已经偏离 0把政策前 1 期从基准期剔除;估计 anticipation
某事件期观测太少:远端 leads SE 巨大导致检验失真看各 event-time 处理组观测数合并相邻期(见 s2.3)

6b.2 合法补救方案(按推荐顺序)

  1. 局部窗口(local window):只取政策前后 ±2~3 年。窗口选择必须由制度依据决定(如"政策实施前后最近一期完整数据"),不能事后挑。
  2. 条件平行趋势 + 协变量平衡:csdid 的 covariates() 选项,按可观测特征做 IPW 加权。
  3. Rambachan–Roth 敏感性界:允许 pre 期存在一定幅度偏离(Mbar)时,post 效应的 CI 范围。若 Mbar=1(允许线性偏离)下 CI 仍不含 0,则结论对温和 pre-trend 偏离稳健。
  4. 合成 DID / synthetic control:找不到干净对照组时,用合成控制构造反事实。
  5. Bacon 分解 + 组别-时期估计:找出是哪个队列在污染 pre-trend,单独报告该队列 ATT。
  6. 合并观测量检验:远端 leads 观测太少时合并相邻期(见 s2.3)。
  7. 如实报告 + 敏感性分析:承认平行趋势受限,把结论限定为"相关性 + 诊断性证据"而非强因果;换 RDD/IV 设计。
⛔ 红线:为"凑通过"而事后改窗口/合并分组/选择性报告

审稿人识破手段:① 要求全部事件期系数表——只画显著性好的那几期图藏不住;② 要求原始数据与代码复现——事后改窗口的痕迹在 do 文件里一目了然;③ 问"你为什么选 ±3 年窗口而不是 ±5 年?"——答不出制度/理论依据即露馅;④ 要求 pretrends 功效报告——低功效下宣称"通过"是自相矛盾。平行趋势检验不是"调显著"的旋钮,而是识别可信度的体检报告。体检异常不代表结论作废,但篡改体检报告等于学术不端。

stata · s6b_remedies.do
* s6b 平行趋势不通过时的合法补救(红线示例注释在内)
* 沿用 s7 模拟数据

* 合法方案1:局部窗口(±2~3 年,事前由制度依据决定)
preserve
    keep if year>=2010 & year<=2014      // 政策2012前后各2年
    csdid y, ivar(id) time(year) gvar(gvar) method(dripw)
    estat event
restore

* 合法方案2:csdid 加协变量做条件平行趋势
csdid y alpha_i gamma_t, ivar(id) time(year) gvar(gvar) ///
    method(dripw) covariates(alpha_i gamma_t)
estat event

* 合法方案3:Bacon 分解找出污染队列
* bacondecomp y treat, ddetail
* 若 "Later T vs Earlier C" 权重 > 0.3 → 换 csdid

* 合法方案4:Rambachan-Roth 敏感性界
*   Stata 中需配合 R 包 Honor & Roth;
*   报告模板:
*   "Even when Mbar=1 (allowing a linear pre-trend of
*    the same magnitude as estimated), the lower bound
*    of the ATT CI remains positive at 0.21."

* ⛔ 红线(严禁):
*   - 先跑全样本 pre-trend 不通过,回头把窗口改成 ±2 年"凑通过"
*   - 把漂移严重的队列从样本里删掉,理由是"该组异常"
*   - 只在正文报"pre-trend 通过",附录才出现不通过版本

07 完整 Stata 代码

下面这段代码完整演示:(1) 构造交叠 DID 模拟数据;(2) 经典 TWFE DID;(3) 事件研究图;(4) PSM-DID;(5) csdid / did_multiplegt / did_imputation 三大新估计量;(6) 安慰剂检验。所有命令都可以在 Stata 16+ 上运行。

stata · did_full.do
*==============================================================*
* DID 全套演示:经典 DID + 事件研究 + PSM-DID + 交叠 DID 新方法
*==============================================================*
clear all
set more off
set seed 20260911

* --- 首次运行安装外部命令 ---
* ssc install csdid,             replace
* ssc install drdid,              replace
* ssc install eventdd,            replace
* ssc install eventstudyinteract, replace
* ssc install did_multiplegt,     replace
* ssc install did_imputation,    replace
* ssc install bacondecomp,        replace   // Bacon 分解诊断
* ssc install stackedev,         replace   // Stacked DID
* ssc install psmatch2,           replace
* ssc install coefplot,           replace

* --- 1. 构造交叠 DID 模拟面板 ---
* 100 个个体,2000-2010 共 11 年
* 处理组分组:
*   g1: 2005 年开始处理(30 个)
*   g2: 2008 年开始处理(30 个)
*   g0: 从未处理(40 个,对照组)
set obs 100
gen id = _n
gen g = cond(_n<=30, 2005, cond(_n<=60, 2008, 0))   // g=0 表示 never-treated
expand 11
bysort id: gen year = 1999 + _n

xtset id year

* 处理变量:treat=1 表示该个体在该年已被处理
gen treat = (g!=0 & year >= g)

* 结果变量:包含个体 FE、时间 FE、处理效应(处理后每年 +0.8)
gen eps = rnormal()
gen alpha_i = mod(id,10)
gen gamma_t = (year-2000)*0.1
gen y = 5 + alpha_i + gamma_t + 0.8*treat + eps

* --- 2. 经典 TWFE DID ---
reghdfe y treat, absorb(id year) vce(cluster id)
estimates store twfe

* --- 3. 事件研究图(手工构造相对时点虚拟变量) ---
gen rel_time = year - g if g!=0
forvalues k=-5/5 {
    local kk = `k' + 100          // 避免变量名以负号开头
    gen D`kk' = (rel_time==`k') if g!=0
    replace D`kk' = 0 if g==0
}
* 省略 k=-1(即 rel_time==-1, D099)作为基准
reghdfe y D095 D096 D097 D098 D100 D101 D102 D103 D104 D105, ///
    absorb(id year) vce(cluster id)
coefplot, keep(D*) vertical yline(0) ///
    xtitle("相对政策时点") ytitle("估计系数") ///
    title("事件研究图(平行趋势检验)")

* --- 4. PSM-DID ---
* 先用处理前数据估计倾向得分
preserve
    keep if year<2005
    bysort id: keep if _n==1
    gen ever_treat = (g!=0)
    psmatch2 ever_treat alpha_i gamma_t, out(y) neighbor(1) common
    keep id _weight
    tempfile weights
    save "`weights'"
restore
merge m:1 id using "`weights'", nogen

* 在匹配后的共同支撑样本上做 DID
reghdfe y treat [pw=_weight], absorb(id year) vce(cluster id)
estimates store psmdid

* --- 5. Callaway-Sant'Anna (csdid) ---
* 需要 didvar 是"首次处理年份"(never-treated 为 0)
gen gvar = g
csdid y alpha_i gamma_t, ivar(id) time(year) gvar(gvar) method(dripw)
estat all                           // 汇总 ATT
csdid_plot, title("CSDID 事件研究图")

* --- 6. Sun-Abraham (eventstudyinteract) ---
* 先构造 cohort 虚拟变量
forvalues gg=2005(3)2008 {
    gen C`gg' = (g==`gg')
}
eventstudyinteract y D095 D096 D097 D098 D100 D101 D102 D103 D104 D105, ///
    cohort(C2005 C2008) control_cohort(C0) ///
    absorb(id year) vce(cluster id)

* --- 7. de Chaisemartin-D'Haultfoeuille (did_multiplegt) ---
did_multiplegt y id year treat, robust_dynamic dynamic(5) breps(100) cluster(id)

* --- 8. Borusyak-Jaravel-Spiess (did_imputation) ---
did_imputation y id year gvar, horizons(0/5) autosample minn(0)

* --- 9. 安慰剂检验(随机分配伪处理时点) ---
permute treat, reps(500) seed(1): ///
    reghdfe y treat, absorb(id year) vce(cluster id)
* 真实估计系数应落在 placebo 分布的 5% 或 95% 之外

* --- 10. 导出结果 ---
esttab twfe psmdid using "table_did.rtf", replace ///
    b(%9.3f) se(%9.3f) star(* 0.10 ** 0.05 *** 0.01) ///
    mtitles("TWFE DID" "PSM-DID") ///
    stats(N r2, fmt(0 3)) title("DID 结果对比") nogaps compress

07b 进阶扩展:两阶段 DID、插补 DID、分位数 DID 与面板匹配

s5 已经覆盖交叠 DID 的四大"无负权重"估计量(CS / SA / dCDH / Stacked)。本节再补四块 2021 年后的进阶内容:(1) Gardner (2021) 的两阶段 DID did2s;(2) Borusyak–Jaravel–Spiess (2024, ReStud) 的插补 DID did_imputation;(3) 估计处理对结果分布的影响(分位数 DID / QTT);(4) 面板匹配与事件研究的标准化做法。

7b.1 Gardner (2021) 两阶段 DID(did2s

原理:Gardner (2021) 的观察是——TWFE 之所以出负权重,是因为它把"估计个体/时间固定效应"和"估计处理效应"塞进同一次 OLS,处理观测被同时用来估固定效应,污染了对照。两阶段做法把这两件事拆开:

第一阶段:只用未处理观测估固定效应
在 $D_{it}=0$ 的子样本上跑 $y_{it}=\alpha_i+\gamma_t+\varepsilon_{it}$,把个体 FE $\hat\alpha_i$ 和时间 FE $\hat\gamma_t$ 估出来。处理观测不参与这一步,避免污染。
第二阶段:去固定效应后估处理效应
构造残差 $\tilde y_{it}=y_{it}-\hat\alpha_i-\hat\gamma_t$,再在全样本上把 $\tilde y_{it}$ 对处理虚拟变量回归,系数就是无负权重的 DID 估计量。
Eq. 7b.1 — Gardner 两阶段 DID
$$ \text{Stage 1: } \hat\alpha_i,\hat\gamma_t \leftarrow \min_{\alpha_i,\gamma_t}\sum_{i,t:D_{it}=0}(y_{it}-\alpha_i-\gamma_t)^2, \qquad \text{Stage 2: } \hat\tau \leftarrow \frac{\sum_{i,t}D_{it}(y_{it}-\hat\alpha_i-\hat\gamma_t)}{\sum_{i,t}D_{it}} $$

它与 BJS 插补估计量在大样本下渐近等价(见 7b.2),但 did2s 写法直观、计算快、天然支持事件研究设定。Stata 实现是 Kyle Butts 编写的 did2s(与 Gardner 合作)。

stata · s7b1_did2s.do
*--------------------------------------------------------------*
* Gardner (2021) 两阶段 DID:did2s
* ssc install did2s, replace
*--------------------------------------------------------------*
set seed 20260911

* 沿用 s7 模拟面板:id, year, g(首次处理年,0=never), treat
xtset id year

* --- 静态 ATT ---
* first_stage  = 用来估固定效应的变量(个体 FE + 时间 FE)
* second_stage = 处理变量
did2s y, first_stage(i.id i.year) second_stage(treat) ///
    treatment(treat) cluster(id)
estimates store did2s_att

* --- 事件研究形式(推荐,看动态效应 + pre-trend) ---
* 先构造事件时间虚拟变量(省略 k=-1 为基准,同 s7)
gen rel_time = year - g if g!=0
forvalues k=-5/5 {
    local kk = `k' + 100
    gen D`kk' = (rel_time==`k') if g!=0
    replace D`kk' = 0 if g==0
}
did2s y, first_stage(i.id i.year) ///
    second_stage(D095 D096 D097 D098 D100 D101 D102 D103 D104 D105) ///
    treatment(treat) cluster(id)
* 第二阶段系数即无负权重的动态效应;pre 期 D095..D098 应联合不显著。

7b.2 Borusyak–Jaravel–Spiess (2024) 插补 DID(did_imputation

原理:Borusyak, Jaravel & Spiess (2024, Review of Economic Studies 91(6): 3253–3285) 证明:在交叠 DID + 异质性处理效应下,插补估计量(imputation estimator)是渐近有效的。做法与 Gardner 两阶段同源——只用未处理观测估"个体 FE + 时间 FE",再用这套 FE 插补每个处理观测的反事实结果 $Y_{it}(0)=\hat\alpha_i+\hat\gamma_t$,残差 $Y_{it}-\hat Y_{it}(0)$ 就是该观测的处理效应,最后按目标加权平均。

Eq. 7b.2 — BJS 插补估计量
$$ \hat\tau_{it}=Y_{it}-\hat\alpha_i-\hat\gamma_t \quad(\text{仅用 }D_{it}=0\text{ 估 }\hat\alpha,\hat\gamma), \qquad \hat\tau=\frac{1}{N_T}\sum_{i,t:D_{it}=1}\hat\tau_{it} $$

它的优势是效率最高(充分利用未处理观测的信息)、支持任意事件时间聚合、对"所有单位最终都被处理"的情形也能用 auto 对照。Stata 命令 did_imputation。注意:本页 s5 表格与 s7 代码已引用它,这里给出完整原理与多 horizon 写法。

作者归属澄清

插补 DID 的正式发表论文是 Borusyak, Jaravel & Spiess (2024, ReStud);did_imputation 命令即实现该文。它与 Gardner (2021) 两阶段在思想上独立提出、大样本渐近等价,实务中二者互为稳健性。

stata · s7b2_did_imputation.do
*--------------------------------------------------------------*
* Borusyak-Jaravel-Spiess (2024, ReStud) 插补 DID
* ssc install did_imputation, replace
*--------------------------------------------------------------*
set seed 20260911
xtset id year

* 语法: did_imputation outcome panelvar timevar cohortvar, ...
*   cohortvar = 每个体首次处理年份;从未处理 = 0
*   horizons(0/5) = 估计处理后 0..5 期的动态效应
*   autosample    = 自动选未处理对照样本
*   minn(0)       = 不要求最小队列规模
did_imputation y id year gvar, horizons(0/5) autosample minn(0) cluster(id)

* 静态总效应(不展开 horizon):
did_imputation y id year gvar, autosample minn(0) cluster(id)

* 无 never-treated 组时:用 not-yet-treated 作插补对照
* did_imputation y id year gvar, horizons(0/5) autosample minn(0) ///
*     control(notyet) cluster(id)

* 输出按事件时间排列,可直接 coefplot 画事件研究图:
* coefplot, keep(tau*) vertical yline(0)

7b.3 分布处理效应与分位数 DID(QTT / Callaway–Li–Oka)

前面所有 DID 估计量都只回答"政策对均值的影响"。但政策往往改变整个结果分布:最低工资可能把工资分布底部抬起来、却对中位数以上无影响;减税可能主要利好高收入分位。分位数 DID(Quantile DID)估计处理对结果分布各分位数的影响,即分位数处理效应 $QTT_\tau$:

Eq. 7b.3 — 分位数处理效应 QTT
$$ QTT_\theta = Q_{Y(1)}(\theta \mid T=1) - Q_{Y(0)}(\theta \mid T=1), \qquad \theta \in (0,1) $$

代表文献是 Callaway, Li & Oka (2019, Quantitative Economics) "Quantile treatment effects in difference in differences models with panel data",在面板数据下识别并估计各分位数的处理效应;其交错推广由 R 包 qteqdid_gt() 实现。Stata 端没有官方 qdid 命令,下面给出手工分位数 DID(对结果变化量做分位数回归)的可运行写法,并注明 R 端标准实现。

什么时候必须报分布效应

当你的机制故事是"政策压平/拉大收入差距""只影响尾部""对穷人 vs 富人作用不同"时,只报均值 ATT 是不够的。审稿人会要求看 0.1/0.5/0.9 分位的效应是否分化。

stata · s7b3_quantile_did.do
*--------------------------------------------------------------*
* 分位数 DID / QTT(Callaway-Li-Oka 2019)Stata 手工实现
* 思路:在"结果变化量 Δy"上对处理虚拟变量做分位数回归
*--------------------------------------------------------------*
set seed 20260911

* 构造两期面板(政策前 year0 / 政策后 year1)
* 假设已有:id, year, y, treat(处理组=1)
preserve
    keep if year==2005 | year==2012
    reshape wide y, i(id) j(year)
    gen dy = y2012 - y2005              // 结果变化量
    gen treat = (g!=0)

    * 各分位 QTT:τ = 0.1, 0.25, 0.5, 0.75, 0.9
    foreach q in 0.1 0.25 0.5 0.75 0.9 {
        qreg dy treat, quantile(`q') vce(robust)
        display "QTT_`q' = " _b[treat]
    }
    * 若 QTT_0.1 > QTT_0.5:政策主要抬升分布底部 → 缩小差距的故事
restore

* --- 标准实现(推荐论文用):R 包 qte 的 qdid_gt ---
* R:  library(qte)
*     qdid_gt(yname="y", tname="year", idname="id",
*             gname="g", x=~1, data=panel_data,
*             probs=c(0.1,0.25,0.5,0.75,0.9))
* 输出各队列×分位的 QTT,交错 DID 下无负权重。

* --- csdid 的分布扩展 ---
* csdid 本身估均值 ATT(g,t);分布版本需走 R 包 did + qte。
* Stata 中可用 csdid 出均值,再用上面 qreg(dy) 出分位作对照。

7b.4 面板匹配与事件研究的标准化做法

平行趋势假设最常见的违反来源是:处理组和对照组事前就处于不同的结果水平/趋势上。面板匹配(panel matching)的思想是:在政策实施前,为每个处理单位找一个"事前结果路径尽量像它"的对照单位,用这一对相似单位来做 DID,而不是全体对照。

① 只用政策前数据构造匹配变量
用处理前几期的结果水平、趋势、协变量定义"距离",禁止用政策后信息。
② 近邻匹配/卡尺匹配
为每个处理单位在对照池中找事前路径最接近的 k 个单位(nearest neighbor on pre-trend level & slope),设 caliper 防止错配。
③ 在匹配样本上跑事件研究
匹配后处理组/对照组事前趋势应平行;再用 csdid/eventstudyinteract 出干净的动态效应。
stata · s7b4_panel_match.do
*--------------------------------------------------------------*
* 面板匹配:匹配事前结果路径,再做事件研究
*--------------------------------------------------------------*
set seed 20260911

* --- 1) 提取每个单位政策前的"结果水平 + 趋势"特征 ---
preserve
    keep if year < 2005                 // 只用政策前数据
    * 政策前结果均值
    collapse (mean) pre_y_mean = y (sd) pre_y_sd = y, by(id g)
    * 政策前趋势斜率:y 对 year 的斜率(另一组回归,此处示意用首末期差)
    tempfile prefeat
    save "`prefeat'"
restore
merge m:1 id using "`prefeat'", nogen

* --- 2) 近邻匹配:用 psmatch2 在对照池找事前最像的对照 ---
* 处理组 g!=0,对照 g==0;匹配变量 = 事前结果均值/标准差
gen ever_treat = (g!=0)
psmatch2 ever_treat pre_y_mean pre_y_sd, out(y) neighbor(1) caliper(0.05) common
keep id _weight
tempfile wmatch
save "`wmatch'"
use, clear                            // 重新载入完整面板
* (实际流程中在原面板 merge _weight 后继续)

* --- 3) 匹配样本上跑标准化事件研究 ---
* match 样本上处理/对照事前趋势应平行;
* 再用无负权重估计量出动态效应:
* csdid y, ivar(id) time(year) gvar(gvar) method(dripw)
* estat event
* csdid_plot
*
* 标准化事件研究 checklist:
*   (a) 基准期省略 k=-1(不是 k=0!);
*   (b) pre 期 leads 联合 F 检验 + pretrends 功效;
*   (c) 远端观测太少的相邻期合并(见 s2.3);
*   (d) 用 csdid/SA 的干净系数,不用 TWFE 事件研究做 pre-trend 判断。
面板匹配 vs PSM-DID(s3)

s3 的 PSM-DID 匹配的是可观测协变量;面板匹配匹配的是事前结果路径(水平+趋势),直接对准平行趋势假设本身。两者可叠加:先面板匹配事前趋势,再在匹配样本上做 csdid。

08 论文案例与常见错误

论文案例

English · AER
Minimum Wages and Employment: A Case Study of the Fast-Food Industry in New Jersey and Pennsylvania
David Card & Alan B. Krueger · AER, 1994
DID 的开山之作。新泽西 1992 年提高最低工资,宾州未变。用 410 家快餐店的工资、就业数据,构造 2×2 DID,发现最低工资上升并未减少就业。这是"政策时点相同、处理组不同"的经典 2×2 设定。
English · JoE
Difference-in-Differences with Variation in Treatment Timing
Andrew Goodman-Bacon · Journal of Econometrics, 2021
Bacon 分解(Bacon decomposition)的原始论文。证明交叠 DID 中的 TWFE 估计量本质上是所有可能的 2×2 DID 估计量的加权平均,权重由各队列的处理暴露(exposure)决定;其中"已处理组 vs 未处理组"的坏对照(forbidden comparison)在异质性处理效应下会得到负权重。理解负权重问题的第一篇必读文献。
English · JoE
Difference-in-Differences with Multiple Time Periods
Brantly Callaway & Pedro H. C. Sant'Anna · Journal of Econometrics, 2021
csdid 命令的原始论文。提出估计组×期层面 $ATT(g,t)$ 的两步估计量,并用 inverse probability tilting 做权重。Stata 命令 csdid 直接实现。
English · AER
Two-Way Fixed Effects Estimators with Heterogeneous Treatment Effects
Clément de Chaisemartin & Xavier d'Haultfœuille · American Economic Review, 2020
证明当处理效应异质时,TWFE 估计量是 2×2 DID 估计量的带权平均,权重可能为负。给出 $DID_M$ 估计量。
English · JoE
Estimating Dynamic Treatment Effects in Event Studies with Heterogeneous Treatment Effects
Liyang Sun & Sarah Abraham · Journal of Econometrics, 2021
指出传统事件研究回归中的 leads/lags 系数会被其他队列的处理效应污染(good/bad comparisons 混杂)。提出交互加权估计量:按队列×期构造干净的动态处理效应,剔除负权重。Stata 命令 eventstudyinteract 直接实现。
English · QJE
The Effect of Minimum Wages on Low-Wage Jobs
Doruk Cengiz, Arindrajit Dube, Attila Lindner & Ben Zipperer · Quarterly Journal of Economics, 2019, 134(3): 1405–1454
Stacked DID 的开山应用论文。利用美国 138 个州级最低工资上调事件,为每个处理时点切一个窗口、堆叠成新面板再做 TWFE,发现最低工资上调对低工资岗位的就业弹性接近零。是"所有单位最终都被处理、无 never-treated 组"时的标准范式。Stata 命令 stackedev 直接实现。
English · NBER WP
Two-Way Fixed Effects, the Two-Way Mundlak Regression, and Difference-in-Differences Estimators
Jeffrey M. Wooldridge · NBER Working Paper, 2021
从计量理论上证明:堆叠 DID 与"处理变量×队列"交互的扩展 TWFE Mundlak 回归在数学上等价,并给出了在异质性处理效应下仍然一致的估计量形式。为 Stacked DID 提供了理论基础。
English · JoE
What's Trending in Difference-in-Differences? A Synthesis of the Recent Econometrics Literature
Jonathan Roth, Pedro H. C. Sant'Anna, Alyssa Bilinski & John Poel · Journal of Econometrics, 2023
交叠 DID 新文献的权威综述。系统梳理 Bacon 分解、负权重问题,以及 csdid / eventstudyinteract / did_multiplegt / did_imputation 等新估计量的适用条件与相互权衡,并给出实务清单(何时必须报告新估计量、平行趋势的敏感性分析)。入门交叠 DID 的最佳路线图。
English · JoE
Pretends: A Stata Package to Assess the Sensitivity of Event-Study Estimates to Violations of Parallel Trends
Jonathan Roth · Journal of Econometrics, 2022; Stata command pretrends
证明 pre-trend 联合检验在大多数实务样本里功效只有 30–50%,不能把"不显著"当"平行趋势成立"。提供 pretrends 命令估计可检测的最小 pre-trend 斜率,并配合 Rambachan–Roth 敏感性界做"允许 pre 期偏离时 ATT CI 还跨不跨 0"的分析。本页 s2.2、s5.6、s6b 的核心方法依据。
中文 · 《经济研究》
《增值税转型改革对企业投资的影响》类交叠 DID 文章
《经济研究》《管理世界》近年多篇 · 通用范式
中国的"营改增""高新技术企业认定""自贸区设立"等政策几乎都是分批次、分地区推开的。近年顶刊文章已经普遍在主回归报告 csdid / did_multiplegt 结果,并附上经典 TWFE 作为对照。这是中文实证的"新规范"。
English · WP
Two-Stage Differences in Differences
John Gardner · Working Paper, 2021(UCL);Stata 实现 Kyle Butts did2s
提出两阶段 DID:第一阶段只用未处理观测估个体/时间固定效应,第二阶段去固定效应后估处理效应,干净规避交叠 DID 的负权重。与 BJS (2024) 渐近等价。本页 7b.1 的方法依据。
English · ReStud
Revisiting Event-Study Designs: Robust and Efficient Estimation
Kirill Borusyak, Xavier Jaravel & Jann Spiess · Review of Economic Studies, 2024, 91(6): 3253–3285
证明交叠 DID 下插补估计量的渐近有效性:只用未处理观测估双向固定效应、插补处理观测的反事实结果再取残差均值。Stata 命令 did_imputation。本页 7b.2 的方法依据。
English · Quant. Econ.
Quantile Treatment Effects in Difference in Differences Models with Panel Data
Brantly Callaway, Tong Li & Takuya Oka · Quantitative Economics, 2019, 10(4): 1579–1618
面板数据下识别并估计各分位数的处理效应(QTT),把 DID 从均值拓展到整个分布。交错推广由 R 包 qteqdid_gt() 实现。本页 7b.3 的方法依据。
中文 · 《管理世界》
《环境规制/产业政策分批推开》类分布与动态 DID 文章
《经济研究》《管理世界》近年 · 通用范式
中央环保督察、智能制造试点、创新型城市等政策在城市/企业层面分批次推开。规范做法已是:Bacon 分解 + csdid/did_imputation 主回归 + 事件研究图;若机制涉及"贫富分化/尾部效应",进一步报告分位数 DID。

常见错误

错误 1:忽略交叠 DID 的负权重问题

只要政策时点不统一,直接 reghdfe y TreatPost 就可能得到符号错误的结论。必须至少报告一个新估计量。

错误 2:事件研究图政策前系数不显著却不报告 pre-trend F 检验

单期看不显著可能是功效低。必须把所有政策前 leads 联合做 F 检验,并报告 p 值。

错误 2b:把 pre-trend 检验不显著直接写成"平行趋势成立"(Roth 2022)

pre-trend 联合检验的功效常常只有 30–50%。p=0.21 只意味着"不能拒绝平行趋势",不意味着"平行趋势成立"。必须配合 pretrends 命令报告检验功效与可检测的最小 pre-trend 斜率,并在功效低时做 Rambachan–Roth 敏感性界。

错误 2c:交叠 DID 只用 TWFE 事件研究图做 pre-trend 诊断

交叠 DID 下 TWFE 的 leads 系数本身会被负权重污染(见 s4),不能直接用于 pre-trend 检验。必须用 csdid 的 estat event 输出干净的事件期系数后再做联合检验(见 s5.6)。

错误 3:对照组选错

在交叠 DID 中,"已经被处理的组"不能直接当作"对照组"。csdid 要求明确指定 never-treated 或 not-yet-treated。

错误 4:PSM-DID 只做匹配不做 DID

PSM 只能平衡可观测特征,不可观测自选择依然存在。必须在匹配后的样本上继续做 DID,不能退化成 PSM。

错误 5:交叠 DID 只报 TWFE,不做 Bacon 分解/负权重诊断,也不列新估计量对照

政策时点不统一时,主回归必须换成 csdid / did_imputation / did2s 之一,并在同一张表列出 TWFE 作对照。直接交一个 reghdfe y TreatPost 就交差,审稿人会要求补做全套诊断。

错误 6:事件研究基准期(省略期)选错

事件研究必须省略政策前一期 $k=-1$ 作基准(系数=0),不能省略 $k=0$(政策当年)或 $k=1$。基准期选成政策后,会把已实现的处理效应污染进所有 leads/lags,图形与 pre-trend 判断全部失真。

错误 7:只报一个平均 ATT,不报告动态效应 / 事件研究图,也不看分布

政策效应很可能先升后降或只影响分布尾部。规范做法必须报 estat event 的动态效应图;若机制故事涉及"拉大/缩小差距",再补分位数 DID(7b.3)。只有一个均值系数、一张事件研究图都不画的 DID,2024 年后很难过审。

进阶资料

  • Goodman-Bacon (2021)、de Chaisemartin & D'Haultfœuille (2020)、Callaway & Sant'Anna (2021)、Sun & Abraham (2021)、Roth et al. (2023) —— 详见上方"论文案例"中的交叠 DID 文献卡片。
  • 孙圣凯、侯新烁等中文教程 — 国内经济学者整理的 csdid / did_multiplegt 实操笔记。