diff_diff.CallawaySantAnna#
- class diff_diff.CallawaySantAnna[source]#
Bases:
CallawaySantAnnaBootstrapMixin,CallawaySantAnnaAggregationMixinCallaway-Sant’Anna (2021) estimator for staggered Difference-in-Differences.
This estimator handles DiD designs with variation in treatment timing (staggered adoption) and heterogeneous treatment effects. It avoids the bias of traditional two-way fixed effects (TWFE) estimators by:
Computing group-time average treatment effects ATT(g,t) for each cohort g (units first treated in period g) and time t.
Aggregating these to summary measures (overall ATT, event study, etc.) using appropriate weights.
- Parameters:
control_group (str, default="never_treated") – Which units to use as controls: - “never_treated”: Use only never-treated units (recommended) - “not_yet_treated”: Use never-treated and not-yet-treated units
anticipation (int, default=0) – Number of periods before treatment where effects may occur. Set to > 0 if treatment effects can begin before the official treatment date.
estimation_method (str, default="dr") – Estimation method: - “dr”: Doubly robust (recommended) - “ipw”: Inverse probability weighting - “reg”: Outcome regression
alpha (float, default=0.05) – Significance level for confidence intervals.
cluster (str, optional) – Column name for cluster-robust standard errors. When set, the influence-function aggregator clusters at the named level via a synthesized
SurveyDesign(psu=cluster_col)threaded through the existing PSU-meat machinery (_compute_stratified_psu_meat) and PSU-level multiplier bootstrap. WhenNone(default), the aggregator uses per-unit IF variance (Williams 2000 form). Whensurvey_design=SurveyDesign(psu=...)is also provided, the explicit PSU takes precedence; aUserWarningfires if the barecluster=partition differs from the explicit PSU partition.vcov_type (str, default="hc1") – Variance family. CallawaySantAnna accepts
{"hc1"}only —hc1means per-unit IF variance whencluster=Noneand CR1 Liang-Zeger on the IF whencluster=Xis set. The analytical-sandwich families (classical,hc2,hc2_bm) and spatial-HAC (conley) are rejected at__init__because CS’s per-(g,t) doubly-robust / IPW / outcome-regression structure has no single design matrix to compute hat-matrix leverage or Bell-McCaffrey Satterthwaite DOF on. See REGISTRY.md “IF-based variance estimators vs analytical-sandwich estimators” for the structural taxonomy.n_bootstrap (int, default=0) –
Number of bootstrap iterations for inference. If 0, uses analytical standard errors. Recommended: 999 or more for reliable inference.
Note
Memory Usage The bootstrap stores all weights in memory as a (n_bootstrap, n_units) float64 array. For large datasets, this can be significant: - 1K bootstrap × 10K units = ~80 MB - 10K bootstrap × 100K units = ~8 GB Consider reducing n_bootstrap if memory is constrained.
bootstrap_weights (str, default="rademacher") – Type of weights for multiplier bootstrap: - “rademacher”: +1/-1 with equal probability (standard choice) - “mammen”: Two-point distribution (asymptotically valid, matches skewness) - “webb”: Six-point distribution (recommended when n_clusters < 20)
seed (int, optional) – Random seed for reproducibility.
rank_deficient_action (str, default="warn") –
Action when design matrix is rank-deficient (linearly dependent columns):
”warn”: Issue warning and drop linearly dependent columns (default)
”error”: Raise ValueError
”silent”: Drop columns silently without warning
base_period (str, default="varying") –
Method for selecting the base (reference) period for computing ATT(g,t). Options:
”varying”: For pre-treatment periods (t < g - anticipation), use t-1 as base (consecutive comparisons). For post-treatment, use g-1-anticipation. Requires t-1 to exist in data.
”universal”: Always use g-1-anticipation as base period.
Both produce identical post-treatment effects. Matches R’s did::att_gt() base_period parameter.
cband (bool, default=True) – Whether to compute simultaneous confidence bands (sup-t) for event study aggregation. Requires
n_bootstrap > 0. When True, results includecband_crit_valueand per-event-timecband_conf_intentries controlling family-wise error rate.pscore_trim (float, default=0.01) – Trimming bound for propensity scores. Scores are clipped to
[pscore_trim, 1 - pscore_trim]before weight computation in IPW and DR estimation. Must be in(0, 0.5).panel (bool, default=True) – Whether the data is a balanced/unbalanced panel (units observed across multiple time periods). Set to
Falsefor stationary repeated cross-sections where each observation has a unique unit ID and units do not repeat across periods. Requires that the cross-sectional samples are drawn from the same population in each period (stationarity). Uses cross-sectional DRDID (Sant’Anna & Zhao 2020, Section 4) with per-observation influence functions.epv_threshold (float, default=10) – Events Per Variable threshold for propensity score logit. When the ratio of minority-class observations to predictor variables (excluding intercept) falls below this value, a warning is emitted (or
ValueErrorraised ifrank_deficient_action="error"). Based on Peduzzi et al. (1996). Only applies to IPW and DR estimation methods. Usediagnose_propensity()for a pre-estimation check across all cohorts.pscore_fallback (str, default="error") –
Action when propensity score estimation fails entirely (
LinAlgErrororValueErrorfrom IRLS):”error”: Raise the exception (default). Ensures the user is aware of estimation failures.
”unconditional”: Fall back to unconditional propensity with a warning. For IPW, this drops all covariates. For DR, the propensity model becomes unconditional but outcome regression still uses covariates.
When
rank_deficient_action="error", errors are always re-raised regardless of this setting.
- results_#
Estimation results after calling fit().
- Type:
Examples
Basic usage:
>>> import pandas as pd >>> from diff_diff import CallawaySantAnna >>> >>> # Panel data with staggered treatment >>> # 'first_treat' = period when unit was first treated (0 if never treated) >>> data = pd.DataFrame({ ... 'unit': [...], ... 'time': [...], ... 'outcome': [...], ... 'first_treat': [...] # 0 for never-treated, else first treatment period ... }) >>> >>> cs = CallawaySantAnna() >>> results = cs.fit(data, outcome='outcome', unit='unit', ... time='time', first_treat='first_treat') >>> >>> results.print_summary()
With event study aggregation:
>>> cs = CallawaySantAnna() >>> results = cs.fit(data, outcome='outcome', unit='unit', ... time='time', first_treat='first_treat', ... aggregate='event_study') >>> >>> # Plot event study >>> from diff_diff import plot_event_study >>> plot_event_study(results)
With covariate adjustment (conditional parallel trends):
>>> # When parallel trends only holds conditional on covariates >>> cs = CallawaySantAnna(estimation_method='dr') # doubly robust >>> results = cs.fit(data, outcome='outcome', unit='unit', ... time='time', first_treat='first_treat', ... covariates=['age', 'income']) >>> >>> # DR is recommended: consistent if either outcome model >>> # or propensity model is correctly specified
Notes
The key innovation of Callaway & Sant’Anna (2021) is the disaggregated approach: instead of estimating a single treatment effect, they estimate ATT(g,t) for each cohort-time pair. This avoids the “forbidden comparison” problem where already-treated units act as controls.
The ATT(g,t) is identified under parallel trends conditional on covariates:
E[Y(0)_t - Y(0)_g-1 | G=g] = E[Y(0)_t - Y(0)_g-1 | C=1]
where G=g indicates treatment cohort g and C=1 indicates control units. This uses g-1 as the base period, which applies to post-treatment (t >= g). With base_period=”varying” (default), pre-treatment uses t-1 as base for consecutive comparisons useful in parallel trends diagnostics.
References
Callaway, B., & Sant’Anna, P. H. (2021). Difference-in-Differences with multiple time periods. Journal of Econometrics, 225(2), 200-230.
Methods
__init__([control_group, anticipation, ...])diagnose_propensity(df, outcome, unit, time, ...)Check Events Per Variable (EPV) across all cohorts without estimation.
fit(data, outcome, unit, time, first_treat)Fit the Callaway-Sant'Anna estimator.
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 estimation results.
Attributes
n_bootstrapbootstrap_weightsalphaseedanticipationbase_period- __init__(control_group='never_treated', anticipation=0, estimation_method='dr', alpha=0.05, cluster=None, n_bootstrap=0, bootstrap_weights=None, seed=None, rank_deficient_action='warn', base_period='varying', cband=True, pscore_trim=0.01, panel=True, epv_threshold=10, pscore_fallback='error', vcov_type='hc1')[source]#
- Parameters:
control_group (str)
anticipation (int)
estimation_method (str)
alpha (float)
cluster (str | None)
n_bootstrap (int)
bootstrap_weights (str | None)
seed (int | None)
rank_deficient_action (str)
base_period (str)
cband (bool)
pscore_trim (float)
panel (bool)
epv_threshold (float)
pscore_fallback (str)
vcov_type (str)
- classmethod __new__(*args, **kwargs)#