Reading a regression summary

statsmodels · Essentials · 12 min

The summary table is what you report and decide from, so you must read it correctly.

res.summary() prints three blocks.

Top block (model fit): number of observations, R² (share of variance explained) and adjusted R² (penalised for the number of predictors), the F-statistic and its p-value (do the predictors together explain anything?), log-likelihood, AIC and BIC (lower is better when comparing models on the same data).

Coefficient table, one row per term:

  • coef: the estimated effect: the change in y for a one-unit change in that predictor, holding the others fixed,
  • std err: its uncertainty,
  • t = coef / std err and P>|t|: the test of "this coefficient is zero",
  • [0.025 0.975]: the 95% confidence interval.

Bottom block (residual diagnostics): Omnibus and Jarque-Bera test normality of residuals, Durbin-Watson checks autocorrelation (around 2 is fine), and the condition number warns about multicollinearity or badly scaled variables; a note appears when it is large.

Interpretation cautions: a small p-value says the effect is distinguishable from zero, not that it is large or causal. Coefficients of correlated predictors are individually uncertain even when the model fits well. All of it assumes the model is reasonably specified, which diagnostics check. Programmatic access is through res.params, res.pvalues, res.conf_int() and res.summary2().tables[1] as a DataFrame.

Example

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

rng = np.random.default_rng(2)
df = pd.DataFrame({"ads": rng.uniform(0, 10, 80), "noise": rng.normal(size=80)})
df["sales"] = 5 + 1.5 * df["ads"] + rng.normal(0, 3, 80)

res = smf.ols("sales ~ ads + noise", data=df).fit()
table = res.summary2().tables[1]               # coefficient table as a DataFrame
print(table[["Coef.", "Std.Err.", "P>|t|"]].round(3))
#            Coef.  Std.Err.  P>|t|
# Intercept  4.250     0.813  0.000
# ads        1.644     0.140  0.000
# noise     -0.067     0.423  0.874   (no evidence of an effect)
print(round(res.rsquared, 3), round(res.rsquared_adj, 3))   # 0.642 0.632
print(f"{res.f_pvalue:.1e}")                              # 7.0e-18

Remember

  • coef = effect of a one-unit change holding others fixed
  • p-value tests coef = 0; the confidence interval shows the plausible range
  • Use AIC/BIC and adjusted R² to compare models on the same data

Watch out for

  • Reading a large p-value as proof that a predictor has no effect.

Check yourself

  1. In a summary, a coefficient is 0.8 with 95% CI [0.1, 1.5]. Which statement is right?

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.regression.linear_model.RegressionResults.html