Skip to content

feat(v4): item 8b of the 4.0 cut - unified to_dataframe(level='event_study') schema on every results class (M-093 done) - #883

Merged
igerber merged 1 commit into
mainfrom
feat/v4-pr8b-m093-event-study-schema
Nov 1, 2026
Merged

igerber merged 1 commit into
mainfrom
feat/v4-pr8b-m093-event-study-schema

Conversation

@igerber

@igerber igerber commented Oct 31, 2026

Copy link
Copy Markdown
Owner

Summary

  • Item 8b of the 4.0 cut - the second half of [M-093], which flips to done: every results class's to_dataframe(level="event_study") emits the unified EVENT_STUDY_SCHEMA (event_time, att, se, t_stat, p_value, conf_int_lower, conf_int_upper, cband_lower, cband_upper, n, df, is_reference, estimand) as an identical PREFIX. Routing follows the M-153 LWDiD precedent: the AggregationMixin classes (CallawaySantAnna/DMLDiD, EfficientDiD, ImputationDiD, TwoStageDiD, StackedDiD, ContinuousDiD, TripleDifference staggered, ChaisemartinDHaultfoeuille, HAD event-study, DurationDiD) return aggregate("event_study").to_dataframe(); SunAbraham, LPDiD, WooldridgeDiD and SpilloverDiD return build_event_study_surface(self).to_dataframe(). WooldridgeDiD appends cohort_trends / aggregation_weights after the schema (LWDiD's three config columns are the shipped precedent); docs/v4-design.md section 5 states the prefix rule in the same diff.
  • The 3.x bespoke frames are gone (relative_period -> event_time; effect / att_glob / LPDiD's coefficient -> att; conf_int_lo/hi, conf_low/high, HAD's conf_int_low/high -> conf_int_lower/upper; n_obs -> n, NaN on the is_reference row; dCDH's horizon / per-horizon estimand labels / n_obs 0 reference row -> event_time on l1_first_switch, estimand "att", is_reference at 0). The "not stored on 4.0 fits" raises on CallawaySantAnna / EfficientDiD / TripleDifference and analytical ImputationDiD / TwoStageDiD / ContinuousDiD are gone - the level recomputes from the retained kit (bootstrapped CS/EDiD/DDD fits replay the fit-time bootstrap per call; kit-less or pre-replay legacy pickles that still carry a stored dict now raise where they returned a frame). WooldridgeDiD before aggregate("event_study", weights=) raises instead of returning an empty frame. New levels: SpilloverDiDResults.to_dataframe(level="simple" | "event_study"), HeterogeneousAdoptionDiDEventStudyResults.to_dataframe(level="event_study"), DurationDiDResults "event_study". level="group" on CS/EDiD/TripleDiff still raises (TODO.md row).
  • Ledger: M-093 done with test_ref tests/test_v4_event_study_schema.py and a tenth amendment note; spec item 8 complete. REGISTRY (CS, EfficientDiD, Imputation/TwoStage/Stacked relay and reference-row Notes, DDD, HAD; new to_dataframe-schema Notes for dCDH, SunAbraham, WooldridgeDiD, LPDiD, DurationDiD, SpilloverDiD), the migration guide, llms-full.txt, the practitioner guide, docs/api/wooldridge_etwfe.rst and docs/api/spillover.rst updated. Notebook 16 re-executed under the Rust backend (outputs refreshed; no markdown restates a moved number; it leaves the TODO.md tutorial re-execution row), notebook 19 reads event_time / att / is_reference, notebook 14's export prose updated.

Methodology references (required if estimator / math changes)

  • Method name(s): N/A - no estimator math changes. Results-contract change only (the per-class to_dataframe(level="event_study") frame; spec section 5, ledger row M-093).
  • Paper / source link(s): docs/v4-design.md section 5 (unified event-study representation; the prefix rule amended here); REGISTRY to_dataframe-schema Notes.
  • Any intentional deviations from the source (and why): None. Point estimates, SEs and inference never move; the frames are bit-equal to the aggregate("event_study") / builder frames.

Validation

  • Tests added/updated: new tests/test_v4_event_study_schema.py (M-093 test_ref: schema prefix + trailing config columns on 21 producer fits incl. bootstrap relay/replay routes, bit-equality with the aggregate / builder route, default-level classes, reference / estimand / count spot checks, the error contract - Wooldridge pre-aggregate, Spillover aggregate mode, 2x2x2 DDD, LPDiD absent/empty frames, Stacked and kit-less legacy pickles, new bad-level texts - and the ledger row). tests/test_v4_sentinel_retirement.py keeps the 8a pins (frame + ledger pins updated). Per-class frame pins moved to the unified schema in test_stacked_did.py, test_sun_abraham.py, test_continuous_did.py, test_wooldridge.py, test_lpdid.py, test_chaisemartin_dhaultfoeuille.py, test_had.py, test_staggered.py, test_imputation.py, test_efficient_did.py, test_two_stage.py, test_staggered_triple_diff.py, test_v4_unify_ddd.py, test_v4_storage_flip.py.
  • Backtest / simulation / notebook evidence (if applicable): docs/tutorials/16_wooldridge_etwfe.ipynb re-executed (DIFF_DIFF_BACKEND=rust); 13/14/16/19/20 nbmake-validated; t19/t20 drift suites green. Full default suite green; CI-pinned mypy clean; py3.9 compile clean.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

Changelog

  • changelog.d/ fragment added (or N/A - no user-visible change): Yes (changelog.d/20261031-v4-pr8b-m093-event-study-schema.md)

…study') schema on every results class (M-093 done)

Since 4.0 every results class's to_dataframe(level="event_study") emits
the EVENT_STUDY_SCHEMA columns (event_time, att, se, t_stat, p_value,
conf_int_lower, conf_int_upper, cband_lower, cband_upper, n, df,
is_reference, estimand) as an identical PREFIX - the frame the unified
EventStudyResults container returns. The routing rule is the M-153 LWDiD
precedent: the AggregationMixin classes (CallawaySantAnna/DMLDiD,
EfficientDiD, ImputationDiD, TwoStageDiD, StackedDiD, ContinuousDiD,
TripleDifference staggered, ChaisemartinDHaultfoeuille, HAD event-study,
DurationDiD) return aggregate("event_study").to_dataframe(); SunAbraham,
LPDiD, WooldridgeDiD and SpilloverDiD return
build_event_study_surface(self).to_dataframe(). WooldridgeDiD appends
cohort_trends / aggregation_weights after the schema (LWDiD's three
config columns are the shipped M-153 precedent); spec section 5 states
the prefix rule in the same diff.

The 3.x bespoke frames are gone: relative_period -> event_time; effect /
att_glob / LPDiD's coefficient -> att; conf_int_lo/hi, conf_low/high and
HAD's conf_int_low/high -> conf_int_lower/upper; n_obs -> n (NaN on the
is_reference row, never a zero count); the dCDH native frame (horizon,
a per-horizon estimand label DID^pl_l / "ref" / DID_l, the n_obs 0
reference row) is retired - event_time on l1_first_switch, estimand
"att", is_reference at 0. The "not stored on 4.0 fits" raises on
CallawaySantAnna / EfficientDiD / TripleDifference and on analytical
ImputationDiD / TwoStageDiD / ContinuousDiD are gone: the level
recomputes from the retained kit (bootstrapped CS/EDiD/DDD fits replay
the fit-time bootstrap on every call); a kit-less or pre-replay legacy
pickle that still carries a stored dict now raises where it returned a
frame. WooldridgeDiD before aggregate("event_study", weights=) raises
instead of returning an empty frame (the weighting scheme is never
chosen silently). New levels: SpilloverDiDResults.to_dataframe(level=
"simple" | "event_study") (default "simple" = the former one-row frame;
aggregate-mode fits raise), HeterogeneousAdoptionDiDEventStudyResults
.to_dataframe(level="event_study"), DurationDiDResults "event_study".
level="group" on CS/EDiD/TripleDiff still raises with the post-fit
pointer (TODO.md row). results_base._ABSENT_SURFACE_HINTS gains a
SunAbrahamResults entry.

