Skip to content

Commit c0e14c7

Browse files
igerberclaude
andcommitted
Coverage MC extension + REGISTRY Notes + docs sweep for SDID survey (placebo, jackknife)
Second commit for the SDID survey-placebo/jackknife PR. Extends the coverage Monte Carlo artifact with jackknife on the stratified_survey DGP (bootstrap calibration unchanged); promotes the deferred REGISTRY §SyntheticDiD gap bullets to two landed Notes; updates user-facing docs to reflect restored capability. Coverage MC changes ------------------- * benchmarks/python/coverage_sdid.py: _stratified_survey_design now returns ("bootstrap", "jackknife") on the methods tuple. Placebo is omitted because the DGP's cohort packs into a single stratum with 0 never-treated units — stratified-permutation placebo is structurally infeasible on this DGP (raises Case C at fit-time). Module docstring explains the exclusion and the jackknife anti-conservatism caveat. * benchmarks/data/sdid_coverage.json: regenerated stratified_survey block at n_seeds=500, n_bootstrap=200. Bootstrap validates near- nominal (α=0.05 rejection = 0.058, SE/trueSD = 1.13). Jackknife row reports α=0.05 rejection = 0.45, SE/trueSD = 0.46 — documented anti- conservatism from the stratified jackknife formula with 2 PSUs per stratum (1 effective DoF per stratum, Rust & Rao 1996 limitation). REGISTRY.md §SyntheticDiD ------------------------- * Survey support matrix updated: all three variance methods now support strata/PSU/FPC (not just bootstrap). * Two new landed Notes: - "Note (survey + placebo composition)": stratified-permutation allocator, weighted-FW refit, ω_eff composition, fit-time feasibility guards (Case B / Case C), scope note on what is NOT randomized (within-stratum PSU axis). Cites Pesarin (2001) / Pesarin & Salmaso (2010). - "Note (survey + jackknife composition)": PSU-level LOO algorithm, explicit stratum-aggregation SE² formula, FPC handling (population- count form from survey.py:338-356), fixed-weights rationale, degenerate-LOO skip semantics, scope note, known anti-conservatism with few PSUs per stratum. Cites Rust & Rao (1996). * "Allocator asymmetry" paragraph in the survey support matrix documents the intentional asymmetry (placebo ignores PSU, jackknife respects it) with rationale rooted in each method's role (null- distribution test vs design-based variance approximation). * Coverage MC table adds the stratified_survey × jackknife row with anti-conservatism narrative; placebo row explicitly marked N/A-on- this-DGP (with pointer to the unit-test coverage). * Requirements checklist entries updated to describe full-design support for placebo and jackknife. Docs sweep ---------- * docs/methodology/survey-theory.md: new bullets describing the stratified-permutation placebo allocator and the PSU-level LOO jackknife, parallel to the existing hybrid-bootstrap bullet. * docs/tutorials/16_survey_did.ipynb cell 35: support matrix SDID row updated from "bootstrap only (PR #352)" to "Full (all three variance methods)"; legend amended; "Note on SyntheticDiD" block rewritten to describe all three allocators with the jackknife few-PSU caveat. * docs/survey-roadmap.md: Phase 5 matrix row closes the placebo/ jackknife gap; Phase 6 bullet updated to describe all three allocators; Current Limitations table entry removed (only replicate- weight limitation remains, merged into one row). * CHANGELOG.md: "### Added" entry for placebo + jackknife full-design support (no new section header — folded into existing Unreleased block); "### Changed (PR #355)" tweaked to note the separate follow-up for placebo/jackknife. * TODO.md row 107 deleted (capability gap closed). * diff_diff/synthetic_did.py __init__ docstring: survey_design parameter description rewritten to describe all three methods. Placebo fallback-guidance comment updated to remove stale "placebo and jackknife reject strata/PSU/FPC" line. * diff_diff/guides/llms-full.txt: Phase 5 bootstrap bullet updated to describe all three survey allocators (UTF-8 fingerprint preserved — `D'Haultfœuille` still appears throughout). * tests/test_methodology_sdid.py::TestCoverageMCArtifact: narrative and assertions updated to reflect that placebo=0-fits is expected structurally on stratified_survey (documented Case C), while jackknife now runs successfully with the known anti-conservatism caveat intentionally unasserted at the calibration-gate level. Verification ------------ * pytest tests/test_survey_phase5.py::TestSDIDSurveyPlaceboFullDesign tests/test_survey_phase5.py::TestSDIDSurveyJackknifeFullDesign tests/test_survey_phase5.py::TestSyntheticDiDSurvey tests/test_methodology_sdid.py::{TestBootstrapSE,TestPlaceboSE,TestJackknifeSE,TestCoverageMCArtifact} tests/test_guides.py → 82 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 259076d commit c0e14c7

