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 + SingularMatrixWarningRemember
- 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.