Reading and controlling the κ fallback

import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf
from pymargins import Margins

rng = np.random.default_rng(42)
n = 2000
df = pd.DataFrame({
    "age": rng.integers(20, 75, n),
    "female": rng.binomial(1, 0.52, n),
    "treated": rng.binomial(1, 0.40, n),
})
lp = -1.5 + 0.04 * df["age"] - 0.3 * df["female"] + 0.8 * df["treated"]
df["y"] = rng.binomial(1, 1 / (1 + np.exp(-lp)))

fit = smf.glm("y ~ age + female + treated", data=df,
              family=sm.families.Binomial()).fit()
m = Margins.log_scale(fit, at="overall")

Every result records the inference path actually used. When the session’s κ threshold is exceeded, delta auto-falls-back to simulation, and the summary annotates the fallback reason.

m = Margins.log_scale(fit, kappa_threshold=0.3, method="delta", n_sim=4000)
res = m.predict(atexog={"age": [25, 45, 65]})
print(res.summary())     # notes 'fallback: simulation (κ=0.42 > 0.30)'
============================================================
             Margins Result (delta, level=0.95)             
============================================================
        estimate  std err         z  P>|z|  [95% Conf. Int.]
------------------------------------------------------------
age=25    0.4120   0.0453  -19.5680  0.000    0.3770, 0.4503
age=45    0.6124   0.0184  -26.7196  0.000    0.5908, 0.6349
age=65    0.7819   0.0173  -14.1802  0.000    0.7558, 0.8089
============================================================

n = 2000
Note: std err is on the inference scale; estimate and CI are on the reporting scale.
κ: max=0.062
Delta-vs-sim disagreement: 0.498%

Pre-flight diagnostic

print(m.diagnose().summary())
Session diagnostic (50 design points)
  Session: Margins session
  Model: GLMResultsWrapper
  Adapter: StatsmodelsGLMAdapter
  Inference scale: log
  Variance: default
  Confidence level: 0.95
  At: overall
  Method: delta (κ-threshold=0.3)
  n_sim: 4000
  n_boot: 1000 
  Gradient backend: autodiff
  Diagnostics: enabled
  Strict: False
  κ min:    0.031
  κ median: 0.064
  κ max:    0.132
  Verdict:  delta_borderline
  Delta method is borderline. Consider running specific estimands with method='simulation' or enabling automatic fallback via the session's kappa_threshold.

diagnose() samples the estimand surface and reports the κ distribution; if it is uniformly large, change the inference scale (see Inference scales and the κ diagnostic) before paying for simulation on every call.

What to do when κ is high

A high κ means the delta-method linearization is a poor approximation for your estimand. Three strategies, in order of preference:

  1. Change the inference scale. Often the estimand is nearly linear on a different scale (log instead of identity, logit instead of probability). See Inference scales and the κ diagnostic.

  2. Accept the simulation fallback. If the scale is already the most natural one, Krinsky–Robb simulation is a robust alternative. Just make sure n_sim is large enough (≥ 4000) for stable tail quantiles.

  3. Switch to bootstrap. Bootstrap does not rely on local linearity at all; it is the safest choice when both curvature and model misspecification are concerns. See Bootstrap inference.

Disabling the fallback

Set kappa_threshold=float("inf") to force the chosen method= to run regardless of curvature.

m = Margins.log_scale(fit, kappa_threshold=float("inf"))

Disabling diagnostics altogether

In tight loops, set diagnostics=False to skip the κ computation:

m = Margins.log_scale(fit, diagnostics=False)