feat(aggregation): CS post-fit aggregate() replays the multiplier bootstrap on bootstrapped fits (M-020) - #780
Conversation
…ootstrapped fits
CallawaySantAnnaResults.aggregate('event_study'/'group') on a bootstrapped
fit (n_bootstrap > 0) previously raised NotImplementedError; the recompute
levels now REPLAY the fit-time multiplier bootstrap from a kit-retained
BootstrapReplaySpec and publish percentile inference matching a fit-time
aggregation to BLAS reassociation (assert_allclose ~1 ULP on se/CI/cband;
the discrete percentile p-value compared at 2/n_bootstrap). M-020 notes
amendment; retires the TODO bootstrap re-aggregation row (#726).
Mechanism (wholesale reuse):
- _run_multiplier_bootstrap gains a keyword-only _replay_bitgen_state and
snapshots the RNG state at weight-stream construction (nothing consumes
the rng earlier, so the state fully determines the stream WITHIN one
weight backend); the state + generation-branch identity ride
CSBootstrapResults as plain __post_init__ attributes (public dataclass
shape unchanged) into the kit's BootstrapReplaySpec (by value:
seed=None fits replay, pickles carry it, post-fit set_params/attribute
mutation cannot alter it).
- staggered_results gains _KitBootstrapAggregator, a value-bound host
re-running the SAME engine with the injected state; the fit-time
override blocks (percentile se/CI/p + t, group df_used clearing, sup-t
cband rows) are extracted verbatim into shared staggered_bootstrap
helpers so fit and replay cannot drift.
- Backend guard: Rust and NumPy generators produce DIFFERENT draws from
the same bit-generator state (absolute Xoshiro row-seeding vs direct
PCG64 stream), so the spec stamps bootstrap_chunking's new
effective_weight_backend() at capture - branch-aware: stratified/
single-PSU survey generation and census-FPC zero weights are provably
backend-independent and stamp "portable" - and the replay fails closed
on a mismatch (and on backend=None) naming both backends and the
remedies. Legacy pickles without the spec fail closed with a refit
message.
- The combined-IF gate in _prepare_event_study_aggregation also accepts
precomputed-only callers (the kit-backed replay threads df=None).
Ripples:
- DiagnosticReport's ES-gated checks now RUN on bootstrapped plain CS
fits (parallel_trends via Bonferroni fallback, pretrends_power/
sensitivity via the diagonal-covariance fallback; replay warnings
recorded and republished per section).
- practitioner_next_steps advises the post-fit route on bootstrapped CS
fits (new pins; the deprecated fit-time kwarg form is gone from the
advice bundle).
- Sibling estimators' (EfficientDiD/Imputation/TwoStage/Continuous)
bootstrap gates and the SDDD engine's fit-time override copy are
untouched (the SDDD twin unification is sequenced with M-014; deferral
recorded in the M-020 notes).
Tests: TestBootstrapReplay (fit-time-vs-post-fit parity for ES/group/
balance_e against the NATIVE stored surface; seed=None idempotence;
set_params/mutation immunity; pickle round-trip; relay order
independence; legacy and backend fail-closed pins; low-draws re-warn
pin), TestBootstrapReplayDesigns (bare-cluster PSU expansion, stratified
survey portability, FPC, RCS, unbalanced panel, single-PSU NaN
surfaces), TestBootstrapReplayConsumers (pretrends diag fallback +
honest_did diagonal warning), effective_weight_backend unit tests, DR
derives-and-runs + warning-republication pins, practitioner pins.
Docs: M-020 notes + code_refs (+staggered_bootstrap.py,
+bootstrap_chunking.py), REGISTRY CS aggregate note + relay-level
re-warn scoping + weight-backend note in the Survey-Aware Bootstrap
section, REPORTING.md, llms.txt / llms-full.txt / llms-practitioner.txt
(the four-estimator exception block SPLIT: CS replays, siblings still
raise), troubleshooting.rst, migration-4.0.md warning block + CS row
blurbs, v4-design.md families count, CHANGELOG Unreleased entry,
tutorials 02 (cells 19-21) and 09 (eight cells) rewritten to the
post-fit route and re-executed. TODO row deleted; the DDD container-port
and EfficientDiD cross-references trued up.
Overall assessment✅ Looks good — no unmitigated P0/P1 findings. Executive summary
Methodology
The replay at Code QualityNo findings. Shared override helpers avoid duplicated inference logic, and all modified inference paths use Performance
MaintainabilityNo findings. The replay host snapshots every estimator attribute read by the bootstrap engine, and backend selection is centralized in Tech Debt
SecurityNo findings. The added retained state contains bootstrap configuration and RNG state, while raw unit identifiers remain canonicalized in Documentation/TestsNo PR findings. Tests cover parity, seedless replay, mutation immunity, pickling, backend mismatches, survey/cluster designs, empty groups, and NaN inference. Static parsing succeeded; runtime tests could not be executed because the review environment lacks NumPy, pandas, and pytest. |
Summary
CallawaySantAnnaResults.aggregate('event_study')/aggregate('group')now WORK on bootstrapped fits (n_bootstrap > 0): the recompute levels REPLAY the fit-time multiplier bootstrap from a kit-retainedBootstrapReplaySpec— the RNG state captured at weight-stream construction plus the run parameters BY VALUE — producing percentile se/CI and sup-t bands that match a fit-timefit(aggregate=...)aggregation to floating-point reassociation (assert_allclose~1 ULP; the discrete percentile p-value is a count statistic compared at2/n_bootstrap). Previously these levels raisedNotImplementedError(M-020 notes amendment; retires the TODO#726bootstrap re-aggregation row)._run_multiplier_bootstrapgains a keyword-only_replay_bitgen_stateinjection; a value-bound_KitBootstrapAggregatorhost re-runs the SAME engine post-fit, and the fit-time percentile-override blocks are extracted verbatim into shared helpers so fit and replay cannot drift.seed=Nonefits replay, pickles replay, and post-fitset_params/attribute mutation cannot alter the replay.bootstrap_chunking.effective_weight_backend()at capture (branch-aware — stratified/single-PSU survey and census-FPC generation is provably backend-independent and stamps"portable") and the replay fails closed on a mismatch or a pre-replay legacy pickle, naming the remedies.np.column_stack([])) — reachable pre-existing via the deprecated fit-timeaggregate='group'and newly via the replay; now guarded to the supported zero-row result, with a regression test covering both routes.DiagnosticReport's ES-gated checks now RUN on bootstrapped plain CS fits (Bonferroni / diagonal-covariance fallbacks; replay warnings recorded and republished per section);practitioner_next_stepsadvises the post-fit route on bootstrapped CS fits. Sibling estimators' (EfficientDiD/ImputationDiD/TwoStageDiD/ContinuousDiD) bootstrap gates and the deprecated SDDD engine's fit-time override copy are untouched (SDDD unification sequenced with M-014; deferral recorded in the M-020 notes).code_refs, REGISTRY (CS aggregate note, relay-level re-warn scoping, a weight-backend note in the Survey-Aware Bootstrap section), REPORTING.md, all three LLM guides (the four-estimator exception block SPLIT — CS replays, siblings still raise), troubleshooting.rst, migration-4.0.md, v4-design.md, CHANGELOG; tutorials 02 (cells 19–21) and 09 (eight cells) rewritten to the post-fit route and re-executed against this branch's venv kernel.Methodology references (required if estimator / math changes)
docs/methodology/REGISTRY.mdCallawaySantAnna section (post-fitaggregate()note, rows M-020/M-117) and the Survey-Aware Bootstrap section's new weight-backend note.assert_allclose(~1 ULP BLAS reassociation), never bit-identity; the derived container publishes no analytical vcov/df beside percentile inference; cross-backend replay fails closed because the two weight generators are not cross-reproducible from one RNG state.Validation
tests/test_aggregate_contract.py(TestBootstrapReplay — ES/group/balance_e parity against the NATIVE fit-time surface, seedless idempotence, mutation immunity, pickle round-trip, relay order-independence, legacy/backend fail-closed pins, low-draws re-warn pin, empty-group zero-row regression on both routes; TestBootstrapReplayDesigns — bare-cluster PSU expansion, stratified-survey portability, FPC, repeated cross-sections, unbalanced panels, single-PSU NaN surfaces; TestBootstrapReplayConsumers — pretrends diag fallback + honest_did diagonal warning),tests/test_bootstrap_chunking.py(effective_weight_backendpins incl. the stale-extension case),tests/test_diagnostic_report.py(bootstrapped CS derives-and-runs + warning republication with a dedicated 49-draw fixture),tests/test_practitioner.py(bootstrapped-CS advice pins incl. positiveaggregate('group')and no fit-time-kwarg assertions).Security / privacy