σ StatsDoge Causal inference workflows
10
Workflow·4 steps·branched

Event-study DiD with Sun & Abraham (fixest)

Source fixest — Laurent Bergé
Summary by StatsDoge

Fast fixed-effects event study that survives staggered timing — sunab() vs naive TWFE, plotted against the truth.

1

Input · what goes in

A panel with staggered treatment cohorts: unit id, period, cohort (first-treatment period), and an outcome.

Show data format & exampleHide example

Format — one row per (unit, period). cohort = first treated period.

 id  period  cohort   y
  1      1       3    2.0
  1      2       3    2.1
  2      1       5    1.8
2

Pipeline · the recipe ⑂ has parallel branches

↑ Click any step in the diagram to read its logic, code, assumptions & discussion.

1
Data prep

Assemble panel with cohort timing

Data preparation — shapes the raw inputs into what the estimator expects.

What happens here

Panel id × period; cohort = the period each unit is first treated.

Reads from the input data Feeds into #2#3
Key code
data(base_stagg)
Discussion on this step (0)
  • No comments on this step yet — be the first.
2
Estimation

[fixest] Sun & Abraham event study — sunab()

The core estimate — where the causal quantity itself is computed.

What happens here

Fit feols with sunab(cohort, period) — interaction-weighted, robust to heterogeneous timing.

Formula
Y_{it}=\alpha_i+\lambda_t+ extstyle\sum_{e e-1}eta_e\,\mathbb{1}[\,t-g_i=e\,]+\varepsilon_{it}
The estimator

Sun & Abraham event study — sunab() — Interaction-weighted event-study estimator robust to heterogeneous treatment timing, in a fast fixed-effects framework.

Reads from #1 Feeds into #4
Key code
m <- feols(y ~ sunab(year_treated, year) | id + year, base_stagg)
Discussion on this step (0)
  • No comments on this step yet — be the first.
3
Robustness check

Naive TWFE comparison

A robustness check — does the headline result survive a different lens?

What happens here

Estimate plain TWFE event-study to see the bias Sun-Abraham corrects.

Reads from #1 Feeds into #4
Key code
twfe <- feols(y ~ i(time_to_treat, ref = -1) | id + year, base_stagg)
Discussion on this step (0)
  • No comments on this step yet — be the first.
4
Reporting

iplot(): SA20 vs TWFE vs truth

Reporting — turn the numbers into a figure or table a reader can act on.

What happens here

Overlay the event-study coefficients; Sun-Abraham tracks the true effect, TWFE drifts.

Reads from #2#3 Feeds into the final output
Key code
iplot(list(m, twfe))

Reference / docs ↗

Discussion on this step (0)
  • No comments on this step yet — be the first.
3

Output · what you get 3 figures

Sun–Abraham event-study coefficients from sunab() — unbiased under heterogeneous timing.
Fig 1Sun–Abraham event-study coefficients from sunab() — unbiased under heterogeneous timing.
Naive TWFE event study on the same data, showing the bias Sun–Abraham corrects.
Fig 2Naive TWFE event study on the same data, showing the bias Sun–Abraham corrects.
iplot() overlay: Sun–Abraham tracks the true effect where TWFE drifts away.
Fig 3iplot() overlay: Sun–Abraham tracks the true effect where TWFE drifts away.

Figures reproduced from fixest — Laurent Bergé — unofficial community showcase; all credit to the original authors.

From the fixest walkthrough. Build a staggered panel, fit the Sun-Abraham interaction-weighted event study, and compare it to naive TWFE. Unofficial summary.

Discussion (2)

  • 2

    feols is stupid fast and sunab() makes the SA correction a one-liner. No reason to hand-roll event studies anymore.

  • 5

    Nice complement to the did package — same idea (don't trust naive TWFE under staggered timing), FE flavour.

    4

    Exactly. iplot() overlaying SA20 vs TWFE vs truth is the clearest illustration of the bias I've seen.