fix(staggered): CS panel reg/ipw influence functions + per-cell SEs to DRDID parity#619
Conversation
…o DRDID parity
Two defects fixed (point estimates unchanged - the omitted terms are
mean-zero, so ATTs always matched R at ~1e-11 while SEs drifted):
1. reg+covariates IF omitted DRDID::reg_did_panel's OLS estimation-effect
term (asy.lin.rep.ols %*% M1): per-cell SEs 4-13% and aggregated SEs
3-20% from R, anti-conservative on some designs. The projection is
evaluated in the centered basis (1/n_c + (x-xbar_c)'G^{-1}(xbar_t-xbar_c)),
offset-invariant by construction, with the bread hoisted per control
group on the vectorized path.
2. unweighted ipw+covariates per-cell SE used a weighted POPULATION
variance never scaled by an effective sample size (~7x inflated), and
the IF lacked std_ipw_did_panel's PS estimation-effect correction
(aggregated ~2.4% off). Mirrors the already-correct survey branch,
with the correction fused as (D-ps)*(X @ (H^-1 M2)).
All panel reg/ipw per-cell SEs (incl. no-covariate) now derive from the
same IF that feeds aggregation: sqrt(sum(phi^2)) (dr convention).
Downstream consumers inherit: t/p/CI, aggregated SEs, event_study_vcov,
cluster-override CR1, multiplier bootstrap.
Validation: R-free numpy reconstructions of both DRDID IFs (incl. survey,
not_yet_treated, golden-cell variants); golden per-cell + aggregated
reg/ipw SE assertions enabled (~5e-12 observed); fresh R did 2.5.1 ipw
aggregation yardsticks (~1e-10); behavioral tests for NaN-bread,
underdetermined cells, ps fallback, universal base period, rank-guard
warning (now fires for reg/ipw), uniform-weights producer equivalence.
Perf guard at 100k units x 20 periods: reg within noise (+2.8/4.6%),
ipw +2.8-9.5% (intrinsic per-cell PS Hessian of the new term).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4AzrFUMqJxcUumSH31mSr
Overall AssessmentExecutive Summary
MethodologyFinding 1 — P1: Centered reg bread rank-0 incorrectly NaN-poisons valid SEsLocation: Impact: The new centered-basis projection for The DRDID source supports the intended methodology: panel OR uses the OLS estimation-effect term and IF-based SE; panel standardized IPW uses Hajek weights plus the PS score/Hessian correction and IF-based SE. The issue here is not the high-level DRDID correction, but the rank-0 handling introduced by the centered implementation. (raw.githubusercontent.com) Concrete fix: Add a centered-bread helper for reg OR IFs: if the centered covariate Gram has rank 0, use a zero matrix for the centered correction and keep Code QualityNo additional code-quality blockers found. The fused IPW correction and batch inference usage are coherent. PerformanceNo findings. Bread hoisting and fused IPW correction are reasonable implementation choices, not methodology issues. MaintainabilityNo findings beyond the P1 above. A small helper for centered OR bread would reduce drift between the vectorized and survey/general reg paths. Tech DebtP3: Residual deferred items are trackedLocation: Impact: DR no-covariate per-cell SE parity and IPW fixture regeneration are explicitly tracked/documented. Concrete fix: No action required for this PR. SecurityNo findings. I did not see secrets or security-sensitive changes. Documentation / TestsP1: Missing regression test for constant-only reg covariatesLocation: Impact: Existing tests cover Concrete fix: Add tests with Path to Approval
|
…n, not NaN CI review P1: with a constant as the ONLY reg covariate the centered estimation-effect Gram is rank-0, and _safe_inv's all-NaN sentinel NaN-poisoned per-cell/aggregated SEs for cells whose intercept-only projection is fully identified (pre-fix raw-Gram column-drop and the no-covariate fit both give finite SEs). New _centered_or_bread helper (shared by all three reg sites) maps the rank-0 sentinel to a zero correction - proj collapses to 1/sum(W_c), equal to the no-covariate fit - while partial deficiency and the aggregate rank-guard warning are unchanged. ipw/dr breads are over [1, X] and keep NaN propagation (true pathology); the rank-0 monkeypatch test is re-scoped to ipw/dr with constant-only reg equivalence tests (unweighted + survey) added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X4AzrFUMqJxcUumSH31mSr
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment✅ Looks good — no unmitigated P0/P1 findings. Executive Summary
MethodologyP3 — Prior P1 resolved, no action requiredLocation: Impact: The previous centered reg rank-0 bug is fixed. Concrete fix: None. P3 — Documented methodology deviations are acceptableLocation: Impact: The remaining deviations or deferred parity work are explicitly documented: propensity-score clipping vs R drop-trimming, no-covariate IPW’s unconditional presentation, DR no-covariate per-cell SE, and fixture regeneration for IPW aggregation yardsticks. Under the review rules, these are not blockers. Concrete fix: None required for this PR. The DRDID source cross-check is consistent with the PR’s direction: Code QualityNo blocking findings. The vectorized reg path, general/survey reg path, and IPW path now use the same IF-derived SE convention, and the NaN guards avoid the PerformanceNo findings. Hoisting the centered OR bread for balanced never-treated reg cells is a reasonable optimization and preserves the same algebra. MaintainabilityP3 — Stale comments around centered reg rank-0 behaviorLocation: Impact: Comments still say a rank-0 reg bread yields NaN, but Concrete fix: Update those comments to distinguish centered reg bread rank-0 from Tech DebtP3 — Residual items tracked in TODOLocation: Impact: DR no-covariate per-cell SE parity and IPW fixture regeneration are tracked, deferrable items. Concrete fix: None required for this PR. SecurityNo findings. I saw no secret material or security-sensitive changes in the modified files. Documentation / TestsNo blocking findings. The PR adds targeted DRDID IF reconstruction tests and the missing constant-only reg covariate tests from the prior review. I could not run the tests because the environment has neither |
CI review P3: two comments still said "a rank-0 bread yields NaN" on the reg paths, contradicting _centered_or_bread's zero-correction collapse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X4AzrFUMqJxcUumSH31mSr
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good — no unmitigated P0/P1 findings. Executive Summary
Methodology No P0/P1 findings. External source cross-check: P3 — Documented deviations are acceptable Code Quality No findings. The modified paths consistently compute per-cell SE from the same IF stored for aggregation, and NaN/zero SE inference is gated through shared helpers. Performance No findings. Hoisting the centered reg bread for balanced never-treated control designs at Maintainability No findings. The prior stale-comment issue is addressed: Tech Debt P3 — Deferred work is tracked Security No findings. A secret-pattern scan of the changed files did not identify credentials; matches were unrelated existing documentation text. Documentation / Tests P3 — Local verification unavailable |
Summary
DRDID::reg_did_panel's OLS estimation-effect term (asy.lin.rep.ols %*% M1). Per-cell SEs sat 4-13% and aggregated (simple/group/event-study) SEs 3-20% from the R golden fixtures, anti-conservative on some designs (0.958x on the two-period golden). The projection is evaluated in the centered basis1/n_c + (x - x̄_c)'G^{-1}(x̄_t - x̄_c)- algebraically identical to R's raw-Gram form but offset-invariant (the raw Gram squares the design conditioning; a large constant covariate offset would push the equilibrated rank check below threshold and silently truncate a genuine direction). The bread is hoisted per control group on the vectorized path; the intercept-only collapse is exactly the old-resid_c/n_c.std_ipw_did_panel's propensity-score estimation-effect correction (asy.lin.rep.ps %*% M2; aggregated SEs ~2.4% off). The survey ipw branch already carried both (Phase 7a); the fix mirrors it with the correction fused as(D - ps) * (X @ (H^{-1} M2)).var_c*(1-p)/(n_c*p)plug-in equaled R only at treated-share 0.5) now derive from the same IF that feeds aggregation:sqrt(sum(phi^2)), the convention dr always used. Downstream consumers inherit: t/p/CI, aggregated SEs,event_study_vcov(HonestDiD input), bare-cluster=CR1 per-cell override, multiplier-bootstrap SEs._outcome_regression_rc/_ipw_estimation_rccarry the corrections), DR everywhere, survey-ipw branch, triple-diff (own code), ContinuousDiD (continuous_did.py:995-1191is a DRDID-validated twin of the same algebra).Methodology references (required if estimator / math changes)
DRDID::reg_did_panel/DRDID::std_ipw_did_panel(DRDID 1.3.0)pscore_trimrather than R's drop-trimming attrim.level=0.995(pre-existing; differs only at extreme propensities); no-covariate ipw treated as unconditional (R's intercept-only logit has an identically-zero estimation effect in the IF, so values match); DR no-covariate per-cell SE keeps its ddof=1 plug-in (O(1/n) from R; TODO row). All documented indocs/methodology/REGISTRY.md(CallawaySantAnna standard-error notes).Validation
tests/test_methodology_callaway.py(R-free numpy reconstructions of both DRDID influence functions: unweighted, survey-weighted, not_yet_treated per-pair-bread, and golden-cell variants - written first and failing on the unfixed tree with the known gaps),tests/test_csdid_ported.py(previously-unasserted golden per-cell + aggregated reg/ipw SE blocks enabled at 1e-8/1e-6, observed ~5e-12; fresh R did 2.5.1 ipw aggregation yardsticks with provenance),tests/test_staggered.py(rank-guard warning + rank-0 NaN-bread tests parametrized over all methods; new behavioral tests: ps-fallback equivalence, underdetermined cells, universal base period + anticipation, uniform-weights producer equivalence), stale docstrings updated intests/test_se_accuracy.py/tests/test_survey_r_crossvalidation.py;benchmarks/R/generate_csdid_test_values.Rextended with the ipw aggte blocks for the next fixture regeneration (JSON not regenerated here).b56931a6at 100k units x 20 periods (5-rep medians): cov5/cov20 reg +2.8%/+4.6%, cov20 ipw +2.8%, cov5 ipw +9.5% (+69ms; the per-cell PS Hessian the correction requires - R pays the same cost). ATT |delta| = 0.0 in all 8 arms; memory flat. Full suite: 8358 passed; the singletest_survey_dcdhbit-identity failure under full-suite ordering reproduces byte-identically on pristineorigin/main(pre-existing backend-arm mis-detection; TODO row filed). Tutorials 02/16 contain no reg/ipw+covariate cells, so no notebook re-execution is needed.Security / privacy
🤖 Generated with Claude Code
https://claude.ai/code/session_01X4AzrFUMqJxcUumSH31mSr