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.2.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.2-py3-none-any.whl (223.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pymargins-0.1.2.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pymargins-0.1.2.tar.gz
Algorithm Hash digest
SHA256 a6b72208c976768a12ddb5dddefd2166e56e76f1b14b17decd98d4bf1b7b87fe
MD5 ccda7d5f8fbe1f3b90c3d3357ec61ca4
BLAKE2b-256 a61815cc80ea7415c3b0c83c194bec5abb015788ab2abdf06dfa4253196a6d1f

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymargins-0.1.2.tar.gz:

Publisher: release.yml on huntermills707/pymargins

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

File details

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

File metadata

  • Download URL: pymargins-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 223.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pymargins-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 445e29f8ff9965ce3787cacdeccd028c848da8394cd1e4e2ef3cafb70463f53d
MD5 ecf271675967d9539a8a7867fa4b932e
BLAKE2b-256 99bd002882bb246d18bde7648960c884844ab7c139ccc8824279d3fbcb9bb885

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymargins-0.1.2-py3-none-any.whl:

Publisher: release.yml on huntermills707/pymargins

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