REGRESS_DIAG
Overview
The REGRESS_DIAG function performs a comprehensive suite of diagnostic tests on regression residuals to evaluate model assumptions. It returns results for six statistical tests across three categories: heteroskedasticity, autocorrelation, and normality. This implementation uses the statsmodels library’s diagnostic module.
Heteroskedasticity Tests check whether the variance of residuals is constant across observations—a key assumption of ordinary least squares (OLS) regression. The function includes:
- Breusch-Pagan test: A Lagrange Multiplier test that regresses squared residuals on the independent variables. Significant results (p < 0.05) indicate heteroskedasticity.
- White test: A more general test that includes cross-products and squared terms of regressors, making it sensitive to both heteroskedasticity and model misspecification.
Autocorrelation Tests detect serial correlation in residuals, which violates the independence assumption and can lead to inefficient coefficient estimates:
- Ljung-Box test: A portmanteau test that examines whether any of the first k autocorrelations are non-zero. The function uses k = \min(10, n/5) lags.
- Durbin-Watson statistic: A test specifically for first-order autocorrelation. Values near 2 indicate no autocorrelation; values below 1.5 or above 2.5 suggest potential problems.
Normality Tests assess whether residuals follow a normal distribution, which affects the validity of confidence intervals and hypothesis tests:
- Jarque-Bera test: Examines whether residual skewness and kurtosis match a normal distribution. The test statistic follows a chi-squared distribution with 2 degrees of freedom.
- Omnibus test: D’Agostino’s K-squared test, which combines measures of skewness and kurtosis to test for normality.
Each test returns a statistic, p-value (except Durbin-Watson), and an interpretation based on a 0.05 significance threshold. For more details on the underlying algorithms, see the statsmodels documentation and GitHub repository.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=REGRESS_DIAG(residuals, exog, endog)
residuals(list[list], required): A column vector of regression residuals from the fitted model.exog(list[list], required): A matrix of independent variables (predictors) used in the regression model, including a constant column if applicable.endog(list[list], required): A column vector of the dependent variable from the regression model.
Returns (list[list]): 2D list with regression diagnostics, or error message string.
Examples
Example 1: Demo case 1
Inputs:
| residuals | exog | endog | |
|---|---|---|---|
| 0.1 | 1 | 1 | 2.1 |
| 0.2 | 1 | 2 | 4.2 |
| -0.1 | 1 | 3 | 6.1 |
| 0.05 | 1 | 4 | 8.05 |
| -0.15 | 1 | 5 | 9.85 |
| 0.08 | 1 | 6 | 12.08 |
| -0.05 | 1 | 7 | 13.95 |
| 0.12 | 1 | 8 | 16.12 |
| -0.08 | 1 | 9 | 17.92 |
| 0.03 | 1 | 10 | 20.03 |
Excel formula:
=REGRESS_DIAG({0.1;0.2;-0.1;0.05;-0.15;0.08;-0.05;0.12;-0.08;0.03}, {1,1;1,2;1,3;1,4;1,5;1,6;1,7;1,8;1,9;1,10}, {2.1;4.2;6.1;8.05;9.85;12.08;13.95;16.12;17.92;20.03})
Expected output:
| test_category | test_name | statistic | p_value | conclusion |
|---|---|---|---|---|
| Heteroskedasticity | Breusch-Pagan | 2.306 | 0.1289 | No problem detected |
| Heteroskedasticity | White | 2.307 | 0.3156 | No problem detected |
| Autocorrelation | Ljung-Box | 4.819 | 0.08988 | No problem detected |
| Autocorrelation | Durbin-Watson | 2.71 | Problem detected | |
| Normality | Jarque-Bera | 0.5026 | 0.7778 | Normal |
| Normality | Omnibus | 0.5237 | 0.7696 | Normal |
Example 2: Demo case 2
Inputs:
| residuals | exog | endog | |
|---|---|---|---|
| 0.01 | 1 | 2 | 4.01 |
| 0.02 | 1 | 3 | 6.02 |
| -0.01 | 1 | 4 | 7.99 |
| 0.03 | 1 | 5 | 10.03 |
| -0.02 | 1 | 6 | 11.98 |
| 0.015 | 1 | 7 | 14.015 |
| -0.015 | 1 | 8 | 15.985 |
| 0.025 | 1 | 9 | 18.025 |
Excel formula:
=REGRESS_DIAG({0.01;0.02;-0.01;0.03;-0.02;0.015;-0.015;0.025}, {1,2;1,3;1,4;1,5;1,6;1,7;1,8;1,9}, {4.01;6.02;7.99;10.03;11.98;14.015;15.985;18.025})
Expected output:
| test_category | test_name | statistic | p_value | conclusion |
|---|---|---|---|---|
| Heteroskedasticity | Breusch-Pagan | 0.6363 | 0.4251 | No problem detected |
| Heteroskedasticity | White | 0.9164 | 0.6324 | No problem detected |
| Autocorrelation | Ljung-Box | 6.676 | 0.009773 | Problem detected |
| Autocorrelation | Durbin-Watson | 2.966 | Problem detected | |
| Normality | Jarque-Bera | 0.8672 | 0.6482 | Normal |
| Normality | Omnibus | 2.275 | 0.3206 | Normal |
Example 3: Demo case 3
Inputs:
| residuals | exog | endog | |
|---|---|---|---|
| 0.5 | 1 | 1 | 2.5 |
| -0.3 | 1 | 2 | 3.7 |
| 0.2 | 1 | 3 | 6.2 |
| -0.1 | 1 | 4 | 7.9 |
| 0.4 | 1 | 5 | 10.4 |
| -0.2 | 1 | 6 | 11.8 |
| 0.3 | 1 | 7 | 14.3 |
| -0.4 | 1 | 8 | 15.6 |
| 0.1 | 1 | 9 | 18.1 |
| -0.5 | 1 | 10 | 19.5 |
| 0.2 | 1 | 11 | 22.2 |
| -0.1 | 1 | 12 | 23.9 |
Excel formula:
=REGRESS_DIAG({0.5;-0.3;0.2;-0.1;0.4;-0.2;0.3;-0.4;0.1;-0.5;0.2;-0.1}, {1,1;1,2;1,3;1,4;1,5;1,6;1,7;1,8;1,9;1,10;1,11;1,12}, {2.5;3.7;6.2;7.9;10.4;11.8;14.3;15.6;18.1;19.5;22.2;23.9})
Expected output:
| test_category | test_name | statistic | p_value | conclusion |
|---|---|---|---|---|
| Heteroskedasticity | Breusch-Pagan | 0.5924 | 0.4415 | No problem detected |
| Heteroskedasticity | White | 0.7465 | 0.6885 | No problem detected |
| Autocorrelation | Ljung-Box | 13.1 | 0.001431 | Problem detected |
| Autocorrelation | Durbin-Watson | 3.061 | Problem detected | |
| Normality | Jarque-Bera | 0.7312 | 0.6938 | Normal |
| Normality | Omnibus | 1.251 | 0.5349 | Normal |
Example 4: Demo case 4
Inputs:
| residuals | exog | endog | ||
|---|---|---|---|---|
| 0.1 | 1 | 1.5 | 2 | 5.1 |
| 0.15 | 1 | 2.5 | 3 | 8.15 |
| 0.12 | 1 | 3.5 | 4 | 11.12 |
| 0.18 | 1 | 4.5 | 5 | 14.18 |
| 0.14 | 1 | 5.5 | 6 | 17.14 |
| 0.16 | 1 | 6.5 | 7 | 20.16 |
| 0.13 | 1 | 7.5 | 8 | 23.13 |
| 0.17 | 1 | 8.5 | 9 | 26.17 |
Excel formula:
=REGRESS_DIAG({0.1;0.15;0.12;0.18;0.14;0.16;0.13;0.17}, {1,1.5,2;1,2.5,3;1,3.5,4;1,4.5,5;1,5.5,6;1,6.5,7;1,7.5,8;1,8.5,9}, {5.1;8.15;11.12;14.18;17.14;20.16;23.13;26.17})
Expected output:
| test_category | test_name | statistic | p_value | conclusion |
|---|---|---|---|---|
| Heteroskedasticity | Breusch-Pagan | 1.87 | 0.3925 | No problem detected |
| Heteroskedasticity | White | 2.428 | 0.297 | No problem detected |
| Autocorrelation | Ljung-Box | 1.957 | 0.1618 | No problem detected |
| Autocorrelation | Durbin-Watson | 0.06753 | Problem detected | |
| Normality | Jarque-Bera | 0.3931 | 0.8216 | Normal |
| Normality | Omnibus | 0.2669 | 0.8751 | Normal |
Python Code
import math
import numpy as np
from statsmodels.stats.diagnostic import het_breuschpagan, het_white, acorr_ljungbox
from statsmodels.stats.stattools import durbin_watson, jarque_bera, omni_normtest
def regress_diag(residuals, exog, endog):
"""
Performs comprehensive regression diagnostic tests.
See: https://www.statsmodels.org/stable/stats.html#residual-diagnostics-and-specification-tests
This example function is provided as-is without any representation of accuracy.
Args:
residuals (list[list]): A column vector of regression residuals from the fitted model.
exog (list[list]): A matrix of independent variables (predictors) used in the regression model, including a constant column if applicable.
endog (list[list]): A column vector of the dependent variable from the regression model.
Returns:
list[list]: 2D list with regression diagnostics, or error message string.
"""
def to2d(x):
return [[x]] if not isinstance(x, list) else x
def validate_2d_numeric(data, name):
# Validate 2D list structure
if not isinstance(data, list):
return f"Invalid input: {name} must be a 2D list."
if not data:
return f"Invalid input: {name} cannot be empty."
for i, row in enumerate(data):
if not isinstance(row, list):
return f"Invalid input: {name} row {i} must be a list."
if not row:
return f"Invalid input: {name} row {i} cannot be empty."
for j, val in enumerate(row):
if not isinstance(val, (int, float)):
return f"Invalid input: {name}[{i}][{j}] must be numeric."
if math.isnan(val) or math.isinf(val):
return f"Invalid input: {name}[{i}][{j}] must be finite."
return None
def interpret_pvalue(p_value, test_category):
# Interpretation based on p-value threshold of 0.05
if test_category in ('Heteroskedasticity', 'Autocorrelation'):
return 'Problem detected' if p_value < 0.05 else 'No problem detected'
elif test_category == 'Normality':
return 'Non-normal' if p_value < 0.05 else 'Normal'
return 'N/A'
# Normalize inputs
residuals_2d = to2d(residuals)
exog_2d = to2d(exog)
endog_2d = to2d(endog)
# Validate inputs
error = validate_2d_numeric(residuals_2d, 'residuals')
if error:
return error
error = validate_2d_numeric(exog_2d, 'exog')
if error:
return error
error = validate_2d_numeric(endog_2d, 'endog')
if error:
return error
# Convert to numpy arrays
try:
resid_array = np.array(residuals_2d, dtype=float)
exog_array = np.array(exog_2d, dtype=float)
endog_array = np.array(endog_2d, dtype=float)
except Exception as exc:
return f"Invalid input: unable to convert inputs to arrays: {exc}"
# Flatten residuals to 1D array
if resid_array.shape[1] != 1:
return "Invalid input: residuals must be a column vector (2D list with 1 column)."
resid_1d = resid_array.flatten()
# Flatten endog to 1D array
if endog_array.shape[1] != 1:
return "Invalid input: endog must be a column vector (2D list with 1 column)."
endog_1d = endog_array.flatten()
# Validate dimensions
n_obs = len(resid_1d)
if exog_array.shape[0] != n_obs:
return f"Invalid input: number of residuals ({n_obs}) must match number of observations in exog ({exog_array.shape[0]})."
if len(endog_1d) != n_obs:
return f"Invalid input: number of residuals ({n_obs}) must match number of observations in endog ({len(endog_1d)})."
if n_obs < 3:
return "Invalid input: insufficient data for diagnostic tests (need at least 3 observations)."
# Initialize results
results = [['test_category', 'test_name', 'statistic', 'p_value', 'conclusion']]
# Run diagnostic tests
try:
# Heteroskedasticity tests
# Breusch-Pagan test
lm_stat, lm_pvalue, f_stat, f_pvalue = het_breuschpagan(resid_1d, exog_array)
interp = interpret_pvalue(lm_pvalue, 'Heteroskedasticity')
results.append(['Heteroskedasticity', 'Breusch-Pagan', float(lm_stat), float(lm_pvalue), interp])
# White test
white_lm, white_lm_pvalue, white_f, white_f_pvalue = het_white(resid_1d, exog_array)
interp = interpret_pvalue(white_lm_pvalue, 'Heteroskedasticity')
results.append(['Heteroskedasticity', 'White', float(white_lm), float(white_lm_pvalue), interp])
# Autocorrelation tests
# Ljung-Box test (using lag=min(10, n_obs//5))
max_lag = min(10, max(1, n_obs // 5))
lb_result = acorr_ljungbox(resid_1d, lags=max_lag, return_df=True)
# Use the last lag result
last_row = lb_result.iloc[-1]
lb_stat = float(last_row['lb_stat'])
lb_pvalue = float(last_row['lb_pvalue'])
interp = interpret_pvalue(lb_pvalue, 'Autocorrelation')
results.append(['Autocorrelation', 'Ljung-Box', lb_stat, lb_pvalue, interp])
# Durbin-Watson test (note: DW doesn't have a p-value, only statistic)
dw_stat = durbin_watson(resid_1d)
# DW statistic interpretation: values around 2 indicate no autocorrelation
# < 2: positive autocorrelation, > 2: negative autocorrelation
if dw_stat < 1.5 or dw_stat > 2.5:
dw_interp = 'Problem detected'
else:
dw_interp = 'No problem detected'
results.append(['Autocorrelation', 'Durbin-Watson', float(dw_stat), None, dw_interp])
# Normality tests
# Jarque-Bera test
jb_stat, jb_pvalue, skew, kurtosis = jarque_bera(resid_1d)
interp = interpret_pvalue(jb_pvalue, 'Normality')
results.append(['Normality', 'Jarque-Bera', float(jb_stat), float(jb_pvalue), interp])
# Omnibus test
omnibus_stat, omnibus_pvalue = omni_normtest(resid_1d)
interp = interpret_pvalue(omnibus_pvalue, 'Normality')
results.append(['Normality', 'Omnibus', float(omnibus_stat), float(omnibus_pvalue), interp])
except Exception as exc:
return f"Error running diagnostic tests: {exc}"
# Validate results
for row in results[1:]:
if not isinstance(row[2], (int, float)) or math.isnan(row[2]) or math.isinf(row[2]):
return "Error: test produced invalid statistic."
# p_value can be None for Durbin-Watson
if row[3] is not None:
if not isinstance(row[3], (int, float)) or math.isnan(row[3]) or math.isinf(row[3]):
return "Error: test produced invalid p-value."
return results