σ StatsDoge Causal inference workflows
13
Workflow·5 steps·branched

Difference-in-differences with multiple periods (did)

Source did — Callaway & Sant'Anna
Summary by StatsDoge

Staggered-adoption DiD done right: group-time ATT(g,t) → event-study / group / calendar aggregations, with honest pre-trends.

1

Input · what goes in

A long panel with staggered treatment timing: unit id, period, the unit's first-treatment period (group), and an outcome.

Show data format & exampleHide example

Format — one row per (unit, period). first.treat = the period a unit is first treated (0 / Inf = never treated).

 id  period  first.treat   Y
  1     2004        2006    8.1
  1     2005        2006    8.4
  2     2004           0    7.9   # never treated
2

Pipeline · the recipe ⑂ has parallel branches

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

1
Data prep

Build the staggered panel

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

What happens here

One row per (unit, period); never-treated units get group 0 / Inf.

Reads from the input data Feeds into #2
Key code
# id · period · first.treat (group) · Y
head(panel)
Discussion on this step (0)
  • No comments on this step yet — be the first.
2
Estimation

[did] Group-time ATT — att_gt()

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

What happens here

Estimate ATT(g,t) for every cohort and period against not-yet-treated controls.

Formula
\mathrm{ATT}(g,t)=\mathbb{E}\!\left[\,Y_t(g)-Y_t(0)\mid G=g\, ight]
The estimator

Group-time ATT — att_gt() — Difference-in-differences with multiple periods and staggered adoption: ATT(g,t) with clean (not-yet-treated) controls.

Reads from #1 Feeds into #3#4
Key code
att <- att_gt("Y", "period", "id", "first.treat", data = panel)
Discussion on this step (0)
  • No comments on this step yet — be the first.
3
Inference

aggte(type = 'dynamic')

Uncertainty quantification — standard errors, intervals, and aggregation.

What happens here

Aggregate to an event study — effect by length of exposure; pre-periods test parallel trends.

Reads from #2 Feeds into #5
Key code
es <- aggte(att, type = "dynamic")

Reference / docs ↗

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

aggte(type = 'group')

Heterogeneity — who is affected, and by how much, not just on average.

What happens here

Average effect within each treatment cohort.

Reads from #2 Feeds into #5
Key code
aggte(att, type = "group")

Reference / docs ↗

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

ggdid() event-study plot

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

What happens here

Plot pre-trends + dynamic effects with simultaneous confidence bands.

Reads from #3#4 Feeds into the final output
Key code
ggdid(es)

Reference / docs ↗

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

Output · what you get 4 figures

Group-time ATT(g,t) from att_gt() — the effect for each treatment cohort in each period.
Fig 1Group-time ATT(g,t) from att_gt() — the effect for each treatment cohort in each period.
Raw outcome paths by group — the picture before any aggregation.
Fig 2Raw outcome paths by group — the picture before any aggregation.
Event-study aggregation (aggte, dynamic): effect by length of exposure, with pre-period placebo checks.
Fig 3Event-study aggregation (aggte, dynamic): effect by length of exposure, with pre-period placebo checks.
Group / calendar aggregations summarising the ATT a few different ways.
Fig 4Group / calendar aggregations summarising the ATT a few different ways.

Figures reproduced from did — Callaway & Sant'Anna — unofficial community showcase; all credit to the original authors.

The flagship 'did' vignette (Callaway & Sant'Anna). Estimate ATT(g,t) against not-yet-treated controls, then aggregate to a dynamic event study. Unofficial summary.

Discussion (2)

  • 4

    The whole point: never compare a treated unit to an already-treated one. att_gt() bakes that in. TWFE users, please switch.

    4

    And the dynamic aggregation gives you the event study for free, with simultaneous bands. Chef's kiss.

  • 6

    The not-yet-treated control group is what makes this robust to heterogeneous timing. Great default.