Skip to content

Introduction to Regression Analysis

What you’ll learn

  • what separates a regression problem from a classification problem
  • the general linear model y^=θx\hat{y} = \boldsymbol{\theta}^\top \mathbf{x}, and why the bias term gets folded into the feature vector
  • the two ways to fit it — closed form and iterative — and when each wins
  • how to compute a baseline first, and why a model that cannot beat it is worthless
  • the assumptions behind linear regression, in the order you should check them
  • the vocabulary the rest of Phase 3 relies on

Intuition

Regression answers how much, not which one. How much will this house sell for, how many units will ship next quarter, how many days until this machine fails. The answer is a number on a continuous scale, and being close counts — predicting 302 when the truth is 300 is nearly right, in a way that predicting “cat” when the truth is “dog” never is.

That single difference drives everything: the loss functions, the metrics, even the plots.

figureSame features, different target typematplotlib
Two panels. Left: a scatter of size against price with a fitted line, labelled regression. Right: the same feature with points at two discrete heights, labelled classification.Two panels. Left: a scatter of size against price with a fitted line, labelled regression. Right: the same feature with points at two discrete heights, labelled classification.
Regression outputs land anywhere on a line. Classification outputs land in one of a fixed set of buckets. The features can be identical; the job is not.
diagram Diagram mermaid

The math

The general linear model

With nn features, a linear model computes a weighted sum plus a bias:

y^=θ0+θ1x1+θ2x2++θnxn\hat{y} = \theta_0 + \theta_1 x_1 + \theta_2 x_2 + \cdots + \theta_n x_n

Carrying that leading θ0\theta_0 around separately is a nuisance, so by convention we prepend a constant feature x0=1x_0 = 1 to every instance. The model collapses into a dot product:

y^=θx=j=0nθjxj,x=[1x1xn],θ=[θ0θ1θn]\hat{y} = \boldsymbol{\theta}^\top \mathbf{x} = \sum_{j=0}^{n} \theta_j x_j, \qquad \mathbf{x} = \begin{bmatrix} 1 \\ x_1 \\ \vdots \\ x_n \end{bmatrix}, \quad \boldsymbol{\theta} = \begin{bmatrix} \theta_0 \\ \theta_1 \\ \vdots \\ \theta_n \end{bmatrix}

For a whole dataset of mm instances stacked into a matrix XRm×(n+1)\mathbf{X} \in \mathbb{R}^{m \times (n+1)}, all predictions at once are one matrix-vector product:

y^=Xθ\hat{\mathbf{y}} = \mathbf{X}\boldsymbol{\theta}

Training means choosing θ\boldsymbol{\theta}

Fitting is a search for the parameter vector that makes a cost function smallest. Almost always that cost is mean squared error:

J(θ)=1mi=1m(θx(i)y(i))2J(\boldsymbol{\theta}) = \frac{1}{m}\sum_{i=1}^{m}\left(\boldsymbol{\theta}^\top\mathbf{x}^{(i)} - y^{(i)}\right)^2

There are exactly two routes to the minimum, and Phase 3 covers both:

diagram Diagram mermaid
Normal EquationGradient Descent
Cost in features nnO(n2.4)O(n^{2.4}) to O(n3)O(n^3) — cubic-ishLinear
Cost in samples mmLinearLinear per epoch
Feature scaling neededNoYes
Works out of coreNoYes (stochastic / mini-batch)
HyperparametersNoneLearning rate, epochs
Comfortable up to~10,000 featuresMillions

Worked example by hand

Three houses, two features. Price in thousands, size in hundreds of square feet:

iix0x_0size x1x_1bedrooms x2x_2actual yy
11102145
21153210
31204280

Suppose a model arrives with θ=[50, 6, 18]\boldsymbol{\theta} = [50,\ 6,\ 18]^\top — a base price of 50, six thousand per hundred square feet, eighteen thousand per bedroom.

Predictions. Each one is a dot product:

y^(1)=50+6(10)+18(2)=50+60+36=146\hat{y}^{(1)} = 50 + 6(10) + 18(2) = 50 + 60 + 36 = 146
y^(2)=50+6(15)+18(3)=50+90+54=194\hat{y}^{(2)} = 50 + 6(15) + 18(3) = 50 + 90 + 54 = 194
y^(3)=50+6(20)+18(4)=50+120+72=242\hat{y}^{(3)} = 50 + 6(20) + 18(4) = 50 + 120 + 72 = 242

Errors.

