Add LPDiD complex-survey-design support (Phase D1)#590
Conversation
Adds a `survey_design=` argument to `LPDiD.fit()` (a `SurveyDesign` with probability weights + optional strata/PSU/FPC), matching the library-wide fit()-time convention. On the variance-weighted default path each horizon's long-difference regression is fit by WLS on the survey weights, and the SE is the stratified-PSU Taylor-linearization (Binder TSL) sandwich with `df = n_PSU - n_strata`, reusing `diff_diff/survey.py` (`compute_survey_vcov`). The design is re-resolved on each realized (post-clean-control) sample so weights/strata/PSU align with the regression rows; with no explicit PSU the unit is injected as the PSU. Fails closed to NaN on under-identified samples. Rejects `survey_design` with `reweight=True` (the equally-weighted / regression-adjustment IF path), replicate-weight designs, and non-pweight types (deferred follow-ups). `LPDiDResults` gains `survey_metadata` / `n_strata` / `n_psu`, a `"survey_tsl"` vcov_type, and a Survey Design block in `summary()`. The non-survey path is byte-for-byte unchanged. Validated against `survey::svyglm` on the stacked long difference (numeric golden parity is the D2 follow-up); 15 new pure-Python invariant tests (reduction/unit-clustering, FPC-shrinks-SE, stratification, lonely-PSU, NaN-consistency, weighting-moves-point, metadata, rejection paths). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Overall Assessment Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
…ey fits CI-codex P2: under a survey design the effective variance cluster is the PSU (cluster_name reports the PSU column), but for only_event=True fits (pooled is None) headline_n_clusters fell back to the panel unit count -- so an explicit PSU design with n_psu != n_units could display the unit count mislabeled as G=<psu>. Per-row event-study n_clusters and inference were already computed on the realized survey design, so this was a metadata/labeling issue only, not a wrong SE/p-value. Fix: when a survey design is active, seed headline_n_clusters from the panel-level effective PSU count (the pooled-post override still prefers the realized survey-sample count when available). Regression test added (only_event=True, explicit PSU, n_psu != n_units). 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
|
…icient contract) CI-codex P1: `_estimate_survey_sample` computed the Binder TSL sandwich on the UNREDUCED design and recomputed `response - design @ coef`. When the rank handler drops a redundant direct-inclusion covariate / absorbed dummy / lag (setting that coef to NaN while `treatment_entry` stays identified), the NaN coef propagated through the residuals and the full-design X'WX bread singularized, collapsing an otherwise-identified treatment SE/t/p/CI to NaN -- violating the library's rank-deficient contract that the non-survey solve_ols path honors. Fix: keep solve_ols's returned residuals (original-scale, computed on the identified reduced design) and build `compute_survey_vcov` on `design[:, kept]` where `kept = isfinite(coef)`, mapping treatment back to its kept-column index. If treatment itself is dropped, the effect is NaN and the SE stays NaN; `rank_deficient_action="error"` still raises from solve_ols. Regression test (duplicate + constant covariate, `silent`) asserts the treatment SE stays finite and equals the non-redundant reference fit, and that `error` raises. CI-codex P2: type-check `survey_design` before `_survey_columns` accesses its attributes, so a non-SurveyDesign argument raises the intended TypeError rather than an incidental AttributeError (test added). 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 found in the re-review. Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
Summary
LPDiD(Phase D, final phase of the LP-DiD salvage initiative). Asurvey_design=argument onLPDiD.fit()(aSurveyDesignwith probability weights + optional strata/PSU/FPC), matching the library-wide fit()-time convention (CallawaySantAnna / SpilloverDiD / ...).meat = Σ_h (1−f_h)·(n_h/(n_h−1))·Σ_j (S_hj−S̄_h)(S_hj−S̄_h)'withdf = n_PSU − n_strata, reusingdiff_diff/survey.py(compute_survey_vcov/_compute_stratified_psu_meat). Nosurvey.pychange.survey_designwithreweight=True(the equally-weighted / regression-adjustment influence-function path), replicate-weight designs, and non-pweight (fweight/aweight) types — each a deferred follow-up.LPDiDResultsgainssurvey_metadata/n_strata/n_psu, a"survey_tsl"vcov_type, and a Survey Design block insummary(). The non-survey path is byte-for-byte unchanged (gated onsurvey_design is None); all prior LPDiD tests pass identically.Methodology references (required if estimator / math changes)
docs/methodology/REGISTRY.md§LPDiD (Note Add Synthetic Difference-in-Differences (SDID) estimator #8, new) and §Survey Data Supportsurvey::svyglmreference); the reweighted/RA influence-function variance, replicate weights, and non-pweight types are rejected and documented as deferred follow-ups (REGISTRY Note Add Synthetic Difference-in-Differences (SDID) estimator #8 + TODO). The paper specifies no SE formula; the survey SE is an implementation choice validated againstsurvey::svyglm, documented under Deviations.Validation
tests/test_lpdid.py— newTestLPDiDSurvey(15 pure-Python invariant tests: reduction/unit-clustering, FPC-shrinks-SE, stratification, lonely-PSU, NaN-consistency, weighting-moves-point, metadata + fit-idempotence, and rejection paths). All prior LPDiD tests unchanged (non-survey path bit-identical). Green on both pure-Python and Rust backends.compute_survey_vcovTSL sandwich was validated to matchsurvey::svyglmon the stacked long-difference design (point/SE/df) across full strata+PSU+FPC, no-FPC, and weights-only-with-injection variants. The committed numericsvyglmgolden + parity test is the D2 follow-up.Security / privacy
🤖 Generated with Claude Code