Staggered DID: TWFE Bias and New Estimators (CS/SA/BJS)
An in-depth explanation of TWFE bias in staggered DID designs, introducing the principles and practical implementation of three new estimators: Callaway-Sant'Anna, Sun-Abraham, and Borusyak-Jaravel-Spiess.
Article Structure
- Principles: Why TWFE is biased under staggered treatment
- Intuition: An intuitive understanding of the negative weighting problem
- Code: A comparative implementation of CS/SA/BJS estimators in Stata
Layer 1: Principles
Why is TWFE biased?
Under the standard staggered treatment setting, the TWFE estimator is:
where , and is the time when unit first receives treatment.
The Problem: TWFE's is a weighted average of all DID estimators, but the weights can be negative!
When , even if all , can be negative or severely deviate from the true ATT.
Three New Estimators
| Estimator | Core Idea | Control Group Definition |
|---|---|---|
| Callaway & Sant'Anna (2021) | Estimate separately by cohort-time, then aggregate | Never-treated + not-yet-treated |
| Sun & Abraham (2021) | Interaction-weighted estimator | Never-treated |
| Borusyak, Jaravel & Spiess (2024) | Imputation method, first estimate , then take differences | Never-treated + not-yet-treated |
Layer 2: Intuition
An Intuitive Understanding of Negative Weights
Imagine three states implementing a policy in 2010, 2012, and 2014, respectively. When TWFE estimates the effect for the 2012 state, it uses the states already treated in 2010 as the "control group"—but these states have already received treatment!
If the effects for early-treated states are growing over time, using them as "controls" will underestimate the effects for later-treated groups, potentially even producing negative weights.
Layer 3: Stata Code
// ═══════════════════════════════════════════════
// Staggered DID: TWFE vs CS vs SA
// ═══════════════════════════════════════════════
clear all
set seed 11111
// Simulate staggered treatment data
local N = 300
local T = 15
set obs `=`N'*`T''
gen id = ceil(_n/`T')
bysort id: gen t = _n + 2005
gen treat_time = .
replace treat_time = 2010 if id <= 100
replace treat_time = 2012 if id > 100 & id <= 200
gen treated = (t >= treat_time) if treat_time != .
replace treated = 0 if treat_time == .
// Heterogeneous treatment effects
gen tau = 0
replace tau = 2 if treated == 1 & treat_time == 2010
replace tau = 4 if treated == 1 & treat_time == 2012
gen y = 1 + 0.5*t + tau + rnormal(0, 1)
// ═══ TWFE (biased) ═══════════════════════════
reghdfe y treated, absorb(id t) cluster(id)
// ═══ Callaway & Sant'Anna ═══════════════════
gen gvar = treat_time
replace gvar = 0 if treat_time == .
csdid y, ivar(id) time(t) gvar(gvar) agg(simple)
csdid y, ivar(id) time(t) gvar(gvar) agg(event)
csdid_plot
The complete code file can be downloaded from the code library.
References
- Callaway, B., & Sant'Anna, P. H. C. (2021). Difference-in-Differences with Multiple Time Periods. Journal of Econometrics, 225(2), 200-230.
- Sun, L., & Abraham, S. (2021). Estimating Dynamic Treatment Effects in Event Studies with Heterogeneous Treatment Effects. Journal of Econometrics, 225(2), 175-199.
- Borusyak, K., Jaravel, X., & Spiess, J. (2024). Revisiting Event-Study Designs: Robust and Efficient Estimation. Review of Economic Studies.
- de Chaisemartin, C., & D'Haultfœuille, X. (2020). Two-Way Fixed Effects Estimators with Heterogeneous Treatment Effects. AER, 110(9), 2964-2996.