Tests: new tests/test_v4_event_study_schema.py (M-093 test_ref) pins the
schema prefix and trailing config columns on 21 producer fits, bit-equality
with the aggregate / builder route (bootstrap relay and replay routes
included), the default-level classes, reference / estimand / count spot
checks per producer, the error contract (Wooldridge pre-aggregate,
Spillover aggregate mode, 2x2x2 DDD, LPDiD absent and empty frames,
Stacked and kit-less legacy pickles, the new bad-level texts) and the
ledger row. tests/test_v4_sentinel_retirement.py keeps the 8a pins (its
frame pin and ledger pin updated); the per-class frame pins in the
stacked, sun_abraham, continuous, wooldridge, lpdid, dCDH, had, staggered,
imputation, efficient, two_stage, staggered_triple_diff, unify_ddd and
storage_flip suites move to the unified schema.

Ledger: M-093 -> done (test_ref tests/test_v4_event_study_schema.py,
tenth amendment note; results.py covers SpilloverDiD only). Spec item 8
complete. REGISTRY (CS, EfficientDiD, Imputation/TwoStage/Stacked relay
and reference-row Notes, DDD, HAD; new to_dataframe-schema Notes for
dCDH, SunAbraham, WooldridgeDiD, LPDiD, DurationDiD, SpilloverDiD), the
migration guide, llms-full.txt, the practitioner guide,
docs/api/wooldridge_etwfe.rst and docs/api/spillover.rst describe the
frame. Notebook 16 re-executed under the Rust backend (outputs refreshed;
no markdown restates a moved number - it leaves the TODO.md tutorial
re-execution row), notebook 19 reads event_time / att / is_reference,
notebook 14's export prose updated; 13/14/16/19/20 nbmake-validated.
@github-actions