11 files changed

Lines changed: 201 additions & 72 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3636
- **SDID `variance_method="bootstrap"` survey support restored** via a hybrid pairs-bootstrap + Rao-Wu rescaling composed with a weighted Frank-Wolfe kernel. Each bootstrap draw first performs the unit-level pairs-bootstrap resampling specified by Arkhangelsky et al. (2021) Algorithm 2 (`boot_idx = rng.choice(n_total)`), and *then* applies Rao-Wu rescaled per-unit weights (Rao & Wu 1988) sliced over the resampled units — NOT a standalone Rao-Wu bootstrap. New Rust kernel `sc_weight_fw_weighted` (and `_with_convergence` sibling) accepts a per-coordinate `reg_weights` argument so the FW objective becomes `min ||A·ω - b||² + ζ²·Σ_j reg_w[j]·ω[j]²`. New Python helpers `compute_sdid_unit_weights_survey` and `compute_time_weights_survey` thread per-control survey weights through the two-pass sparsify-refit dispatcher (column-scaling Y by `rw` for the loss, `reg_weights=rw` for the penalty on the unit-weights side; weighted column-centering + row-scaling Y by `sqrt(rw)` for the loss with uniform reg on the time-weights side). `_bootstrap_se` survey branch composes the per-draw `rw` (Rao-Wu rescaling for full designs, constant `w_control` for pweight-only fits) with the weighted-FW helpers, then composes `ω_eff = rw·ω/Σ(rw·ω)` for the SDID estimator. Coverage MC artifact extended with a `stratified_survey` DGP (BRFSS-style: N=40, strata=2, PSU=2/stratum); the bootstrap row's near-nominal calibration is the validation gate (target rejection ∈ [0.02, 0.10] at α=0.05). New regression tests across `test_methodology_sdid.py::TestBootstrapSE` (single-PSU short-circuit, full-design and pweight-only succeeds-tests, zero-treated-mass retry, deterministic Rao-Wu × boot_idx slice) and `test_survey_phase5.py::TestSyntheticDiDSurvey` (full-design ↔ pweight-only SE differs assertion). See REGISTRY.md §SyntheticDiD ``Note (survey + bootstrap composition)`` for the full objective and the argmin-set caveat.
3737

