diff_diff.TripleDifference#
- class diff_diff.TripleDifference[source]#
Bases:
_StaggeredTripleDiffEngineMixin,CallawaySantAnnaBootstrapMixin,CallawaySantAnnaAggregationMixin,BaseEstimatorTriple Difference (DDD) estimator.
Estimates the Average Treatment effect on the Treated (ATT) when treatment requires satisfying two criteria: belonging to a treated group AND being in an eligible partition of the population. The DDD design was popularized by Gruber (1994) [2].
This implementation follows Ortiz-Villavicencio & Sant’Anna (2025) [1], which shows that naive DDD implementations (difference of two DiDs, three-way fixed effects) are invalid when covariates are needed for identification.
- Parameters:
estimation_method (str, default="dr") –
Estimation method to use:
”dr”: Doubly robust (recommended). Consistent if either the outcome model or propensity score model is correctly specified.
”reg”: Regression adjustment (outcome regression).
”ipw”: Inverse probability weighting.
robust (bool, optional) – DEPRECATED (row M-046; warns with
FutureWarning, removed in 4.0 - usevcov_type=). Influence-function SEs are inherently robust to heteroskedasticity, so the flag never had an effect here; it was retained only for API compatibility.cluster (str, optional) – Column name for cluster-robust standard errors. When provided, SEs are computed using the Liang-Zeger cluster-robust variance estimator on the influence function.
vcov_type (str, default="hc1") – Variance estimator. Permanently narrow to
{"hc1"}per the IF-based variance decomposition: TripleDifference uses an efficient influence function and has no single design matrix on which the analytical-sandwich families (classical,hc2,hc2_bm) could compute hat-matrix leverage or Bell-McCaffrey Satterthwaite DOF.conleyis deferred. Withhc1, default SE isstd(IF)/sqrt(n); withhc1+cluster=<col>, Liang-Zeger CR1 on the combined IF.alpha (float, default=0.05) – Significance level for confidence intervals.
pscore_trim (float, default=0.01) – Trimming threshold for propensity scores. Scores below this value or above (1 - pscore_trim) are clipped to avoid extreme weights.
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
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.pscore_fallback (str, default="error") –
Action when propensity score estimation fails:
”error”: Raise the exception (default)
”unconditional”: Fall back to unconditional propensity with a warning. For IPW, 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.control_group (str, default="not_yet_treated") – Comparison cohort for staggered mode:
"not_yet_treated"(units whose enabling period is still in the future) or"never_treated"(the never-enabled cohort only). The compact R spellings"notyettreated"/"nevertreated"are accepted only by the deprecatedStaggeredTripleDifferenceand die with it at 4.0.anticipation (int, default=0) – Number of periods before the enabling period in which units may already respond. Must be a non-negative integer. Shifts each cohort’s base period earlier and excludes cohorts entering treatment within the window from the comparison group.
base_period (str, default="varying") –
"varying"uses consecutive comparisons - each pre-treatment periodtis compared tot - 1, while post-treatment periods use the fixed cohort referenceg - 1 - anticipation."universal"usesg - 1 - anticipationfor every period, pre and post alike. The two therefore differ only on the PRE-treatment effects, which is what makes"varying"the pre-trend-readable default.n_bootstrap (int, default=0) – Multiplier-bootstrap replications for staggered mode.
0means the analytical influence-function SEs (the library-wide0 = offconvention, row M-081). Clustered inference in staggered mode is only available through this path -cluster=is rejected there.bootstrap_weights (str, default="rademacher") – Multiplier distribution:
"rademacher","mammen"or"webb".seed (int, optional) – Seed for the multiplier bootstrap.
cband (bool, default=True) – Whether to compute simultaneous (sup-t) confidence bands alongside the pointwise ones.
- results_#
Estimation results after calling fit(). The 2x2x2 mode returns
TripleDifferenceResults; the staggered mode returnsStaggeredTripleDiffResults(the two containers unify at 4.0, row M-014).
Examples
Basic usage with a DataFrame:
>>> import pandas as pd >>> from diff_diff import TripleDifference >>> >>> # Data where treatment affects women (partition=1) in states >>> # that enacted a policy (group=1) >>> data = pd.DataFrame({ ... 'outcome': [...], ... 'group': [1, 1, 0, 0, ...], # 1=policy state, 0=control state ... 'partition': [1, 0, 1, 0, ...], # 1=women, 0=men ... 'post': [0, 0, 1, 1, ...], # 1=post-treatment period ... }) >>> >>> # Fit using doubly robust estimation >>> ddd = TripleDifference(estimation_method="dr") >>> results = ddd.fit( ... data, ... outcome='outcome', ... group='group', ... partition='partition', ... post='post' ... ) >>> print(results.att) # ATT estimate
With covariates (properly handled unlike naive DDD):
>>> results = ddd.fit( ... data, ... outcome='outcome', ... group='group', ... partition='partition', ... post='post', ... covariates=['age', 'income'] ... )
Notes
The DDD estimator is appropriate when:
Treatment affects only units satisfying BOTH criteria: - Belonging to a treated group (G=1), e.g., states with a policy - Being in an eligible partition (P=1), e.g., women, low-income
The DDD parallel trends assumption holds: the differential trend between eligible and ineligible partitions would have been the same across treated and control groups, absent treatment.
This is weaker than requiring separate parallel trends for two DiDs, as biases can cancel out in the differencing.
Which parameters belong to which mode.
control_group,anticipation,base_period,n_bootstrap,bootstrap_weights,seedandcbandapply to the STAGGERED mode only (fit(..., first_treat=...)).The first four raise a
ValueErrorif given a non-default value and then used to fit the 2x2x2 design, rather than being silently ignored: that engine has one pre/post contrast, so there is no comparison-cohort choice, no anticipation window, no base-period rule and no multiplier bootstrap.The other three -
bootstrap_weights,seedandcband- are ACCEPTED and inert in 2x2x2 mode. They act only throughn_bootstrap > 0, which that mode already rejects, so they are unreachable by construction rather than silently ignored. The power helpers apply the same boundary, so an estimator that is legal tofit()is legal to simulate.References
Methods
__init__([estimation_method, robust, ...])fit(data, outcome[, group, partition, post, ...])Fit the Triple Difference model, in either of its two designs.
get_params([deep])Get estimator parameters (sklearn-compatible).
print_summary()Print summary to stdout.
set_params(**params)Set estimator parameters (sklearn-compatible, transactional).
summary()Get summary of estimation results.
Attributes
estimation_methodcontrol_groupalphaanticipationbase_periodn_bootstrapcbandpscore_trimclusterrank_deficient_actionepv_thresholdpscore_fallbackbootstrap_weightsseed- __init__(estimation_method='dr', robust=None, cluster=None, vcov_type='hc1', alpha=0.05, pscore_trim=0.01, rank_deficient_action='warn', epv_threshold=10, pscore_fallback='error', control_group='not_yet_treated', anticipation=0, base_period='varying', n_bootstrap=0, bootstrap_weights='rademacher', seed=None, cband=True)[source]#
- Parameters:
estimation_method (str)
robust (bool | None)
cluster (str | None)
vcov_type (str)
alpha (float)
pscore_trim (float)
rank_deficient_action (str)
epv_threshold (float)
pscore_fallback (str)
control_group (str)
anticipation (int)
base_period (str)
n_bootstrap (int)
bootstrap_weights (str)
seed (int | None)
cband (bool)
- classmethod __new__(*args, **kwargs)#