moderndid.did_multiplegt#
- moderndid.did_multiplegt(data, yname, tname, idname, dname, cluster=None, weightsname=None, xformla='~1', effects=1, placebo=0, normalized=False, effects_equal=False, predict_het=None, predict_het_hc2bm=False, switchers='', only_never_switchers=False, same_switchers=False, same_switchers_pl=False, trends_lin=False, trends_nonparam=None, continuous=0, ci_level=95.0, less_conservative_se=False, more_granular_demeaning=False, keep_bidirectional_switchers=False, drop_missing_preswitch=False, boot=False, biters=1000, random_state=None, n_partitions=None)[source]#
Estimate intertemporal treatment effects with non-binary, non-absorbing treatments.
Implements difference-in-differences estimation for settings where treatment may be non-binary, non-absorbing (time-varying), and where lagged treatments may affect the outcome, following [3]. Unlike standard DID which assumes binary absorbing treatment, this estimator handles complex treatment patterns where units can experience treatment increases, decreases, or multiple changes over time.
Let \(F_g\) denote the first period when group \(g\)’s treatment changes, and let \(D_{g,1}\) be its baseline (period-1) treatment. The key parameter of interest is the actual-versus-status-quo (AVSQ) effect
\[\delta_{g,\ell} = \mathbb{E}\left[Y_{g,F_g-1+\ell} - Y_{g,F_g-1+\ell}(D_{g,1}, \ldots, D_{g,1}) \mid \boldsymbol{D}\right]\]which measures the expected difference between group \(g\)’s actual outcome at \(F_g - 1 + \ell\) and the counterfactual “status quo” outcome it would have obtained if its treatment had remained equal to its period-one value.
The estimator computes
\[\text{DID}_{g,\ell} = Y_{g,F_g-1+\ell} - Y_{g,F_g-1} - \frac{1}{N_{F_g-1+\ell}^g} \sum_{g': D_{g',1}=D_{g,1}, F_{g'}>F_g-1+\ell} \left(Y_{g',F_g-1+\ell} - Y_{g',F_g-1}\right)\]comparing the outcome evolution of switchers to that of groups with the same baseline treatment that have not yet switched. These are aggregated into event-study effects \(\delta_\ell\), the average effect of having been exposed to a weakly higher treatment dose for \(\ell\) periods.
When
normalized=True, the estimator computes \(\delta_\ell^n\), which normalizes by the cumulative treatment change and can be interpreted as a weighted average of the effects of the current treatment and its \(\ell - 1\) first lags on the outcome.- Parameters:
- data
DataFrame Panel data in long format. Accepts any object implementing the Arrow PyCapsule Interface (
__arrow_c_stream__), including polars, pandas, pyarrow Table, and cudf DataFrames.- yname
str Name of the outcome variable.
- tname
str Name of the time period variable.
- idname
str Name of the unit identifier variable.
- dname
str Name of the treatment variable. Can be binary or continuous, and can vary over time for the same unit (non-absorbing). Must be non-negative.
- cluster
str, optional Name of the cluster variable for clustered standard errors. If None, standard errors are computed using the influence function at the unit level.
- weightsname
str, optional Name of the sampling weights column. If None, all observations have equal weight.
- xformla
str, default=”~1” A formula for the covariates to include in the model. Should be of the form “~ X1 + X2” (intercept is always included). Use “~1” for no covariates.
- effects
int, default=1 Number of post-treatment horizons to estimate (1, 2, …, effects). \(\delta_\ell\) estimates the effect of \(\ell\) periods of exposure to changed treatment.
- placebo
int, default=0 Number of pre-treatment horizons to estimate for placebo tests (-1, -2, …, -placebo). These compare outcome trends of switchers and non-switchers before switching occurs, testing the parallel trends assumption.
- normalizedbool, default=False
If True, compute normalized effects \(\delta_\ell^n\) by dividing by the average cumulative treatment change. The normalized effect is a weighted average of the effects of the current treatment and its lags, useful when treatment magnitudes vary across units.
- effects_equalbool or
strortuple, default=False Test whether treatment effects are equal across horizons.
Trueor"all": test all effects"lb, ub"string: test effects in the range [lb, ub](lb, ub)tuple: test effects in the range [lb, ub]
Returns a chi-squared test statistic and p-value.
- predict_het
tuple[list[str],list[int]], optional Analyze heterogeneous effects by covariates. A tuple of (covariates, horizons) where covariates is a list of time-invariant covariate names and horizons is a list of effect horizons to analyze (use [-1] for all horizons). Runs WLS regressions to test whether effects vary by covariates.
- predict_het_hc2bmbool, default=False
If True, use HC2 Bell-McCaffrey degrees-of-freedom adjusted standard errors for the
predict_hetregressions, which are more robust in small samples. Requirespredict_hetto be specified. Clusters onclusterif set, otherwiseidname.- switchers{“”, “in”, “out”}, default=””
Which switchers to include in estimation:
"": All switchers (treatment increases and decreases)"in": Only treatment increases (\(D_{g,F_g} > D_{g,1}\))"out": Only treatment decreases (\(D_{g,F_g} < D_{g,1}\))
- only_never_switchersbool, default=False
If True, use only never-switchers as controls. If False (default), also use not-yet-switchers as controls.
- same_switchersbool, default=False
If True, use the same set of switchers across all effect horizons. This ensures comparability across horizons but may reduce sample size.
- same_switchers_plbool, default=False
If True, use the same set of switchers across all placebo horizons.
- trends_linbool, default=False
If True, include unit-specific linear time trends in the estimation.
- trends_nonparam
list[str], optional Variables for non-parametric group-specific trends.
- continuous
int, default=0 Polynomial degree for continuous treatment. If > 0, treatment is modeled as continuous with polynomial terms of the specified degree.
- ci_level
float, default=95.0 Confidence level for confidence intervals (e.g., 95.0 for 95% CI).
- less_conservative_sebool, default=False
If True, use less conservative standard error estimation with degrees-of-freedom adjustment based on the number of clusters or switchers.
- more_granular_demeaningbool, default=False
If True, enable path-based variance demeaning. This is a semantic alias that automatically sets
less_conservative_se=True.- keep_bidirectional_switchersbool, default=False
If True, keep units that experience both treatment increases AND decreases over time. By default, these units are dropped because their \(\delta_{g,\ell}\) may not satisfy the no-sign-reversal property.
- drop_missing_preswitchbool, default=False
If True, drop observations where treatment is missing before the first switch time.
- bootbool, default=False
If True, compute standard errors using the multiplier bootstrap instead of asymptotic influence function-based inference. The bootstrap resamples at the cluster level when
clusteris specified.- biters
int, default=1000 Number of bootstrap iterations when
boot=True.- random_state
int,Generator, optional Random seed for reproducibility of bootstrap.
- n_partitions
int, optional Number of partitions for distributed computation when
datais a Dask or Spark DataFrame. IfNone, defaults to the framework’s default parallelism.
- data
- Returns:
DIDInterResultResult object containing:
effects: EffectsResult with treatment effects at each horizon, including point estimates, standard errors, confidence intervals, and sample sizes
placebos: PlacebosResult with placebo effects (if placebo > 0)
ate: ATEResult with the average total effect \(\delta\), which can be used for cost-benefit analysis
n_units: Total number of units in the sample
n_switchers: Number of switching units
n_never_switchers: Number of never-switching units
ci_level: Confidence level used for intervals
effects_equal_test: Chi-squared test for equal effects (if requested)
placebo_joint_test: Joint test that all placebo effects are zero
influence_effects: Influence functions for effects (for custom inference)
influence_placebos: Influence functions for placebos
heterogeneity: Heterogeneous effects analysis (if predict_het specified)
estimation_params: Dictionary of estimation parameters used
See also
Notes
Identification relies on a parallel trends assumption for groups with the same baseline treatment. If two groups have the same period-one treatment, they have the same expected evolution of their status-quo outcome. This is weaker than standard parallel trends across all groups, which would rule out both dynamic treatment effects and time-varying effects.
With binary staggered treatment and uniform baseline, this is equivalent to the
att_gtevent-study estimator. With varying baseline treatments, the estimators differ because this method compares switchers only to non-switchers with the same baseline, preserving validity under a conditional parallel trends assumption that allows for lagged and time-varying effects.By default, units that experience both treatment increases and decreases are dropped (
keep_bidirectional_switchers=False) because their \(\delta_{g,\ell}\) can be written as a linear combination with negative weights of effects of different treatment lags, potentially violating the no-sign-reversal property.The ATE parameter \(\delta\) measures the average total effect per unit of treatment, where total effect includes both contemporaneous and lagged effects. It can be compared to the average treatment cost to assess whether treatment changes were beneficial.
References
[1]Bell, R., & McCaffrey, D. (2002). Bias Reduction in Standard Errors for Linear Regression with Multi-Stage Samples. Survey Methodology, 28(2), 169-181.
[2]Callaway, B., & Sant’Anna, P. H. (2021). Difference-in-Differences with Multiple Time Periods. Journal of Econometrics, 225(2), 200-230. https://doi.org/10.1016/j.jeconom.2020.12.001
[3]de Chaisemartin, C., & D’Haultfoeuille, X. (2024). Difference-in- Differences Estimators of Intertemporal Treatment Effects. Review of Economics and Statistics, 106(6), 1723-1736. https://doi.org/10.1162/rest_a_01414
Examples
Estimate intertemporal treatment effects using the Favara and Imbs (2015) banking deregulation data, where treatment (interstate branching) is non-binary and potentially non-absorbing.
In [1]: import moderndid as md ...: df = md.load_favara_imbs() ...: df.head() ...: Out[1]: shape: (5, 7) ┌──────┬────────┬─────────┬─────────────┬───────────┬──────────┬──────────┐ │ year ┆ county ┆ state_n ┆ Dl_vloans_b ┆ inter_bra ┆ w1 ┆ Dl_hpi │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ i64 ┆ f64 ┆ i64 ┆ f64 ┆ f64 │ ╞══════╪════════╪═════════╪═════════════╪═══════════╪══════════╪══════════╡ │ 1994 ┆ 1001 ┆ 1 ┆ 0.270248 ┆ 0 ┆ 0.975312 ┆ 0.003176 │ │ 1995 ┆ 1001 ┆ 1 ┆ -0.038427 ┆ 0 ┆ 0.975312 ┆ 0.048912 │ │ 1996 ┆ 1001 ┆ 1 ┆ 0.161633 ┆ 0 ┆ 0.975312 ┆ 0.058203 │ │ 1997 ┆ 1001 ┆ 1 ┆ 0.056523 ┆ 0 ┆ 0.975312 ┆ 0.044366 │ │ 1998 ┆ 1001 ┆ 1 ┆ 0.034236 ┆ 1 ┆ 0.975312 ┆ 0.047092 │ └──────┴────────┴─────────┴─────────────┴───────────┴──────────┴──────────┘
Estimate effects at multiple horizons with placebo tests.
In [2]: result = md.did_multiplegt( ...: data=df, ...: yname="Dl_vloans_b", ...: idname="county", ...: tname="year", ...: dname="inter_bra", ...: effects=8, ...: placebo=3, ...: cluster="state_n", ...: normalized=True, ...: same_switchers=True, ...: effects_equal=True, ...: ) ...: result ...: Out[2]: =============================================================================== Intertemporal Treatment Effects =============================================================================== Average Total Effect: ┌────────┬────────────┬────────────────────────┬───────┬───────────┐ │ ATE │ Std. Error │ [95% Conf. Interval] │ N │ Switchers │ ├────────┼────────────┼────────────────────────┼───────┼───────────┤ │ 0.0551 │ 0.0165 │ [ 0.0226, 0.0875] * │ 14745 │ 6184 │ └────────┴────────────┴────────────────────────┴───────┴───────────┘ Treatment Effects by Horizon: ┌─────────┬──────────┬────────────┬────────────────────────┬──────┬───────────┐ │ Horizon │ Estimate │ Std. Error │ [95% Conf. Interval] │ N │ Switchers │ ├─────────┼──────────┼────────────┼────────────────────────┼──────┼───────────┤ │ 1 │ 0.0240 │ 0.0183 │ [ -0.0119, 0.0600] │ 3368 │ 773 │ │ 2 │ 0.0117 │ 0.0106 │ [ -0.0092, 0.0325] │ 2604 │ 773 │ │ 3 │ 0.0143 │ 0.0083 │ [ -0.0019, 0.0306] │ 2031 │ 773 │ │ 4 │ 0.0115 │ 0.0082 │ [ -0.0045, 0.0275] │ 1541 │ 773 │ │ 5 │ 0.0162 │ 0.0068 │ [ 0.0028, 0.0295] * │ 1412 │ 773 │ │ 6 │ 0.0134 │ 0.0062 │ [ 0.0012, 0.0256] * │ 1293 │ 773 │ │ 7 │ 0.0098 │ 0.0049 │ [ 0.0002, 0.0195] * │ 1248 │ 773 │ │ 8 │ 0.0101 │ 0.0043 │ [ 0.0017, 0.0185] * │ 1248 │ 773 │ └─────────┴──────────┴────────────┴────────────────────────┴──────┴───────────┘ Placebo Effects (Pre-treatment): ┌─────────┬──────────┬────────────┬────────────────────────┬──────┬───────────┐ │ Horizon │ Estimate │ Std. Error │ [95% Conf. Interval] │ N │ Switchers │ ├─────────┼──────────┼────────────┼────────────────────────┼──────┼───────────┤ │ -1 │ 0.0304 │ 0.0251 │ [ -0.0188, 0.0796] │ 2335 │ 764 │ │ -2 │ -0.0218 │ 0.0230 │ [ -0.0669, 0.0234] │ 957 │ 497 │ │ -3 │ -0.0137 │ 0.0255 │ [ -0.0636, 0.0362] │ 511 │ 358 │ └─────────┴──────────┴────────────┴────────────────────────┴──────┴───────────┘ Joint test (placebos = 0): p-value = 0.2676 Test of equal effects: p-value = 0.0760 ------------------------------------------------------------------------------- Signif. codes: '*' confidence interval does not cover 0 ------------------------------------------------------------------------------- Data Info ------------------------------------------------------------------------------- Number of units: 1045 Switchers: 916 Never-switchers: 129 ------------------------------------------------------------------------------- Estimation Details ------------------------------------------------------------------------------- Effects estimated: 8 Placebos estimated: 3 Normalized: Yes Same switchers across horizons: Yes ------------------------------------------------------------------------------- Inference ------------------------------------------------------------------------------- Confidence level: 95% Clustered standard errors: state_n =============================================================================== See de Chaisemartin and D'Haultfoeuille (2024) for details.