diff_diff.BaconDecomposition#
- class diff_diff.BaconDecomposition[source]#
Bases:
objectGoodman-Bacon (2021) decomposition of Two-Way Fixed Effects estimator.
This class decomposes a TWFE estimate into a weighted average of all possible 2x2 DiD comparisons, revealing the implicit comparisons that drive the TWFE estimate and their relative importance.
The decomposition identifies three types of comparisons:
Treated vs Never-treated: Uses never-treated units as controls. These are “clean” comparisons without bias concerns.
Earlier vs Later treated: Units treated earlier are compared to units treated later, using the later group as controls before they are treated. These are valid comparisons.
Later vs Earlier treated: Units treated later are compared to units treated earlier, using the earlier group as controls AFTER they are already treated. These are “forbidden comparisons” that can introduce bias when treatment effects vary over time.
- Parameters:
weights (str, default="exact") –
Weight calculation method:
”exact” (default): Variance-based weights from Goodman-Bacon (2021) Theorem 1, Eqs. 7-9 and 10e-g. Produces the paper-faithful decomposition where the weighted sum matches the TWFE estimate to machine precision. Use for publication-quality work and the standard methodology contract.
”approximate”: Fast simplified formula using group shares and treatment variance, with post-hoc sum-to-1 normalization. Opt in for speed-sensitive diagnostic loops where the relative weight structure is sufficient. Approximate-mode results may differ numerically from R
bacondecomp::bacon().
- results_#
Decomposition results after calling fit().
Examples
Basic usage:
>>> import pandas as pd >>> from diff_diff import BaconDecomposition >>> >>> # Panel data with staggered treatment >>> data = pd.DataFrame({ ... 'unit': [...], ... 'time': [...], ... 'outcome': [...], ... 'first_treat': [...] # 0 for never-treated ... }) >>> >>> bacon = BaconDecomposition() >>> results = bacon.fit(data, outcome='outcome', unit='unit', ... time='time', first_treat='first_treat') >>> results.print_summary()
Visualizing the decomposition:
>>> from diff_diff import plot_bacon >>> plot_bacon(results)
Notes
The key insight from Goodman-Bacon (2021) is that TWFE with staggered treatment timing implicitly makes comparisons using already-treated units as controls. When treatment effects are dynamic (changing over time since treatment), these “forbidden comparisons” can bias the TWFE estimate, potentially even reversing its sign.
The decomposition helps diagnose this issue by showing: - How much weight is on each type of comparison - Whether forbidden comparisons contribute significantly to the estimate - How the 2x2 estimates vary across comparison types
If forbidden comparisons have substantial weight and different estimates than clean comparisons, consider using robust estimators like Callaway-Sant’Anna that avoid these problematic comparisons.
References
Goodman-Bacon, A. (2021). Difference-in-differences with variation in treatment timing. Journal of Econometrics, 225(2), 254-277.
See also
CallawaySantAnnaRobust estimator for staggered DiD
TwoWayFixedEffectsThe TWFE estimator being decomposed
Methods
__init__([weights])Initialize BaconDecomposition.
fit(data, outcome, unit, time, first_treat)Perform the Goodman-Bacon decomposition.
get_params()Get estimator parameters (sklearn-compatible).
print_summary()Print summary to stdout.
set_params(**params)Set estimator parameters (sklearn-compatible).
summary()Get summary of decomposition results.
- __init__(weights='exact')[source]#
Initialize BaconDecomposition.
- Parameters:
weights (str, default="exact") –
Weight calculation method:
”exact” (default): Variance-based weights from Goodman-Bacon (2021) Theorem 1 (paper-faithful Eqs. 7-9 and 10e-g).
”approximate”: Fast simplified formula. Opt in for speed.
- classmethod __new__(*args, **kwargs)#