Copy link
Copy Markdown

Overall assessment: ✅ Looks good

No unmitigated P0 or P1 findings identified.

Executive summary

  • Export methods consistently delegate to existing aggregation or builder routes; no new estimator, weighting, or variance formulas were introduced.
  • Reference-row handling, estimand labels, and documented bootstrap behavior agree with the Methodology Registry.
  • Missing-surface and legacy-result errors are explicit; DMLDiD’s wrapper preserves parameter forwarding.
  • The breaking change includes a changelog fragment and migration documentation.
  • Static checks passed. Runtime tests could not run because the environment lacks pytest, NumPy, pandas, and SciPy.

Methodology

Affected exports: CallawaySantAnna/DMLDiD, EfficientDiD, ImputationDiD, TwoStageDiD, StackedDiD, ContinuousDiD, TripleDifference, dCDH, HAD, DurationDiD, SunAbraham, LPDiD, WooldridgeDiD, and SpilloverDiD.

Cross-checked the routes against Registry entries, docstrings, and available checked-in paper reviews. No undocumented methodological change, missing assumption check introduced by this diff, or incorrect inference transformation was identified.

  • P3 — Informational: documented representation differences. dCDH retains its l1_first_switch indexing; HAD retains WAS estimand labels; Wooldridge retains its documented absent reference/count/df channels.
    Locations: docs/methodology/REGISTRY.md:L1581, L2790; diff_diff/had.py:L1012-L1033.
    Impact: These differences are intentional and documented.
    Concrete fix: None required.

Code Quality

No findings. Delegation removes duplicated export logic, and no new inline inference computations or partial NaN guards were introduced.

Performance

  • P3 — Informational: exporting can replay bootstrap inference.
    Location: diff_diff/staggered_results.py:L963-L980; corresponding EfficientDiD and TripleDifference routes.
    Impact: Repeated exports can be expensive; this behavior is documented.
    Concrete fix: None required for approval; callers can retain the exported frame.

Maintainability

No findings. Shared builders enforce column ordering and reference normalization consistently.

Tech Debt

  • P3 — Informational: group-export convergence is tracked.
    Location: TODO.md:L89.
    Impact: Group exports remain inconsistent with the unified event-study route, with explicit errors and a tracked follow-up.
    Concrete fix: None required in this PR.

Security

No findings in the changed code or supplied notebook content.

Documentation/Tests

No actionable findings. The changelog fragment, migration guide, Registry notes, and schema/error-contract tests accompany the behavior change.

All 32 changed Python files parsed with Python 3.9 grammar and compiled in memory. Code cells in all three changed notebooks compiled successfully. These checks do not establish runtime or numerical correctness; the reported full-suite results could not be independently reproduced in this environment.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Oct 31, 2026
@igerber
igerber merged commit 3d41bb0 into main Nov 1, 2026
64 of 66 checks passed
@igerber
igerber deleted the feat/v4-pr8b-m093-event-study-schema branch November 1, 2026 00:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-ci Triggers CI test workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant