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.3.0.tar.gz (1.6 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.3.0-py3-none-any.whl (237.8 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for pymargins-0.3.0.tar.gz
Algorithm Hash digest
SHA256 a776e399508ca608dbe2f3591574bd4a35e2c3e2566b5f990562387e140eae89
MD5 67b2b4f1773b989df03b6f8b591c1ce6
BLAKE2b-256 bf516a7a9db56698630b295025d26ba125421b0d418fb1d282574a91615c2f63

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymargins-0.3.0.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.3.0-py3-none-any.whl.

File metadata

  • Download URL: pymargins-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 237.8 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 874d3d4c1585370ffea72db8a4f5b06d3b34676b323a68893b13ed062095ff7e
MD5 15b74cf6cb4effa161b28ccf16ec2c0a
BLAKE2b-256 b2a93535708ca9d06458a46b539d182373078727265dc2c5775b9a9f19ef1dfe

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymargins-0.3.0-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