Skip to content

Commit 5449bbb

Browse files
igerberclaude
andcommitted
Guard non-finite original_effect in compute_effect_bootstrap_stats
Return all-NaN inference when the point estimate is NaN/Inf, preventing finite SE/CI/p-value from a valid bootstrap distribution when the estimate itself is undefined. Adds parametrized regression tests and updates REGISTRY.md bootstrap notes for both CallawaySantAnna and SunAbraham sections. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fddb6c3 commit 5449bbb

3 files changed

Lines changed: 16 additions & 2 deletions

File tree

diff_diff/bootstrap_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ def compute_effect_bootstrap_stats(
234234
p_value : float
235235
Bootstrap p-value.
236236
"""
237+
if not np.isfinite(original_effect):
238+
return np.nan, (np.nan, np.nan), np.nan
239+
237240
finite_mask = np.isfinite(boot_dist)
238241
n_valid = np.sum(finite_mask)
239242
n_total = len(boot_dist)

docs/methodology/REGISTRY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ The multiplier bootstrap uses random weights w_i with E[w]=0 and Var(w)=1:
347347
- Parameter: `rank_deficient_action` controls behavior: "warn" (default), "error", or "silent"
348348
- Non-finite inference values:
349349
- Analytic SE: Returns NaN to signal invalid inference (not biased via zeroing)
350-
- Bootstrap: Drops non-finite samples, warns, and adjusts p-value floor accordingly. SE, CI, and p-value are all NaN if SE is non-finite or zero (e.g., n_valid=1 with ddof=1, or identical samples)
350+
- Bootstrap: Drops non-finite samples, warns, and adjusts p-value floor accordingly. SE, CI, and p-value are all NaN if the original point estimate is non-finite, SE is non-finite or zero (e.g., n_valid=1 with ddof=1, or identical samples)
351351
- Threshold: Returns NaN if <50% of bootstrap samples are valid
352352
- Per-effect t_stat: Uses NaN (not 0.0) when SE is non-finite or zero (consistent with overall_t_stat)
353353
- **Note**: This is a defensive enhancement over reference implementations (R's `did::att_gt`, Stata's `csdid`) which may error or produce unhandled inf/nan in edge cases without informative warnings
@@ -488,7 +488,7 @@ where weights ŵ_{g,e} = n_{g,e} / Σ_g n_{g,e} (sample share of cohort g at eve
488488
- NaN inference for undefined statistics:
489489
- t_stat: Uses NaN (not 0.0) when SE is non-finite or zero
490490
- Analytical inference: p_value and CI also NaN when t_stat is NaN (NaN propagates through `compute_p_value` and `compute_confidence_interval`)
491-
- Bootstrap inference: p_value and CI computed from bootstrap distribution. SE, CI, and p-value are all NaN if SE is non-finite or zero, or if <50% of bootstrap samples are valid
491+
- Bootstrap inference: p_value and CI computed from bootstrap distribution. SE, CI, and p-value are all NaN if the original point estimate is non-finite, SE is non-finite or zero, or if <50% of bootstrap samples are valid
492492
- Applies to overall ATT, per-effect event study, and aggregated event study
493493
- **Note**: Defensive enhancement matching CallawaySantAnna behavior; R's `fixest::sunab()` may produce Inf/NaN without warning
494494
- Inference distribution:

tests/test_bootstrap_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ def test_bootstrap_stats_mostly_valid_but_identical(self):
5757
assert np.isnan(ci[1])
5858
assert np.isnan(p_value)
5959

60+
@pytest.mark.parametrize("bad_value", [np.nan, np.inf, -np.inf])
61+
def test_nonfinite_original_effect_with_finite_boot_dist(self, bad_value):
62+
"""Non-finite original_effect must return all-NaN even with finite boot_dist."""
63+
boot_dist = np.arange(100.0)
64+
se, ci, p_value = compute_effect_bootstrap_stats(
65+
original_effect=bad_value, boot_dist=boot_dist
66+
)
67+
assert np.isnan(se)
68+
assert np.isnan(ci[0]) and np.isnan(ci[1])
69+
assert np.isnan(p_value)
70+
6071
def test_bootstrap_stats_normal_case(self):
6172
"""Normal case with varied values: all fields finite."""
6273
boot_dist = np.arange(100.0)

0 commit comments

Comments
 (0)