pymc-labs / pymc-labs/CausalPy
Add Staggered Difference-in-Differences-in-Differences (DDD) via subgroup contrasts
@drbenvincent is already working on this.
Since Feb 10, 2026.
- Dominant language
- Python
- Stars
- 1.2k
- Forks
- 115
- Avg merge
- 6d 1h
- Merged PRs (30d)
- 11
Description
Summary
This issue proposes adding support for staggered Difference-in-Differences-in-Differences (DDD; triple differences), where treatment adoption occurs at different times across units (multiple cohorts). The request targets an imputation-based staggered DiD workflow (consistent with the current CausalPy staggered implementation) and extends it to DDD by:
- estimating subgroup-specific staggered effects, then
- computing contrasts of those effects (difference-of-DiDs) across levels of a third dimension.
DDD differs across three dimensions:
- Time (relative to treatment / event time)
- Treatment assignment (treated vs not-yet-treated / never-treated)
- Third dimension (pre-defined subgroup / eligibility / exposure tier)
DDD is about dimensions, not levels
The third dimension can be binary or multi-level (K>2). With K>2, the output is a set of contrasts among subgroup-specific staggered effects, not a single scalar.
Motivation
Many policy and business interventions are adopted at different times across geographies or units:
- taxes or regulations adopted by different jurisdictions in different years
- platform policy rollouts by region
- retail refurbishments rolled out by wave
In these settings, standard (non-staggered) DDD is not appropriate because a single “pre/post” split does not exist across all treated units. Users need:
- cohort-robust effect estimation aligned by event time, and
- the ability to estimate DDD contrasts across a third dimension.
This feature would allow users to answer questions like:
“How much larger was the treatment effect for subgroup A than subgroup B, accounting for staggered adoption?”
Proposed feature
Provide a new experiment-level interface that computes staggered DDD using the existing staggered DiD estimator as the base.
Name suggestion:
StaggeredDifferenceInDifferencesInDifferences
Recommended implementation shape:
- A thin wrapper around
StaggeredDifferenceInDifferencesthat runs it by subgroup level and then computes contrasts.
Why a direct “triple-interaction TWFE” approach is not suitable
A common temptation is to fit a single TWFE regression with a triple interaction (treated × post × subgroup). In staggered settings, that approach can produce non-intuitive estimands and is sensitive to heterogeneous effects and cohort composition.
CausalPy’s staggered implementation is imputation-based; a staggered DDD feature should follow the same approach rather than introducing a separate TWFE-based path.
Motivating example: staggered sugar taxes, geography, and drink brands (by sugar profile)
Policy question
Different jurisdictions (countries/states/cities) introduce a sugar tax at different times. We want to estimate how the tax affects sales.
Third dimension (multi-level brands)
The third dimension is brand (or brand family). Brands can be grouped (pre-defined, time-invariant for the analysis window) by typical sugar profile, for example:
- Low-sugar brands (e.g., zero/low-sugar formulations)
- Medium-sugar brands
- High-sugar brands
This is intentionally a brand/category dimension rather than a “treatment intensity” dimension.
What users want to estimate
- Staggered effects by brand group (event-time ATT curves or aggregated ATT):
ATT_low(event_time)ATT_medium(event_time)ATT_high(event_time)
- DDD contrasts between brand groups (reference-based or pairwise), e.g.:
ATT_high − ATT_low(incremental impact on high-sugar vs low-sugar brands)ATT_high − ATT_medium
Interpretation:
- Shared jurisdiction-level post-adoption shocks (e.g., unrelated economic shifts) affect overall beverage demand and can move all brands.
- Contrasting brand-group-specific staggered effects differences out those shared shocks and isolates the incremental impact on high-sugar brands relative to lower-sugar brands.
Design notes (Implementation approach)
Proposed implementation: wrapper around StaggeredDifferenceInDifferences
The core estimator should remain StaggeredDifferenceInDifferences (imputation-based). The DDD capability is an orchestration + contrast layer:
- Split/stratify the dataset by
third_dimension_name(categorical: K levels). - Fit
StaggeredDifferenceInDifferencesfor each level using the same:- unit definition
- time index
- treatment timing field
- model/covariates configuration
- Extract level-specific effects (overall ATT and/or event-time ATTs).
- Compute contrasts across levels (difference of effects) using a user-specified scheme:
- reference-based:
DiD(level=k) − DiD(level=ref)(recommended default) - explicit pairwise contrasts:
[("tier2","tier0"), ("tier2","tier1"), ...]
- reference-based:
Uncertainty propagation
Contrasts must carry valid uncertainty (avoid subtracting point estimates with naïve SE algebra).
- Bayesian: compute contrasts at the draw level if posterior draws are available.
- Frequentist: provide bootstrap resampling aligned to the clustering level (typically the unit of treatment assignment).
Output objects
The wrapper should return:
- per-level staggered effects (overall and event-time)
- contrast effects (overall and event-time)
- a tidy results table suitable for downstream analysis
Plotting strategy
Staggered DDD plots should emphasize contrasts and scalability with K levels:
- Event-time curves faceted by level:
ATT_level(event_time) - Contrast event-time curves:
ATT_levelA − ATT_levelBvs event time - Summary dot-and-interval plot for aggregated effects (per level + contrasts)
For K>2 levels, contrast plots (reference-based) should be the default visualization.
Acceptance criteria
-
Provide a staggered DDD interface that:
- follows the existing imputation-based staggered DiD approach
- estimates subgroup-specific staggered effects for a categorical third dimension
- computes user-specified contrasts among subgroup-specific effects
- propagates uncertainty correctly for contrasts
- includes DDD-appropriate plotting (event-time by level + event-time contrasts)
-
Documentation:
- conceptual guide explaining staggered DDD as “subgroup-specific staggered DiD + contrasts”
- a worked example using staggered sugar taxes and product intensity tiers
Out of scope
- A single-regression TWFE-based staggered DDD implementation
- General N-way differencing beyond DDD
- Continuous-dose DDD without discretization / tiering
- Automatic cohort selection rules beyond what staggered DiD already supports
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.