Skip to main content

PyPI version Downloads Tests Docs Last commit GitHub stars GitHub issues License

twowayfeweights (Python)

Estimates the weights and the sensitivity measures attached to two-way fixed effects (TWFE) and first-difference regressions, following de Chaisemartin & D'Haultfoeuille (2020). This is a Python port of the Stata and R command twowayfeweights. It reproduces the Stata results: see Parity with Stata.

Documentation: https://credible-answers.github.io/py_twowayfeweights/

A TWFE coefficient estimates a weighted sum of the treatment effects in each (g,t) cell. Some of those weights can be negative. When they are, the coefficient can have the opposite sign of every individual treatment effect. twowayfeweights computes these weights, reports how many are negative and how much they sum to, and measures how robust the coefficient is to heterogeneous effects.

Installation

pip install twowayfeweights

Requires Python ≥ 3.9. The only dependencies are numpy, pandas and scipy.

Quick start

from twowayfeweights import twowayfeweights, load_wagepan

df = load_wagepan()
res = twowayfeweights(df, Y="lwage", G="nr", T="year", D="union",
                      type="feTR", summary_measures=True, test_random_weights="educ")
print(res)
Under the common trends assumption,
the TWFE coefficient beta, equal to 0.1066, estimates a weighted sum of 967 ATTs.
820 ATTs receive a positive weight, and 147 receive a negative weight.
1016 (g,t) cells receive the treatment, but the ATTs of 49 cells receive a weight equal to zero.
------------------------------------------------
Treat. var: union       # ATTs      Σ weights
------------------------------------------------
Positive weights        820         1.0105
Negative weights        147         -0.0105
------------------------------------------------
Total                   967         1.0000
------------------------------------------------

Summary Measures:
TWFE coefficient (β_fe) = 0.1066
min σ(Δ) compatible with β_fe and Δ_TR = 0: 0.0969
min σ(Δ) compatible with treatment effect of opposite sign than β_fe in all (g,t) cells: 3.1759
Reference: Corollary 1, de Chaisemartin, C and D'Haultfoeuille, X (2020a)

Regression of variables possibly correlated with the treatment effect on the weights

B[1,4]
              Coef           SE       t-stat  Correlation
educ    -.13445527    .07136021   -1.8841771   -.11825874


The development of this package was funded by the European Union (ERC, REALLYCREDIBLE,GA N°101043899).

Usage

twowayfeweights(data, Y, G, T, D, type="feTR", D0=None, summary_measures=False,
                controls=None, weights=None, other_treatments=None,
                test_random_weights=None, path=None)
argument Stata equivalent description
Y, G, T, D varlist outcome, group, time period, treatment
type type() "feTR", "feS", "fdTR" or "fdS" (see below)
D0 5th variable treatment level (not differenced); required with fdTR
controls controls() control variables
weights weight() analytic weights
other_treatments other_treatments() other treatments in the regression (feTR only)
test_random_weights test_random_weights() variables regressed on the weights
summary_measures summary_measures print the sensitivity measures (they are always computed)
path path() save the (g,t) weights to .csv, .dta or .parquet

data must be a pandas DataFrame (convert other data frames first, e.g. df.to_pandas() for polars). Group and time identifiers can be numeric, strings or categoricals.

Estimation types

  • feTR: regression with group and period fixed effects, under the common trends assumption.
  • feS: the same regression, also assuming that each group's treatment effect does not change over time.
  • fdTR: first-difference regression under common trends. Y and D are the first differences, and D0 is the treatment level.
  • fdS: first-difference regression, also assuming stable treatment effects.

The result is a TwoWayFEWeightsResult. Its attributes follow the R package:

