fix: clamp negative vcov diagonal in LinearRegression SE (no NaN SE for degenerate coefs)#542
Conversation
A high-leverage / degenerate coefficient (e.g. an absorbed-FE dummy near-collinear with the treatment, whose Bell-McCaffrey Satterthwaite DOF already hits the noise-floor guard) can have a CR2/HC variance of ~0 (≈1e-32) whose vcov diagonal lands just-below-zero under BLAS-dependent float rounding. `np.sqrt` of the negative then produced a NaN SE NONDETERMINISTICALLY — passing single-threaded but failing only under the parallel pure-Python full-suite run (the Pure Python Fallback CI leg), on test_methodology_wls_cr2.py::TestLinearRegressionFENanGuardEndToEnd. - linalg.py: clamp the vcov diagonal at 0 before sqrt in both SE sites (get_se, get_inference). No-op for positive variances; the SE is now finite (0 for a genuinely-zero variance), deterministic, BLAS-independent. - test: relax the guarded-coef assertion to finite & >= 0 (rounding-robust) + a deterministic regression test (a tiny-negative diagonal yields a finite 0 SE, not NaN). - REGISTRY (Variance Estimation, Cluster-Robust SE) + CHANGELOG documented. Pre-existing failure surfaced by PR #539's labeled full-suite run; main does not run the Pure Python Fallback leg outside labeled PRs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR Review ReportOverall Assessment✅ Looks good — No unmitigated P0/P1 findings. Executive Summary
MethodologyFinding: P3 informational — Documented numerical clamp
No P0/P1 methodology issue found. The zero-SE inference path remains NaN-consistent via Code QualityNo findings. The changed code is localized and both direct SE extraction sites in PerformanceNo findings. The clamp is O(1) and has no material performance impact. MaintainabilityNo findings. The duplicated clamp in Tech DebtNo findings. No new untracked deferral or silent correctness risk observed. SecurityNo findings. No secrets, I/O surface, subprocess use, or security-sensitive behavior introduced. Documentation/TestsFinding: P3 informational — Regression test could also pin inference fields
Verification note: attempted focused pytest execution, but the environment lacks |
Summary
Fixes a pre-existing, nondeterministic
NaNstandard error inLinearRegressionfor degenerate/high-leverage coefficients, which surfaced as the lone failure in the Pure Python Fallback CI leg of PR #539 (a docs PR that can't have caused it).Root cause: a high-leverage / degenerate coefficient (e.g. an absorbed-FE dummy near-collinear with the treatment, whose Bell-McCaffrey Satterthwaite DOF already hits the noise-floor guard) has a CR2/HC variance of ~0 (measured
vcov[i,i] ≈ 3e-32, min vcov eigenvalue-3.2e-17). That tiny diagonal lands just-below-zero under BLAS-dependent float rounding, sonp.sqrt(vcov[i,i])returnedNaN— passing single-threaded / in isolation, failing only under the parallel-n autopure-Python full-suite run.Fix: both SE sites (
get_se,get_inference) clamp the vcov diagonal at 0 beforesqrt. The SE is now finite (0 for a genuinely-zero variance), deterministic, and BLAS-independent. The clamp is a no-op for any positive variance, so no non-degenerate SE / t / p / CI changes and no R-parity impact.diff_diff/linalg.py— clamp at both SE sites (max(vcov[i,i], 0.0)).tests/test_methodology_wls_cr2.py— relax the guarded-coef assertionse > 0→ finite &>= 0(rounding-robust), and add a deterministic regression test (test_negative_variance_artifact_yields_finite_se_not_nan) that injects a tiny-negative diagonal and asserts a finite 0 SE, notNaN.docs/methodology/REGISTRY.md(Variance Estimation, Cluster-Robust SE) +CHANGELOG.mddocumented.Why separate from #539: #539 is a docs PR; this is an unrelated pre-existing numerical bug.
maindoesn't run the Pure Python Fallback leg outside labeled PRs, so it had been latent. Once this merges, #539 rebases onto it and its full suite goes green.Methodology references (required if estimator / math changes)
LinearRegressionCR2/HC standard-error extraction (clubSandwich CR2-BM; Bell-McCaffrey / Pustejovsky-Tipton 2018).Validation
tests/test_methodology_wls_cr2.py(relaxed guarded assertion + new deterministic clamp regression test). Consumer sweep green:test_linalg.py+test_methodology_wls_cr2.py+test_estimators.py+test_methodology_did.py(344 passed, default backend; 136 passed pure-Python).black/ruffclean; pre-existing mypy notes only (none from this change).Security / privacy
🤖 Generated with Claude Code