Skip to content

Commit 4c63569

Browse files
igerberclaude
andcommitted
Fix CS aggregate='group' to use design-based survey SE path
P1: _aggregate_by_group now calls _compute_aggregated_se_with_wif (matching overall/event_study paths) for design-based variance P3: Narrow R subbootstrap match claim to no-FPC case P3: Update TODO.md CS resolved entry with PR #237 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1b5c2f0 commit 4c63569

4 files changed

Lines changed: 17 additions & 9 deletions

File tree

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ 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 — **Resolved**. Aggregated SEs now use `compute_survey_if_variance()`. Bootstrap uses PSU-level multiplier weights. | `staggered.py` | #233 | Resolved |
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 |
5757
| 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 |

diff_diff/staggered.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,8 @@ def fit(
14941494
influence_func_info,
14951495
treatment_groups,
14961496
precomputed=precomputed,
1497+
df=df,
1498+
unit=unit,
14971499
)
14981500

14991501
# Run bootstrap inference if requested

diff_diff/staggered_aggregation.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -645,17 +645,19 @@ def _aggregate_by_group(
645645
influence_func_info: Dict,
646646
groups: List[Any],
647647
precomputed: Optional["PrecomputedData"] = None,
648+
df: Optional[pd.DataFrame] = None,
649+
unit: Optional[str] = None,
648650
) -> Dict[Any, Dict[str, Any]]:
649651
"""
650652
Aggregate effects by treatment cohort.
651653
652654
Computes average effect for each cohort across all post-treatment periods.
653655
654-
Standard errors use influence function aggregation to account for
655-
covariances across time periods within a cohort.
656+
Standard errors use influence function aggregation with WIF adjustment
657+
to account for covariances across time periods within a cohort.
658+
When a full survey design is present in precomputed, uses design-based
659+
variance via compute_survey_if_variance().
656660
"""
657-
n_units = len(precomputed["all_units"]) if precomputed is not None else None
658-
659661
# Collect all group aggregation data first
660662
group_data_list = []
661663
for g in groups:
@@ -682,8 +684,11 @@ def _aggregate_by_group(
682684
weights = np.ones(len(effs)) / len(effs)
683685
agg_effect = np.sum(weights * effs)
684686

685-
agg_se = self._compute_aggregated_se(
686-
gt_pairs, weights, influence_func_info, n_units=n_units
687+
# Use WIF-adjusted SE (with survey design support)
688+
groups_for_gt = np.array([gg for (gg, t) in gt_pairs])
689+
agg_se = self._compute_aggregated_se_with_wif(
690+
gt_pairs, weights, effs, groups_for_gt,
691+
influence_func_info, df, unit, precomputed
687692
)
688693
group_data_list.append((g, agg_effect, agg_se, len(g_effects)))
689694

docs/methodology/REGISTRY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,8 +1965,9 @@ ContinuousDiD, EfficientDiD):
19651965
- **Note:** Bootstrap paths support `lonely_psu="remove"` and `"certainty"` only.
19661966
`lonely_psu="adjust"` raises `NotImplementedError` for survey-aware bootstrap;
19671967
use analytical inference for designs requiring `adjust` semantics.
1968-
- **Deviation from R:** R `survey::as.svrepdesign(type="subbootstrap")` uses the same
1969-
formula. Our implementation matches.
1968+
- **Deviation from R:** For the no-FPC case (`m_h = n_h - 1`), this matches R
1969+
`survey::as.svrepdesign(type="subbootstrap")`. The FPC-adjusted resample size
1970+
`m_h = round((1-f_h)*(n_h-1))` follows Rao, Wu & Yue (1992) Section 3.
19701971

19711972
**CallawaySantAnna Design-Based Aggregated SEs**:
19721973

0 commit comments

Comments
 (0)