attribute content
beta the TWFE / FD coefficient
nr_plus, nr_minus, nr_weights number of positive, negative, and nonzero weights
sum_plus, sum_minus sum of positive and of negative weights
tot_cells number of (g,t) cells that receive the treatment
sensibility, sensibility2 the two sensitivity measures (Stata e(lb_se_te), e(lb_se_te2))
mat test_random_weights table (Coef, SE, t-stat, Correlation)
other_treatments one OtherTreatmentResult per other treatment
weights DataFrame of the weight in every (g,t) cell (what Stata's path() saves)
M Stata's e(M) matrix

print(res) shows the Stata-style table. res.to_dict() returns the scalars. print_twowayfeweights(res) and dictionary access such as res["beta"] are kept for backward compatibility.

Example: first-difference regression with many controls

This reproduces chapter 5 of the DiD book, using data from Gentzkow, Shapiro & Sinkinson (2011):

styr = [c for c in df.columns if c.startswith("styr") and c != "styr"]   # 683 state-year dummies
res = twowayfeweights(df, "changeprestout", "cnty90", "year", "changedailies",
                      type="fdTR", D0="numdailies", controls=styr, test_random_weights="year")
# beta = 0.0026: 5371 positive weights (sum 2.4271), 4505 negative weights (sum -1.4271)

Parity with Stata

The test suite (tests/) checks 36 specifications (17 of them feS/fdS) against the Stata command from SSC. The specifications cover all four types, with and without controls, weights, other treatments and random-weight tests. They use the wagepan data, the Gentzkow et al. data with 683 controls, and a simulated unbalanced panel with gaps, several observations per cell and missing values. Stata's results are stored in tests/fixtures/stata/results.json (the command, every number it returns and the text it prints) and are regenerated by tests/reference/build_reference.py. The checks:

  • the numbers of positive, negative and treated-cell weights match exactly;
  • beta matches to 1e-7 relative;
  • the weight sums and the sensitivity measures match to 1e-6 relative, the random-weights regression (Coef, SE, t-stat, Correlation) to 5e-6, and every (g,t) weight to 2e-6 of the largest weight.

python benchmarks/compare_r.py compares the results and run times with the R package (TwoWayFEWeights). Where R runs and agrees with Stata, Python matches R to 1e-8 or better.

The small remaining differences come from Stata. It stores intermediate variables (gen, predict, egen) as 4-byte floats, while this package computes everything in double precision.

One known difference: for type="feS" with several observations per (g,t) cell, Stata builds the running average of residuals one observation at a time, and then keeps an arbitrary observation of each cell. Its output then depends on the row order, and it changes from run to run because Stata breaks sort ties randomly. This package uses the formula from the paper: all observations in the periods t' ≥ t. With one observation per cell, which is the usual case, the results are identical.

Performance

Regressions run on (g,t) cells with summed weights, not on individual observations. The time dummies are never built as a dense matrix: after the group effects are absorbed, their normal equations come from a sparse group × period weight table. The system is solved with a scaled pseudo-inverse, which handles collinearity, plus iterative refinement. Everything else is vectorized numpy code.

Seconds per call on the same data, on one Windows laptop (Stata 18 MP, R 4.5 with TwoWayFEWeights 2.1.0):

data type Stata R Python
200k rows (10k groups × 20 periods, 5 controls, weights) feTR 3.6 16.4 0.17
feS 1.8 13.4 0.20
fdTR 3.1 19.5 0.14
fdS 0.9 10.5 0.13
1M rows (50k groups × 20 periods, 5 controls, weights) feTR 12.9 77.8 1.05
feS 9.6 56.6 1.16
fdTR 12.6 82.9 1.17
fdS 7.1 50.2 0.76
Gentzkow et al. (16,872 rows, 683 controls) feTR 27.4 1245 0.58
feS 29.5 – 0.59
fdTR 24.0 – 0.54
fdS 24.0 – 0.55

python benchmarks/bench.py times panels of up to 3M rows.

References

  • de Chaisemartin, C. and D'Haultfoeuille, X. (2020). Two-Way Fixed Effects Estimators with Heterogeneous Treatment Effects. American Economic Review, 110(9), 2964–2996.
  • de Chaisemartin, C. and D'Haultfoeuille, X. (2023). Two-way fixed effects and differences-in-differences estimators with several treatments. Journal of Econometrics.

Stata: ssc install twowayfeweights. R: install.packages("TwoWayFEWeights").

The development of the original package was funded by the European Union (ERC, REALLYCREDIBLE, GA N°101043899).

Metadata

Release files for twowayfeweights 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for twowayfeweights 0.1.0
File Size Uploaded
twowayfeweights-0.1.0.tar.gz 1.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for twowayfeweights 0.1.0
File Interpreter ABI Platform
twowayfeweights-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.2 MB

Release files / twowayfeweights-0.1.0.tar.gz

Download URL twowayfeweights-0.1.0.tar.gz
Size 1.1 MB
Tags Source
SHA-256 checksum
How to use checksums
73105cf58f4da1e857f472733dcc7b115da98ee27d44913db1c55b42b26ec7c9
BLAKE2b-256 checksum
How to use checksums
013d72badaef67edf6a3d94a709d1dd4af970ad38226cb29086513a7dc1c3db7
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 Oct 6, 2026.

Transparency log

Release files / twowayfeweights-0.1.0-py3-none-any.whl

Download URL twowayfeweights-0.1.0-py3-none-any.whl
Size 149.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
97573c388fc2b4eb9131a2c1a4aa4e94908b19ead06b0639b931ca6bcbd9571f
BLAKE2b-256 checksum
How to use checksums
fd3b1cfa0c55511883dbd318f4b4cce46184c1d903ee9972b04b5ae0b695d673
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 Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page