Skip to content

Commit c693675

Browse files
authored
Merge pull request #237 from igerber/survey-improvements
Add survey-aware bootstrap for all estimators (Phase 6)
2 parents b8ff7ce + ea86541 commit c693675

32 files changed

Lines changed: 2876 additions & 947 deletions

TODO.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ Deferred items from PR reviews that were not addressed before merge.
5252
| ImputationDiD dense `(A0'A0).toarray()` scales O((U+T+K)^2), OOM risk on large panels | `imputation.py` | #141 | Medium (deferred — only triggers when sparse solver fails; fixing requires sparse least-squares alternatives) |
5353
| EfficientDiD: API docs / tutorial page for new public estimator | `docs/` | #192 | Medium |
5454
| Multi-absorb weighted demeaning needs iterative alternating projections for N > 1 absorbed FE with survey weights; unweighted multi-absorb also uses single-pass (pre-existing, exact only for balanced panels) | `estimators.py` | #218 | Medium |
55-
| CallawaySantAnna survey: strata/PSU/FPC rejected at runtime. Full design-based SEs require routing the combined IF/WIF through `compute_survey_vcov()`. Currently weights-only. | `staggered.py` | #233 | Medium |
55+
| CallawaySantAnna survey: strata/PSU/FPC **Resolved**. Aggregated SEs (overall, event study, group) use `compute_survey_if_variance()`. Bootstrap uses PSU-level multiplier weights. | `staggered.py` | #237 | Resolved |
5656
| CallawaySantAnna survey + covariates + IPW/DR: DRDID panel nuisance-estimation IF corrections not implemented. Currently gated with NotImplementedError. Regression method with covariates works (has WLS nuisance IF correction). | `staggered.py` | #233 | Medium |
57-
| SyntheticDiD/TROP survey: strata/PSU/FPC deferred. Full design-based bootstrap (Rao-Wu rescaled weights) needed for survey-aware resampling. Currently pweight-only. | `synthetic_did.py`, `trop.py` || Medium |
57+
| SyntheticDiD/TROP survey: strata/PSU/FPC **Resolved**. Rao-Wu rescaled bootstrap implemented for both. TROP uses cross-classified pseudo-strata. Rust TROP remains pweight-only (Python fallback for full design). | `synthetic_did.py`, `trop.py` || Resolved |
5858
| EfficientDiD hausman_pretest() clustered covariance uses stale `n_cl` after filtering non-finite EIF rows — should recompute effective cluster count and remap indices after `row_finite` filtering | `efficient_did.py` | #230 | Medium |
5959
| EfficientDiD `control_group="last_cohort"` trims at `last_g - anticipation` but REGISTRY says `t >= last_g`. With `anticipation=0` (default) these are identical. With `anticipation>0`, code is arguably more conservative (excludes anticipation-contaminated periods). Either align REGISTRY with code or change code to `t < last_g` — needs design decision. | `efficient_did.py` | #230 | Low |
6060
| TripleDifference power: `generate_ddd_data` is a fixed 2×2×2 cross-sectional DGP — no multi-period or unbalanced-group support. Add a `generate_ddd_panel_data` for panel DDD power analysis. | `prep_dgp.py`, `power.py` | #208 | Low |
@@ -78,6 +78,7 @@ Deferred items from PR reviews that were not addressed before merge.
7878
| CS R helpers hard-code `xformla = ~ 1`; no covariate-adjusted R benchmark for IRLS path | `tests/test_methodology_callaway.py` | #202 | Low |
7979
| ~376 `duplicate object description` Sphinx warnings — caused by autodoc `:members:` on dataclass attributes within manual API pages (not from autosummary stubs); fix requires restructuring `docs/api/*.rst` pages to avoid documenting the same attribute via both `:members:` and inline `autosummary` tables | `docs/api/*.rst` || Low |
8080
| Plotly renderers silently ignore styling kwargs (marker, markersize, linewidth, capsize, ci_linewidth) that the matplotlib backend honors; thread them through or reject when `backend="plotly"` | `visualization/_event_study.py`, `_diagnostic.py`, `_power.py` | #222 | Medium |
81+
| Survey bootstrap test coverage: add FPC census zero-variance, single-PSU NaN, full-design bootstrap for CS/ContinuousDiD/EfficientDiD, and TROP Rao-Wu vs block bootstrap equivalence tests | `tests/test_survey_phase*.py` | #237 | Medium |
8182

8283
---
8384

diff_diff/_backend.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
# Check for backend override via environment variable
1515
# DIFF_DIFF_BACKEND can be: 'auto' (default), 'python', or 'rust'
16-
_backend_env = os.environ.get('DIFF_DIFF_BACKEND', 'auto').lower()
16+
_backend_env = os.environ.get("DIFF_DIFF_BACKEND", "auto").lower()
1717

1818
# Try to import Rust backend for accelerated operations
1919
try:
@@ -38,6 +38,7 @@
3838
# Diagnostics
3939
rust_backend_info as _rust_backend_info,
4040
)
41+
4142
_rust_available = True
4243
except ImportError:
4344
_rust_available = False
@@ -61,7 +62,7 @@
6162
_rust_backend_info = None
6263

6364
# Determine final backend based on environment variable and availability
64-
if _backend_env == 'python':
65+
if _backend_env == "python":
6566
# Force pure Python mode - disable Rust even if available
6667
HAS_RUST_BACKEND = False
6768
_rust_bootstrap_weights = None
@@ -82,7 +83,7 @@
8283
_rust_compute_noise_level = None
8384
_rust_sc_weight_fw = None
8485
_rust_backend_info = None
85-
elif _backend_env == 'rust':
86+
elif _backend_env == "rust":
8687
# Force Rust mode - fail if not available
8788
if not _rust_available:
8889
raise ImportError(
@@ -111,23 +112,23 @@ def rust_backend_info():
111112

112113

113114
__all__ = [
114-
'HAS_RUST_BACKEND',
115-
'rust_backend_info',
116-
'_rust_bootstrap_weights',
117-
'_rust_synthetic_weights',
118-
'_rust_project_simplex',
119-
'_rust_solve_ols',
120-
'_rust_compute_robust_vcov',
115+
"HAS_RUST_BACKEND",
116+
"rust_backend_info",
117+
"_rust_bootstrap_weights",
118+
"_rust_synthetic_weights",
119+
"_rust_project_simplex",
120+
"_rust_solve_ols",
121+
"_rust_compute_robust_vcov",
121122
# TROP estimator acceleration (local method)
122-
'_rust_unit_distance_matrix',
123-
'_rust_loocv_grid_search',
124-
'_rust_bootstrap_trop_variance',
123+
"_rust_unit_distance_matrix",
124+
"_rust_loocv_grid_search",
125+
"_rust_bootstrap_trop_variance",
125126
# TROP estimator acceleration (global method)
126-
'_rust_loocv_grid_search_global',
127-
'_rust_bootstrap_trop_variance_global',
127+
"_rust_loocv_grid_search_global",
128+
"_rust_bootstrap_trop_variance_global",
128129
# SDID weights (Frank-Wolfe matching R's synthdid)
129-
'_rust_sdid_unit_weights',
130-
'_rust_compute_time_weights',
131-
'_rust_compute_noise_level',
132-
'_rust_sc_weight_fw',
130+
"_rust_sdid_unit_weights",
131+
"_rust_compute_time_weights",
132+
"_rust_compute_noise_level",
133+
"_rust_sc_weight_fw",
133134
]

0 commit comments

Comments
 (0)