Regression & Prediction · Lesson 2 of 7
Model Diagnostics
~16 min
The Concept
The regression model has R² = 0.72. Your supervisor is not satisfied. 'Before you trust any R², look at the residuals,' she says. She pulls up a plot of residuals against fitted values. Instead of random scatter, there's a clear curve, residuals are positive for low and high predicted values, negative in the middle. 'That's a nonlinearity you've missed,' she says. 'The model violates its own assumptions.'
Linear regression assumes residuals are randomly scattered around zero, no patterns. If you see patterns in the residuals, the model is wrong in some way: a curve means nonlinearity, a funnel means heteroscedasticity (variance increases with fitted value), a time pattern means autocorrelation.
The four key diagnostic plots for any linear regression are: (1) residuals vs fitted values, (2) a Q-Q plot (are residuals normally distributed?), (3) scale-location (is variance constant?), and (4) residuals vs leverage (are any single points unduly influencing the fit?).
The Analogy
Checking residuals is like checking the leftover crumbs after cutting a cake into equal slices. If the crumbs are scattered randomly everywhere, your cuts were even. If crumbs pile up in one spot, something about your cutting method is off, and looking closely at the pile tells you exactly what went wrong.
Code (optional)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from scipy import stats
rainfall = np.load("bluewater_monthly_rainfall.npy").reshape(-1, 1)
streamflow = np.load("bluewater_monthly_streamflow.npy")
model = LinearRegression().fit(rainfall, streamflow)
fitted = model.predict(rainfall)
residuals = streamflow - fitted
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
# Residuals vs fitted
axes[0].scatter(fitted, residuals, alpha=0.6)
axes[0].axhline(0, color="red", lw=1)
axes[0].set_xlabel("Fitted values"); axes[0].set_ylabel("Residuals")
axes[0].set_title("Residuals vs Fitted")
# Q-Q plot
stats.probplot(residuals, plot=axes[1])
axes[1].set_title("Normal Q-Q")
plt.tight_layout(); plt.savefig("diagnostics.png")Line by line
- fitted = model.predict() gives the model's predictions; residuals = actual − predicted.
- The residuals-vs-fitted plot reveals patterns that violate the linearity or homoscedasticity assumptions.
- A Q-Q plot compares residual quantiles to theoretical normal quantiles, deviations from the diagonal suggest non-normality.
Why Real Researchers Care
Residual diagnostics are not optional, they're part of the scientific analysis. Many published regression models in hydrology and environmental science have had their conclusions questioned or retracted because authors skipped this step. The UK Environment Agency's statistical methods guidelines explicitly require residual plots before any regression model is accepted for decision-making.
Quick Check
Q1. Your residuals-vs-fitted plot shows a 'U' shape (high residuals at low and high fitted values, low residuals in the middle). What does this suggest?
Your Goal
You fit a regression of streamflow on rainfall and get R² = 0.68. You then check the residuals and see a clear funnel shape (small residuals for low fitted values, large ones for high). What does this mean, and what transformation might fix it?
Hint: A funnel = heteroscedasticity. log-transforming either the response or the predictor often stabilises variance.
Teach It Back
Explain why a high R² doesn't guarantee that a regression model is valid, using the 'curved residuals' example from this lesson.