iiy^(i)\hat{y}^{(i)}y(i)y^{(i)}errorsquared
1146145+11
2194210−16256
3242280−381444
1701
J(θ)=17013=567,RMSE=56723.8J(\boldsymbol{\theta}) = \frac{1701}{3} = 567, \qquad \text{RMSE} = \sqrt{567} \approx 23.8

So this θ\boldsymbol{\theta} is off by about 23,800 on a typical house — and it under-predicts more as houses get bigger, which is the tell that the size coefficient is too small. Training is the process of finding the θ\boldsymbol{\theta} that drives JJ as low as it will go.

See it move

Each bar below is one term of the weighted sum. Watch them accumulate left to right into the final prediction — that is all a linear model does at inference time.

sketch Building a prediction term by term p5.js
Each bar is one term of the weighted sum; they build up left-to-right until the total becomes the prediction.

From scratch

The hypothesis function is four lines of NumPy:

hypothesis.py
import numpy as np
 
 
def add_bias(X):
    """Prepend the x0 = 1 column so the bias rides inside the dot product."""
    X = np.asarray(X, dtype=float)
    return np.c_[np.ones(len(X)), X]
 
 
def predict(X_with_bias, theta):
    """All m predictions at once: one matrix-vector product."""
    return X_with_bias @ theta
 
 
X = [[10, 2], [15, 3], [20, 4]]        # size (100 sqft), bedrooms
y = np.array([145.0, 210.0, 280.0])
theta = np.array([50.0, 6.0, 18.0])    # bias, w_size, w_beds
 
Xb = add_bias(X)
print(Xb)
# [[ 1. 10.  2.]
#  [ 1. 15.  3.]
#  [ 1. 20.  4.]]
 
pred = predict(Xb, theta)
print(pred)                                             # [146. 194. 242.]
print(f"MSE  = {((pred - y) ** 2).mean():.1f}")         # MSE  = 567.0
print(f"RMSE = {((pred - y) ** 2).mean() ** 0.5:.1f}")  # RMSE = 23.8
hypothesis.py
import numpy as np
 
 
def add_bias(X):
    """Prepend the x0 = 1 column so the bias rides inside the dot product."""
    X = np.asarray(X, dtype=float)
    return np.c_[np.ones(len(X)), X]
 
 
def predict(X_with_bias, theta):
    """All m predictions at once: one matrix-vector product."""
    return X_with_bias @ theta
 
 
X = [[10, 2], [15, 3], [20, 4]]        # size (100 sqft), bedrooms
y = np.array([145.0, 210.0, 280.0])
theta = np.array([50.0, 6.0, 18.0])    # bias, w_size, w_beds
 
Xb = add_bias(X)
print(Xb)
# [[ 1. 10.  2.]
#  [ 1. 15.  3.]
#  [ 1. 20.  4.]]
 
pred = predict(Xb, theta)
print(pred)                                             # [146. 194. 242.]
print(f"MSE  = {((pred - y) ** 2).mean():.1f}")         # MSE  = 567.0
print(f"RMSE = {((pred - y) ** 2).mean() ** 0.5:.1f}")  # RMSE = 23.8

Exactly the hand calculation, to the digit.

Baselines matter

Before any model, compute the score of the laziest possible predictor. For regression that is “always predict the mean of the training targets”. Everything else has to beat it, and it is startling how often something does not:

baselines.py
from sklearn.datasets import load_diabetes
from sklearn.dummy import DummyRegressor
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
 
X, y = load_diabetes(return_X_y=True)
 
models = {
    "mean baseline": DummyRegressor(strategy="mean"),
    "linear regression": LinearRegression(),
    "decision tree": DecisionTreeRegressor(random_state=0),
    "random forest": RandomForestRegressor(n_estimators=120, random_state=0),
}
 
for name, model in models.items():
    rmse = -cross_val_score(
        model, X, y, cv=5, scoring="neg_root_mean_squared_error"
    ).mean()
    print(f"{name:<20} RMSE = {rmse:6.2f}")
 
# mean baseline        RMSE =  77.26
# linear regression    RMSE =  54.69
# decision tree        RMSE =  79.55
# random forest        RMSE =  57.79
baselines.py
from sklearn.datasets import load_diabetes
from sklearn.dummy import DummyRegressor
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
 
X, y = load_diabetes(return_X_y=True)
 
models = {
    "mean baseline": DummyRegressor(strategy="mean"),
    "linear regression": LinearRegression(),
    "decision tree": DecisionTreeRegressor(random_state=0),
    "random forest": RandomForestRegressor(n_estimators=120, random_state=0),
}
 
