diff_diff.TripleDifference#

class diff_diff.TripleDifference[source]#

Bases: _StaggeredTripleDiffEngineMixin, CallawaySantAnnaBootstrapMixin, CallawaySantAnnaAggregationMixin, BaseEstimator

Triple 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 - use vcov_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. conley is deferred. With hc1, default SE is std(IF)/sqrt(n); with hc1 + 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 ValueError raised if rank_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 deprecated StaggeredTripleDifference and 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 period t is compared to t - 1, while post-treatment periods use the fixed cohort reference g - 1 - anticipation. "universal" uses g - 1 - anticipation for 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. 0 means the analytical influence-function SEs (the library-wide 0 = off convention, 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 returns StaggeredTripleDiffResults (the two containers unify at 4.0, row M-014).

Type:

TripleDifferenceResults or StaggeredTripleDiffResults

is_fitted_#

Whether the model has been fitted.

Type:

bool

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:

  1. 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

  2. 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, seed and cband apply to the STAGGERED mode only (fit(..., first_treat=...)).

The first four raise a ValueError if 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, seed and cband - are ACCEPTED and inert in 2x2x2 mode. They act only through n_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 to fit() 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_method

control_group

alpha

anticipation

base_period

n_bootstrap

cband

pscore_trim

cluster

rank_deficient_action

epv_threshold

pscore_fallback

is_fitted_

bootstrap_weights

seed

__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)#