feat(rdd): covariate-adjusted RD - covariates= on fit() with rdrobust 4.0.0 parity (CCFT 2019)#691
feat(rdd): covariate-adjusted RD - covariates= on fit() with rdrobust 4.0.0 parity (CCFT 2019)#691igerber wants to merge 1 commit into
Conversation
… 4.0.0 parity (CCFT 2019) Adds the CCFT 2019 additive common-coefficient covariate adjustment to RegressionDiscontinuity (sharp AND fuzzy), parity-targeting R rdrobust 4.0.0 end-to-end. The estimand is unchanged (precision only, unlike the DiD estimators' conditional-parallel-trends covariates role); bandwidths are covariate-aware (Z threads into every pilot of all three selector chains with a per-pilot partialled gamma); collinear covariates are dropped with a warning naming them under covs_drop=True (R's exact dqrdc2 rank/pivot semantics incl. the name-length column sort, ported directly; covs_drop=False is a deterministic strict error). Degenerate covariate adjustment is GUARDED, not reproduced (documented Deviation from R): R's ginv(tol=1e-20) inverts a float-noise singular value on constant covariates / full dummy sets, silently returning platform-dependent estimates; diff-diff excludes per-column degeneracies (a constant covariate reproduces the fit without it bit-for-bit), applies a scale-invariant stabilized cut for rank-deficient sets (a full dummy set reproduces the drop-one-category fit - span invariance), and warns naming the columns. Well-posed systems match R at machine precision. Surfaces: covariates= (fit-time, library-wide name), covs_drop= constructor knob, name-keyed covariate_coefficients / first_stage_covariate_coefficients + covariates/covariates_dropped/ covs_drop result echoes, covariate-adjusted summary() banner. Goldens 23 -> 32 configs (9 covariate configs incl. msetwo/cercomb2 chain pins, collinear-drop, ties, fuzzy, fuzzy-sharpbw; all 23 pre-existing configs reproduced exactly on regeneration; worst covariate-config disagreement 6.2e-12). R-free anchors: the CCFT 2019 partial-out identity tau_adj = tau_unadj - gamma' tau_Z exact at common manual bandwidths (both conventional and bias-corrected rows), span/order invariance, CI shrinkage. Docs: REGISTRY covariate subsection + deviation notes, CCFT-2019/2017 review checkbox flips, api rst, llms guides, README, CHANGELOG, references, choosing-estimator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4
|
Overall Assessment ✅ Looks good. No unmitigated P0/P1 findings. Executive Summary
Methodology
Code Quality No findings. The new public parameter is validated and included in Performance No findings. Covariates add expected matrix work to bandwidth pilots and estimation; no avoidable repeated high-cost path stood out in the changed logic. Maintainability No findings. The R source anchors and Registry cross-references are explicit, and the covariate-specific logic is centralized in Tech Debt No untracked blocker found. The deferred covariate-balance helper is documented in the Registry; broader RD v1 seams remain documented. Security No findings. I did not see secrets or unsafe external-input behavior in the changed code paths. Documentation/Tests
|
Summary
RegressionDiscontinuity(sharp AND fuzzy) viafit(..., covariates=[...])(R'scovs=) - additive common-coefficient specification with a pooled-across-sides gamma; the estimand is UNCHANGED (precision only, explicitly contrasted with the DiD estimators' conditional-parallel-trendscovariatesrole), and the covariate-balance placebo recipe (fit each covariate as the outcome) is documented in the module docstring and REGISTRY.covs_drop=Trueconstructor knob (R name/default): redundant covariates are dropped via a direct port of LINPACK dqrdc2's rank/pivot loop (R's exactqr(z, tol=1e-7)semantics, incl. the name-length column sort - order never leaks: every user-facing covariate surface is name-keyed and order-invariance is tested) with a warning NAMING the dropped columns;covs_drop=Falseis a deterministic strict error.ginv(tol=1e-20)inverts a float-noise singular value on constant covariates / full one-hot dummy sets, silently returning platform-dependent estimates (28% cross-implementation gamma spread, ~0.5% tau shifts observed). diff-diff excludes per-column degeneracies (a constant covariate reproduces the fit without it bit-for-bit), applies a scale-invariant stabilized cut for rank-deficient sets (a full dummy set reproduces the drop-one-category fit - span invariance at 1e-15), and warns once per fit naming the columns; well-posed systems solve with the samepinv(rcond=1e-20)semantics as R and match at machine precision.covariate_coefficients(+ fuzzyfirst_stage_covariate_coefficients) nuisance-gamma echoes,covariates/covariates_dropped/covs_dropconfig echoes, covariate-adjustedsummary()banner mirroring R's rdmodel string.Methodology references (required if estimator / math changes)
docs/methodology/papers/calonico-cattaneo-farrell-titiunik-2019-review.md); parity source: CRAN rdrobust 4.0.0 (tarball sha256 pinned indiff_diff/_rdrobust_port.py)docs/methodology/REGISTRY.md(RegressionDiscontinuity section) - named-column collinearity warning (R's is generic), rank-0 fail-closed error, deterministiccovs_drop=Falsestrict mode (R'schol()failure is roundoff-dependent), and the guarded degenerate-adjustment solve above (R's behavior there is platform-noise, i.e. irreproducible even R-to-R across BLAS builds)Validation
tests/test_rdrobust_port.py(TestCovsPortGoldenParity - 9 configs incl. gamma matrices and per-side biases; TestCovsPortValidation; TestCovsDropFun - dqrdc2 rank/pivot unit pins vs live-Rqr()incl. the tiny-scaled-column trap; TestCovsDegenerateGuard),tests/test_rdd_parity.py(covariate configs + gamma pins; config count lock 23 -> 32),tests/test_rdd_methodology.py(TestCovariates - the CCFT 2019 partial-out identitytau_adj = tau_unadj - gamma' tau_ZEXACT at common manual bandwidths for both conventional and bias-corrected rows, span/order invariance, CI shrinkage, degenerate-guard contracts),tests/test_rdd.py(TestCovariatesAPI)Security / privacy
Generated with Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4