Modern difference-in-differences estimators.
Project description
moderndid is a unified Python implementation of modern difference-in-differences (DiD) methodologies, bringing together the fragmented landscape of DiD estimators into a single, coherent framework. This package consolidates methods from leading econometric research and various R packages into one comprehensive Python library with a consistent API.
[!WARNING] This package is currently in active development with core estimators and some sensitivity analysis implemented. The API is subject to change.
Installation
uv pip install moderndid
Or install from source:
uv pip install git+https://github.com/jordandeklerk/moderndid.git
Features
- Multiple DiD estimators — Staggered adoption, doubly robust, continuous treatment, triple difference, and sensitivity analysis
- Fast computation — Polars for data wrangling, NumPy vectorization, and Numba JIT compilation for performance-critical paths
- Native plots — Built on plotnine with full customization support
- Robust inference — Analytical standard errors, bootstrap (weighted and multiplier), and simultaneous confidence bands
- Documentation — https://moderndid.readthedocs.io/en/latest/index.html
Consistent API
All estimators share a unified interface, making it easy to switch between methods:
# Staggered DiD
result = did.att_gt(data, yname="y", tname="t", idname="id", gname="g", ...)
# Triple DiD
result = did.ddd(data, yname="y", tname="t", idname="id", gname="g", pname="p", ...)
# Continuous DiD
result = did.cont_did(data, yname="y", tname="t", idname="id", gname="g", dname="dose", ...)
# Doubly robust 2-period DiD
result = did.drdid(data, yname="y", tname="t", idname="id", treatname="treat", ...)
Example Datasets
Several classic datasets from the DiD literature are included for learning and experimentation:
did.load_mpdta() # County teen employment (staggered adoption)
did.load_nsw() # NSW job training program (2-period panel)
did.load_ehec() # Medicaid expansion (sensitivity analysis)
did.load_engel() # Household expenditure (continuous treatment)
Quick Start
This example uses county-level teen employment data to estimate the effect of minimum wage increases. States adopted higher minimum wages at different times (2004, 2006, or 2007), making this a staggered adoption design.
import moderndid as did
# County teen employment data
data = did.load_mpdta()
# Estimate group-time average treatment effects
result = did.att_gt(
data=data,
yname="lemp",
tname="year",
idname="countyreal",
gname="first.treat",
est_method="dr",
)
print(result)
The output shows treatment effects for each group (defined by when they were first treated) at each time period:
Reference: Callaway and Sant'Anna (2021)
Group-Time Average Treatment Effects:
Group Time ATT(g,t) Std. Error [95% Simult. Conf. Band]
2004 2004 -0.0105 0.0232 [ -0.0743, 0.0533]
2004 2005 -0.0704 0.0319 [ -0.1582, 0.0173]
2004 2006 -0.1373 0.0356 [ -0.2352, -0.0394] *
2004 2007 -0.1008 0.0331 [ -0.1918, -0.0098] *
2006 2004 0.0065 0.0241 [ -0.0597, 0.0727]
2006 2005 -0.0028 0.0202 [ -0.0582, 0.0527]
2006 2006 -0.0046 0.0179 [ -0.0538, 0.0446]
2006 2007 -0.0412 0.0211 [ -0.0992, 0.0167]
2007 2004 0.0305 0.0145 [ -0.0095, 0.0705]
2007 2005 -0.0027 0.0173 [ -0.0502, 0.0448]
2007 2006 -0.0311 0.0190 [ -0.0833, 0.0211]
2007 2007 -0.0261 0.0168 [ -0.0721, 0.0200]
---
Signif. codes: '*' confidence band does not cover 0
P-value for pre-test of parallel trends assumption: 0.1681
Control Group: Never Treated,
Anticipation Periods: 0
Estimation Method: Doubly Robust
These group-time effects can be aggregated into an event study to see how effects evolve relative to treatment:
event_study = did.aggte(result, type="dynamic")
print(event_study)
==============================================================================
Aggregate Treatment Effects (Event Study)
==============================================================================
Call:
aggte(MP, type='dynamic')
Overall summary of ATT's based on event-study/dynamic aggregation:
ATT Std. Error [95% Conf. Interval]
-0.0772 0.0214 [-0.1191, -0.0353] *
Dynamic Effects:
Event time Estimate Std. Error [95% Simult. Conf. Band]
-3 0.0305 0.0151 [-0.0084, 0.0694]
-2 -0.0006 0.0132 [-0.0346, 0.0335]
-1 -0.0245 0.0139 [-0.0602, 0.0113]
0 -0.0199 0.0120 [-0.0508, 0.0109]
1 -0.0510 0.0172 [-0.0951, -0.0068] *
2 -0.1373 0.0371 [-0.2326, -0.0419] *
3 -0.1008 0.0352 [-0.1912, -0.0104] *
------------------------------------------------------------------------------
Signif. codes: '*' confidence band does not cover 0
Control Group: Never Treated
Anticipation Periods: 0
Estimation Method: Doubly Robust
==============================================================================
We can also use built-in plotting functionality to plot the event study results:
did.plot_event_study(event_study)
Available Methods
Each core module includes a dedicated walkthrough covering methodology background, API usage, and guidance on interpreting results.
Core Implementations
| Module | Description | Reference |
|---|---|---|
moderndid.did |
Staggered DiD with group-time effects | Callaway & Sant'Anna (2021) |
moderndid.drdid |
Doubly robust 2-period estimators | Sant'Anna & Zhao (2020) |
moderndid.didhonest |
Sensitivity analysis for parallel trends | Rambachan & Roth (2023) |
moderndid.didcont |
Continuous/multi-valued treatments | Callaway et al. (2024) |
moderndid.didtriple |
Triple difference-in-differences | Ortiz-Villavicencio & Sant'Anna (2025) |
Planned Development
| Module | Description | Reference |
|---|---|---|
moderndid.didinter |
Intertemporal DiD with non-absorbing treatment | Chaisemartin & D'Haultfœuille (2024) |
moderndid.didml |
Machine learning approaches to DiD | Hatamyar et al. (2023) |
moderndid.drdidweak |
Robust to weak overlap | Ma et al. (2023) |
moderndid.didcomp |
Compositional changes in repeated cross-sections | Sant'Anna & Xu (2025) |
moderndid.didimpute |
Imputation-based estimators | Borusyak, Jaravel, & Spiess (2024) |
moderndid.didbacon |
Goodman-Bacon decomposition | Goodman-Bacon (2019) |
moderndid.didlocal |
Local projections DiD | Dube et al. (2025) |
moderndid.did2s |
Two-stage DiD | Gardner (2021) |
moderndid.etwfe |
Extended two-way fixed effects | Wooldridge (2021), Wooldridge (2023) |
moderndid.functional |
Specification tests | Roth & Sant'Anna (2023) |
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
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 moderndid-0.0.3.tar.gz.
File metadata
- Download URL: moderndid-0.0.3.tar.gz
- Upload date:
- Size: 576.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fdc6b51d8b6a857445ec07e8e2d7c1ac9122dda46beb5469833979223eb59b65
|
|
| MD5 |
3f7018d344aa632edfeaaac6d1d08895
|
|
| BLAKE2b-256 |
902f7adfcc765fe4507419b824127b0405c3b1ee75d68ea845a58c4481c561c8
|
Provenance
The following attestation bundles were made for moderndid-0.0.3.tar.gz:
Publisher:
publish.yml on jordandeklerk/moderndid
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moderndid-0.0.3.tar.gz -
Subject digest:
fdc6b51d8b6a857445ec07e8e2d7c1ac9122dda46beb5469833979223eb59b65 - Sigstore transparency entry: 843880088
- Sigstore integration time:
-
Permalink:
jordandeklerk/moderndid@59fbab5dd64f0b1ccf89ddee7f483b1beda243d3 -
Branch / Tag:
refs/tags/v0.0.3 - Owner: https://github.com/jordandeklerk
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@59fbab5dd64f0b1ccf89ddee7f483b1beda243d3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file moderndid-0.0.3-py3-none-any.whl.
File metadata
- Download URL: moderndid-0.0.3-py3-none-any.whl
- Upload date:
- Size: 714.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4da8d8fcd98e9a61c3a15707f80cd64dc457e93e6329a5fe2cac3a6cac2555ad
|
|
| MD5 |
f0418a28fb8f95a00a704c4f78685fb2
|
|
| BLAKE2b-256 |
6e5e82564bf799e30ab771f183a553d5ddf564f4ce9a3f5aee2f5dece3cd9404
|
Provenance
The following attestation bundles were made for moderndid-0.0.3-py3-none-any.whl:
Publisher:
publish.yml on jordandeklerk/moderndid
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moderndid-0.0.3-py3-none-any.whl -
Subject digest:
4da8d8fcd98e9a61c3a15707f80cd64dc457e93e6329a5fe2cac3a6cac2555ad - Sigstore transparency entry: 843880089
- Sigstore integration time:
-
Permalink:
jordandeklerk/moderndid@59fbab5dd64f0b1ccf89ddee7f483b1beda243d3 -
Branch / Tag:
refs/tags/v0.0.3 - Owner: https://github.com/jordandeklerk
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@59fbab5dd64f0b1ccf89ddee7f483b1beda243d3 -
Trigger Event:
push
-
Statement type: