Skip to main content

Expert-mode marginal effects for Python with session-level analytical pre-commitment.

Project description

pymargins

PyPI Python versions Documentation License: MIT

Expert-mode marginal effects for Python: adjusted predictions, slopes, contrasts, difference-in-differences, and arbitrary differentiable estimands from fitted statistical models — with session-level analytical pre-commitment, JAX-native autodiff, and a κ-driven simulation fallback when the delta method is unsafe.

The design targets Stata's margins and R's marginaleffects, with one substantive difference: a Margins object is a session. Once constructed it commits to an inference scale, variance estimator, confidence level, default evaluation point, and inference method. Every subsequent computation inherits those commitments — so a reviewer sees the entire methodological posture in one constructor call, and posture changes show up as new sessions in the audit trail.

Why marginal effects?

A fitted nonlinear model answers questions on the wrong scale. A logistic regression of diabetes on bmi returns a coefficient in log-odds: β = 0.087. No stakeholder, and few analysts, can act on "a one-unit BMI increase adds 0.087 to the log-odds." The quantity people actually need — how much does the probability of diabetes move? — is a marginal effect, and in a nonlinear model it is not any coefficient. It is a derivative (or a discrete contrast) that depends on where in covariate space you evaluate it, combines several coefficients whenever the model has interactions or polynomials, and carries its own standard error.

pymargins computes that quantity and its uncertainty in the units the decision is made in:

m = Margins.log_scale(fit, vcov="HC3")

m.dydx("bmi")               # AME: avg change in P(diabetes) per unit BMI
m.predict(atexog={"bmi": [25, 30, 35]})   # adjusted P at representative profiles
m.contrasts(scenarios=[                   # treated-vs-control risk difference
    {"atexog": {"treatment": 1}, "label": "treated"},
    {"atexog": {"treatment": 0}, "label": "control"},
], contrasts=[+1, -1])

Reach for this when:

  • The model is nonlinear — GLM, logit/probit, Poisson, Cox, AFT: coefficients are not effects on the outcome scale.
  • Effects are conditional — interactions, polynomials, or splines mean the effect of X is a combination of terms, not one of them.
  • The audience needs outcome units — percentage points, expected counts, predicted survival — not log-odds or hazard ratios.
  • Heterogeneity is the question — the effect by age, by region, by treatment arm, evaluated over a grid.
  • You need a counterfactual contrast — "what if everyone were treated?" as an average contrast, not a coefficient.

These map onto the three estimands: predict (adjusted predictions), dydx (slopes), and contrasts / evaluate (differences and arbitrary differentiable combinations). Picking whose effect — the sample average (AME), a typical unit (MEM), or a representative profile (MER) — is the aggregation axis described in the docs.

Installation

pip install pymargins

Requires Python ≥3.10. Core dependencies (jax, numpy, scipy, pandas) install automatically. Modeling backends are detected at runtime and pull in only what you have; install the extras you need:

pip install "pymargins[statsmodels]"     # statsmodels GLM/OLS/GEE/...
pip install "pymargins[linearmodels]"    # IV, panel, absorbing, Fama–MacBeth
pip install "pymargins[lifelines]"       # Cox, AFT, spline survival models
pip install "pymargins[sklearn]"         # scikit-learn estimators (bootstrap)
pip install "pymargins[polars]"          # Polars input frames
pip install "pymargins[matching]"        # pysmatch propensity matching

Quick example

import statsmodels.formula.api as smf
import statsmodels.api as sm
from pymargins import Margins

fit = smf.glm(
    "outcome ~ treatment + age + sex",
    data=df,
    family=sm.families.Binomial(),
).fit()

# Open a session, committing to log-scale analysis with HC3 SEs
m = Margins.log_scale(fit, vcov="HC3", level=0.95)
print(m.summary())          # methods-section paragraph

# Pre-flight: is the delta method reliable here?
print(m.diagnose().summary())

