Commit 61b30bb
BaconDecomposition methodology audit (Goodman-Bacon 2021)
Promotes BaconDecomposition from In Progress → Complete (R parity pending)
in METHODOLOGY_REVIEW.md. Operationalizes the paper review landed in
PR #451 against diff_diff/bacon.py.
Audit findings and corrections:
1. Theorem 1 exact-weights rewrite (bacon.py:_recompute_exact_weights).
The prior "exact" mode did not actually compute Eqs. 7-9 / 10e-g —
it was missing the (1 - n_kU) factor in the within-subsample treatment
variance, did not square the sample share, and added an extraneous
unit_share factor not present in the paper. The post-hoc sum-to-1
normalization masked the relative-weight error but produced ~0.3%
decomposition error vs TWFE (0.007 absolute on a 3-cohort + never-
treated DGP). Rewrote the function to compute exact numerators of
Eqs. 10e/f/g and let post-hoc normalization handle the V_hat^D
denominator (Theorem 1 guarantees V_hat^D = sum(numerators)). Now
matches TWFE at atol=1e-10 on noisy and hand-calculable DGPs.
2. Default `weights` flipped from "approximate" to "exact" at 3 entry
points: BaconDecomposition.__init__() (bacon.py:397), bacon_decompose()
(bacon.py:1064), TwoWayFixedEffects.decompose() (twfe.py:684). The
approximate path remains opt-in for speed-sensitive diagnostic loops.
diff_diff/diagnostic_report.py:1740 updated to pass explicit
weights="exact". The existing test_weighted_sum_equals_twfe tolerance
was tightened from < 0.1 to < 1e-10 to lock the Theorem 1 algebraic
identity contract.
**Survey-design behavior change**: weights="exact" routes through
_validate_unit_constant_survey, which rejects survey designs whose
weights / strata / PSU / FPC columns vary within a unit across
periods. The previous approximate default tolerated time-varying
within-unit survey weights via observation-level weighted means.
Migration: pass weights="approximate" explicitly to retain the
legacy path. Documented in CHANGELOG Changed entry and the new
bacon_decompose() docstring survey_design parameter block.
3. Always-treated warn+remap per paper footnote 11 (bacon.py:fit()).
Units with first_treat <= min(time) (excluding never-treated
sentinels 0 and np.inf) are auto-remapped to U via an internal
column (__bacon_first_treat_internal__), preserving the user's
first_treat column unchanged. The count is exposed on the result
via a new BaconDecompositionResults.n_always_treated_remapped field
and rendered in summary() when nonzero.
**n_never_treated reports TRUE never-treated only**, computed from
the original user column before remap — remapped always-treated
units appear separately as n_always_treated_remapped, no double-
counting.
**Loop gate uses POST-remap U count**: the treated_vs_never
comparison loop gates on n_units_in_U_bucket (post-remap) so panels
whose U is composed entirely of remapped always-treated units still
emit beta_kU^{2x2} terms. Without this distinction the loop would
silently drop those terms and break the Theorem 1 identity.
**Ordered-time logic**: detection uses first_treat <= min(time)
(not positive-sign restriction), so event-time panels with
negative or zero-crossing period labels (e.g. time ∈ [-2,..,3])
work correctly. A cohort at first_treat=-1 on such a panel is a
valid timing group; a cohort at first_treat=-3 is remapped to U.
Both timing_groups filters updated to exclude only the U
sentinels, not positive values.
REGISTRY.md replacement:
- Replaced ## BaconDecomposition block with paper-review-sourced content
plus four sub-notes (weight modes, always-treated remap with ordered-
time logic, R parity status, unbalanced-panel deviation).
- Explicitly removed the prior block's "Weights may be negative for
later-vs-earlier comparisons" claim. Theorem 1 weights are strictly
positive and sum to 1 (positivity is the headline of the theorem);
negative weights are an estimand-level phenomenon (Borusyak-Jaravel
2017, de Chaisemartin-D'Haultfoeuille 2020) at the ATT level, not
estimator-level.
- Narrowed the machine-precision claim to balanced panels only; the
unbalanced-panel library extension is documented as an explicit
Deviation block (Goodman-Bacon Appendix A's proof assumes balanced
panels; under unbalance, the Theorem 1 identity holds only
approximately, though outputs remain finite).
New artifacts:
- tests/test_methodology_bacon.py (~1050 lines, ~28 tests across 6
classes):
- TestBaconHandCalculation: hand-checks Eqs. 7-9 + 10b-d at
atol=1e-10 on a minimal hand-derived balanced panel (weights
{0.3, 0.4, 0.1, 0.2} hand-computed from sample shares and
treatment-share variances).
- TestBaconParityR: skips on missing R goldens.
- TestBaconAlwaysTreatedRemap: regression-tests warn+remap
mechanics including user-data-preservation, U-bucket-only-from-
remap (the bug from the loop-gate fix), negative first_treat
as a valid cohort (event-time encoding), and remap of negative
first_treat below min(time).
- TestBaconEdgeCases: no-untreated, single-cohort, unbalanced
panel (finite but NOT machine precision), constant-ATT recovery.
- TestBaconWeightModes: locks exact-is-default contract.
- TestBaconSurveyDesignNarrowing: survey_design composes with
exact mode + warn+remap; defaulted BaconDecomposition(),
bacon_decompose(), and the survey-time-varying-weights
rejection contract are pinned.
- benchmarks/R/generate_bacon_golden.R (234 lines): R generator script
for bacondecomp::bacon() parity goldens across 3 DGP fixtures.
JSON goldens deferred until bacondecomp R package is installed in
the local R library (TODO.md follow-up row).
CHANGELOG entries: ### Changed (default flip + survey behavior change),
TODO.md: prior BaconDecomposition row replaced by narrower R-parity
goldens deferral row.
Test results: 96 passed, 3 skipped (R parity) across all bacon/decompose
callers (test_bacon.py, test_methodology_bacon.py, test_business_report,
test_methodology_twfe, test_practitioner, test_target_parameter,
test_survey_phase3, test_survey_phase6).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent b926a10 commit 61b30bb
10 files changed
Lines changed: 1738 additions & 189 deletions
File tree
- benchmarks/R
- diff_diff
- docs/methodology
- tests
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| |||
909 | 909 | | |
910 | 910 | | |
911 | 911 | | |
912 | | - | |
913 | | - | |
| 912 | + | |
| 913 | + | |
914 | 914 | | |
915 | | - | |
916 | | - | |
917 | | - | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
918 | 930 | | |
919 | | - | |
920 | | - | |
921 | | - | |
922 | | - | |
923 | | - | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
924 | 947 | | |
925 | 948 | | |
926 | 949 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
| 77 | + | |
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
0 commit comments