for name, model in models.items():
    rmse = -cross_val_score(
        model, X, y, cv=5, scoring="neg_root_mean_squared_error"
    ).mean()
    print(f"{name:<20} RMSE = {rmse:6.2f}")
 
# mean baseline        RMSE =  77.26
# linear regression    RMSE =  54.69
# decision tree        RMSE =  79.55
# random forest        RMSE =  57.79
figureFour models against the same baselinematplotlib
Bar chart of cross-validated RMSE: mean baseline 77.3, linear regression 54.7, decision tree 79.6, random forest 57.8.Bar chart of cross-validated RMSE: mean baseline 77.3, linear regression 54.7, decision tree 79.6, random forest 57.8.
A single unpruned decision tree scores worse than predicting the mean. Plain linear regression beats a 120-tree forest. Complexity is not competence.

Reading the plot

  • Linear regression wins. On 442 samples with ten mostly-linear features, the simplest model is also the best. That is common, and worth internalising early.
  • The lone decision tree loses to the baseline. It memorises the training set and generalises worse than a constant. Without a baseline you would never notice.
  • The forest recovers most of the gap but still loses. Extra capacity has to be paid for with signal that actually exists in the data.

Assumptions, in checking order

#AssumptionSymptom when violatedFirst move
1Relationship is linear in the parametersCurved residual plotPolynomial terms, log transform
2Observations are independentResiduals correlated over timeTime-series model
3Constant residual varianceFan shape in the residual plotTransform yy, weighted least squares
4Residuals roughly normalSkewed Q-Q plotFit is fine; intervals are not
5Features not perfectly collinearWild, unstable coefficientsDrop a feature, or use Ridge

Note the wording of the first row: linear in the parameters, not in the features. y^=θ0+θ1x+θ2x2\hat{y} = \theta_0 + \theta_1 x + \theta_2 x^2 is still a linear model — it is linear in θ\boldsymbol{\theta}, and that is what lets the same math fit a curve. Polynomial Regression is built entirely on that loophole.

Where regression shows up

DomainTargetTypical features
Real estateSale priceSize, location, age, rooms
RetailUnits sold next weekPrice, promotion, season, stock
EnergyLoad in megawattsTemperature, hour, day type
HealthcareDisease progressionLabs, BMI, blood pressure, age
FinanceExpected returnFundamentals, momentum, volatility
OperationsDelivery timeDistance, traffic, vehicle, weather

Pitfalls

quizCheck yourself
  1. Which of these is a regression problem?

    Show answer

    B — Predicting tomorrow's peak electricity demand in megawatts — The target is a continuous quantity on a numeric scale. The others are classification and clustering problems.

  2. Why do we prepend a constant feature equal to 1 to every instance?

    Show answer

    B — So the bias term is absorbed into the dot product instead of being handled separately — With x0 = 1 the whole model becomes theta transpose x, which keeps the math and the code uniform. scikit-learn actually manages the intercept for you.

  3. Your model scores RMSE 76 and the mean baseline scores RMSE 77. What does that tell you?

    Show answer

    B — The model has learned almost nothing useful — Beating a constant predictor by one unit is essentially no signal. Either the features do not carry information about the target, or the model is the wrong shape for it.

  4. Is a model with an x-squared term still a linear model?

    Show answer

    B — Yes, because it is linear in the parameters even though it curves in the features — Linearity refers to the parameters. Treat x-squared as just another column and the ordinary least-squares machinery applies unchanged.

🧪 Try It Yourself

Exercise 1 – Build the feature vector

Exercise 2 – Predict with the hypothesis function

Exercise 3 – Score the parameters

Exercise 4 – Beat the mean baseline

Exercise 5 – A curve, still fitted by a linear model

Recap

  • Regression predicts a continuous number; closeness counts, which shapes every loss and metric.
  • Folding x0=1x_0 = 1 into the feature vector turns the model into a single dot product y^=θx\hat{y} = \boldsymbol{\theta}^\top\mathbf{x}.
  • Training means minimising a cost — via the Normal Equation for modest feature counts, via gradient descent for large ones.
  • Always compute the mean baseline. On the diabetes data it is RMSE 77.26; linear regression reaches 54.69 while a single decision tree manages only 79.55.
  • “Linear” describes the parameters, not the shape of the curve.

Exercise 6 – Price each model against the baseline

Next

Continue to Simple Linear Regression — the one-feature case, derived from scratch and worked through by hand on five points.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did