Skip to content

Commit a86498e

Browse files
committed
PR #457 R10 polish: case-insensitive R-row selector + refresh golden meta
Fresh R10 verdict was Looks good with 2 P3 informational items: 1. P3 (Maintainability): the always-treated fold-back test selected R rows via case-sensitive literal substrings ("Untreated", "Always Treated", "Later"), while the neighboring _classify_r_type classifier uses case-insensitive semantic matching. Made the selector consistent — case-insensitive matching on "untreated" / "never" / "always" tokens, so the fold-back survives bacondecomp label variation across versions. 2. P3 (Documentation/Tests): committed golden JSON's meta.description still advertised full per-component (treated, control, type) tuple parity as the contract, but PR #457 intentionally replaces that for the always_treated_remapped U-bucket rows with aggregate + fold-back parity. Updated meta.description to describe the actual three-tier contract (aggregate / direct per-component on non-remap + 6 timing-vs-timing rows / cohort fold-back for U bucket) with a pointer to the REGISTRY Notes that document the convention divergence. Tests: 34/34 still pass.
1 parent e592a5b commit a86498e

2 files changed

Lines changed: 9 additions & 4 deletions

File tree

benchmarks/data/r_bacondecomp_golden.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"generated_at": "2026-05-16",
44
"bacondecomp_version": "0.1.1",
55
"r_version": "R version 4.5.2 (2025-10-31)",
6-
"description": "Goodman-Bacon (2021) decomposition parity goldens for diff-diff BaconDecomposition. Parity target: atol=1e-6 on per-component (treated, control, type) tuples plus the TWFE coefficient."
6+
"description": "Goodman-Bacon (2021) decomposition parity goldens for diff-diff BaconDecomposition. Parity target at atol=1e-6: (1) aggregate TWFE coefficient + weights-sum across all 3 fixtures; (2) direct per-component (treated, control, type) parity on the 2 non-remap fixtures AND on the 6 timing-vs-timing rows of always_treated_remapped; (3) cohort-level fold-back parity for the U bucket on always_treated_remapped (Python's paper-footnote-11 remap folds R's separate Later-vs-Always-Treated + Treated-vs-Untreated rows into a single treated_vs_never cell per cohort; aggregate is invariant per Theorem 1, breakdown differs by convention). See REGISTRY Note (R parity convention divergence on always-treated) + Deviation (first-period boundary extension)."
77
},
88
"uniform_3groups_with_never_treated": {
99
"panel": {

tests/test_methodology_bacon.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -519,11 +519,16 @@ def test_always_treated_remapped_fold_back_matches_r(self, golden) -> None:
519519
}
520520
# Aggregate R's two U-bucket types per treated cohort.
521521
# R uses ctrl=99999 for untreated and ctrl=1 (the always-treated cohort)
522-
# for the `Later vs Always Treated` rows.
522+
# for the `Later vs Always Treated` rows. Match on case-insensitive
523+
# semantic tokens so the selector survives `bacondecomp` label
524+
# variation across versions (same convention as the neighboring
525+
# ``_classify_r_type`` helper used by the per-component test).
523526
r_agg: dict = {}
524527
for c in fix["r_components"]:
525-
ctype = c.get("type", "")
526-
if "Untreated" in ctype or ("Always Treated" in ctype and "Later" in ctype):
528+
tlow = (c.get("type") or "").lower()
529+
is_untreated = "untreated" in tlow or "never" in tlow
530+
is_always_treated_compare = "always" in tlow
531+
if is_untreated or is_always_treated_compare:
527532
k = float(c["treated_group"])
528533
w = float(c["weight"])
529534
e = float(c["estimate"])

0 commit comments

Comments
 (0)