Commit bdfa2b8
SpilloverDiD: event_study=True per-event-time × ring decomposition (Wave C)
Replaces the Wave B NotImplementedError gate at spillover.py:1430-1442 with
the full per-event-time × ring decomposition from Butts (2021) Section 5 /
Table 2. Emits per-event-time direct effects tau_k and per-(ring, event-time)
spillover effects delta_jk as att_dynamic: pd.DataFrame (indexed by k) and
MultiIndex spillover_effects (levels (ring_label, event_time)). A TwoStageDiD-
compatible event_study_effects: Dict[int, Dict] alias (mirroring
two_stage.py:1355-1389 schema with conf_int = (low, high) tuple) is also
emitted for consumption by plot_event_study and diagnostic_report.
Methodology: the implementation operationalizes Butts' single K_it symbol as
TWO event-time clocks — K_direct = t - effective_first_treat(i) for ever-
treated rows, and K_spill = t - earliest-in-range-cohort-onset(i) for spillover
rows (running min across activated cohorts; NaN for pre-trigger and far-away
rows). K_spill >= 0 structurally; negative-k spillover cells emit rectangularly
with coef = NaN, n_obs = 0.
Reference period: ref_period = -1 - anticipation (TwoStageDiD parity at
two_stage.py:486). When horizon_max is set, ref_period must fall inside
[-horizon_max, +horizon_max] or fit raises ValueError — silent floor-shift to
-horizon_max would change identification (rejected per feedback_no_silent_failures).
Reference row uses coef = 0.0, se = 0.0, n_obs = 0, conf_int = (0.0, 0.0).
horizon_max semantics (divergence from TwoStageDiD): bins event-times outside
[-H, +H] into endpoint pools, no observations dropped. TwoStageDiD filters
those rows. Divergence intentional + cross-documented. horizon_max=None auto-
detects the bin set from observed K values.
Scalar att aggregation: sample-share-weighted average of post-treatment tau_k
(att = sum_{k>=0} w_k * tau_k with w_k = n_treated_at_k / total). SE from
linear-combination inference Var(att) = w' V_subset w on the post-treatment
block of the stage-2 vcov — no separate fit.
Reduce-to-aggregate equivalence: under constant-tau DGP with horizon_max=None,
the lincom-weighted scalar att reproduces Wave B's aggregate tau_total bit-
identically. Note: horizon_max=0 does NOT reduce to Wave B (binning collapses
pre-treatment K values into k=0, making D^0 = D_i ever-treated indicator
rather than D_it).
Backward compatibility: event_study=False leaves all Wave C fields
(att_dynamic, event_study_effects, horizon_max, reference_period) as None
and reproduces Wave B SEs bit-identically.
Variance caveat: per-event-time SEs use solve_ols's standard variance
(HC1 / Conley / cluster) WITHOUT the Gardner GMM first-stage uncertainty
correction; planned Wave D follow-up closes this.
Tests: 30 new event-study test methods covering API, two-clock K helper,
horizon binning, design builder, reference period, reduce-to-aggregate,
identification MC (50 seeds, per-event-time tau_k recovery within 0.025),
placebo pre-trends (Type I rate <= 0.30 over 50 seeds at alpha=0.10),
singularity (rectangular schema), Conley integration (vcov shape +
np.diag >= 0), summary/to_dict/pickle round-trip, event_study_effects
schema parity with TwoStageDiD, lincom-att hand-computed, validation
(horizon_max < 0, ref_period < -horizon_max), and fit idempotence.
DGP factory generate_butts_staggered_dgp extended with tau_per_event_time
and delta_per_ring_per_event_time callable kwargs (backward-compatible —
both default to None, producing the Wave B scalar DGP bit-identically;
verified by tests/test_dgp_utils.py with pinned SHA-256 baselines).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>1 parent 176174c commit bdfa2b8
11 files changed
Lines changed: 1825 additions & 107 deletions
File tree
- diff_diff
- guides
- docs
- api
- _autosummary
- methodology
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
130 | 130 | | |
131 | 131 | | |
132 | 132 | | |
133 | | - | |
134 | 133 | | |
135 | 134 | | |
136 | 135 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
477 | 477 | | |
478 | 478 | | |
479 | 479 | | |
480 | | - | |
481 | | - | |
| 480 | + | |
| 481 | + | |
482 | 482 | | |
483 | 483 | | |
484 | 484 | | |
| |||
502 | 502 | | |
503 | 503 | | |
504 | 504 | | |
505 | | - | |
506 | | - | |
| 505 | + | |
507 | 506 | | |
508 | 507 | | |
509 | 508 | | |
| |||
0 commit comments