feat(staggered): materialize non-estimable (g,t) cells as NaN in CallawaySantAnna#582
Conversation
…awaySantAnna
Uniformly materialize a NaN entry (with a machine-readable skip_reason) for every
non-estimable (g,t) group-time cell across all CS estimation paths (no-covariate
regression, covariate regression, IPW/DR, repeated cross-section, survey-weighted)
instead of omitting it. Previously only the covariate-singular case materialized
NaN; the other paths dropped the cell silently from the grid.
Cells carry no influence-function entry, so they are excluded from every
aggregation (simple/group/calendar/event-study), balance_e, and bootstrap -- all
aggregate point estimates and SEs, plus event-study n_groups / by-group n_periods,
are numerically unchanged and continue to match R did's aggte(). A fit where no
cell is estimable still raises ValueError. to_dataframe("group_time") now includes
the NaN rows and a skip_reason column.
Documented per-cell surface deviation from R's att_gt (which omits the rows).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Overall Assessment ✅ Looks good. I found no unmitigated P0/P1 issues. The R Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
I could not run the selected tests in this environment because |
#582) - The covariate-regression non-finite cell now materializes via _nan_gt_entry with NO influence-function entry, matching the other paths and the documented REGISTRY/helper contract (previously it wrote a zero-IF entry and ran batch inference). Aggregates and SEs are unchanged -- the cell was finite-masked / IF-membership-filtered out either way; now the "NaN cells carry no IF entry" invariant holds uniformly across all paths. - Extend the materialization test to cover covariate IPW/DR (panel + RCS) paths. - Remove the now-implemented CallawaySantAnna NaN-cell row from TODO.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Path to Approval
|
…/RCS paths (review #582 P1) The general (IPW/DR) and RCS estimable builders treated `att_gt is not None` as estimable even when att_gt was non-finite (NaN/inf): they stored effect=att_gt (which could surface inf), kept the influence-function entry, and did not count the cell in the consolidated skip total. Both now branch on `att_gt is None or not np.isfinite(att_gt)` first and materialize via _nan_gt_entry(skip_reason="non_finite_regression") with NO IF entry, so the documented contract (non-estimable cells are NaN entries, carry no IF, excluded from the bootstrap) holds uniformly across every path. Aggregate estimates and SEs are unchanged (these cells were finite-masked / IF-filtered out either way). Adds a regression test mimicking the inf-ATT-with-IF leak. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ⛔ Blocker — one unmitigated P0. The prior general panel/RCS non-finite ATT issue is fixed, but the no-covariate vectorized path still violates the new registry contract. Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Path to Approval
|
…view #582 P0) Completes the uniform invariant: every CallawaySantAnna estimable-cell builder (no-covariate vectorized, covariate regression, general IPW/DR, RCS) now routes a non-finite ATT(g,t) to _nan_gt_entry(skip_reason="non_finite_regression") with NO influence-function entry, skipping batch inference and bootstrap membership. The no-covariate diff-in-means ATT is finite given n_t,n_c>0, but a non-finite outcome (inf survives the NaN-only valid mask) could otherwise store inf as the effect and produce t_stat=inf / p=0 / infinite CI via safe_inference_batch. Aggregate estimates and SEs are unchanged. Adds a regression test injecting an inf outcome through the no-covariate path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Path to Approval
|
…nt study (review #582 P1) _aggregate_event_study() appended an all-NaN row (effect=NaN, se=NaN, n_groups=0) for a relative-time bucket whose cells are all non-estimable, instead of omitting the bucket. With NaN-cell materialization this surfaced new all-NaN event-study rows where the bucket previously had no cells (and thus no row) -- an aggregate- surface change vs the prior omit behavior and R did::aggte(). The bucket is now dropped when finite-filtering leaves no cell (tracked via a kept-periods list so the result lists stay aligned), matching _aggregate_by_group, which already omits all-NaN groups. Adds a test asserting an all-non-estimable relative time is absent from event_study_effects. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good — no unmitigated P0/P1 findings. Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
…cell notes (review #582 P3) CallawaySantAnna's aggregation options are simple / event_study / group / all (there is no calendar aggregation). Remove "calendar" from the REGISTRY edge-case Note and the CHANGELOG entry listing which aggregations exclude materialized NaN cells. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good - no unmitigated P0/P1 findings. Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
…ells (review #582 P2) The per-cell helper sentinel returns (and the covariate-reg empty-control batch site) hardcoded n_treated=n_control=0 for materialized NaN cells even after the observation masks had been computed, so group_time_effects / to_dataframe could show zero counts for a cell that actually had treated (or control) observations. The zero-control / zero-weight exits in _compute_att_gt_fast and _compute_att_gt_rc, and the covariate-reg empty-control batch site, now return the observed counts; missing-period exits (masks not yet built) keep 0. Display-only metadata -- estimates, SEs, and aggregation are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good - no unmitigated P0/P1 findings. Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
Three test_csdid_ported.py tests relied on non-estimable (g,t) cells being OMITTED from group_time_effects; with the new NaN-cell materialization those cells are present as NaN, so the tests' membership / golden-iteration guards no longer skip them. Update them to preserve their intent on the FINITE cells: - test_some_units_treated_first_period: a first-period cohort (no base period) is now all-NaN (missing_period) rather than absent -> assert it is all-NaN. - test_zero_pretreatment_outcomes: skip NaN pre-cells (the last cohort under not_yet_treated has no controls); finite pre-cells are still ~0. - test_golden_fewer_periods: skip NaN cells (gapped panel where base g-1 is unobserved -> missing_period; R falls back to an available base) -> R-parity on the finite cells. No source change; the cells are correctly non-estimable, only now visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good — no unmitigated P0/P1 findings. Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Validation note: focused pytest could not run because this sandbox lacks |
Summary
CallawaySantAnnanow materializes non-estimable(g,t)cells as NaN entries (carrying a machine-readableskip_reason) uniformly across all estimation paths — no-covariate regression, covariate regression, IPW/DR, repeated cross-section, and survey-weighted — instead of silently dropping them from the grid. Previously only the covariate-regression singular case did this; the other paths omitted the cell.balance_e, and from the bootstrap. All aggregate point estimates and standard errors — and the event-studyn_groups/ by-groupn_periodsmetadata — are numerically unchanged and continue to match Rdid'saggte().ValueError.to_dataframe("group_time")now includes the NaN rows and askip_reasoncolumn; theGroupTimeEffectdataclass documents the new field.Methodology references (required if estimator / math changes)
docs/methodology/REGISTRY.md→ "CallawaySantAnna" edge casesdid::att_gt, which omits non-estimable cells from its result table; diff-diff materializes them as NaN rows (withskip_reason) for inspectability. R'saggte()aggregation behavior is matched exactly — non-estimable cells contribute nothing to any aggregate. Documented with a**Deviation from R:**label in REGISTRY.md.Validation
tests/test_staggered.py— newTestCallawaySantAnnaNonEstimableMaterialization(per-path materialization parametrized over reg/ipw/dr panel + reg RCS,skip_reasonmapping,to_dataframecolumn, all-non-estimable raise); extendedtest_nan_cell_preserved_not_droppedto assertskip_reason. Existing CS NaN / aggregation /balance_eandtest_methodology_callaway.pyR-parity suites unchanged and green; shared-aggregation-mixin consumers (ContinuousDiD, TripleDifference, HonestDiD, ImputationDiD, EfficientDiD) and the diagnostic-report suite all green.Security / privacy
Generated with Claude Code