Datasets#
Built-in datasets from published studies for examples, tutorials, and testing.
Canonical data are downloaded from public sources and verified against a pinned SHA-256
on every download and cache read, then cached locally at
~/.cache/diff_diff/datasets/. Pass force_download=True to any loader to refresh
the cache. load_card_krueger(), load_castle_doctrine() and load_mpdta() pin an
immutable upstream commit, so the URL itself cannot change under them;
load_prop99() and load_walmart() read a mutable SSC path over plain HTTP, where
the checksum alone establishes integrity.
Every loader reports its provenance through df.attrs["source"]. A fresh download that
fails verification - transport error, size limit, or checksum mismatch - falls back to a
checksum-valid cache entry when one exists, so canonical data is never downgraded to
generated data while verified bytes are on disk. A checksum mismatch additionally emits a
UserWarning naming the integrity failure, since it means the upstream file no longer
matches the pin. When no verified copy is available at all, the loader emits one
UserWarning containing SYNTHETIC and returns a generated fallback frame marked
df.attrs["source"] == "synthetic_fallback". For
load_card_krueger(), load_castle_doctrine() and load_mpdta() that fallback
also covers parse and schema-validation failures; load_prop99() and load_walmart()
run their validation outside the fallback boundary and raise instead. Numbers computed on
a fallback frame are not replications of the published study. load_divorce_laws() has
no verified source configured and returns synthetic data on every call.
Dataset Loaders#
load_card_krueger#
Card & Krueger (1994) minimum wage study. Classic 2x2 DiD comparing fast-food employment in New Jersey (treated) and Pennsylvania (control) around NJ’s 1992 minimum wage increase.
- diff_diff.load_card_krueger(force_download=False)[source]#
Load the Card & Krueger (1994) minimum wage dataset.
This classic dataset examines the effect of New Jersey’s 1992 minimum wage increase on employment in fast-food restaurants, using Pennsylvania as a control group.
The study is a canonical example of the Difference-in-Differences method.
- Parameters:
force_download (bool, default=False) – If True, re-download the dataset even if cached.
- Returns:
Dataset with columns: - store_id : int - Unique store identifier - state : str - ‘NJ’ (New Jersey, treated) or ‘PA’ (Pennsylvania, control) - chain : str - Fast food chain (‘bk’, ‘kfc’, ‘roys’, ‘wendys’) - emp_pre : float - Full-time equivalent employment before (Feb 1992) - emp_post : float - Full-time equivalent employment after (Nov 1992) - wage_pre : float - Starting wage before - wage_post : float - Starting wage after - treated : int - 1 if NJ, 0 if PA - emp_change : float - Change in employment (emp_post - emp_pre)
- Return type:
pd.DataFrame
Notes
The minimum wage in New Jersey increased from $4.25 to $5.05 on April 1, 1992. Pennsylvania’s minimum wage remained at $4.25.
Original finding: No significant negative effect of minimum wage increase on employment (ATT ≈ +2.8 FTE employees).
The canonical survey is incomplete: 12 stores lack
emp_pre, 14 lackemp_post, andwage_pre/wage_postare missing for 20 and 21 stores. Drop the missing outcome rows before fitting, as the example below does; estimators reject missing outcomes rather than dropping them silently. The synthetic fallback frame is complete, so code that skips this step will work offline and fail once the canonical source is reachable.The canonical data are checksum-verified and returned with
df.attrs["source"] == "card_krueger_public_data". A download failure falls back to a checksum-valid cache entry when one exists, returning that canonical data; a pin mismatch additionally warns that the upstream file has changed. Only when neither a verified cache entry nor a verified fresh download is available - or when the source fails to parse or validate - does the loader emit oneUserWarningcontainingSYNTHETICand returndf.attrs["source"] == "synthetic_fallback".References
Card, D., & Krueger, A. B. (1994). Minimum Wages and Employment: A Case Study of the Fast-Food Industry in New Jersey and Pennsylvania. American Economic Review, 84(4), 772-793.
Examples
>>> from diff_diff.datasets import load_card_krueger >>> from diff_diff import DifferenceInDifferences >>> >>> # Load and prepare data >>> ck = load_card_krueger() >>> ck_long = ck.melt( ... id_vars=['store_id', 'state', 'treated'], ... value_vars=['emp_pre', 'emp_post'], ... var_name='period', value_name='employment' ... ) >>> ck_long['post'] = (ck_long['period'] == 'emp_post').astype(int) >>> >>> # 26 store-waves have no employment reading in the source survey >>> ck_long = ck_long.dropna(subset=['employment']) >>> >>> # Estimate DiD >>> did = DifferenceInDifferences() >>> results = did.fit(ck_long, outcome='employment', treatment='treated', post='post')
Example#
from diff_diff.datasets import load_card_krueger
from diff_diff import DifferenceInDifferences
ck = load_card_krueger()
# Reshape to long format for DiD estimation
ck_long = ck.melt(
id_vars=['store_id', 'state', 'treated'],
value_vars=['emp_pre', 'emp_post'],
var_name='period', value_name='employment'
)
ck_long['post'] = (ck_long['period'] == 'emp_post').astype(int)
# 26 store-waves have no employment reading in the source survey; estimators
# reject missing outcomes rather than dropping them silently
ck_long = ck_long.dropna(subset=['employment'])
did = DifferenceInDifferences()
results = did.fit(ck_long, outcome='employment', treatment='treated', post='post')
load_castle_doctrine#
Castle doctrine (Stand Your Ground) gun law study. Staggered adoption of self-defense law expansions across U.S. states (2000–2010), suitable for Callaway–Sant’Anna or Sun–Abraham estimation.
- diff_diff.load_castle_doctrine(force_download=False)[source]#
Load Castle Doctrine / Stand Your Ground laws dataset.
This dataset tracks the staggered adoption of Castle Doctrine (Stand Your Ground) laws across U.S. states, which expanded self-defense rights. It’s commonly used to demonstrate heterogeneous treatment timing methods like Callaway-Sant’Anna or Sun-Abraham.
- Parameters:
force_download (bool, default=False) – If True, re-download the dataset even if cached.
- Returns:
Panel dataset with columns: - state : str - State abbreviation - year : int - Year (2000-2010) - first_treat : int - Year of law adoption (0 = never adopted) - homicide_rate : float - Homicides per 100,000 population - population : int - State population - income : float - State median income - treated : int - 1 if law in effect, 0 otherwise - treatment_exposure : float - Fraction of the year the law was in effect - cohort : int - Alias for first_treat
- Return type:
pd.DataFrame
Notes
Castle Doctrine laws remove the duty to retreat before using deadly force in self-defense. States adopted these laws at different times between 2005 and 2009, creating a staggered treatment design.
The canonical data are checksum-verified and returned with
df.attrs["source"] == "cheng_hoekstra_castle_data". A download failure falls back to a checksum-valid cache entry when one exists, returning that canonical data; a pin mismatch additionally warns that the upstream file has changed. Only when neither a verified cache entry nor a verified fresh download is available - or when the source fails to parse or validate - does the loader emit oneUserWarningcontainingSYNTHETICand mark the returned frame as"synthetic_fallback".treatment_exposureis fractional only on canonical frames; synthetic fallback frames set it to a binary 0/1 copy oftreatedand therefore carry no partial-year information.Replicating Cheng-Hoekstra (2013) requires the paper’s regressor and outcome: regress
log(homicide_rate)ontreatment_exposure(theirCDL_it, the proportion of the year the law was in effect), not the binarytreated. See “Castle Doctrine treatment coding” indocs/methodology/REGISTRY.md.References
Cheng, C., & Hoekstra, M. (2013). Does Strengthening Self-Defense Law Deter Crime or Escalate Violence? Evidence from Expansions to Castle Doctrine. Journal of Human Resources, 48(3), 821-854.
Examples
>>> from diff_diff.datasets import load_castle_doctrine >>> from diff_diff import CallawaySantAnna >>> >>> castle = load_castle_doctrine() >>> cs = CallawaySantAnna(control_group="never_treated") >>> results = cs.fit( ... castle, ... outcome="homicide_rate", ... unit="state", ... time="year", ... first_treat="first_treat" ... )
Example#
from diff_diff.datasets import load_castle_doctrine
from diff_diff import CallawaySantAnna
castle = load_castle_doctrine()
cs = CallawaySantAnna(control_group="never_treated")
results = cs.fit(
castle,
outcome="homicide_rate",
unit="state",
time="year",
first_treat="first_treat"
)
load_divorce_laws#
Always-synthetic teaching panel of unilateral (no-fault) divorce law reforms, modelled on the staggered adoption studied by Stevenson & Wolfers (2006), with outcomes for divorce rate, female labor force participation, and female suicide rate. No verified source is configured for this loader, so it warns and returns generated data on every call and is not suitable for replication.
- diff_diff.load_divorce_laws(force_download=False)[source]#
Load the synthetic-only unilateral divorce-laws dataset.
This dataset tracks the staggered adoption of unilateral (no-fault) divorce laws across U.S. states. It’s a classic example for studying staggered DiD methods and was used in Stevenson & Wolfers (2006).
- Parameters:
force_download (bool, default=False) – Retained for API compatibility. No verified source currently satisfies the loader’s composite schema.
- Returns:
Panel dataset with columns: - state : str - State abbreviation - year : int - Year - first_treat : int - Year unilateral divorce became available (0 = never) - divorce_rate : float - Divorces per 1,000 population - female_lfp : float - Female labor force participation rate - suicide_rate : float - Female suicide rate - treated : int - 1 if law in effect, 0 otherwise - cohort : int - Alias for first_treat
- Return type:
pd.DataFrame
Notes
Unilateral divorce laws allow one spouse to obtain a divorce without the other’s consent. States adopted these laws at different times, primarily between 1969 and 1985.
No verified source currently reproduces all documented columns without deriving new variables or changing pre-panel treatment semantics. This loader therefore emits one
UserWarningcontainingSYNTHETICand returnsdf.attrs["source"] == "synthetic_fallback".References
Stevenson, B., & Wolfers, J. (2006). Bargaining in the Shadow of the Law: Divorce Laws and Family Distress. Quarterly Journal of Economics, 121(1), 267-288.
Wolfers, J. (2006). Did Unilateral Divorce Laws Raise Divorce Rates? A Reconciliation and New Results. American Economic Review, 96(5), 1802-1820.
Examples
>>> from diff_diff.datasets import load_divorce_laws >>> from diff_diff import CallawaySantAnna, SunAbraham >>> >>> divorce = load_divorce_laws() >>> cs = CallawaySantAnna(control_group="never_treated") >>> results = cs.fit( ... divorce, ... outcome="divorce_rate", ... unit="state", ... time="year", ... first_treat="first_treat" ... )
Example#
from diff_diff.datasets import load_divorce_laws
from diff_diff import CallawaySantAnna
divorce = load_divorce_laws()
cs = CallawaySantAnna(control_group="never_treated")
results = cs.fit(
divorce,
outcome="divorce_rate",
unit="state",
time="year",
first_treat="first_treat"
)
load_mpdta#
County teen-employment panel (Callaway & Sant’Anna 2021): the canonical
2,500-row mpdta dataset distributed with the R did package, covering
staggered minimum wage increases (2003–2007).
- diff_diff.load_mpdta(force_download=False)[source]#
Load the Minimum Wage Panel Dataset for DiD Analysis (mpdta).
This example dataset from the R did package contains county-level teen employment data under staggered minimum wage increases. It is commonly used to teach the Callaway-Sant’Anna estimator.
- Parameters:
force_download (bool, default=False) – If True, re-download the dataset even if cached.
- Returns:
Panel dataset with columns: - countyreal : int - County identifier - year : int - Year (2003-2007) - lpop : float - Log population - lemp : float - Log employment (outcome) - first_treat : int - Year of minimum wage increase (0 = never) - treat : int - 1 if ever treated, 0 otherwise
- Return type:
pd.DataFrame
Notes
This dataset is included in the R did package and is commonly used in tutorials demonstrating the Callaway-Sant’Anna estimator.
The canonical data are checksum-verified and returned with
df.attrs["source"] == "callaway_santanna_mpdta". A download failure falls back to a checksum-valid cache entry when one exists, returning that canonical data; a pin mismatch additionally warns that the upstream file has changed. Only when neither a verified cache entry nor a verified fresh download is available - or when the source fails to parse or validate - does the loader emit oneUserWarningcontainingSYNTHETICand mark the returned frame as"synthetic_fallback".References
Callaway, B., & Sant’Anna, P. H. (2021). Difference-in-differences with multiple time periods. Journal of Econometrics, 225(2), 200-230.
Examples
>>> from diff_diff.datasets import load_mpdta >>> from diff_diff import CallawaySantAnna >>> >>> mpdta = load_mpdta() >>> cs = CallawaySantAnna() >>> results = cs.fit( ... mpdta, ... outcome="lemp", ... unit="countyreal", ... time="year", ... first_treat="first_treat" ... )
Example#
from diff_diff.datasets import load_mpdta
from diff_diff import CallawaySantAnna
mpdta = load_mpdta()
cs = CallawaySantAnna()
results = cs.fit(
mpdta,
outcome="lemp",
unit="countyreal",
time="year",
first_treat="first_treat"
)
load_prop99#
California Proposition 99 tobacco program study (Lee–Wooldridge cohort format of the Abadie–Diamond–Hainmueller 2010 data). Log per capita cigarette sales for 39 states (1970–2000) with a single treated unit (California, treated from 1989) – the canonical small-sample DiD and synthetic control setting.
- diff_diff.load_prop99(force_download=False)[source]#
Load the California Proposition 99 smoking dataset (Lee-Wooldridge format).
This dataset tracks per capita cigarette sales across 39 U.S. states (California plus 38 never-treated donor states) from 1970 to 2000. California passed Proposition 99, a large tobacco tax and control program, effective in 1989. With a single treated unit, it is the canonical setting for small-sample DiD inference and synthetic control comparisons.
- Parameters:
force_download (bool, default=False) – If True, re-download the dataset even if cached.
- Returns:
Panel dataset with columns: - state : str - State name - year : int - Year (1970-2000) - first_year : int - Treatment start year (1989 for California, 0 = never) - lcigsale : float - Log per capita cigarette sales (packs) - treated : int - 1 if treatment in effect, 0 otherwise - cohort : int - Alias for first_year
- Return type:
pd.DataFrame
Notes
This is the cohort-format version of the Abadie, Diamond & Hainmueller (2010) California tobacco data distributed (MIT license) with the authors’ Stata
lwdidpackage by Hur, Lee and Wooldridge. The donor pool excludes states with their own tobacco programs, leaving exactly one treated state and 38 controls.Downloads are verified against a pinned SHA-256 and validated against the source invariants (39 states, 1970-2000, single 1989 cohort). If the real data cannot be obtained, a SYNTHETIC same-schema fallback is returned with a
UserWarning; checkdf.attrs["source"]("lwdid_ssc_ancillary"= real data,"synthetic_fallback"= synthetic - never use the fallback for replication).References
Lee, S. J., & Wooldridge, J. M. (2026). Simple Approaches to Inference with Difference-in-Differences Estimators with Small Cross-Sectional Sample Sizes. SSRN Working Paper No. 5325686.
Abadie, A., Diamond, A., & Hainmueller, J. (2010). Synthetic Control Methods for Comparative Case Studies: Estimating the Effect of California’s Tobacco Control Program. Journal of the American Statistical Association, 105(490), 493-505.
Examples
>>> from diff_diff.datasets import load_prop99 >>> from diff_diff import DifferenceInDifferences >>> >>> prop99 = load_prop99() >>> prop99["treated_state"] = (prop99["first_year"] > 0).astype(int) >>> prop99["post"] = (prop99["year"] >= 1989).astype(int) >>> >>> did = DifferenceInDifferences() >>> results = did.fit( ... prop99, outcome="lcigsale", treatment="treated_state", post="post" ... )
Example#
from diff_diff.datasets import load_prop99
from diff_diff import DifferenceInDifferences
prop99 = load_prop99()
prop99["treated_state"] = (prop99["first_year"] > 0).astype(int)
prop99["post"] = (prop99["year"] >= 1989).astype(int)
did = DifferenceInDifferences()
results = did.fit(
prop99, outcome="lcigsale", treatment="treated_state", post="post"
)
load_walmart#
Walmart entry county panel (Lee & Wooldridge 2025 sample, derived from County Business Patterns data as constructed by Brown & Butts). Log retail and wholesale employment for 1,277 counties (1977–1999) with staggered first store openings (1986–1999) and 391 never-treated counties.
- diff_diff.load_walmart(force_download=False)[source]#
Load the Walmart entry county panel (Lee-Wooldridge sample).
This dataset tracks log retail and wholesale employment for 1,277 U.S. counties from 1977 to 1999, with staggered first Walmart store openings between 1986 and 1999 and 391 counties never receiving a store. It is used to study the local labor-market effects of Walmart entry under staggered treatment adoption.
- Parameters:
force_download (bool, default=False) – If True, re-download the dataset even if cached.
- Returns:
Panel dataset with columns: - cid : int - County identifier - year : int - Year (1977-1999) - first_year : int - Year of first Walmart opening (0 = never) - log_retail_emp : float - Log county retail employment (outcome) - log_wholesale_emp : float - Log county wholesale employment - x1 : float - County poverty rate - x2 : float - Share with high-school education - x3 : float - Manufacturing employment share - treated : int - 1 if a Walmart has opened, 0 otherwise - cohort : int - Alias for first_year
- Return type:
pd.DataFrame
Notes
The panel derives from County Business Patterns data as constructed by Brown & Butts, and is distributed (MIT license) with the authors’ Stata
lwdidpackage by Hur, Lee and Wooldridge. The covariate labels follow the Lee & Wooldridge application.Downloads are verified against a pinned SHA-256 and validated against the source invariants (1,277 counties, 1977-1999, cohorts 1986-1999, 391 never-treated). If the real data cannot be obtained, a SYNTHETIC same-schema fallback (200 counties) is returned with a
UserWarning; checkdf.attrs["source"]("lwdid_ssc_ancillary"= real data,"synthetic_fallback"= synthetic - never use the fallback for replication).References
Lee, S. J., & Wooldridge, J. M. (2025). A Simple Transformation Approach to Difference-in-Differences Estimation for Panel Data. SSRN Working Paper No. 4516518.
Brown, N., & Butts, K. (2025). Dynamic Treatment Effect Estimation with Interactive Fixed Effects and Short Panels. Journal of Econometrics.
Examples
>>> from diff_diff.datasets import load_walmart >>> from diff_diff import CallawaySantAnna >>> >>> walmart = load_walmart() >>> cs = CallawaySantAnna(control_group="never_treated") >>> results = cs.fit( ... walmart, ... outcome="log_retail_emp", ... unit="cid", ... time="year", ... first_treat="first_year", ... )
Example#
from diff_diff.datasets import load_walmart
from diff_diff import CallawaySantAnna
walmart = load_walmart()
cs = CallawaySantAnna(control_group="never_treated")
results = cs.fit(
walmart,
outcome="log_retail_emp",
unit="cid",
time="year",
first_treat="first_year"
)
Utility Functions#
load_dataset#
Generic loader that fetches a dataset by name.
- diff_diff.load_dataset(name, force_download=False)[source]#
Load a dataset by name.
- Parameters:
- Returns:
The requested dataset.
- Return type:
pd.DataFrame
- Raises:
ValueError – If the dataset name is not recognized.
Examples
>>> from diff_diff.datasets import load_dataset, list_datasets >>> print(list_datasets()) >>> df = load_dataset("card_krueger")
list_datasets#
List all available datasets with descriptions.
clear_cache#
Remove all cached dataset files from ~/.cache/diff_diff/datasets/.
- diff_diff.clear_cache()[source]#
Clear the local dataset cache.
Also removes any
.<name>.<ext>.<suffix>scratch files left behind by an atomic cache write that was interrupted between creating the temporary file and replacing the cache entry (a hard kill, for instance). Those are hidden and do not match the plain*.csv/*.dtapatterns, so without this they would accumulate and survive the documented remedy.- Return type:
None
Listing and Loading Datasets#
from diff_diff.datasets import list_datasets, load_dataset
# See what's available
for name, description in list_datasets().items():
print(f"{name}: {description}")
# Load by name
df = load_dataset("card_krueger")
print(df.shape)
print(df.columns.tolist())