# Relative risk: a contrast of two counterfactual scenarios
rr = m.contrasts(
    scenarios=[
        {"atexog": {"treatment": 1}, "label": "treated"},
        {"atexog": {"treatment": 0}, "label": "control"},
    ],
    contrasts=[+1, -1],
)
print(rr.summary())         # estimate, asymmetric CI, κ, etc.

Session constructors declare the inference scale up front: Margins.linear_scale, Margins.log_scale, Margins.logit_scale, Margins.correlation_scale, or Margins.from_formula to fit and wrap in one call.

Supported model backends

Adapters are auto-detected from the fitted object. Currently shipping:

  • statsmodels — OLS/WLS/GLS, GLM, discrete (Logit/Probit/Poisson/ NegBin/zero-inflated), MNLogit, ordered, GEE (nominal/ordinal), MixedLM, RLM, QuantReg, PHReg
  • linearmodels — IV/2SLS, panel (fixed/random effects), absorbing regression, Fama–MacBeth
  • lifelines — CoxPH, time-varying Cox, Weibull/LogNormal/LogLogistic AFT, generalized gamma, piecewise exponential, cubic-spline (CRC), with survival-curve estimands
  • scikit-learn — any estimator, via bootstrap inference
  • custom — register your own with register_adapter

Inference methods

  • Delta method — JAX-native exact gradients and Hessians
  • Krinsky–Robb simulation — parametric Monte Carlo on the coefficient distribution
  • Bootstrap — nonparametric, plus cluster and block variants; parallelizable, with percentile/BCa/normal CIs

A κ (kappa) diagnostic quantifies local nonlinearity and automatically recommends — or falls back to — simulation when the delta-method linearization is untrustworthy.

Documentation

Full documentation, tutorials, and theory at pymargins.readthedocs.io — including per-backend tutorials (logit, Poisson, OLS, MNLogit, Cox, AFT, IV/2SLS, panel FE, GEE, mixed effects, sklearn), task-focused how-to guides (robust/clustered SEs, bootstrap variants, simultaneous CIs, DiD, elasticities, custom adapters, matching, plotting, exporting), and design explanations.

Performance notes

  • Bootstrap with n_jobs > 1: parallel bootstrap uses a ThreadPoolExecutor for model refitting, but JAX evaluation is always serial in the main thread to avoid XLA compilation races. BLAS threads are pinned to 1 per worker to prevent oversubscription.

  • Large scenario grids: expand_scenario materializes one block of rows per grid point. A 10-point grid over a 1M-row dataset is 10M rows. Use representative samples (at="typical") or explicit data= overrides when exploring high-dimensional counterfactuals.

  • Memory retention: MarginsResult objects hold references to the parent session, design matrices, and gradients. Call result.materialize() promptly on results you intend to keep; it drops the heavy machinery while preserving estimates, standard errors, and confidence intervals.

Status

Alpha. APIs may change before 1.0. Bug reports and feedback welcome at the issue tracker.

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 Distribution

pymargins-0.1.1.tar.gz (1.1 MB view details)

Uploaded Source

Built Distribution

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

pymargins-0.1.1-py3-none-any.whl (222.8 kB view details)

Uploaded Python 3

File details

Details for the file pymargins-0.1.1.tar.gz.

File metadata

  • Download URL: pymargins-0.1.1.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for pymargins-0.1.1.tar.gz
Algorithm Hash digest
SHA256 43be0b605fc6cdbf367bb453df968e166e24befc6bed8b8b4c4ba9a9078340e0
MD5 e6439d9812de79bba81d3e270e72012f
BLAKE2b-256 1f5460641ecea642c201a17c401a0f5f0164c3550a10077c5633371d845ed276

See more details on using hashes here.

File details

Details for the file pymargins-0.1.1-py3-none-any.whl.

File metadata

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

File hashes

Hashes for pymargins-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 824ddc88047decbe1c33f830f13cf2b51dda5e92dd6258bb445bab8ab0fadcf7
MD5 3c2d05f16b58ed3d30ea7a4ede794d36
BLAKE2b-256 d58ba7d47e210816e5310439addcbc1a5cb8e2d31ccc21cfbef13d2bb2851993

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