Commit 259076d
Restore SDID survey support for placebo and jackknife variance methods
Closes the last SDID survey gap (TODO.md row 107). PR #355 restored
variance_method="bootstrap" for strata/PSU/FPC via hybrid pairs-
bootstrap + Rao-Wu + weighted-FW. This commit extends the same full-
design capability to variance_method="placebo" and "jackknife".
Placebo allocator — stratified permutation (Pesarin 2001).
Pseudo-treated indices drawn within each stratum containing actual
treated units; weighted-FW re-estimates ω and λ per draw with per-
control survey weights threaded into both loss and regularization
(reuses compute_sdid_unit_weights_survey + compute_time_weights_survey
from PR #355). New private method _placebo_variance_se_survey.
Fit-time front-door guards (per feedback_front_door_over_retry_swallow.md)
distinguish two infeasible permutation configurations with targeted
ValueError messages: Case B (stratum with treated units has zero
controls) and Case C (stratum with treated units has fewer controls
than treated). Partial-permutation fallback rejected — it silently
changes the null-distribution semantics.
Jackknife allocator — PSU-level leave-one-out with stratum
aggregation (Rust & Rao 1996). SE² = Σ_h (1-f_h)·(n_h-1)/n_h·
Σ_{j∈h}(τ̂_{(h,j)} - τ̄_h)². FPC form: f_h = n_h_sampled / fpc[h]
(population-count form from survey.py::SurveyDesign.resolve;
confirmed via survey.py:338-356 where fpc_h < n_psu_h is the
validation constraint). λ held fixed across LOOs; ω subset + rw-
composed-renormalized (matches Arkhangelsky Algorithm 3 non-survey
semantics — jackknife is variance-approximation, not refit-variance).
Strata with n_h < 2 skip silently; total-zero-variance → NaN +
UserWarning. Unstratified designs with PSU treated as single-stratum
JK1. New private method _jackknife_se_survey.
Gate relaxation — deletes the placebo+jackknife+strata/PSU/FPC raise
at synthetic_did.py:352-369. Replicate-weight gate at L329-337
unchanged (separate methodology; closed-form replicate variance
double-counts with Rao-Wu-like rescaling). fit() dispatcher adds
_placebo_use_survey_path / _jackknife_use_survey_path flags routing
to the new methods when appropriate; non-survey and pweight-only
paths bit-identical by construction (guarded by the same branch
isolation pattern used in PR #355 _bootstrap_se).
Allocator asymmetry — placebo ignores PSU axis; jackknife respects
it. Intentional: placebo is a null-distribution test (stratified unit-
level permutation is classical — PSU-level permutation on few PSUs is
near-degenerate), while jackknife is a design-based variance
approximation (PSU-level LOO is canonical per Rust & Rao). Both
respect strata. Rationale documented in method docstrings and
REGISTRY (follow-up commit).
Tests — tests/test_survey_phase5.py:
- TestSyntheticDiDSurvey: flip test_full_design_placebo_raises and
test_full_design_jackknife_raises from NotImplementedError→succeeds;
assert finite SE > 0, populated survey_metadata, .summary() round-trip.
- TestSDIDSurveyPlaceboFullDesign (new class): pseudo-treated-stays-
within-treated-strata (monkeypatched recorder), Case B / Case C
front-door guards (targeted ValueError match), se-differs-from-
pweight-only, deterministic dispatch.
- TestSDIDSurveyJackknifeFullDesign (new class): stratum-aggregation
self-consistency, fpc-reduces-se magnitude (SE_fpc = SE_nofpc/sqrt(2)
at f=0.5, rtol=1e-10), se-differs-from-pweight-only, single-PSU-
stratum silently skipped, unstratified short-circuit, all-strata-
skipped UserWarning + NaN, deterministic dispatch.
Non-survey and pweight-only regressions — all 32 tests in
TestBootstrapSE + TestPlaceboSE + TestJackknifeSE pass unchanged;
bit-identity preserved by the new-path-gating pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent e28080d commit 259076d
2 files changed
Lines changed: 1134 additions & 85 deletions
0 commit comments