Skip to main content

Difference-in-Differences with Heterogeneous Adoption Design (HAD) estimator

Project description

did-had

A Python implementation of the Heterogeneous Adoption Design (HAD) estimator for difference-in-differences analysis from de Chaisemartin et al. (2025).

PyPI version Python 3.8+ License: MIT

Overview

The did-had package implements the HAD estimator for settings where:

  • All groups receive treatment but with different intensities (no pure control group)
  • Treatment adoption occurs at the same time for all groups, but doses vary
  • Some groups have treatment doses close to zero, serving as "quasi-untreated" groups

This is a Python port of the official Stata package did_had, producing numerically identical results.

Installation

pip install did-had

For plotting support:

pip install did-had[plot]

Quick Start

import pandas as pd
from did_had import DidHad

# Load your panel data
df = pd.read_stata("tutorial_data.dta")

# Create and fit the model
model = DidHad(kernel="tri")
results = model.fit(
    df=df,
    outcome="y",        # outcome variable
    group="g",          # group identifier
    time="t",           # time period
    treatment="d",      # treatment dose
    effects=5,          # post-treatment periods
    placebo=4           # pre-treatment placebos
)

# View results
print(results)

# Get ATT
print(f"Average Treatment Effect: {results.att():.4f}")

# Save results
model.save_results("results.csv", format="csv")

Example Output

===========================================================================
DID-HAD Estimation Results
===========================================================================
Number of groups: 1,000
Number of periods: 10
Adoption period (F): 6.0
Kernel: tri
Confidence level: 95%

---------------------------------------------------------------------------
                          Effect Estimates                      QUG* Test
         --------------------------------------------------- ---------------
          Estimate       SE     LB.CI     UB.CI     N      BW    N.BW        T    p.val
Effect_1   4.28198  0.55814   2.71751   4.90538 1,000 0.36133    371  3.96182  0.20154
Effect_2   3.59260  0.66675   2.02563   4.63925 1,000 0.27104    282  3.96182  0.20154
Effect_3   4.25466  0.67544   2.71354   5.36123 1,000 0.31407    324  3.96182  0.20154
...

Features

  • Exact replication of Stata's did_had command
  • Bias-corrected inference using local polynomial regression (lprobust-style)
  • Quasi-untreated group tests to validate the estimation strategy
  • Event-study plots for visualization
  • Multiple output formats: CSV, Stata, Excel, pickle

API Reference

DidHad Class

DidHad(kernel="epa", alpha=0.05, nnmatch=3)

Parameters:

  • kernel: Kernel function - "epa" (Epanechnikov), "tri" (triangular), "uni" (uniform), "gau" (Gaussian)
  • alpha: Significance level for confidence intervals (default: 0.05 for 95% CI)
  • nnmatch: Number of nearest neighbors for variance estimation

fit() Method

results = model.fit(
    df,                      # Panel DataFrame
    outcome,                 # Outcome variable name
    group,                   # Group identifier name
    time,                    # Time period name
    treatment,               # Treatment dose name
    effects=1,               # Number of post-treatment periods
    placebo=0,               # Number of pre-treatment placebos
    dynamic=False,           # Use cumulative treatment dose
    bandwidth=None,          # Global bandwidth (or Silverman if None)
    bandwidth_effect=None,   # Effect-specific bandwidths (dict or scalar)
    bandwidth_placebo=None   # Placebo-specific bandwidths (dict or scalar)
)

DidHadResults Object

results.summary()           # Formatted summary string
results.to_dataframe()      # Full results as DataFrame
results.att()               # Average treatment effect on treated
results.effects             # DataFrame of effect estimates
results.placebos            # DataFrame of placebo estimates

Plotting

# Basic plot
model.plot()

# Customized plot
model.plot(
    figsize=(10, 6),
    title="My Event Study",
    xlabel="Periods since treatment",
    ylabel="Treatment Effect",
    show_ci=True
)

Saving Results

model.save_results("results.csv", format="csv")
model.save_results("results.dta", format="stata")
model.save_results("results.xlsx", format="excel")
model.save_results("results.pkl", format="pickle")

Comparison with Stata

This package produces numerically identical results to the official Stata did_had command when using the same bandwidths:

Estimate Python Stata Match
Effect_1 4.28198 4.28198 Yes
Effect_2 3.59260 3.59260 Yes
Effect_3 4.25466 4.25466 Yes
... ... ... ...

Requirements

  • Python >= 3.8
  • NumPy >= 1.20.0
  • Pandas >= 1.3.0
  • Matplotlib >= 3.4.0 (optional, for plotting)

References

  • de Chaisemartin, C., D'Haultfoeuille, X., Pasquier, F., & Vazquez-Bare, G. (2025). "Difference-in-Differences Estimators for Treatments Continuously Distributed at Every Period". SSRN

  • Calonico, S., Cattaneo, M. D., & Farrell, M. H. (2019). "nprobust: Nonparametric Kernel-Based Estimation and Robust Bias-Corrected Inference". Journal of Statistical Software.

License

MIT License. See LICENSE for details.

Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

did_had-0.1.0.tar.gz (18.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

did_had-0.1.0-py3-none-any.whl (14.8 kB view details)

Uploaded Python 3

File details

Details for the file did_had-0.1.0.tar.gz.

File metadata

  • Download URL: did_had-0.1.0.tar.gz
  • Upload date:
  • Size: 18.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for did_had-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b498d9c41749ff8b9af64281e53fecbad72f2c4bdb77b22d0f1db60811a287c9
MD5 9259106a39e47ac6a08242467b5f8db6
BLAKE2b-256 87e4c975e98a8ce2c3a76e4fa334ee9d968c909434cdb3981d5821655261f6eb

See more details on using hashes here.

File details

Details for the file did_had-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: did_had-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for did_had-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c4dae48dd2e9f7e35755a5c074b7aa8ab73b9763bc04c8432f7dd8424bfc7f2
MD5 e83bed2114515ea56d2759b12797e664
BLAKE2b-256 d0a020760a8b3272800eec6010eb0f2f76f1f157a0c4cc7a6d0624da0af0bce9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page