Skip to main content

The scikit-learn of empirical economics.

Project description

open-econs

PyPI version Python versions CI License: MIT Downloads

Python econometrics with Stata/R parity.

open-econs brings familiar empirical economics methods from Stata and R into a unified Python workflow. Every estimator uses a consistent API, with numerical validation against established reference implementations.

  • 142+ Stata-parity tests verify coefficients and standard errors match reference implementations at machine precision.
  • One consistent interface across all estimators: .summary(), .tidy(), .vcov(), .predict(), .export(), .to_latex().
  • Immutable, named pandas outputs — no raw arrays crossing the public API.

Installation

pip install open-econs                           # core: OLS, Oaxaca, FE, IV, Logit, Probit
pip install open-econs[plot]                      # + matplotlib for .plot()
pip install open-econs[nls]                       # + sympy for nls() (nonlinear least squares)
pip install open-econs[dev,lint]                  # + development & linting tools
pip install git+https://github.com/qmanhbeo/open-econs.git    # latest dev

Requires Python ≥ 3.10.

Why open-econs?

Empirical researchers often use Python for data cleaning, Stata for estimation, R for specialized methods, and LaTeX or custom scripts for output — a fragmented workflow that hurts reproducibility.

open-econs keeps everything in one Python environment:

  • Familiar estimators — OLS, fixed effects, IV/2SLS, logit, probit, difference-in-differences, event studies, RDD, panel models, matching
  • Consistent API — the same .summary() / .tidy() / .vcov() / .predict() interface across every method
  • Named pandas outputs — coefficients, standard errors, and diagnostics return as pd.Series or pd.DataFrame with clear labels
  • Immutable results — no accidental mutation after estimation
  • Reproducible exports — JSON, CSV, LaTeX, HTML from any result
import open_econs as oe
import pandas as pd

df = pd.DataFrame({
    "income":    [30, 45, 55, 70, 85, 40, 60, 95],
    "education": [10, 12, 14, 16, 18, 11, 15, 20],
    "age":       [25, 30, 35, 40, 45, 28, 38, 50],
    "female":    [0,  0,  0,  0,  1,  1,  1,  1],
    "province":  ["A","A","B","B","C","C","A","B"],
})

r = oe.ols("income ~ education + age", data=df, cluster="province")
print(r.tidy())

For Stata and R users

If you know the Stata or R command, you already know the open-econs equivalent.

Stata

Stata open-econs
regress oe.ols()
xtreg, fe oe.fe()
ivregress 2sls oe.iv()
logit / probit oe.logit() / oe.probit()
mlogit oe.mlogit() (multinomial logit)
oaxaca oe.oaxaca()
xtabond2 oe.abond()
csdid oe.staggered_did()
rdrobust oe.rdd()
teffects psmatch oe.psm()

R

R open-econs
fixest / plm oe.fe() / oe.PanelContext()
AER::ivreg oe.iv()
did oe.staggered_did()
MatchIt oe.psm()

See docs/migrating_from_stata.md for a detailed migration guide.

Quick Start

import open_econs as oe
import pandas as pd

df = pd.DataFrame({
    "income":    [30, 45, 55, 70, 85, 40, 60, 95],
    "education": [10, 12, 14, 16, 18, 11, 15, 20],
    "age":       [25, 30, 35, 40, 45, 28, 38, 50],
    "female":    [0,  0,  0,  0,  1,  1,  1,  1],
    "province":  ["A","A","B","B","C","C","A","B"],
})

# --- OLS with cluster-robust SEs ---
r = oe.ols("income ~ education + age", data=df, cluster="province")
r.coefficients          # pd.Series with named index
r.tidy()                # coefficient table as DataFrame
r.predict(df.head(2))   # out-of-sample predictions
print(r.summary())      # printable summary

# --- Logit / Probit ---
r_logit = oe.logit("female ~ education + age", data=df)
r_logit.tidy()          # coef, z, P>|z| table
r_logit.margins()       # average marginal effects
r_logit.predict(proba=False)

# --- Fixed effects ---
r_fe = oe.fe("income ~ education + age", data=df, entity="province")
r_fe.tidy()

# --- IV / 2SLS ---
r_iv = oe.iv("income ~ education | age", data=df)
r_iv.tidy()             # 2SLS coefficients
r_iv.first_stage()      # first-stage F-stat

# --- Oaxaca-Blinder decomposition ---
d = oe.oaxaca("income ~ education + age + female", data=df, by="female")
d.explained             # covariate-driven gap
d.unexplained           # coefficient-driven gap
d.total_gap             # female mean - male mean

