11"""StatsPAI RD CCT bias-corrected parity (Python side) -- Module 06.
22
3- Runs sp.rdrobust on the Lee 2008 senate replica with the package
4- defaults (kernel = triangular, p = 1, q = 2, bwselect = mserd) and
5- emits both the conventional and the robust bias-corrected estimates.
6- The companion R script runs rdrobust::rdrobust with identical
7- defaults .
3+ Runs sp.rdrobust(..., bwselect="cct") on the Lee 2008 senate replica
4+ so the Track A row exercises the same Calonico-Cattaneo-Titiunik
5+ ``rdrobust`` bandwidth selector used by R and Stata. The legacy
6+ StatsPAI internal ``mserd`` selector is still recorded as a diagnostic
7+ row, but the parity headline is the canonical CCT path .
88
9- Tolerance: rel < 1e-3 (iterative bandwidth selection) .
9+ Tolerance: rel < 1e-6 against R/Stata CCT defaults .
1010"""
1111from __future__ import annotations
1212
@@ -22,8 +22,9 @@ def main() -> None:
2222 df = sp .datasets .lee_2008_senate ()
2323 dump_csv (df , MODULE )
2424
25- # Default mserd bandwidth selector.
26- fit = sp .rdrobust (df , y = "voteshare_next" , x = "margin" , c = 0.0 )
25+ # Canonical R/Stata rdrobust bandwidth selector via the official
26+ # rdrobust Python port.
27+ fit = sp .rdrobust (df , y = "voteshare_next" , x = "margin" , c = 0.0 , bwselect = "cct" )
2728
2829 rows : list [ParityRecord ] = []
2930 for label in ("conventional" , "robust" ):
@@ -52,11 +53,31 @@ def main() -> None:
5253 )
5354 )
5455
55- # Forced-bandwidth replicate at h = b = 0.042287 so the
56- # bandwidth-selector convention difference is isolated from the
57- # local-polynomial estimator math itself. R-side mirrors this
58- # with rdrobust(..., h = 0.042287, b = 0.042287).
59- H_FORCED = 0.042287
56+ # Legacy internal-selector diagnostic. This keeps the old default
57+ # visible without mixing it with the R/Stata default-h parity rows.
58+ legacy = sp .rdrobust (df , y = "voteshare_next" , x = "margin" , c = 0.0 )
59+ rows .append (
60+ ParityRecord (
61+ module = MODULE , side = "py" ,
62+ statistic = "legacy_internal_mserd_bandwidth_h" ,
63+ estimate = float (legacy .model_info ["bandwidth_h" ]), n = int (len (df )),
64+ )
65+ )
66+ rows .append (
67+ ParityRecord (
68+ module = MODULE , side = "py" ,
69+ statistic = "legacy_internal_mserd_robust_est" ,
70+ estimate = float (legacy .model_info ["robust" ]["estimate" ]),
71+ se = float (legacy .model_info ["robust" ]["se" ]),
72+ ci_lo = float (legacy .model_info ["robust" ]["ci" ][0 ]),
73+ ci_hi = float (legacy .model_info ["robust" ]["ci" ][1 ]),
74+ n = int (len (df )),
75+ )
76+ )
77+
78+ # Forced-bandwidth replicate at the legacy h = b = 0.042287 so the
79+ # local-polynomial estimator math remains separately pinned.
80+ H_FORCED = float (legacy .model_info ["bandwidth_h" ])
6081 fit_forced = sp .rdrobust (df , y = "voteshare_next" , x = "margin" , c = 0.0 ,
6182 h = H_FORCED , b = H_FORCED )
6283 for label in ("conventional" , "robust" ):
@@ -80,23 +101,14 @@ def main() -> None:
80101 "p" : fit .model_info ["polynomial_p" ],
81102 "q" : fit .model_info ["polynomial_q" ],
82103 "bwselect" : fit .model_info ["bwselect" ],
83- "bandwidth_selector_gap" : (
84- "On this Lee-2008 replica, sp.rdrobust's MSE-RD "
85- "bandwidth selector returns h=0.042 while "
86- "rdrobust::rdrobust returns h=0.176 -- a ~4x "
87- "difference that propagates into the headline "
88- "estimate (sp default ~0.062; R default ~0.076). "
89- "When BOTH implementations are forced to the same "
90- "bandwidth h=0.042287 the bias-corrected point "
91- "estimate matches at rel ~ 1e-6 and the conventional "
92- "SE at ~6%. The discrepancy is in the regularisation "
93- "term of the optimal-bandwidth formula and is "
94- "documented in tests/reference_parity/test_rd_parity.py "
95- "as a known gap; reviewers should treat the bandwidth "
96- "selector as a calibrated package-default choice that "
97- "differs across implementations, not as evidence of a "
98- "numerical bug in the underlying local-polynomial "
99- "estimator."
104+ "bandwidth_parity_note" : (
105+ "Track A uses sp.rdrobust(..., bwselect='cct'), which "
106+ "delegates to the official rdrobust Python port and "
107+ "matches R/Stata rdrobust default mserd bandwidths on "
108+ "the Lee-2008 fixture. The legacy StatsPAI internal "
109+ "mserd selector is retained as legacy_internal_mserd_* "
110+ "diagnostic rows and should not be used as the "
111+ "cross-language default-h parity claim."
100112 ),
101113 },
102114 )
0 commit comments