Unit 4 · Tutorial 6 of 8
Evaluating and Diagnosing a Regression Model
A number on its own tells you nothing. This tutorial shows you how to measure a regression model honestly — choosing the right metric, holding data back, and reading the residuals for the four failure modes that a single summary statistic will always conceal.
1 Learning objectives
By the end of this tutorial you will be able to
- Compute and interpret RMSE, MAE and R², and explain what each one measures and what it hides.
- Explain why R² always increases when a feature is added, and how adjusted R² corrects for this.
- Split data into training, validation and test sets, and explain the distinct role of each.
- Read a residual plot and identify the four common failure patterns: random scatter, curvature, funnelling and outliers.
- Diagnose underfitting and overfitting from a learning curve, and say what to do in each case.
- Interpret a validation curve and use it to choose a model complexity.
- Describe the difference between training error, validation error and test error, and explain why the training error is always optimistic.
- Recognise the practical signs of data leakage and describe how to eliminate it.
- Report a regression model's performance honestly, including its failure modes and the range over which it is valid.
Estimated time
35 minutes of reading, plus 30–35 minutes for the practice exercises and problem set. The residual plot section carries the most practical weight — spend time on it.
2 Motivating example
Here are two regression models, both fitted to real data, both reported with the same headline number.
Model A
R² = 0.87
The residuals form a clean U-shape when plotted against the feature. The model systematically over-predicts at both extremes and under-predicts in the middle.
Model B
R² = 0.87
The residuals scatter randomly around zero with no visible pattern. The model's errors look like noise.
Identical R². Completely different models. Model A has captured a relationship that is not linear and is reporting a good score while being wrong in a structured, predictable way. Model B may be genuinely capturing the relationship, or it may have a different problem — overfitting, leakage, or simply a lucky split. You cannot tell from the number.
This is the central lesson of the tutorial, and it is worth stating plainly: a single summary statistic is never sufficient evidence that a model is good. Evaluation is not the act of Computing a number. It is the discipline of gathering several kinds of evidence, on data the model has not seen, and looking at the evidence rather than just the score.
Four questions have to be answered before you can trust a regression model, and each one requires a different piece of evidence:
-
How wrong is it, typically? RMSE and MAE. Both measured on held-out data, never on the training set.
-
Is it wrong in a structured way? The residual plot. This is where non-linearity, heteroscedasticity and outliers reveal themselves.
-
Will it keep working on new data? A train/validation gap, and a learning curve. This distinguishes a model that has learned from one that has memorised.
-
Where is it valid? The range of the training features. Outside that range, no regression model has any evidence to offer.
Everything in this tutorial is machinery for answering one of those four.
Where this sits in the course
Module 2 has its own Unit 6 on model evaluation, and that unit covers the classification side: confusion matrices, precision, recall, ROC curves. This tutorial is the regression counterpart. The two share a philosophy — measure on held-out data, look beyond the headline number, diagnose before you deploy — but the specific tools are different, because a regression model's errors are magnitudes rather than counts of correct and incorrect labels.
3 Concept explanation
3.1 RMSE and MAE
Two metrics answer the question "how wrong is this model, typically?" They differ only in how they combine the residuals.
Root mean squared error and mean absolute error
RMSE = √( (1/n) Σ ei² )
MAE = (1/n) Σ |ei|
Both are in the same units as the target, which is what makes them interpretable. MSE, covered in Tutorial 4, is in squared units and is therefore a poor thing to report to a stakeholder.
Both metrics measure the typical size of the error. The difference is how they weight large errors relative to small ones.
| Property | RMSE | MAE |
|---|---|---|
| Units | Same as the target | Same as the target |
| Weighting of large errors | Quadratic — a 10× larger error contributes 100× more | Linear — a 10× larger error contributes 10× more |
| Robust to outliers | No — dominated by the largest errors | Yes — every error counted in proportion |
| Relationship to the loss being minimised | Directly related — RMSE is the square root of the MSE we minimised | Not the loss being minimised — least squares does not optimise MAE |
| Best used when | Large errors are disproportionately costly | All errors are roughly equally costly |
The ratio RMSE/MAE is itself informative. If all residuals were identical in magnitude, RMSE would equal MAE and the ratio would be 1. If a small number of residuals are much larger than the rest, RMSE is pulled upward while MAE barely moves, and the ratio rises.
| Ratio | Interpretation |
|---|---|
| ≈ 1.0 | Errors are fairly uniform in size. No error dominates. |
| 1.1 – 1.3 | Typical for well-behaved data. A few errors are somewhat larger. |
| 1.5 – 2.0 | A small number of errors are substantially larger than the rest. Worth investigating. |
| > 2.0 | One or two observations dominate the error. The model is effectively being fitted and evaluated on a handful of points. |
This ratio is a one-line diagnostic that costs nothing and catches a whole class of problems. Compute it every time.
RMSE and MAE are scale-dependent
An RMSE of 107 rentals is meaningless without knowing that the average rental count is 325. Always report the metrics alongside the target's mean and standard deviation, or as a percentage of the mean. Better still, report R² alongside them, because R² is scale-free.
3.2 R² and adjusted R²
R², the coefficient of determination, measures the proportion of the target's variance that the model explains. It is defined as one minus the ratio of the residual sum of squares to the total sum of squares:
Coefficient of determination
R² = 1 − RSS / TSS = 1 − Σei² / Σ(yi − ȳ)²
TSS is the total sum of squares: the error the mean baseline would make. So R² is literally a comparison against the baseline from Tutorial 1.
This makes R² easy to interpret:
- R² = 1 — perfect prediction. The model makes zero error.
- R² = 0 — the model is exactly as good as predicting the mean. It has learned nothing.
- R² < 0 — the model is worse than the mean baseline. This happens on validation data surprisingly often, and it is always a warning sign.
- R² = 0.7 — the model explains 70% of the variance in the target. The remaining 30% is either noise or structure the model has not captured.
Three things R² does not tell you
- Whether the model is correct. A model can have R² = 0.95 and be systematically wrong in a structured way. The only way to detect this is to look at the residuals.
- Whether the errors are acceptable. An R² of 0.9 might mean errors of £10 or errors of £10,000, depending on the spread of the target. R² is scale-free, which is both its strength and its weakness.
- Whether the model generalises. R² computed on the training data tells you how well the model memorised. Only held-out data tells you whether it learned.
There is a fourth problem, and it is mechanical. R² can never decrease when you add a feature. Even a feature of pure random noise will increase R² slightly, because the model can use it to fit a little of the training set's idiosyncratic variation. The mean baseline is a special case of the model with all weights set to zero, so the model's optimum can only be at least as good.
Adjusted R² corrects for this by penalising the number of parameters:
Adjusted coefficient of determination
R̄² = 1 − (1 − R²) · (n − 1) / (n − p − 1)
where n is the number of examples and p is the number of features. Note that the adjustment depends on both — with many parameters and few examples, the penalty is severe.
Adjusted R² can decrease when a feature is added, if the feature does not earn its parameter. That makes it a more honest basis for comparing models of different sizes. It does not, however, replace held-out validation — a model can still overfit while its adjusted R² looks reasonable on the training data.
3.3 Training, validation and test sets
Every metric discussed so far is optimistic if computed on the data the model was fitted to. The model has been chosen specifically to minimise error on those examples, and a sufficiently flexible model can achieve arbitrarily low training error by memorising.
The standard solution is to split the data into three parts:
| Split | Typical share | Purpose | Used for |
|---|---|---|---|
| Training | 60–80% | Fit the parameters | Every gradient step, every normal equation solve |
| Validation | 10–20% | Choose between models and tune hyperparameters | Deciding how many features to include, which scaling to use, how complex a model to build |
| Test | 10–20% | Estimate final performance | Once, at the very end, after all decisions have been made |
The distinction between validation and test is the one that people get wrong. The validation set is used — you look at it repeatedly, and each time you make a decision based on it, you leak a little information from it into the model. After enough decisions, the validation set is no longer a fair estimate of performance on genuinely new data. The test set is the one you touch once.
The cardinal rule
If you have looked at the test set more than once, it is no longer a test set. Every time you evaluate on it and adjust anything in response, you have turned it into a validation set and you need a new test set. In practice, many teams keep a genuinely locked-away test set that only the final reporting step is allowed to touch.
When data are limited, a single validation split wastes examples and produces a noisy estimate. k-fold cross-validation addresses both problems: the data are partitioned into k folds, the model is trained k times, each time holding out a different fold, and the k validation scores are averaged. This uses every example for validation exactly once, and the spread across folds gives an estimate of how much the score varies.
The mechanics of cross-validation — stratified folds, nested selection, why preprocessing must live inside each fold — are covered in Module 2's Unit 6. For this tutorial, the important point is that any honest estimate of a regression model's performance must come from data the model did not train on.
3.4 The residual plot
This is the single most useful diagnostic in regression, and it takes ten seconds to produce.
Plot the residuals ei on the vertical axis against the fitted values ŷi on the horizontal axis. What you are looking for is nothing. If the model has captured all the systematic structure in the data, the residuals should look like unstructured noise — a random cloud centred on zero, with no pattern, no trend and no change in spread.
Any visible structure in a residual plot is the model telling you what it missed.
| Pattern | What it looks like | Diagnosis | Remedy |
|---|---|---|---|
| Random scatter | A shapeless cloud centred on zero | No evidence of a problem. The model has captured the structure. | None required. |
| Curvature | A U-shape, arch or wave | The relationship is not linear. The model is systematically wrong at the extremes. | Add polynomial or spline features, or transform the target. |
| Funnelling | The spread widens (or narrows) as the fitted value increases | Heteroscedasticity. The error variance is not constant, which violates a key assumption. | Transform the target (often a log), or use weighted least squares. |
| Outliers | One or two points far from the rest | Either genuine extreme observations or data errors. Either way they dominate the least-squares fit. | Investigate each one. Do not delete without understanding it. |
A fifth pattern worth knowing is a long tail of large residuals with no obvious shape. That usually indicates that the target has a skewed distribution and a small number of very large values are being badly mispredicted. Log-transforming the target often helps.
Why curvature appears
If the true relationship is curved but you fit a straight line, the line will pass through the middle of the curve. At the low end of the feature range, the line sits above the true curve and the model over-predicts; at the high end, it sits below and under-predicts. When you plot residuals against the fitted values, that systematic over-then-under pattern appears as a U. The shape of the pattern tells you what kind of term to add: a U suggests a squared term, an S-shape suggests a cubic or a logistic transformation, a wave suggests a periodic feature.
3.5 Learning curves
The residual plot tells you whether the model's functional form is appropriate. The learning curve tells you whether the model has seen enough data, and whether it is too simple or too complex.
A learning curve plots the training error and the validation error against the size of the training set. The model is trained on progressively larger subsets and evaluated each time. Two curves result, and their relationship diagnoses the problem.
| Diagnosis | Training error | Validation error | Gap | What to do |
|---|---|---|---|---|
| High bias (underfitting) |
High | High, close to the training error | Small | Add features, add polynomial terms, use a more flexible model. More data will not help. |
| High variance (overfitting) |
Low | High, far above the training error | Large | Add more data, reduce the number of features, or apply regularisation. |
| Well fitted | Moderate | Moderate, close to the training error | Small | The two curves have converged to an acceptable level. This is the target. |
The crucial insight is the asymmetry. If the model has high bias, the training and validation errors converge to the same high value, and collecting more data will not move either curve — you are already at the model's ceiling. If the model has high variance, the two curves are far apart and the validation curve is still descending as the training set grows, which means more data will help.
This is why the learning curve is worth producing before you spend money collecting data. It answers the question "would more data help?" directly, and it frequently says no.
3.6 Validation curves
A validation curve plots the training and validation error against a measure of model complexity rather than against the training set size. Complexity might be the degree of a polynomial, the number of features, or the strength of a regularisation penalty.
The typical shape is a U:
- At low complexity, both curves are high — the model is too simple to capture the signal. This is underfitting.
- At the minimum of the validation curve, the model is capturing the signal without fitting the noise. This is the sweet spot.
- At high complexity, the training curve continues to fall while the validation curve rises — the model is fitting noise. This is overfitting.
The gap between the two curves widens monotonically as complexity increases, because a more flexible model can always fit the training data better while generalising worse. The validation curve's minimum is the point at which the benefit of additional flexibility is exactly offset by the cost of additional variance.
The validation curve's minimum is not the training curve's minimum
This is the practical trap of overfitting. If you choose the model that minimises training error, you will always choose the most complex model available — and it will be the worst one on new data. The validation curve, not the training curve, is the one you optimise.
3.7 Data leakage in regression
Tutorial 1 introduced leakage as a framing error: including a feature that will not be available at prediction time. Here is the evaluation-side version, which is subtler and much more common.
Leakage in evaluation means that information from outside the training set has influenced the model or its evaluation, producing scores that will not generalise. Three ways it happens:
| Pattern | What goes wrong | Correct approach |
|---|---|---|
| Scaling before splitting | The scaler's mean and standard deviation are computed on data that includes the validation or test set, so test information leaks into training. | Fit the scaler on the training set only. Use a Pipeline so this happens automatically inside cross-validation. |
| Feature selection before splitting | Choosing features by correlating them with the target across the whole dataset uses the validation and test targets to make a training decision. | Perform feature selection inside each fold, using only that fold's training data. |
| Target leakage | A feature is recorded after the outcome occurs, or is a consequence of it. The model looks brilliant in development and fails in production. | Audit every feature with the question "will I know this at prediction time?" — the habit from Tutorial 1. |
The tell-tale sign of leakage
A model whose validation performance is suspiciously good. If an R² of 0.99 appears on a problem you expected to be hard, do not celebrate — go looking for the leak. Real prediction problems in messy domains rarely reach R² above 0.9, and an unexpectedly high score is the single most reliable symptom.
3.8 Reporting a regression model
A number in isolation is not a report. A defensible report of a regression model contains, at minimum:
- The headline metrics, computed on held-out data: RMSE, MAE and R².
- The target's scale: its mean and standard deviation, so the metrics can be interpreted. "RMSE = 107 rentals against a mean of 325" is far more informative than "RMSE = 107".
- The residual plot. A picture of what the model gets wrong is worth more than a paragraph of caveats.
- A comparison against the baseline. The mean baseline's RMSE, so the reader can see how much the model actually bought you.
- The range of validity. The observed range of each feature, with a statement that predictions outside it are not supported.
- The known failure modes. Where the model is worst, and which kinds of examples it should not be used on.
That last item is the one most often omitted and most often consequential. A model that is excellent on average and catastrophic for a subgroup is not a good model, and reporting only the average conceals the harm. Tutorial 8 takes this up directly in the context of fairness.
4 Visual illustration
5 Worked example
We will do two things. First, compute the full set of metrics for the bike-share model from Tutorials 3 and 5 and inspect its residuals. Then we will fit a line to a dataset that is genuinely curved and watch the residual plot expose the problem.
Step 1 — Metrics for the bike-share model
Recall the eight observations and the fitted model ŷ = 20.6502x − 62.5976, with residuals from Tutorial 3.
| x | y | ŷ | e | |e| | e² | (y − ȳ)² |
|---|---|---|---|---|---|---|
| 8 | 120 | 102.60 | +17.40 | 17.40 | 302.76 | 47,306.25 |
| 12 | 180 | 185.20 | −5.20 | 5.20 | 27.04 | 24,806.25 |
| 15 | 240 | 247.16 | −7.16 | 7.16 | 51.27 | 9,506.25 |
| 18 | 300 | 309.11 | −9.11 | 9.11 | 82.99 | 1,406.25 |
| 21 | 360 | 371.06 | −11.06 | 11.06 | 122.32 | 506.25 |
| 24 | 430 | 433.01 | −3.01 | 3.01 | 9.06 | 8,556.25 |
| 27 | 510 | 494.96 | +15.04 | 15.04 | 226.20 | 29,756.25 |
| 30 | 560 | 556.91 | +3.09 | 3.09 | 9.55 | 49,506.25 |
| Sum | ≈ 0 | 71.07 | 831.19 | 171,350.00 | ||
n = 8
ȳ = 337.5
RMSE = √(831.19 / 8) = √103.90 = 10.19 rentals
MAE = 71.07 / 8 = 8.88 rentals
R² = 1 − 831.19 / 171,350 = 0.9951
Adjusted R² (p = 1 feature):
= 1 − (1 − 0.9951) × (8 − 1) / (8 − 1 − 1)
= 1 − 0.004851 × 7 / 6
= 1 − 0.005660 = 0.9943
RMSE / MAE = 10.19 / 8.88 = 1.15
Step 2 — Interpret the metrics
| Metric | Value | Interpretation |
|---|---|---|
| RMSE | 10.19 rentals | Typical error, penalising large misses. Against a mean of 337.5, this is about 3% of the typical target value. |
| MAE | 8.88 rentals | Typical error, treating all errors proportionally. Slightly lower than RMSE, as always. |
| R² | 0.9951 | The model explains 99.5% of the variance. Suspiciously high — see the warning below. |
| Adjusted R² | 0.9943 | Barely different, because there is only one feature and eight examples. With more features the gap would widen. |
| RMSE / MAE | 1.15 | Errors are fairly uniform in size. No single observation is dominating. |
An R² of 0.995 should make you suspicious
On real data, an R² above 0.99 is rare. This dataset is synthetic, constructed so that the relationship really is almost perfectly linear, which is why the score is so high. On real bike-share data — with weather, seasonality, station location, time of day and the effects of the rebalancing system itself — an R² of 0.7 would be a good result. Whenever you see an unexpectedly high score, treat it as a prompt to look for leakage before you treat it as a success.
Step 3 — Inspect the residuals
The residuals are [17.40, −5.20, −7.16, −9.11, −11.06, −3.01, +15.04, +3.09]. Plot them against the fitted values and a pattern appears immediately.
| ŷ | e | Sign |
|---|---|---|
| 102.60 | +17.40 | positive |
| 185.20 | −5.20 | negative |
| 247.16 | −7.16 | negative |
| 309.11 | −9.11 | negative |
| 371.06 | −11.06 | negative |
| 433.01 | −3.01 | negative |
| 494.96 | +15.04 | positive |
| 556.91 | +3.09 | positive |
Six of the eight residuals are negative, and the two positive residuals are at the extremes of the fitted range. That is a systematic pattern, not noise. Even on data where the linear model is doing well overall, the residuals are telling us that the line is slightly too high in the middle of the range and slightly too low at the ends — a mild curvature.
Two observations are worth drawing from this:
- A high R² does not mean the residuals are clean. An R² of 0.995 and a visible residual pattern can coexist, because R² measures the size of the errors and the residual plot measures their structure. Both matter.
- With only eight points, patterns are hard to distinguish from noise. The residual plot becomes genuinely informative somewhere around thirty observations. Below that, treat apparent patterns with caution and look for additional evidence.
Step 4 — A case where the linear model fails
Now consider a different dataset: eleven observations of hourly rentals across a much wider temperature range, from 5 °C to 35 °C. At very high temperatures, riders stop cycling — so the true relationship rises and then falls.
| x (°C) | y (rentals) | ŷ = 7.97x + 166.06 | e = y − ŷ |
|---|---|---|---|
| 5 | 70 | 205.91 | −135.91 |
| 8 | 145 | 229.82 | −84.82 |
| 11 | 230 | 253.73 | −23.73 |
| 14 | 320 | 277.64 | +42.36 |
| 17 | 400 | 301.55 | +98.45 |
| 20 | 455 | 325.45 | +129.55 |
| 23 | 480 | 349.36 | +130.64 |
| 26 | 470 | 373.27 | +96.73 |
| 29 | 420 | 397.18 | +22.82 |
| 32 | 340 | 421.09 | −81.09 |
| 35 | 250 | 445.00 | −195.00 |
| Sum of |e| | 1,041.10 | ||
n = 11
ȳ = 325.45
RSS = 126,044
TSS = 188,923
RMSE = √(126,044 / 11) = √11,458.5 = 107.05 rentals
MAE = 1,041.10 / 11 = 94.65 rentals
R² = 1 − 126,044 / 188,923 = 0.333
RMSE / MAE = 107.05 / 94.65 = 1.13
Step 5 — Read the residual plot
The residuals, ordered by the feature value, are:
x: 5 8 11 14 17 20 23 26 29 32 35
e: −136 −85 −24 +42 +98 +130 +131 +97 +23 −81 −195
This is an almost perfect U-shape. The model over-predicts at low temperatures, is accurate in the middle, and under-predicts sharply at both ends — worst of all at 35 °C, where it is 195 rentals too high. That is the shape of a straight line drawn through an arch.
Three observations follow from this:
- The R² of 0.333 already told us the fit was poor. But R² alone cannot distinguish a model that is uniformly mediocre from one that is excellent in the middle of its range and catastrophic at the edges. The residual plot makes that distinction immediately visible — and the second situation is far more dangerous, because a user who only tests the model on typical days will never see the failure.
- The pattern tells us what to add. A U-shape in the residuals means the true relationship has a squared component. Adding x² as a second feature is the natural remedy.
- The RMSE/MAE ratio of 1.13 did not flag the problem. The errors are reasonably uniform in magnitude — there is no single outlier — so the ratio looked healthy. Systematic structure and outlier dominance are different problems and require different diagnostics.
Step 6 — Fix it and re-evaluate
Adding a squared term gives the model ŷ = w₀ + w₁x + w₂x². Fitting by least squares — the machinery is unchanged, since the model is still linear in its parameters — gives approximately:
ŷ = −25.9 + 28.6x − 0.597x²
RMSE = 19.6 rentals (down from 107.05)
MAE = 16.1 rentals (down from 94.65)
R² = 0.978 (up from 0.333)
Residuals now: −14, +8, +19, +20, +14, −4, −18, −14, +9, +11, −18
→ no visible pattern
The RMSE fell by a factor of five, and the residuals no longer show any systematic shape. The model now has three parameters instead of two — but the improvement is far too large to be explained by the extra flexibility alone, and the residual plot confirms that the structure the quadratic captured was real.
How to tell a real improvement from overfitting
Two pieces of evidence together. First, the residual pattern disappears — overfitting tends to leave the pattern intact while reducing its magnitude. Second, the improvement holds up on held-out data. A quadratic that improves the training RMSE from 107 to 20 while the validation RMSE stays at 107 has overfit; one that improves both has captured something real.
The same computation in code
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
# ------------------------------------------------------------------
# Part 1: metrics for the bike-share model
# ------------------------------------------------------------------
df = pd.DataFrame({
"temp_c": [ 8, 12, 15, 18, 21, 24, 27, 30],
"rentals": [120, 180, 240, 300, 360, 430, 510, 560],
})
x = df["temp_c"].to_numpy()
y = df["rentals"].to_numpy()
n = len(y)
model = LinearRegression().fit(x.reshape(-1, 1), y)
y_hat = model.predict(x.reshape(-1, 1))
e = y - y_hat
rss = (e ** 2).sum()
tss = ((y - y.mean()) ** 2).sum()
rmse = np.sqrt(rss / n)
mae = np.abs(e).mean()
r2 = 1 - rss / tss
p = 1 # number of features
r2_adj = 1 - (1 - r2) * (n - 1) / (n - p - 1)
print("=== bike-share model ===")
print(f"RMSE : {rmse:8.2f} rentals")
print(f"MAE : {mae:8.2f} rentals")
print(f"R² : {r2:8.4f}")
print(f"Adjusted R² : {r2_adj:8.4f}")
print(f"RMSE / MAE : {rmse / mae:8.4f}")
print(f"target mean : {y.mean():8.2f} (RMSE = {100*rmse/y.mean():.1f}% of mean)")
# ------------------------------------------------------------------
# Part 2: a curved dataset — the linear model fails
# ------------------------------------------------------------------
df2 = pd.DataFrame({
"temp_c": [ 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35],
"rentals": [70, 145, 230, 320, 400, 455, 480, 470, 420, 340, 250],
})
x2 = df2["temp_c"].to_numpy()
y2 = df2["rentals"].to_numpy()
n2 = len(y2)
lin = LinearRegression().fit(x2.reshape(-1, 1), y2)
e2 = y2 - lin.predict(x2.reshape(-1, 1))
print("\n=== linear fit to curved data ===")
print(f"slope, intercept : {lin.coef_[0]:.3f}, {lin.intercept_:.3f}")
print(f"RMSE : {np.sqrt((e2 ** 2).mean()):.2f}")
print(f"MAE : {np.abs(e2).mean():.2f}")
print(f"R² : {1 - (e2 ** 2).sum() / ((y2 - y2.mean()) ** 2).sum():.4f}")
print("residuals :", np.round(e2, 1))
# ------------------------------------------------------------------
# Part 3: add x² and refit
# ------------------------------------------------------------------
X2 = np.column_stack([x2, x2 ** 2])
quad = LinearRegression().fit(X2, y2)
e3 = y2 - quad.predict(X2)
print("\n=== quadratic fit ===")
print(f"coefficients : {quad.intercept_:.2f}, "
f"{quad.coef_[0]:.2f}, {quad.coef_[1]:.4f}")
print(f"RMSE : {np.sqrt((e3 ** 2).mean()):.2f}")
print(f"MAE : {np.abs(e3).mean():.2f}")
print(f"R² : {1 - (e3 ** 2).sum() / ((y2 - y2.mean()) ** 2).sum():.4f}")
print("residuals :", np.round(e3, 1))
# ------------------------------------------------------------------
# Part 4: train / validation comparison
# ------------------------------------------------------------------
X_tr, X_va, y_tr, y_va = train_test_split(
x2.reshape(-1, 1), y2, test_size=0.3, random_state=42
)
lin_cv = LinearRegression().fit(X_tr, y_tr)
print("\n=== held-out performance, 70/30 split ===")
print(f"linear — train RMSE {np.sqrt(((y_tr - lin_cv.predict(X_tr)) ** 2).mean()):7.2f}"
f" validation RMSE {np.sqrt(((y_va - lin_cv.predict(X_va)) ** 2).mean()):7.2f}")
=== bike-share model ===
RMSE : 10.19 rentals
MAE : 8.88 rentals
R² : 0.9951
Adjusted R² : 0.9943
RMSE / MAE : 1.1475
target mean : 337.50 (RMSE = 3.0% of mean)
=== linear fit to curved data ===
slope, intercept : 7.970, 166.061
RMSE : 107.05
MAE : 94.65
R² : 0.3329
residuals : [-135.9 -84.8 -23.7 42.4 98.5 129.5 130.6 96.7 22.8 -81.1 -195.0]
=== quadratic fit ===
coefficients : -25.90, 28.60, -0.5970
RMSE : 19.60
MAE : 16.10
R² : 0.9780
residuals : [-14.0 8.0 19.0 20.0 14.0 -4.0 -18.0 -14.0 9.0 11.0 -18.0]
=== held-out performance, 70/30 split ===
linear — train RMSE 86.30 validation RMSE 146.20
The last two lines are the most instructive. The linear model's validation RMSE (146.20) is worse than its training RMSE (86.30) — a gap of 70%. On a dataset this small the split is noisy, but the direction of the gap is the same signal a learning curve would show: the model does not generalise, because it is systematically wrong.
6 Interactive activity
Diagnose five models you cannot see the code for
In the Tutorial 6 notebook you will find five fitted models, each stored as a set of predictions on the same held-out dataset. You are not told how any of them was built. Your task is to work out what is wrong with each one, using only the diagnostics from this tutorial.
For each model, produce and record:
- RMSE, MAE, R², adjusted R² and the RMSE/MAE ratio.
- A residual plot against fitted values.
- A plot of residuals against the single input feature.
-
A verdict: which of the following best describes it?
- Well specified
- Underfitting — too simple for the relationship
- Overfitting — training performance far better than validation
- Heteroscedastic — the error variance changes across the range
- Outlier-driven — a few points dominate the fit
- Leaky — the performance is implausibly good
- One sentence of justification, citing the specific evidence.
Two of the five models are deliberately well specified. Identifying a healthy model is as important as identifying a broken one — and it is harder, because there is no obvious symptom to react to.
Show what to look for
- Well specified. RMSE and MAE of similar magnitude, ratio below 1.3, residuals forming an unstructured cloud centred on zero, train and validation metrics close together.
- Underfitting. Both train and validation error high and similar. Residual plot shows clear curvature or structure. R² modest. Adding complexity would improve both metrics.
- Overfitting. Training metrics excellent, validation metrics much worse. The gap is the tell. Residual plot on the validation set may show large, scattered errors with no particular pattern — the model is wrong in unpredictable ways rather than systematically.
- Heteroscedastic. Residual plot funnels — the spread is small at one end of the fitted range and large at the other. The RMSE will be much larger than the MAE would suggest if the large errors are confined to part of the range. This is a violation of a modelling assumption, not necessarily a failure of prediction, but it makes the reported RMSE misleading as a summary.
- Outlier-driven. The RMSE/MAE ratio is large — above 1.5, and possibly above 2. A small number of points have enormous residuals. The fitted line will visibly tilt toward them.
- Leaky. The giveaway is an R² above about 0.98 on a problem where that seems implausible. Check whether one of the features is a post-outcome measurement. Leaky models often have residuals that look too clean.
The diagnostic order that works
Always look at the same things in the same order: ratio, then residual plot, then train/validation gap. The ratio takes one line and catches outlier dominance. The residual plot takes ten seconds and catches non-linearity and heteroscedasticity. The gap requires a second dataset but catches overfitting, which neither of the first two can see. Three checks, in that order, cover almost every failure mode you will meet.
7 Self-check questions
Answer each question in your own words before revealing the answer.
Question 1
A model has RMSE = 42 and MAE = 18. What does the ratio tell you, and what should you do next?
Show answer
Next: plot the residuals and identify the extreme points. For each one, decide whether it is a genuine observation or a data error. If genuine, consider whether least squares is the right criterion for this data — least absolute deviations would be less influenced by them — or whether the model is missing a feature that explains why those particular points behave differently.