# --- VIF diagnostics ---
ctx = oe.Context(df)
ctx.vif("income ~ education + age")

# --- Context remembers the dataset ---
ctx.ols("income ~ education + age")
ctx.logit("female ~ education + age")
ctx.probit("female ~ education + age")

# --- Immutability ---
r.f_statistic = 0.0  # AttributeError: OLSResult is immutable

Supported Methods

Function Description
ols() / reg() OLS with HC1/robust/clustered SEs, multi-way clustering, Newey-West HAC, WLS
fe() Fixed effects (one-way entity, two-way entity + time)
iv() Instrumental variables / 2SLS with first-stage F-stat
logit() Binary logit with .margins(), .predict()
probit() Binary probit (same API as logit)
mlogit() Multinomial logit (MNLogit) with per-outcome .margins() (dict), .predict(), robust/cluster SEs
oaxaca() Oaxaca-Blinder decomposition (two-fold, three-fold; multiple reference types)
nls() Nonlinear least squares (Gauss-Newton via scipy, analytic Jacobian)
abond() Arellano-Bond dynamic panel GMM (one/two-step, Windmeijer SEs, collapsed instruments)
staggered_did() Callaway-Sant'Anna (2021) staggered DiD (doubly-robust dripw or reg)
rdd() Sharp / fuzzy regression discontinuity (local linear, triangular kernel)
did() / event_study() Two-period DiD, event-study with pre-trend diagnostics
psm() Propensity score matching (1:1 nearest-neighbor with replacement, AI 2012 SEs)
cem() Coarsened exact matching (auto or explicit cutpoints, multiple binning methods)
balance() Covariate balance diagnostics (SMD, variance ratio, weighted t-tests)
rosenbaum_bounds() Sensitivity analysis for matched pairs
synth() Synthetic control (Abadie-Diamond-Hainmueller) core point estimator: nested predictor-weight (V) + donor-weight (W) optimization, gap path, R/Stata parity
PanelContext(...) Pooled/FE/RE/FD/Driscoll-Kraay/Hausman/ABond with remembered entity/time
Context(...) Dataset-scoped workflow with access to all above estimators

Result API

Every estimator returns an object with:

Method Returns
.summary() Printable string (also __repr__)
.tidy() pd.DataFrame — coefficient or effect table
.vcov() pd.DataFrame — variance-covariance matrix
.predict(newdata) pd.Series — only on regression models
.export(path) JSON / CSV serialization
.plot() Residual diagnostics plot (requires pip install open-econs[plot])
.to_dict() dict — full result metadata
.to_latex() LaTeX table string
.to_html() HTML table string

Design Philosophy

Reproducibility first

Immutable results prevent accidental mutation. All numeric artifacts are named pd.Series or pd.DataFrame with variable-name indices. Every result can be exported to JSON, CSV, LaTeX, or HTML.

Familiar econometrics

Methods and defaults follow established practice from Stata and R. If you know regress, xtreg, or ivregress, you already know how to use the open-econs equivalent. Default standard errors (HC2) match modern Stata conventions.

One Python workflow

Data processing, estimation, diagnostics, sensitivity analysis, and output generation live in the same environment. No more stitching together separate tools.

Methodology Documentation

Every estimator includes a methodology specification covering:

  • Mathematical formulation
  • Assumptions
  • Estimation procedure
  • Inference (all covariance estimators with exact formulas)
  • Implementation details
  • Stata/R equivalents
  • Academic references

See methodology/ for the full registry.

Comparison

Library Strength
statsmodels General statistical models
linearmodels Panel and IV estimation
scikit-learn Machine learning
open-econs Empirical economics workflows with Stata/R parity

Roadmap

See ROADMAP.md for planned features and development milestones.

Development

pip install -e ".[dev]"
python -m pytest tests/

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

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

open_econs-0.8.2-py3-none-any.whl (127.8 kB view details)

Uploaded Python 3

File details

Details for the file open_econs-0.8.2-py3-none-any.whl.

File metadata

  • Download URL: open_econs-0.8.2-py3-none-any.whl
  • Upload date:
  • Size: 127.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for open_econs-0.8.2-py3-none-any.whl
Algorithm Hash digest
SHA256 595eee4ff9e3237d08b17b6eca1c27de09a9ac061e5bbdc76dae721bcf2e8fcc
MD5 146b622a69dbe3653cdd7bcde3758e99
BLAKE2b-256 270750d2a4d70b6a11a9575999373f23442a4431c173eaf64964abd5e4f0fb77

See more details on using hashes here.

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