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). Computes heterogeneity-robust DID estimators, in heterogeneous adoption designs where all groups start receiving heterogeneous treatment doses at the same date and no group remains fully untreated. In such designs, all groups experience their first treatment change at the same date (there are no stayers), so _stat and _dyn cannot be used.
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 nprobust
pip install did-had
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_hadcommand - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file did_had-0.2.1.tar.gz.
File metadata
- Download URL: did_had-0.2.1.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4c373debe8f3bf4e46fff85567731c04696f6a1b99cfc5de8343af4a95b85d7
|
|
| MD5 |
7777fd5e140a5cb61b8c8778bea6e533
|
|
| BLAKE2b-256 |
d5caaa9416a181ed05edefdfe44b8496ff4a747375f30ccee0d42ad59d98d591
|
Provenance
The following attestation bundles were made for did_had-0.2.1.tar.gz:
Publisher:
publish.yml on anzonyquispe/py_did_had
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
did_had-0.2.1.tar.gz -
Subject digest:
a4c373debe8f3bf4e46fff85567731c04696f6a1b99cfc5de8343af4a95b85d7 - Sigstore transparency entry: 1393419831
- Sigstore integration time:
-
Permalink:
anzonyquispe/py_did_had@28a5a73e5059e179ba860b45cea46ff1face24c2 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/anzonyquispe
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@28a5a73e5059e179ba860b45cea46ff1face24c2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file did_had-0.2.1-py3-none-any.whl.
File metadata
- Download URL: did_had-0.2.1-py3-none-any.whl
- Upload date:
- Size: 16.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57bda67e18c2c6265e82f46770f9bf6975d36aa1c3dda6bdc3280a1b18c9907e
|
|
| MD5 |
1810f23821746ef5580e7f3fbbeffbb4
|
|
| BLAKE2b-256 |
06ac8191403a5a98db3ca3b7a6cd64a5f0fac76bfe6bc1105aa084c9fa8527ac
|
Provenance
The following attestation bundles were made for did_had-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on anzonyquispe/py_did_had
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
did_had-0.2.1-py3-none-any.whl -
Subject digest:
57bda67e18c2c6265e82f46770f9bf6975d36aa1c3dda6bdc3280a1b18c9907e - Sigstore transparency entry: 1393419855
- Sigstore integration time:
-
Permalink:
anzonyquispe/py_did_had@28a5a73e5059e179ba860b45cea46ff1face24c2 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/anzonyquispe
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@28a5a73e5059e179ba860b45cea46ff1face24c2 -
Trigger Event:
push
-
Statement type: