Statsmodels OLS Regression and Tests in Python

The Python statsmodels library provides a comprehensive framework for estimating Ordinary Least Squares (OLS) regression models and conducting rigorous statistical tests. By coupling linear algebra solvers with classical inferential statistics, statsmodels allows practitioners to fit linear models, estimate parameters, and automatically generate detailed summary outputs containing p-values, standard errors, and confidence intervals. Beyond basic model fitting, the library includes a suite of diagnostic tests to evaluate core regression assumptions, such as residual normality, homoscedasticity, and independence.

Setting Up OLS Regression in Statsmodels

To perform OLS regression, statsmodels requires the dependent variable (\(y\)) and independent variables (\(X\)) formatted as NumPy arrays or pandas DataFrames. Unlike libraries such as scikit-learn, statsmodels does not include an intercept term by default. You must explicitly add a constant column using sm.add_constant().

import numpy as np
import statsmodels.api as sm

# Sample data
np.random.seed(42)
X = np.random.rand(100, 2)
y = 2 + 3 * X[:, 0] + 1.5 * X[:, 1] + np.random.normal(0, 0.5, 100)

# Add an intercept (constant term)
X_with_const = sm.add_constant(X)

# Define and fit the OLS model
model = sm.OLS(y, X_with_const)
results = model.fit()

The fit() method uses standard matrix operations—specifically solving the normal equations via QR decomposition or singular value decomposition (SVD)—to calculate the coefficient estimates that minimize the sum of squared residuals.

Interpreting the Statistical Summary

Calling results.summary() produces a structured report divided into three main components:

  1. Model Fit Metrics: Displays the coefficient of determination (\(R^2\)), Adjusted \(R^2\), the overall \(F\)-statistic, and its corresponding p-value (Prob (F-statistic)), which evaluates whether any independent variable significantly predicts the outcome.
  2. Parameter Estimates: Details the estimated coefficients (coef), standard errors (std err), \(t\)-statistics (t), two-tailed p-values (P>|t|), and 95% confidence intervals for each predictor.
  3. Residual Diagnostics: Provides baseline metrics on error behavior, including the Omnibus test, the Jarque-Bera test, and the Durbin-Watson statistic.

Hypothesis Testing on Coefficients

To test linear hypotheses about parameters, statsmodels provides dedicated methods on the fitted results object:

# Test if the first predictor equals 3
t_test_res = results.t_test("x1 = 3")

# Joint hypothesis test (F-test)
f_test_res = results.f_test("x1 = 3, x2 = 1.5")

Residual Diagnostic Tests

Validating the Gauss-Markov assumptions is essential for ensuring that OLS estimators remain the Best Linear Unbiased Estimators (BLUE). The statsmodels.stats module includes tests for these assumptions:

1. Autocorrelation

The Durbin-Watson statistic tests for first-order autocorrelation in residuals and is printed in the standard summary. A value near 2 indicates no autocorrelation, while values approaching 0 or 4 indicate positive or negative autocorrelation, respectively. For higher-order autocorrelation, the Breusch-Godfrey test is available via sm.stats.acorr_breusch_godfrey(results).

2. Heteroscedasticity

When the variance of residuals is non-constant, standard errors are biased. statsmodels provides tests to detect heteroscedasticity:

If heteroscedasticity is detected, robust standard errors (such as HC1, HC2, or HC3) can be calculated directly by refitting with model.fit(cov_type='HC3').

3. Normality of Residuals

Normality is required for valid hypothesis testing in small samples. The summary output includes:

Both tests can be run explicitly using sm.stats.jarque_bera(results.resid).

4. Multicollinearity

High correlation between predictors inflates standard errors. The Variance Inflation Factor (VIF) measures this inflation:

from statsmodels.stats.outliers_influence import variance_inflation_factor

vifs = [
    variance_inflation_factor(X_with_const, i)
    for i in range(X_with_const.shape[1])
]

A VIF greater than 5 or 10 typically suggests problematic multicollinearity.