Convergence, separation and identification

statsmodels · Expert · 12 min

Fits that silently fail produce enormous or meaningless estimates that look like results.

Iteratively fitted models (Logit, GLM, MixedLM, ARIMA, custom MLE) can fail in ways a summary doesn't make obvious. Learn the symptoms.

Non-convergence: a ConvergenceWarning, res.mle_retvals["converged"] == False, or a summary note. Remedies: rescale predictors to similar magnitudes, give better start_params, raise maxiter, or switch optimiser (method="newton", "bfgs", "lbfgs", "nm"). Comparing estimates across two methods is a cheap sanity check.

Perfect separation in binary models: a predictor (or combination) predicts the outcome exactly, so the likelihood keeps increasing as the coefficient grows to infinity. Symptoms: PerfectSeparationWarning, huge coefficients with huge standard errors. Fixes: penalised estimation with fit_regularized(alpha=..., L1_wt=0) (ridge-like), merging sparse categories, or reconsidering the variable.

Collinearity and identification: if columns are linearly dependent (all dummies plus an intercept, or total = a + b together with a and b), OLS uses a pseudo-inverse and still returns numbers, but individual coefficients are arbitrary. Recent versions emit a SingularMatrixWarning, and the summary warns of a small eigenvalue. Check np.linalg.matrix_rank(res.model.exog) against the number of columns.

Boundary estimates: variance components estimated at zero in mixed models, or ARIMA roots near the unit circle, signal an over-specified model.

Treat warnings as errors during development (warnings.simplefilter("error")) so these never slip into reports.

Example

import warnings
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf

df = pd.DataFrame({"x": [1, 2, 3, 4, 5, 6, 7, 8], "y": [0, 0, 0, 0, 1, 1, 1, 1]})  # x > 4.5 -> y = 1

with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter("always")
    res = smf.logit("y ~ x", data=df).fit(disp=0, maxiter=100)
print(sorted({type(x.message).__name__ for x in w}))
# ['ConvergenceWarning', 'PerfectSeparationWarning']
print(abs(res.params["x"]) > 10)          # True (coefficient running off to infinity)

reg = smf.logit("y ~ x", data=df).fit_regularized(alpha=1.0, L1_wt=0.0, disp=0)
print(reg.params.round(2).to_dict())      # {'Intercept': -0.65, 'x': 0.27} (finite, shrunk)

df["x2"] = 2 * df["x"]                     # perfectly collinear copy
exog = smf.ols("y ~ x + x2", data=df).fit().model.exog
print(np.linalg.matrix_rank(exog), exog.shape[1])   # 2 3 + SingularMatrixWarning

Remember

  • Treat convergence and separation warnings as failures, not noise
  • Rescale, give start values or switch optimiser when fits don't converge
  • Check the design matrix rank; collinear columns make coefficients arbitrary

Watch out for

  • Reporting coefficients from a fit that emitted a ConvergenceWarning.

Check yourself

  1. A logit fit returns a coefficient of 45 with a standard error of 9000. What is the likely cause?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.statsmodels.org/stable/generated/statsmodels.discrete.discrete_model.Logit.fit_regularized.html

Builds on

Unlocks

Nothing builds on this yet.