Repository navigation
feat(v4): item 7c of the 4.0 cut - SpilloverDiD df_convention knob (M-154) - #881
Conversation
…-154) SpilloverDiD gains the shared three-value df_convention knob with the 4.0 default "cluster": p-values and confidence intervals on its clustered lanes - the unit auto-cluster that 7b introduced and an explicit cluster= - now use the Stata/fixest t(G-1) reference like every other auto-clustered default, with G the effective cluster count reported as n_clusters. Point estimates, standard errors and t-statistics never move. "residual" restores the 3.x / 7b residual-df inference (n_obs_eff - k_effective) bit-for-bit; "normal" is deliberate z. The ladder is the pure helper spillover._resolve_inference_df, called once per fit and feeding every safe_inference site (aggregate tau_total, per-ring delta_j, event-study per-cell tau_k / delta_jk and the scalar lincom att): a survey_design df keeps precedence under every knob value; a saturated stage-2 sample keeps the warn + df=0 all-NaN sentinel under every value (resolve_tail_df is never consulted there); otherwise utils.resolve_tail_df with G forced to None (residual df) on EVERY Conley fit - an explicit product-kernel cluster= included, the SunAbraham precedent - and on the cluster=False HC1 lane. The knob is validated at __init__, through set_params and again at fit(); SpilloverDiDResults carries df_convention and inference_df (None under z and under the NaN sentinel); the hc2/hc2_bm rejection text no longer claims a "generic residual df". Tests: new tests/test_v4_spillover_df_convention.py (M-154 test_ref) pins the knob surface, the default-lane t(G-1) on the headline and the ring rows, an explicit coarser cluster column (G=10), the residual and normal values, the inert cluster=False / Conley lanes (explicit cluster included), survey-df precedence on weights-only and PSU+strata designs, event-study mode under all three values and the df ladder's saturation / survey / knob branches directly on the helper. The variance-conventions matrix gains spillover_cluster_unit (explicit residual, tail df 358) and its _default_df twin (tail df 59) on a deterministic coordinate overlay of the shared panel; the committed table is regenerated. The t23 drift suite pins the tutorial's default fit at t(199) and the HC1 / Conley fits on the residual df; test_spillover's exact get_params key set gains df_convention. Tutorial 23 section 5 names the reference distribution (no code cell changes - the notebook prints estimates and SEs only). Ledger: M-154 -> done (M-080's note updated); spec section 7 / item 7 complete; REGISTRY knob roster gains SpilloverDiD (out-of-scope list drops it) and the SpilloverDiD df Note describes the ladder; the variance-conventions roster, L3 entry, shared-DGP paragraph and tail-df landscape, the migration guide, llms-full.txt, docs/api/spillover.rst, docs/doc-deps.yaml and TODO.md updated.
Overall assessment✅ Looks good — no unmitigated P0 or P1 findings in the supplied diff. Executive summary
Methodology
Source cross-check used the repository’s reviews of Butts §3.1/§5 and Gardner §3.3, plus the Registry’s Stata/fixest convention. Live source retrieval was unavailable. No undocumented methodology mismatch was identified. Code QualityNo findings. The shared PerformanceNo findings. Cluster counting is reused for inference and metadata; the change introduces no material additional computation. MaintainabilityNo findings. A single resolver supplies aggregate, per-ring, event-study, and scalar lincom inference. Tech Debt
SecurityNo findings in the supplied changes. Documentation/TestsNo actionable findings. The fragment, migration guide, API documentation, Registry, and tutorial describe the new behavior. Eight changed Python files passed in-memory compilation. Additionally, 864 isolated checks of the actual resolver bodies passed using a scalar |
Summary
SpilloverDiDgains the shared three-valuedf_conventionknob with the 4.0 default"cluster"([M-154]). p-values and CIs on its clustered lanes - the unit auto-cluster from 7b and an explicitcluster=- now use the Stata/fixest t(G-1) reference like every other auto-clustered default; point estimates, SEs and t-statistics never move."residual"restores the 3.x / 7b residual-df inference bit-for-bit;"normal"is deliberate z.spillover._resolve_inference_df, called once per fit and feeding everysafe_inferencesite: asurvey_designdf keeps precedence under every value; a saturated stage-2 sample keeps the warn + df=0 all-NaN sentinel under every value; otherwiseutils.resolve_tail_dfwith G forced to None on every Conley fit (an explicit product-kernelcluster=included, the SunAbraham precedent) and on thecluster=FalseHC1 lane, so"cluster"falls back to the residual df there while"normal"still selects z. Validated at__init__, throughset_paramsand atfit().SpilloverDiDResultscarriesdf_convention/inference_df(also into_dict()); the hc2/hc2_bm rejection text no longer claims a "generic residual df".done(M-080's note updated). REGISTRYdf_conventionknob roster gainsSpilloverDiD(out-of-scope list drops it) and the SpilloverDiD df Note describes the ladder; variance-conventions roster / L3 / shared-DGP / tail-df prose, migration guide,llms-full.txt,docs/api/spillover.rst,docs/doc-deps.yaml, spec section 7 (item 7 complete) andTODO.mdupdated. Tutorial 23 section 5 names the reference distribution (markdown only - the notebook prints estimates and SEs); re-validated with nbmake.Methodology references (required if estimator / math changes)
df_conventionroster); Butts (2023) arXiv:2105.03737; Gardner (2022) arXiv:2207.05943."cluster"falls back to the residual df on every Conley fit (the combined Conley+cluster product kernel has no documented G-1 reference - the SunAbraham precedent;"normal"still selects z there) and the survey df keeps precedence under every value; both documented in the REGISTRY SpilloverDiD "inference df" Note.Validation
tests/test_v4_spillover_df_convention.py(M-154 test_ref: knob surface, default-lane t(G-1) on the headline and ring rows, an explicit coarser cluster column, residual / normal, thecluster=False/ Conley lanes, survey-df precedence, event-study mode under all three values, the df ladder's saturation / survey / knob branches);tests/test_variance_conventions.pygains thespillover_cluster_unit/spillover_cluster_unit_default_dfrows (tail df 358 / 59; committed table regenerated);tests/test_t23_spillover_tva_drift.pypins the tutorial's default fit at t(199) and the HC1 / Conley fits on the residual df;tests/test_spillover.pykey-set pin,tests/test_v4_cluster_policy.pyandtests/test_v4_matrix.pyledger pins updated.docs/tutorials/23_spillover_tva.ipynbre-validated under the Rust backend with nbmake (stores no outputs). Full default suite 15470 passed / 0 failed; mypy clean; py3.9 compile clean.Security / privacy
Changelog
changelog.d/20261029-v4-pr7c-m154-spillover-df-convention.md)