Evaluating Regression Models
How do we know if our regression model is any good? For numbers, we can't just measure "accuracy" (it's almost impossible to predict exactly $312,456.78). Instead, we measure how far off our predictions are from the true values.
Drag the target data points up or down and compare absolute errors against squared error squares.
💡 Tip: Click and drag any node up/down to see how it affects evaluation residuals.
Before relying on a library function, calculate each metric once yourself so you know what the number means. Take four houses with true prices and predictions, in thousands of dollars:
| House | Actual | Predicted | Predicted - actual | Squared error |
|---|---|---|---|---|
| 1 | 200 | 210 | 10 | 100 |
| 2 | 300 | 280 | -20 | 400 |
| 3 | 400 | 400 | 0 | 0 |
| 4 | 500 | 470 | -30 | 900 |
MAE is the mean of the absolute errors: (10 + 20 + 0 + 30) / 4 = 15. On average the model is off by $15,000. MSE is the mean of the squared errors: (100 + 400 + 0 + 900) / 4 = 350, in squared units that are hard to interpret. RMSE is its square root, about 18.7, back in thousands of dollars. Notice that RMSE (18.7) is larger than MAE (15). RMSE is always at least as large as MAE, and the more uneven the errors, the bigger the gap. Here the single $30,000 miss pulls RMSE up, which is the squaring penalty at work.
For R², compare your squared error to that of a baseline that always predicts the mean. The mean of the actual prices is 350, so the baseline errors are -150, -50, 50 and 150, with squares summing to 50,000. Our model's squared errors sum to 1,400. R² = 1 - 1,400 / 50,000 = 0.972: the model explains about 97 percent of the variation in prices.
import numpy as np
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
y_true = np.array([200, 300, 400, 500])
y_pred = np.array([210, 280, 400, 470])
mae = mean_absolute_error(y_true, y_pred)
mse = mean_squared_error(y_true, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_true, y_pred)
print(f"MAE={mae:.1f} MSE={mse:.1f} RMSE={rmse:.1f} R2={r2:.3f}")
# MAE=15.0 MSE=350.0 RMSE=18.7 R2=0.972MAE treats every dollar of error equally, so it is robust to a few extreme mistakes and easy to explain to non-technical readers. RMSE punishes large errors disproportionately, so it is the better choice when big misses are especially costly, for example when under-estimating a delivery time by an hour is much worse than missing by five minutes twelve times. A useful diagnostic is to compute both: if RMSE is much larger than MAE, a few large errors are dominating, and the next step is to inspect those rows.
A residual is actual - predicted. A single summary number can hide structure, so plot residuals against predictions. In a healthy linear model they scatter randomly around zero with roughly constant spread. A curve suggests the relationship is non-linear and the model is missing a pattern. A funnel that widens with the prediction means the errors grow with the value, which is common for prices and often fixed by modelling the logarithm of the target. A few isolated points far from the rest are outliers worth checking for data-entry mistakes.
residuals = y_true - y_pred
print(residuals) # [-10 20 0 30]
print(residuals.mean()) # 10.0The mean residual of a linear regression with an intercept, fitted by ordinary least squares on its training data, is zero. A clearly non-zero mean on test data suggests the model is systematically over- or under-predicting.
All of these metrics must be computed on data the model has not seen. The training error will almost always look better than the test error. If training RMSE is 10 and test RMSE is 40, the model has memorized noise (overfitting). If both are high and similar, the model is too simple (underfitting). The gap between them is often more informative than either number alone.
(y_true, y_pred). For MSE the order does not change the value, but for R² it does.Calculate MSE and R² score for these predictions using scikit-learn.
from sklearn.metrics import mean_squared_error, r2_score
# Actual house prices and our model's predictions (in thousands)
y_actual = [300, 450, 200, 600, 350]
y_pred = [310, 440, 210, 580, 360]
# TODO: Calculate MSE
# mse = ???
# TODO: Calculate R2 Score
# r2 = ???
# print(f"MSE: {mse}")
# print(f"R² Score: {r2}")These metrics help us compare different models and choose the one that makes the smallest errors on our test set.