Skip to content

Latest commit

 

History

History
129 lines (96 loc) · 3.88 KB

File metadata and controls

129 lines (96 loc) · 3.88 KB

Stacked Difference-in-Differences

Stacked DiD estimator for staggered adoption designs with corrective Q-weights.

This module implements the methodology from Wing, Freedman & Hollingsworth (2024), which addresses bias in naive stacked DiD regressions by:

  1. Constructing sub-experiments: One per adoption cohort with clean controls
  2. Applying corrective Q-weights: Ensures proper weighting of treatment and control group trends across sub-experiments
  3. Running weighted event-study regression: WLS with Q-weights identifies the "trimmed aggregate ATT"

When to use Stacked DiD:

  • Staggered adoption design with multiple treatment cohorts
  • Want an intuitive sub-experiment-based approach (vs. aggregation methods)
  • Desire compositional balance: treatment group composition fixed across event times
  • Need direct access to the stacked dataset for custom analysis

Reference: Wing, C., Freedman, S. M., & Hollingsworth, A. (2024). Stacked Difference-in-Differences. NBER Working Paper 32054. http://www.nber.org/papers/w32054

StackedDiD

Main estimator class for Stacked Difference-in-Differences.

.. autoclass:: diff_diff.StackedDiD
   :no-index:
   :members:
   :undoc-members:
   :show-inheritance:
   :inherited-members:

   .. rubric:: Methods

   .. autosummary::

      ~StackedDiD.fit
      ~StackedDiD.get_params
      ~StackedDiD.set_params

StackedDiDResults

Results container for Stacked DiD estimation.

.. autoclass:: diff_diff.StackedDiDResults
   :no-index:
   :members:
   :undoc-members:
   :show-inheritance:

   .. rubric:: Methods

   .. autosummary::

      ~StackedDiDResults.summary
      ~StackedDiDResults.print_summary
      ~StackedDiDResults.to_dataframe

Convenience Function

.. autofunction:: diff_diff.stacked_did

Example Usage

Basic usage:

from diff_diff import StackedDiD, generate_staggered_data

data = generate_staggered_data(n_units=200, n_periods=12,
                                cohort_periods=[4, 6, 8], seed=42)

est = StackedDiD(kappa_pre=2, kappa_post=2)
results = est.fit(data, outcome='outcome', unit='unit',
                  time='period', first_treat='first_treat',
                  aggregate='event_study')
results.print_summary()

Accessing the stacked dataset:

# The stacked data is available for custom analysis
stacked = results.stacked_data
print(stacked[['unit', 'period', '_sub_exp', '_event_time', '_D_sa', '_Q_weight']].head())

Different weighting schemes:

# Population-weighted ATT (requires population column)
est = StackedDiD(kappa_pre=2, kappa_post=2, weighting='population')
results = est.fit(data, outcome='outcome', unit='unit',
                  time='period', first_treat='first_treat',
                  population='pop_size')

# Sample-share weighted ATT
est = StackedDiD(kappa_pre=2, kappa_post=2, weighting='sample_share')
results = est.fit(data, outcome='outcome', unit='unit',
                  time='period', first_treat='first_treat')

Comparison with Other Staggered Estimators

Feature Stacked DiD Callaway-Sant'Anna
Approach Pooled WLS on stacked sub-experiments Separate group-time regressions
Compositional balance Enforced by IC1/IC2 trimming Via balanced event study aggregation
Target parameter Trimmed aggregate ATT Weighted average of ATT(g,t)
Custom analysis Full stacked dataset accessible Group-time effects accessible
Covariates Entropy balancing via balance="entropy" + fit(covariates=...) (CBWSDID, Ustyuzhanin 2026); requires weighting="aggregate" + balanced windows, no survey_design Supported (OR, IPW, DR)