3838
### Changed (PR #355)
39-
- **SDID bootstrap SE values under survey fits now differ numerically from the v3.2.x line that shipped PR #351 alone**: the fit no longer raises `NotImplementedError`, and instead returns the weighted-FW + Rao-Wu SE. Non-survey fits are unaffected (the bootstrap dispatcher routes only the survey branch through the new `_survey` helpers; non-survey fits continue to call the existing `compute_sdid_unit_weights` / `compute_time_weights` and stay bit-identical at rel=1e-14 on the `_BASELINE["bootstrap"]` regression). SDID's `placebo` and `jackknife` paths still reject `strata/PSU/FPC` (separate methodology gap; tracked in TODO.md as a follow-up PR).
39+
- **SDID bootstrap SE values under survey fits now differ numerically from the v3.2.x line that shipped PR #351 alone**: the fit no longer raises `NotImplementedError`, and instead returns the weighted-FW + Rao-Wu SE. Non-survey fits are unaffected (the bootstrap dispatcher routes only the survey branch through the new `_survey` helpers; non-survey fits continue to call the existing `compute_sdid_unit_weights` / `compute_time_weights` and stay bit-identical at rel=1e-14 on the `_BASELINE["bootstrap"]` regression). SDID's `placebo` and `jackknife` paths still reject `strata/PSU/FPC` on the v3.2.x line; full-design support for those methods lands separately in the entries below.
40+
41+
### Added
42+
- **SDID `variance_method="placebo"` and `"jackknife"` now support strata/PSU/FPC designs.** Closes the last SDID survey gap. All three variance methods (bootstrap from PR #355, plus placebo and jackknife here) now handle full survey designs. New private methods `SyntheticDiD._placebo_variance_se_survey` and `_jackknife_se_survey` route the full-design path through method-specific allocators:
43+
- **Placebo** — stratified permutation (Pesarin 2001). Each draw samples pseudo-treated indices uniformly without replacement from controls *within each stratum* containing actual treated units; non-treated strata contribute their controls unconditionally. The weighted Frank-Wolfe kernel from PR #355 (`compute_sdid_unit_weights_survey` / `compute_time_weights_survey`) re-estimates ω and λ per draw with per-control survey weights threaded into both loss and regularization; post-optimization composition `ω_eff = rw·ω/Σ(rw·ω)`. Arkhangelsky Algorithm 4 SE formula unchanged.
44+
- **Jackknife** — 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)²` with `f_h = n_h_sampled / fpc[h]` (population-count FPC form). λ held fixed across LOOs; ω subsetted, composed with rw, renormalized. Strata with `n_h < 2` silently skipped; total-zero-variance → NaN + `UserWarning`. Unstratified single-PSU short-circuits to NaN.
45+
- **Fit-time feasibility guards** (placebo): `ValueError` on stratum-level infeasibility (treated-stratum has zero controls, or fewer controls than treated units) with targeted messages distinguishing Case B (zero controls) and Case C (undersupplied) — partial-permutation fallback rejected because it would silently change the null-distribution semantics.
46+
- **Gate relaxed**: the fit-time guard at `synthetic_did.py:352-369` that rejected placebo/jackknife + strata/PSU/FPC is removed. Replicate-weight designs remain rejected (separate methodology — replicate variance is closed-form and would double-count with Rao-Wu-like rescaling). Non-survey and pweight-only paths bit-identical by construction — the new code is gated on `resolved_survey_unit.(strata|psu|fpc) is not None`.
47+
- **Coverage MC**: `benchmarks/data/sdid_coverage.json` extended with jackknife on `stratified_survey`. Bootstrap validates near-nominal (α=0.05 rejection = 0.058, SE/trueSD = 1.13). Jackknife reported with an anti-conservatism caveat: with only 2 PSUs per stratum the stratified jackknife formula has 1 effective DoF per stratum, a well-documented limitation of Rust & Rao (1996) — `se_over_truesd ≈ 0.46` on this DGP. Users needing tight SE calibration with few PSUs should prefer `variance_method="bootstrap"`. Placebo is structurally infeasible on the existing `stratified_survey` DGP (its cohort packs into one stratum with 0 never-treated units — by design a bootstrap-suited DGP); the placebo survey path is exercised via unit tests on a feasible fixture.
48+
- **Regression tests** across `tests/test_survey_phase5.py`: two new classes `TestSDIDSurveyPlaceboFullDesign` and `TestSDIDSurveyJackknifeFullDesign`. Placebo: pseudo-treated-stratum contract, Case B / Case C front-door guards with targeted-message regression, SE-differs-from-pweight-only, deterministic dispatch. Jackknife: stratum-aggregation self-consistency, **FPC magnitude regression** (2-stratum handcrafted panel asserts `SE_fpc == SE_nofpc · sqrt(1-f)` at `rtol=1e-10`), single-PSU-stratum skip, unstratified short-circuit, all-strata-skipped warning + NaN, SE-differs-from-pweight-only, deterministic dispatch. Existing `test_full_design_placebo_raises` and `test_full_design_jackknife_raises` flipped to `_succeeds` assertions. All 19 existing pweight-only and non-survey placebo/jackknife tests pass unchanged (bit-identity preserved via the new-path gating).
49+
- **Allocator asymmetry** (documented in REGISTRY): placebo ignores the PSU axis (unit-level within-stratum permutation — the classical stratified permutation test; PSU-level permutation on few PSUs is near-degenerate); jackknife respects PSU (PSU-level LOO is the canonical survey jackknife). Both respect strata. See `docs/methodology/REGISTRY.md` §SyntheticDiD `Note (survey + placebo composition)` and `Note (survey + jackknife composition)`.
4050

4151
## [3.2.0] - 2026-04-19
4252

TODO.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ Deferred items from PR reviews that were not addressed before merge.
107107
| `HeterogeneousAdoptionDiD` Phase 5: `practitioner_next_steps()` integration, tutorial notebook, and `llms.txt` updates (preserving UTF-8 fingerprint). | `diff_diff/practitioner.py`, `tutorials/`, `diff_diff/guides/` | Phase 2a | Low |
108108
| `HeterogeneousAdoptionDiD` time-varying dose on event study: Phase 2b REJECTS panels where `D_{g,t}` varies within a unit for `t >= F` (the aggregation uses `D_{g, F}` as the single regressor for all horizons, paper Appendix B.2 constant-dose convention). A follow-up PR could add a time-varying-dose estimator for these panels; current behavior is front-door rejection with a redirect to `ChaisemartinDHaultfoeuille`. | `diff_diff/had.py::_validate_had_panel_event_study` | Phase 2b | Low |
109109
| `HeterogeneousAdoptionDiD` repeated-cross-section support: paper Section 2 defines HAD on panel OR repeated cross-section, but Phase 2a is panel-only. RCS inputs (disjoint unit IDs between periods) are rejected by the balanced-panel validator with the generic "unit(s) do not appear in both periods" error. A follow-up PR will add an RCS identification path based on pre/post cell means (rather than unit-level first differences), with its own validator and a distinct `data_mode` / API surface. | `diff_diff/had.py::_validate_had_panel`, `diff_diff/had.py::_aggregate_first_difference` | Phase 2a | Medium |
110-
| **SDID + placebo/jackknife + strata/PSU/FPC** (capability gap remaining after PR #352). PR #352 restored survey-bootstrap support via weighted Frank-Wolfe + Rao-Wu composition; the same composition for `placebo` (which permutes control indices) and `jackknife` (which leaves out one unit at a time) requires its own derivations: placebo's allocator needs a weighted permutation distribution that respects PSU clustering; jackknife needs PSU-level LOO + stratum aggregation. Both reuse the weighted-FW kernel from PR #352 (`_sc_weight_fw(reg_weights=)`); the genuinely new work is the per-method allocator. Tracked but no concrete sketch yet — defer until user demand surfaces. | `synthetic_did.py::_placebo_variance_se`, `synthetic_did.py::_jackknife_se` | follow-up | Low |
111110
| SyntheticDiD: bootstrap cross-language parity anchor against R's default `synthdid::vcov(method="bootstrap")` (refit; rebinds `opts` per draw) or Julia `Synthdid.jl::src/vcov.jl::bootstrap_se` (refit by construction). Same-library validation (placebo-SE tracking, AER §6.3 MC truth) is in place; a cross-language anchor is desirable to bolster the methodology contract. Julia is the cleanest target — minimal wrapping work and refit-native vcov. Tolerance target: 1e-6 on Monte Carlo samples (different BLAS + RNG paths preclude 1e-10). The R-parity fixture from the previous release was deleted because it pinned the now-removed fixed-weight path. | `benchmarks/R/`, `benchmarks/julia/`, `tests/` | follow-up | Low |
112111

113112
#### Performance

benchmarks/data/sdid_coverage.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"n_bootstrap": 200,
55
"library_version": "3.2.0",
66
"backend": "rust",
7-
"generated_at": "2026-04-24T13:01:54.876774+00:00",
7+
"generated_at": "2026-04-24T21:08:20.185764+00:00",
88
"total_elapsed_sec": 2420.61,
99
"methods": [
1010
"placebo",
@@ -156,17 +156,17 @@
156156
"se_over_truesd": 1.1297002530566618
157157
},
158158
"jackknife": {
159-
"n_successful_fits": 0,
159+
"n_successful_fits": 500,
160160
"rejection_rate": {
161-
"0.01": null,
162-
"0.05": null,
163-
"0.10": null
161+
"0.01": 0.358,
162+
"0.05": 0.45,
163+
"0.10": 0.512
164164
},
165-
"mean_se": null,
166-
"true_sd_tau_hat": null,
167-
"se_over_truesd": null
165+
"mean_se": 0.20686834633234263,
166+
"true_sd_tau_hat": 0.4512243070193919,
167+
"se_over_truesd": 0.4584601119980272
168168
},
169-
"_elapsed_sec": 16.48
169+
"_elapsed_sec": 18.62
170170
}
171171
}
172172
}

benchmarks/python/coverage_sdid.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@
88
rates at α ∈ {0.01, 0.05, 0.10} plus the ratio of mean estimated SE to
99
the empirical sampling SD of τ̂.
1010
11-
The ``stratified_survey`` DGP is bootstrap-only — placebo and jackknife
12-
still reject full strata/PSU/FPC survey designs (tracked in ``TODO.md``),
13-
so the harness skips those method × DGP cells via the per-DGP
14-
``survey_design_factory`` in the ``DGPSpec`` registry (PR #352 R5 P3).
11+
The ``stratified_survey`` DGP runs bootstrap and jackknife; placebo is
12+
skipped because its cohort packs into a single stratum with 0 never-
13+
treated units, so the stratified-permutation allocator is structurally
14+
infeasible on this DGP (raises Case C at fit-time). Jackknife is reported
15+
with a documented anti-conservatism caveat — with only 2 PSUs per
16+
stratum, the stratified PSU-level jackknife formula has 1 effective DoF
17+
per stratum, a known limitation (see REGISTRY §SyntheticDiD "Note
18+
(survey + jackknife composition)"). The harness skips unsupported
19+
method × DGP cells via the per-DGP ``survey_design_factory`` in the
20+
``DGPSpec`` registry.
1521
1622
The output JSON underwrites the calibration table in
1723
``docs/methodology/REGISTRY.md`` §SyntheticDiD, including the
@@ -227,13 +233,29 @@ def _stratified_survey_dgp(seed: int) -> Tuple[pd.DataFrame, List[int]]:
227233
def _stratified_survey_design(df: pd.DataFrame) -> Tuple[Any, Tuple[str, ...]]:
228234
"""Build the SurveyDesign for the stratified_survey DGP.
229235
230-
Methods supported: bootstrap only — placebo / jackknife reject
231-
strata/PSU/FPC at fit-time (separate methodology gap).
236+
Methods supported on this DGP:
237+
* **bootstrap** — weighted-FW + Rao-Wu (PR #355). Calibration
238+
validated here.
239+
* **jackknife** — PSU-level LOO with stratum aggregation (Rust &
240+
Rao 1996). Reported here with a known anti-conservatism caveat:
241+
with ``psu_per_stratum=2``, within-stratum jackknife has only
242+
``n_h - 1 = 1`` effective DoF per stratum, which is a well-
243+
documented limitation of the stratified jackknife formula when
244+
PSU counts are low. The reported ``se_over_truesd`` is expected
245+
to land below 1; this is not a bug — users needing tight SE
246+
calibration with few PSUs should prefer ``bootstrap``.
247+
* **placebo** — NOT supported on this DGP: the treated cohort packs
248+
into stratum 1 (which has 0 never-treated units by construction),
249+
so the stratified-permutation allocator raises Case C (fewer
250+
controls than treated in a treated-containing stratum) at
251+
fit-time. This is a property of the DGP, not of the placebo
252+
allocator; the placebo survey method is exercised by
253+
``tests/test_survey_phase5.py::TestSDIDSurveyPlaceboFullDesign``.
232254
"""
233255
from diff_diff import SurveyDesign
234256
return (
235257
SurveyDesign(weights="weight", strata="stratum", psu="psu", fpc="fpc"),
236-
("bootstrap",),
258+
("bootstrap", "jackknife"),
237259
)
238260

239261

diff_diff/guides/llms-full.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1674,7 +1674,7 @@ sd_female, data_female = sd.subpopulation(data, mask=lambda df: df['sex'] == 'F'
16741674
**Key features:**
16751675
- Taylor Series Linearization (TSL) variance with strata + PSU + FPC
16761676
- Replicate weight variance: BRR, Fay's BRR, JK1, JKn, SDR (13 of 16 estimators, including dCDH)
1677-
- Survey-aware bootstrap: multiplier at PSU (Hall-Mammen wild; dCDH, staggered) or Rao-Wu rescaled (SunAbraham, SyntheticDiD, TROP). SyntheticDiD bootstrap composes Rao-Wu rescaled per-draw weights with the weighted Frank-Wolfe variant of `_sc_weight_fw` (PR #352): each draw solves `min ||A·diag(rw)·ω - b||² + ζ²·Σ rw_i ω_i²` and composes `ω_eff = rw·ω/Σ(rw·ω)` for the SDID estimator. Pweight-only fits use constant `rw = w_control`; full designs use Rao-Wu. SDID's placebo and jackknife paths still reject strata/PSU/FPC (separate methodology gap, tracked in TODO.md)
1677+
- Survey-aware bootstrap: multiplier at PSU (Hall-Mammen wild; dCDH, staggered) or Rao-Wu rescaled (SunAbraham, SyntheticDiD, TROP). SyntheticDiD bootstrap composes Rao-Wu rescaled per-draw weights with the weighted Frank-Wolfe variant of `_sc_weight_fw` (PR #355): each draw solves `min ||A·diag(rw)·ω - b||² + ζ²·Σ rw_i ω_i²` and composes `ω_eff = rw·ω/Σ(rw·ω)` for the SDID estimator. Pweight-only fits use constant `rw = w_control`; full designs use Rao-Wu. SDID's placebo (stratified permutation + weighted FW) and jackknife (PSU-level LOO with stratum aggregation, Rust & Rao 1996) paths also support pweight-only and full strata/PSU/FPC designs
16781678
- DEFF diagnostics, subpopulation analysis, weight trimming (`trim_weights`)
16791679
- Repeated cross-sections: `CallawaySantAnna(panel=False)`
16801680
- Compatibility matrix: see `docs/choosing_estimator.rst` Survey Design Support section

diff_diff/synthetic_did.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -247,18 +247,29 @@ def fit( # type: ignore[override]
247247
out before computing the SDID estimator.
248248
survey_design : SurveyDesign, optional
249249
Survey design specification. Only pweight weight_type is
250-
supported. Support matrix (PR #352):
250+
supported. Replicate-weight designs are rejected. All three
251+
variance methods support both pweight-only and full
252+
strata/PSU/FPC designs:
251253
252254
method pweight-only strata/PSU/FPC
253-
bootstrap ✓ weighted FW ✓ weighted FW + Rao-Wu
254-
placebo ✓ ✗ NotImplementedError
255-
jackknife ✓ ✗ NotImplementedError
256-
257-
The bootstrap path composes Rao-Wu rescaled weights per draw
258-
with the weighted-Frank-Wolfe kernel; see REGISTRY.md
259-
§SyntheticDiD ``Note (survey + bootstrap composition)``.
260-
``placebo`` and ``jackknife`` still reject strata/PSU/FPC
261-
(separate methodology gap tracked in TODO.md).
255+
bootstrap ✓ weighted FW ✓ weighted FW + Rao-Wu (PR #355)
256+
placebo ✓ ✓ stratified permutation + weighted FW
257+
jackknife ✓ ✓ PSU-level LOO + stratum aggregation
258+
259+
- **Bootstrap** composes Rao-Wu rescaled weights per draw with
260+
the weighted-Frank-Wolfe kernel; see REGISTRY.md §SyntheticDiD
261+
``Note (survey + bootstrap composition)``.
262+
- **Placebo** under full design uses within-stratum permutation
263+
(pseudo-treated sampled from controls in each treated-containing
264+
stratum) with weighted-FW refit per draw; fit-time feasibility
265+
guards raise ``ValueError`` when a treated stratum has fewer
266+
controls than treated units (see ``Note (survey + placebo
267+
composition)``).
268+
- **Jackknife** under full design uses PSU-level LOO with
269+
stratum aggregation (Rust & Rao 1996); anti-conservative with
270+
few PSUs per stratum — prefer ``bootstrap`` when tight SE
271+
calibration matters in that regime (see ``Note (survey +
272+
jackknife composition)``).
262273
263274
Returns
264275
-------
@@ -1519,13 +1530,12 @@ def _placebo_variance_se(
15191530
# Ensure we have enough controls for the split
15201531
n_pseudo_control = n_control - n_treated
15211532
if n_pseudo_control < 1:
1522-
# Fallback guidance. Placebo and jackknife reject strata/PSU/FPC,
1523-
# but bootstrap (PR #352) supports both pweight-only and
1524-
# full-design surveys, so it's always a valid fallback.
1533+
# Fallback guidance. All three variance methods support
1534+
# pweight-only and full-design surveys (PR #355 and this PR).
15251535
fallback = (
1526-
"variance_method='bootstrap' (supports pweight-only and "
1527-
"strata/PSU/FPC survey designs), variance_method='jackknife' "
1528-
"(pweight-only only), or adding more control units"
1536+
"variance_method='bootstrap' or 'jackknife' (both support "
1537+
"pweight-only and strata/PSU/FPC survey designs), or adding "
1538+
"more control units"
15291539
if w_control is not None
15301540
else "variance_method='bootstrap', variance_method='jackknife', "
15311541
"or adding more control units"

0 commit comments

Comments
 (0)