Skip to content

chc.did

Stability tier: evolving.

Callaway-Sant'Anna staggered-adoption difference-in-differences.

Group-time average treatment effects ATT(g, t) -- the effect at period t on the cohort first treated at period g -- from clean 2x2 DiD comparisons against never- or not-yet-treated units. Under staggered adoption with dynamic/heterogeneous effects, two-way fixed effects is biased: already-treated units enter as controls with negative weights (Goodman-Bacon decomposition), so a single TWFE coefficient is a contaminated weighted average. These cohort-specific comparisons never use an already-treated unit as a control, so they are not.

A statistical estimator, so NumPy float64 throughout (like chc.independence) -- independent of the JAX x64 flag, which must not change an estimate.

callaway_santanna_inference adds the estimates' uncertainty through diff-diff, the did extra, whose Callaway-Sant'Anna reproduces these estimates.

GroupTimeATT dataclass

Callaway-Sant'Anna output: cohort ATT(g,t), the event-study curve, and overall ATT.

pretrend()

Pre-treatment ATT(g,t) for t < g -- a placebo; ~0 supports parallel trends.

EventStudyInference dataclass

callaway_santanna's estimates, with their uncertainty. Experimental.

estimate is callaway_santanna's own. The rest is diff-diff's multiplier bootstrap, which draws whole units, so it holds however a unit's errors are correlated over time. se and band are by relative time e, without the base period e = -1, where the event study is zero by construction. band holds every e at once with probability 1 - alpha: the bootstrap's sup-t band, where a pointwise interval is event_study[e] +- z se[e]. overall_interval is the overall ATT's.

robust_interval(m)

The average effect after treatment, robust to trends that part smoothly.

Rambachan and Roth's smoothness restriction: the treated units' trend may differ from the comparison's by a line whose slope changes by at most m a period, before treatment and after. m = 0 allows exactly the linear difference the periods before treatment can show. The target is the event study's effects after treatment averaged with equal weights, which is not estimate.overall's weighting. The interval is its optimal fixed-length interval at level 1 - alpha, from diff-diff's HonestDiD on the event study's analytic covariance; it agrees with R's HonestDiD to 1e-3. At m = 0, with trends parallel or apart by a line, it covered 0.95 at 300 units, 0.92 at 100 and 0.78-0.79 at 30.

Raises:

Type Description
ValueError

for a negative m.

callaway_santanna(outcomes, group, *, control='notyet', never_treated=-1)

Group-time ATT(g,t) for a balanced panel via never-/not-yet-treated 2x2 DiD.

outcomes is (N, T) with outcomes[i, t] unit i's outcome at period t; group is (N,) with each unit's first-treated period (0-indexed) or never_treated. control="notyet" compares against units not yet treated by t (never-treated included -- more controls, de Chaisemartin-d'Haultfoeuille-robust); "never" uses only never-treated.

Universal base period g - 1: ATT(g,t) = E[Y_t - Y_{g-1} | G=g] - E[Y_t - Y_{g-1} | control]. event_study aggregates by relative time e = t - g; overall is the size-weighted mean of post-treatment effects.

callaway_santanna_inference(outcomes, group, *, control='notyet', never_treated=-1, alpha=0.05, draws=999, seed=0)

callaway_santanna with standard errors, intervals and a uniform band.

Experimental: it may change or be withdrawn in any release.

Takes callaway_santanna's arguments, and computes its estimates. diff-diff's Callaway-Sant'Anna, at the same comparison and base period g - 1, reproduces them; they are checked to agree before its bootstrap is read, so every interval is centred on this library's estimate. draws bootstrap draws are taken with seed.

The levels hold with enough units, and not below. On panels of ten periods, three cohorts and never-treated units, with errors correlated within a unit, at 5%: the overall ATT's interval rejected a true null 5.7-6.6% of the time from 100 units, and 6.9-7.8% at 30; the band 6.4-7.2% at 300 units, 7.9-8.7% at 100, and 21-24% at 30.

Needs the did extra (pip install 'causal-hybrid-control[did]').

Raises:

Type Description
ValueError

on callaway_santanna's refusals; a first-treated period outside the panel, where a unit first treated after it is never treated within it; a panel with no cell after treatment; alpha outside (0, 1); fewer than two draws.

RuntimeError

when diff-diff's estimates do not reproduce these, which would put the intervals around another estimate.

twoway_fixed_effects_att(outcomes, group, *, never_treated=-1)

The single two-way fixed-effects treatment coefficient -- the biased baseline CS beats.

Regresses the twice-demeaned outcome on the twice-demeaned treatment indicator D[i,t] = 1{t >= group[i]}. Under staggered timing with dynamic effects this is a negative-weighted average of the ATT(g,t) (Goodman-Bacon), not the average effect.

de_chaisemartin(outcomes, group, *, never_treated=-1)

de Chaisemartin-d'Haultfoeuille DID_M -- the average instantaneous (first-exposure) effect.

A switcher-count-weighted average, over consecutive-period 2x2 DiDs, of the outcome change of units first treated at t minus that of units still untreated at t (untreated stayers). Like callaway_santanna it is heterogeneity-robust where TWFE is not, but its estimand is the effect at the moment of switching (relative time e = 0), not the size-weighted average over post periods -- a growing effect yields the first-period impact, not the overall ATT.