EduFlow

Section 2 · Regression

Linear Regression and Its Metrics

22 min3 min readPython exercise

Lesson at a glance

Fit a linear regression with scikit-learn, read its intercept and coefficients, and judge it with MAE, RMSE and R² against a mean-predicting baseline.

  1. 1One feature, one line
  2. 2Residuals and the loss
  3. 3All ten features
  4. 4Measuring a regression

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Churn is a yes/no question, and you’ll come back to it in section 3. Regression comes first because its simplest model, a straight line, lets you see exactly what “learning” means: picking numbers so that predictions miss by as little as possible.

The data is scikit-learn’s diabetes dataset: 442 patients, ten baseline measurements each (age, sex, body mass index, blood pressure and six blood serum readings s1–s6), and a target that measures how far the disease progressed one year later. The features are already centred and scaled, which is why they look like small decimals.

One feature, one line

Start with body mass index alone. Linear regression predicts progression = intercept + coef * bmi, and fit chooses the two numbers.

Python
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)

line = LinearRegression().fit(X_train[["bmi"]], y_train)
print("intercept:", round(line.intercept_, 1))
print("coef for bmi:", round(line.coef_[0], 1))
print("first predictions:", line.predict(X_test[["bmi"]])[:3].round(1))

The intercept (152) is the prediction for an average BMI, since the feature is centred at 0. The coefficient (975) is the slope: one unit of this scaled BMI adds 975 to the prediction. Note the double brackets in X_train[["bmi"]]: scikit-learn wants a 2D table even for one feature.

Residuals and the loss

How did fit choose those numbers? For each patient, the residual is the actual value minus the predicted value: the vertical gap between the point and the line. Ordinary least squares picks the intercept and slope that make the sum of squared residuals as small as possible. That sum, the thing being minimised, is the loss.

Scatter of points around a rising line. Dashed vertical segments join each point to the line; these residuals are squared and summed, and least squares picks the line that makes that sum smallest. bmi progression residual
Residuals are the vertical gaps between points and the line

Squaring does two jobs: it makes every miss count as positive, and it makes big misses count much more than small ones. A miss of 20 costs 400; a miss of 40 costs 1,600. Keep that in mind, because it explains the difference between two of the metrics below.

All ten features

Multiple regression is the same idea with more terms: one coefficient per feature, plus the intercept.

Python
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)

model = LinearRegression().fit(X_train, y_train)
for name, coef in zip(X.columns, model.coef_):
    print(f"{name:>4} {coef:8.1f}")

Measuring a regression

score gives R², but you should know three metrics and when each one fits.

Python
from sklearn.datasets import load_diabetes
from sklearn.dummy import DummyRegressor
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, root_mean_squared_error, r2_score
from sklearn.model_selection import train_test_split

X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)

models = {
    "mean (dummy)": (DummyRegressor(), X.columns),
    "bmi only": (LinearRegression(), ["bmi"]),
    "all features": (LinearRegression(), X.columns),
}
for name, (model, cols) in models.items():
    pred = model.fit(X_train[cols], y_train).predict(X_test[cols])
    print(f"{name:13} MAE {mean_absolute_error(y_test, pred):5.1f}  "
          f"RMSE {root_mean_squared_error(y_test, pred):5.1f}  R2 {r2_score(y_test, pred):6.3f}")
  • MAE (mean absolute error) is the average size of a miss, in the target’s units. “On average we’re off by 42 points” is a sentence a doctor or a manager understands. Use it to communicate.
  • RMSE (root mean squared error) is the square root of the average squared miss, also in target units. Because of the squaring it’s always at least as large as MAE, and much larger when a few predictions are badly wrong. Use it when big misses are disproportionately costly.
  • R² compares the model’s squared error with that of always predicting the mean: 1 is perfect, 0 is no better than predicting the mean, and negative is worse than the mean. It’s unitless, so it’s handy for comparing models on the same data, but it hides how big the misses are in real terms.

The full model cuts MAE from 65.5 (the mean baseline) to 41.5 and explains about half the variance. That’s honest progress, and also a reminder that half of what drives this disease isn’t in these ten columns.

You’ve used fit as a black box that returns the best line. Next lesson opens the box: you’ll write the search for the best line yourself, with gradient descent, the same idea that trains neural networks.

Key takeaways

  • Linear regression predicts intercept + coef1 * x1 + coef2 * x2 + … and picks the numbers that minimise the sum of squared residuals.
  • A residual is actual minus predicted; every regression metric is a different summary of the residuals.
  • MAE is the typical miss in the target’s own units; RMSE is in the same units but punishes big misses more.
  • R² is the share of variance explained compared with always predicting the mean: 0 means no better than the mean, and it can go negative.
  • Coefficients of correlated features can be large and opposite in sign, so don’t read them as importance or cause.

Further reading

Check your understanding

3 questions

Question 1 of 3

A model predicting next-year spend has MAE = $40 and RMSE = $150. What does the gap suggest?

Question 2 of 3

A model scores R² = -0.2 on the test set. What does that mean?

Question 3 of 3

In the diabetes model, s1 has coefficient -918 and s2 has +508. Both are cholesterol-related blood measurements. What’s the safest reading?

Practice

Python exercise

A three-feature regression, measured three ways

Fit a LinearRegression on body mass index, blood pressure and s5 (the columns in cols) and measure it on the test set.

  • model: the fitted regression.
  • pred: its predictions for X_test[cols].
  • mae, rmse, r2: the three metrics from this lesson, computed on the test set.
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, root_mean_squared_error, r2_score
from sklearn.model_selection import train_test_split

X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
cols = ["bmi", "bp", "s5"]

# 1. Fit a LinearRegression on the three columns in `cols`
model = LinearRegression()

# 2. Predict on the test rows and compute the three metrics
pred = None
mae = None
rmse = None
r2 = None
Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

(no output)

Checks

  • model is a fitted regression on three features
  • pred holds the test predictions
  • mae is the mean absolute error
  • rmse is the root mean squared error
  • r2 is the test R² (and RMSE ≥ MAE)