Skip to main content

Modern difference-in-differences estimators.

Project description

moderndid logo

A scalable, GPU-accelerated difference-in-differences library for Python.

Docs · API Reference · Tutorials · Changelog

License PyPI -Version Ruff Pixi Badge prek Code Coverage Build Status Documentation Last commit Commit activity PyPI Downloads PyPI Downloads Python version Citation

ModernDiD is a scalable, GPU-accelerated difference-in-differences library for Python. It consolidates modern DiD estimators from leading econometric research and various R and Stata packages into a single framework with a consistent API. Runs on a single machine, NVIDIA GPUs, and distributed Spark and Dask clusters.

Features

For detailed documentation, see ModernDiD Documentation.

Installation

uv pip install moderndid   # Core estimators (did, drdid, didinter, didtriple)

Some estimators and features require additional dependencies that are not installed by default. Extras are additive and build on the base install, so you always get the core estimators (att_gt(), drdid(), did_multiplegt(), ddd()) plus whatever extras you specify:

  • didcont - Continuous treatment DiD (cont_did())
  • didhonest - Sensitivity analysis (honest_did())
  • plots - Batteries-included plots
  • numba - Faster bootstrap inference
  • spark - Distributed estimation via PySpark
  • dask - Distributed estimation via Dask
  • gpu - GPU-accelerated estimation (requires CUDA)
uv pip install "moderndid[all]"             # All extras except gpu and spark
uv pip install "moderndid[didcont,plots]"   # Combine specific extras
uv pip install "moderndid[gpu,spark]"       # GPU + distributed

To install the latest development version directly from GitHub:

uv pip install "moderndid[all] @ git+https://github.com/jordandeklerk/moderndid.git"

See the Installation guide for troubleshooting and GPU-specific setup.

Quick Start

Using county-level panel data from Callaway and Sant'Anna (2021) to estimate the effect of minimum wage increases on teen employment:

import moderndid as did
from plotnine import element_text, labs, theme, theme_gray

data = did.load_mpdta()

# Group-time ATTs
result = did.att_gt(
    data=data,
    yname="lemp",
    tname="year",
    idname="countyreal",
    gname="first.treat",
    xformla="~1",
    est_method="dr",
    boot=True,
)

# Use grammar of graphics to customize plots
p = did.plot_gt(result, ncol=3)
p = (p
    + labs(
        x="Year",
        y="ATT (Log Employment)",
        title="Minimum Wage Effects on Teen Employment",
        subtitle="Group-time average treatment effects by treatment cohort",
    )
    + theme_gray()
    + theme(
        legend_position="bottom",
        strip_text=element_text(size=11, weight="bold"),
    )
)
p.save("att.png", dpi=200, width=8, height=5)
Group-time ATT estimates

The User Guide has tutorials for every estimator. See also the Plotting Guide.

Consistent API

All estimators use the same naming conventions for core arguments:

result = did.att_gt(data, yname="y", tname="t", idname="id", gname="g", ...)
result = did.ddd(data, yname="y", tname="t", idname="id", gname="g", pname="p", ...)
result = did.cont_did(data, yname="y", tname="t", idname="id", gname="g", dname="dose", ...)
result = did.drdid(data, yname="y", tname="t", idname="id", treatname="treat", ...)
result = did.did_multiplegt(data, yname="y", tname="t", idname="id", dname="treat", ...)

Publication Tables

Result objects plug into maketables. Pass them to ETable and estimates, SEs, CIs, and metadata are extracted automatically:

import maketables as mt

# Aggregate results from earlier into an event study
event_study = did.aggte(result, type="dynamic")

tab = mt.ETable(
    [event_study],
    coef_fmt="b:.3f* \\n (se:.3f)",
    keep=[r"^Event "],
    model_stats=["N", "se_type"],
    caption="Dynamic Treatment Effects",
)
tab.make("tex")  # or "html", "docx", "typst"

See the Publication Tables guide for MTable layouts and more examples.

Scaling Up

Pass a Spark or Dask DataFrame and estimation distributes automatically. See the Distributed guide.

from pyspark.sql import SparkSession
spark = SparkSession.builder.master("local[*]").getOrCreate()
result = did.att_gt(data=spark.read.parquet("panel.parquet"),
                    yname="y",
                    tname="t",
                    idname="id",
                    gname="g")

For GPUs, pass backend="cupy". See the GPU guide and benchmarks.

result = did.att_gt(data,
                    yname="lemp",
                    tname="year",
                    idname="countyreal",
                    gname="first.treat",
                    backend="cupy")

Example Datasets

Datasets from published studies and synthetic data generators for simulations:

did.load_mpdta()           # County teen employment
did.load_nsw()             # NSW job training program
did.load_ehec()            # Medicaid expansion
did.load_engel()           # Household expenditure
did.load_favara_imbs()     # Bank lending
did.load_cai2016()         # Crop insurance

did.gen_did_scalable()     # Staggered DiD panel
did.gen_cont_did_data()    # Continuous treatment DiD
did.gen_ddd_2periods()     # Two-period triple DiD
did.gen_ddd_mult_periods() # Staggered triple DiD
did.gen_ddd_scalable()     # Large-scale triple DiD

Planned Development

Acknowledgements

ModernDiD would not be possible without the researchers who developed the underlying econometric methods and implemented them in various R and Stata packages. See our Acknowledgements page for a full list of the software, packages, and papers that have influenced this project.

Citation

If you use ModernDiD in your research, please cite it as:

@software{moderndid,
  author  = {{The ModernDiD Authors}},
  title   = {{ModernDiD: Scalable, GPU-Accelerated Difference-in-Differences for Python}},
  year    = {2025},
  url     = {https://github.com/jordandeklerk/moderndid}
}

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

moderndid-0.2.0.tar.gz (1.4 MB view details)

Uploaded Source

Built Distribution

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

moderndid-0.2.0-py3-none-any.whl (1.6 MB view details)

Uploaded Python 3

File details

Details for the file moderndid-0.2.0.tar.gz.

File metadata

  • Download URL: moderndid-0.2.0.tar.gz
  • Upload date:
  • Size: 1.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for moderndid-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7dc4b89319aa3da619c98b19c5f592cec1ca2939748c481b240b5f43946e3a58
MD5 30450d53d45e0f7f46cdca10504d19d8
BLAKE2b-256 51d880415c3418dd00258444cf37dddf0fd4e27c077159dd077adef6a8562abc

See more details on using hashes here.

Provenance

The following attestation bundles were made for moderndid-0.2.0.tar.gz:

Publisher: publish.yml on jordandeklerk/moderndid

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file moderndid-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: moderndid-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for moderndid-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8a734e115194af3f6121edb3c4807f169b2645ecb7538ca943185750630408b3
MD5 d99fa598ae787e074d6cf94f74e7d7bc
BLAKE2b-256 6427207ac83406535ca6afed0ed8c628a91da636cf1aa1d7fce604f87135e364

See more details on using hashes here.

Provenance

The following attestation bundles were made for moderndid-0.2.0-py3-none-any.whl:

Publisher: publish.yml on jordandeklerk/moderndid

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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