did_multiplegt_stat
Python implementation of the did_multiplegt_stat Stata package by
de Chaisemartin, D'Haultfœuille, Pasquier, Sow, and Vazquez-Bare (2024) —
heterogeneity-robust difference-in-differences estimators with stayers for
binary, discrete, or continuous treatments (and instruments).
The package estimates the Average Slope (AS), Weighted Average Slope (WAS), and IV-WAS parameters in static designs where parallel trends is assumed conditional on the baseline treatment.
Installation
pip install did-multiplegt-stat
Optional extras:
pip install "did-multiplegt-stat[linearmodels]" # alternative IV backend
pip install "did-multiplegt-stat[docs]" # build docs locally
pip install "did-multiplegt-stat[dev]" # tests + tooling
Quick start: default scikit-learn regressions
By default, DIDMultiplegtStat uses scikit-learn's LinearRegression for the
outcome-change nuisance function and LogisticRegression for the stayer
probability nuisance functions. This is equivalent to setting
asinstata=False. The option is written explicitly below so that the backend
used by each example is unambiguous.
Example 1: AS and WAS of gasoline taxes on log consumption
Original Stata command:
did_multiplegt_stat lngca id year tau, or(1) estimator(as was) placebo(3) as_vs_was
Python with the default scikit-learn OLS and logit regressions:
# Uncomment this line when running the example in a Jupyter notebook:
# %pip install pandas statsmodels scikit-learn did-multiplegt-stat
import pandas as pd
from did_multiplegt_stat import DIDMultiplegtStat
data_url = (
"https://raw.githubusercontent.com/Credible-Answers/"
"py_did_multiplegt_stat/main/tests/data/gazoline_did_multiplegt_stat.dta"
)
df = pd.read_stata(data_url)
model = DIDMultiplegtStat(
estimator=["as", "was"],
order=1,
placebo=3,
as_vs_was=True,
asinstata=False, # scikit-learn LinearRegression and LogisticRegression
)
model.fit(df, Y="lngca", ID="id", Time="year", D="tau")
model.summary()
model.plot()
Example 2: AS and WAS with no extrapolation
Original Stata command:
did_multiplegt_stat lngpinc id year tau, or(1) estimator(as was) estimation_method(dr) placebo(3) noextra as_vs_was
Python with the default scikit-learn OLS and logit regressions:
# Uncomment this line when running the example in a Jupyter notebook:
# %pip install pandas statsmodels scikit-learn did-multiplegt-stat
import pandas as pd
from did_multiplegt_stat import DIDMultiplegtStat
data_url = (
"https://raw.githubusercontent.com/Credible-Answers/"
"py_did_multiplegt_stat/main/tests/data/gazoline_did_multiplegt_stat.dta"
)
df = pd.read_stata(data_url)
model = DIDMultiplegtStat(
estimator=["as", "was"],
order=1,
placebo=3,
noextrapolation=True, # Stata: noextra
as_vs_was=True,
asinstata=False, # scikit-learn LinearRegression and LogisticRegression
)
model.fit(df, Y="lngpinc", ID="id", Time="year", D="tau")
model.summary()
model.plot()
Reproducing Stata's OLS and logit results
Set asinstata=True to replace the default scikit-learn nuisance regressions
with the Stata-faithful implementations: statsmodels OLS and the package's
Newton-Raphson logit matching Stata's logit, asis behavior. The following
cell runs both examples with the Stata-faithful backend:
# Uncomment this line when running the example in a Jupyter notebook:
# %pip install pandas statsmodels scikit-learn did-multiplegt-stat
import pandas as pd
from did_multiplegt_stat import DIDMultiplegtStat
data_url = (
"https://raw.githubusercontent.com/Credible-Answers/"
"py_did_multiplegt_stat/main/tests/data/gazoline_did_multiplegt_stat.dta"
)
df = pd.read_stata(data_url)
# Stata-faithful version of Example 1
stata_model_1 = DIDMultiplegtStat(
estimator=["as", "was"],
order=1,
placebo=3,
as_vs_was=True,
asinstata=True,
)
stata_model_1.fit(df, Y="lngca", ID="id", Time="year", D="tau")
stata_model_1.summary()
stata_model_1.plot()
# Stata-faithful version of Example 2
stata_model_2 = DIDMultiplegtStat(
estimator=["as", "was"],
order=1,
placebo=3,
noextrapolation=True,
as_vs_was=True,
asinstata=True,
)
stata_model_2.fit(df, Y="lngpinc", ID="id", Time="year", D="tau")
stata_model_2.summary()
stata_model_2.plot()
Using machine learning to estimate the nuisance functions
DIDMultiplegtStat accepts custom scikit-learn-style estimators for its
nuisance functions. The examples below use a random forest regressor for the
outcome-change model and a random forest classifier for the stayer model.
Supplying these models overrides the built-in OLS and logit regressions,
regardless of the value of asinstata:
# Uncomment this line when running the example in a Jupyter notebook:
# %pip install pandas statsmodels scikit-learn did-multiplegt-stat
import pandas as pd
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from did_multiplegt_stat import DIDMultiplegtStat
data_url = (
"https://raw.githubusercontent.com/Credible-Answers/"
"py_did_multiplegt_stat/main/tests/data/gazoline_did_multiplegt_stat.dta"
)
df = pd.read_stata(data_url)
# Random-forest version of Example 1
ml_model_1 = DIDMultiplegtStat(
estimator=["as", "was"],
order=1,
placebo=3,
as_vs_was=True,
model_deltay=RandomForestRegressor(
n_estimators=100,
random_state=42,
),
model_stayer=RandomForestClassifier(
n_estimators=100,
random_state=42,
),
)
ml_model_1.fit(df, Y="lngca", ID="id", Time="year", D="tau")
ml_model_1.summary()
ml_model_1.plot()
# Random-forest version of Example 2
ml_model_2 = DIDMultiplegtStat(
estimator=["as", "was"],
order=1,
placebo=3,
noextrapolation=True,
as_vs_was=True,
model_deltay=RandomForestRegressor(
n_estimators=100,
random_state=42,
),
model_stayer=RandomForestClassifier(
n_estimators=100,
random_state=42,
),
)
ml_model_2.fit(df, Y="lngpinc", ID="id", Time="year", D="tau")
ml_model_2.summary()
ml_model_2.plot()
Any compatible estimators may be supplied: model_deltay must implement
fit and predict, while model_stayer must implement fit and
predict_proba.
Backends: asinstata
Two regression backends are bundled:
| Backend | When to use | Activate |
|---|---|---|
| scikit-learn (default) | Default behaviour. Faster, modern numerical stack. | asinstata=False |
| Stata-faithful | Need byte-for-byte parity with the Stata ado-file. | asinstata=True |
Stata parity uses statsmodels OLS + a from-scratch Newton-Raphson logit that
matches Stata's logit, asis defaults; results agree to ~1e-7 relative error.
Main options
The Python API follows the terminology used in the paper and Stata package:
estimator(as/was/iv-was)order(scalar, 4-tuple, or 8-tuple for IV)placebo(N)(multi-period placebos)exact_match,noextrapolationswitchers(up/down)as_vs_wasby,by_fd,by_baselinecontrols,weight,clusterother_treatmentscross_fitting,trimming,on_placebo_samplebootstrap+seedtwfe(withsame_sample,full_sample,percentile)cross_validation(k-fold CV for polynomial order)
The package uses doubly robust estimation by default. When exact_match=True,
it uses regression adjustment internally; users do not select RA or PS as a
separate estimation method.
See the full Python documentation for the help-file style reference.
Citation
If you use this software in academic work, please cite the underlying paper:
de Chaisemartin, C., D'Haultfœuille, X., Pasquier, F., Sow, D., Vazquez-Bare, G. (2024). Difference-in-Differences for Continuous Treatments and Instruments with Stayers. arXiv:2201.06898.
A CITATION.cff is bundled for tooling integration.
License
GPL-3.0-or-later — see the license.
Authors
Originally authored by the team behind the Stata package:
- Clément de Chaisemartin (Sciences Po)
- Diego Ciccia (Sciences Po)
- Xavier D'Haultfœuille (CREST-ENSAE)
- Felix Knau (Sciences Po)
- Felix Pasquier (CREST-ENSAE)
- Doulo Sow (Sciences Po)
- Gonzalo Vazquez-Bare (UCSB)
Python port: Anzony Quispe.
Contact: chaisemartin.packages@gmail.com
Metadata
Release files for did-multiplegt-stat 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| did_multiplegt_stat-0.1.2.tar.gz | 822.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| did_multiplegt_stat-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 897.3 kB
Release files / did_multiplegt_stat-0.1.2.tar.gz
| Download URL | did_multiplegt_stat-0.1.2.tar.gz |
|---|---|
| Size | 822.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f48149e710340f6e7afa565119607d0fb981c1923e37cb813415cc0db7f31176
|
|
BLAKE2b-256 checksum How to use checksums |
1c15d423dff0b5400448b0822e1e70be1bbb2c70399066aa3a0c72a43ebd91c5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logRelease files / did_multiplegt_stat-0.1.2-py3-none-any.whl
| Download URL | did_multiplegt_stat-0.1.2-py3-none-any.whl |
|---|---|
| Size | 74.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6590a8f7974cb349bdd9a65ada5dd4bd125e7ae4efc1dff1acac7126ad8e9e28
|
|
BLAKE2b-256 checksum How to use checksums |
bbbab3d595b101aedc76d88808e559b067d5e7388d25f28d642378e6b2bf7e96
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency log