Add LPDiD non-absorbing treatment (entry-effect estimands), Phase C1#584
Conversation
Implement Dube, Girardi, Jorda & Taylor (2025) Section 4.2 non-absorbing treatment for LPDiD via a new `non_absorbing` parameter: - "first_entry" (Eq. 12): effect of entering treatment for the first time and staying treated; reuses the absorbing clean control, restricts only the treated set. Bit-identical to the absorbing path on absorbing panels. - "effect_stabilization" (Eq. 13, `stabilization_window=L`): units whose treatment has been stable for >= L periods serve as clean controls, so estimation is feasible with few/no never-treated units. Default `non_absorbing=None` is unchanged (absorbing path, still rejects non-absorbing input). Mode-aware clean-sample masks evaluate window conditions via cumulative treatment-change/level lookups with a documented "untreated before the first observed period" boundary convention; placebo horizons use the full pre-span window so pre-trends are uncontaminated; a per-horizon clean- treated indicator threads through the estimator / RA / reweight / pooled paths so re-entry events are classified correctly. Non-absorbing modes require a gap-free panel within each unit's observed span. Pure-Python validation (tests/test_lpdid.py::TestLPDiDNonAbsorbing): absorbing reduction, single-cohort reduction, re-entry mechanism, boundary retention, negative-horizon placebos, non-negative weighting, stabilized-control admission, equal-weight recovery, and DGP recovery; absorbing tests + R-parity goldens unchanged. Exit-event dynamics, R-package parity (PR-C2), and survey-design support are tracked follow-ups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex P1: `_build_pooled_sample(kind="pre")` passed horizon=0 to the non-absorbing masks, so the effect_stabilization clean window only covered [t-L, t] instead of the pooled-pre reach-back to the most-negative horizon ([t - max(L, -h), t-1]). A unit with a prior treated spell at t-3 (clean at t-1) leaked into a [-3, -2] pooled-pre placebo and biased it. Pre windows now use min(horizons); the absorbing branch keeps horizon=0 (not-yet-treated at t already implies a clean pre-span, so its R-parity goldens are unchanged). Adds a deterministic regression test (spell entrants excluded from the pooled-pre sample; verified to fail at 0.286 before the fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Overall Assessment✅ Looks good — no unmitigated P0/P1 findings. Executive Summary
MethodologyP3 informational — deferred non-absorbing scope is documented. No P0/P1 methodology findings. Code QualityNo findings. Parameter validation and atomic rollback cover the new mode/window interaction at PerformanceNo findings. MaintainabilityNo findings. The new per-horizon treated indicator is clearly threaded into horizon and pooled samples at Tech DebtP3 informational — tracked deferred work. SecurityNo findings. I saw no accidental secrets in the changed files. Documentation/TestsP3 informational — local test execution unavailable in this review environment. |
Summary
LPDiDvia a newnon_absorbingparameter:"first_entry"(Eq. 12 — the effect of entering treatment for the first time and staying treated) and"effect_stabilization"(Eq. 13, withstabilization_window=L— units whose treatment has been stable for at leastLperiods serve as clean controls, so estimation is feasible with few or no never-treated units). The defaultnon_absorbing=Noneis unchanged: the absorbing path, which still rejects non-absorbing input and is bit-identical to before.h<0) and pooled-pre windows reach back to the deepest horizon ([t-max(L,-h), t-1]) so pre-trends are uncontaminated. A per-horizon clean-treated indicator threads through the estimator / regression-adjustment / reweight / pooled paths so re-entry events (which are not first entries) are classified correctly. Non-absorbing modes require a gap-free panel within each unit's observed span.non_absorbing/stabilization_windoware validated (atomicset_params), propagated toget_params()andLPDiDResults(summary()/to_dict()).Methodology references
docs/methodology/REGISTRY.md## LPDiD):eta_h^{g,n}), R-package parity (PR-C2), and survey-design support are deferred follow-ups (tracked inTODO.md).effect_stabilization(an assumption to confirm against R in PR-C2).[t-L, t+h]window conditions cannot be verified across a gap); extending the absorbing path's interior-gap reindex to non-absorbing is a deferred follow-up.Validation
tests/test_lpdid.py— newTestLPDiDNonAbsorbing+TestLPDiDNonAbsorbingAPI(20 tests: absorbing reduction (bit-identical), single-cohort reduction, re-entry mechanism, boundary retention, negative-horizon placebos, non-negative weighting, stabilized-control admission, equal-weight recovery, DGP recovery, pooled-pre window, one-off/no-control/interior-gap edge cases, param validation), plus the updated absorbing-rejection test. The 70 absorbing tests and 8 R-parity goldens (tests/test_methodology_lpdid.py) are unchanged; all green on both the pure-Python and Rust backends.Security / privacy
Generated with Claude Code