Polynomial Regression
What you’ll learn
Section titled “What you’ll learn”- why adding to the feature matrix lets a linear model fit a curve
- how to solve a quadratic fit by hand on five points
- how
PolynomialFeaturesexplodes combinatorially, and how fast - how to choose the degree with cross-validation and the one-standard-error rule
- how to read a learning curve to tell underfitting from overfitting
- why polynomial pipelines must scale their features
Intuition
Section titled “Intuition”A straight line through curved data is wrong in a specific, visible way: it misses low, then high, then low again. The residual plot shows a smile or a frown instead of a shapeless band.
The fix is disarmingly cheap. Do not change the algorithm — change the columns. Hand the model alongside , and least squares will find the best parabola exactly the way it found the best line, because the model is still a weighted sum of whatever columns you supply. Linear regression is linear in the parameters, and never had an opinion about the features.
flowchart LR X["Raw feature x"] --> PF["PolynomialFeatures(degree=2)"] PF --> C["Columns: x, x²"] C --> LR["The same LinearRegression"] LR --> Y["Curved prediction ŷ"]
The math
Section titled “The math”The model
Section titled “The model”A degree- polynomial in one feature:
Rename and it reads — an ordinary multiple linear regression. The Normal Equation applies unchanged:
That matrix has a name — the Vandermonde matrix — and a reputation: it becomes badly conditioned fast as grows, which is one reason high-degree fits misbehave numerically as well as statistically.
How many terms?
Section titled “How many terms?”With input features and degree , PolynomialFeatures generates every product of total
degree at most :
| features | degree 2 | degree 3 | degree 5 |
|---|---|---|---|
| 1 | 3 | 4 | 6 |
| 3 | 10 | 20 | 56 |
| 10 | 66 | 286 | 3,003 |
| 100 | 5,151 | 176,851 | ≈ 96 million |
Two features at degree 3 already give you interaction terms like — which is genuinely useful, and also how a tidy 10-column dataset becomes 286 columns without anyone deciding to do that.
Worked example by hand
Section titled “Worked example by hand”Five points, chosen symmetric about zero so that and . That kills half the terms in and makes the algebra tractable.
| 1 | −2 | 9 | 4 | −18 | 36 |
| 2 | −1 | 4 | 1 | −4 | 4 |
| 3 | 0 | 1 | 0 | 0 | 0 |
| 4 | 1 | 2 | 1 | 2 | 2 |
| 5 | 2 | 9 | 4 | 18 | 36 |
| 0 | 25 | 10 | −2 | 78 |
Step 1 — assemble the normal equations. With , , and :
Step 2 — the middle row is already solved. , so .
Step 3 — solve the remaining 2×2 system.
Substitute into :
The fitted curve is .
Step 4 — check.
| residual | squared | |||
|---|---|---|---|---|
| −2 | 9 | 9.4 | −0.4 | 0.16 |
| −1 | 4 | 3.2 | +0.8 | 0.64 |
| 0 | 1 | 1.0 | 0.0 | 0.00 |
| 1 | 2 | 2.8 | −0.8 | 0.64 |
| 2 | 9 | 8.6 | +0.4 | 0.16 |
| 0.0 | 1.60 |
Nothing here is new machinery. It is the Normal Equation from the previous page, applied to a design matrix that happens to contain a squared column.
See it move
Section titled “See it move”Three capacities cycling over the same curved dataset — watch the degree-12 curve chase individual points instead of the trend.
From scratch
Section titled “From scratch”Build the Vandermonde matrix explicitly and reuse the Normal Equation:
import numpy as np
def vandermonde(x, degree):
"""Columns [1, x, x^2, ..., x^degree]."""
x = np.asarray(x, dtype=float)
return np.vstack([x**k for k in range(degree + 1)]).T
x = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
y = np.array([9.0, 4.0, 1.0, 2.0, 9.0])
X = vandermonde(x, 2)
print(X)
# [[ 1. -2. 4.]
# [ 1. -1. 1.]
# [ 1. 0. 0.]
# [ 1. 1. 1.]
# [ 1. 2. 4.]]
theta = np.linalg.solve(X.T @ X, X.T @ y)
print(theta.round(4)) # [ 1. -0.2 2. ]
resid = y - X @ theta
print(resid.round(4)) # [-0.4 0.8 0. -0.8 0.4]
print(round((resid ** 2).sum(), 4)) # 1.6Same numbers as the hand calculation, including the residuals.
With scikit-learn
Section titled “With scikit-learn”Never fit PolynomialFeatures outside a pipeline — the expansion has to be re-applied identically
at prediction time, and the scaler after it has to be fitted on training data only:
import numpy as np
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.linear_model import LinearRegression
rng = np.random.default_rng(7)
x = np.sort(rng.uniform(-3, 3, 120)).reshape(-1, 1)
y = 0.5 * x.ravel() ** 2 + x.ravel() + 2 + rng.normal(0, 1.1, 120)
model = make_pipeline(
PolynomialFeatures(degree=2, include_bias=False),
StandardScaler(), # essential: x^5 dwarfs x, and scale wrecks conditioning
LinearRegression(),
)
model.fit(x, y)
print(model.score(x, y).round(4)) # 0.8196
print(model[0].get_feature_names_out()) # ['x0' 'x0^2']
print(model[-1].coef_.round(3)) # [1.78 1.248]include_bias=False matters: LinearRegression already fits an intercept, so leaving the
constant column in gives you two intercepts and a rank-deficient design.
Choosing the degree
Section titled “Choosing the degree”Training error falls forever as you add degrees. It has to — each extra term is a strictly larger family of curves. The only honest signal comes from data the model has not seen:
Reading the plot
Section titled “Reading the plot”- The cliff at degree 2 is the real signal. CV error drops from 2.79 to 1.06 and never improves meaningfully again. The data was generated by a quadratic, and the curve says so.
- Degrees 2 through 10 are a tie. The shaded band is one standard error across folds; every point in that range sits inside it. Picking the exact minimum here would be reading noise.
- The one-standard-error rule. Find the best CV score, add one standard error, and take the simplest model still under that threshold. Here it selects degree 2 — the right answer, and the one that generalises.
- The gap after degree 10 is overfitting made visible. Training error keeps falling while CV error climbs.
Learning curves
Section titled “Learning curves”The degree curve varies capacity at fixed data. A learning curve does the opposite: fixed capacity, growing data. The two failure modes look completely different.
| Symptom | Diagnosis | What actually helps |
|---|---|---|
| Both curves plateau high, close together | Underfitting (high bias) | More capacity: higher degree, more features |
| Training low, validation high, gap persists | Overfitting (high variance) | More data, lower degree, regularisation |
| Both low and converged | Good fit | Ship it |
pch.algoApi sklearn.preprocessing.PolynomialFeatures + sklearn.linear_model.LinearRegression
pch.algoAssumes
- The relationship is smooth and can be approximated by a polynomial on the observed range
- Observations are independent
- Residual variance is constant
pch.algoCost
- pch.algoTrain
O(m·p² + p³)- pch.algoPredict
O(p)- pch.algoMemory
O(p)
m = samples, p = expanded term count = binomial(n + d, d)
pch.algoHyperparams
-
degreedefault 2 The one that matters. Choose by cross-validation, then take the simplest degree within one standard error of the best. -
include_biasdefault True Set False inside a pipeline whose final estimator already fits an intercept. -
interaction_onlydefault False Keep cross terms like x0·x1 but drop pure powers — useful when you want interactions without curvature.
pch.algoReachFor
- A scatter plot or residual plot shows smooth curvature
- You have one to a handful of features and want to keep interpretability
- A physical relationship is genuinely quadratic or cubic
pch.algoLookElsewhere
- You have many features — the term count explodes combinatorially
- You need to predict outside the training range; polynomials diverge violently
- The curve has sharp kinks or steps — use splines or trees instead
Pitfalls
Section titled “Pitfalls”Compare
Section titled “Compare”| Approach | Shape it fits | Extrapolates | Cost with many features | Interpretability |
|---|---|---|---|---|
| Polynomial regression | Smooth global curve | Very badly | Explodes | Good at low degree |
| Splines | Smooth, piecewise, local | Poorly | Linear | Moderate |
| Decision tree | Steps | Flat (constant) | Linear | Moderate |
| Random forest | Smooth steps | Flat | Linear | Poor |
| Kernel ridge (RBF) | Smooth, local | Reverts to mean | Quadratic in samples | Poor |
| Log/sqrt transform | One fixed curve shape | Reasonably | Free | Very good |
If the curvature is monotone, try transforming or with a log before reaching for a polynomial. One column, no degree to tune, and it extrapolates sensibly.
-
Why is polynomial regression still called a linear model?
Linearity refers to how the parameters enter the model. Treat x squared as a new feature and ordinary least squares applies with no modification.
pch.quizShowAnswer
B — Because it is linear in the parameters — x squared is just another column — Linearity refers to how the parameters enter the model. Treat x squared as a new feature and ordinary least squares applies with no modification.
-
Training MSE keeps falling as you raise the degree while CV MSE starts rising after degree 10. What is happening?
Training error must fall with capacity. When held-out error turns upward, the extra flexibility is being spent memorising noise.
pch.quizShowAnswer
B — Overfitting: the extra capacity is fitting noise that does not generalise — Training error must fall with capacity. When held-out error turns upward, the extra flexibility is being spent memorising noise.
-
You have 10 features and set degree=3. Roughly how many columns does PolynomialFeatures produce?
The count is binomial(n + d, d) = binomial(13, 3) = 286, including the bias term. The growth is combinatorial, not linear.
pch.quizShowAnswer
C — 286 — The count is binomial(n + d, d) = binomial(13, 3) = 286, including the bias term. The growth is combinatorial, not linear.
-
Your CV curve is flat between degrees 2 and 10. Which degree should you ship?
Differences inside one standard error are noise. The one-standard-error rule takes the simplest model that is statistically indistinguishable from the best, which is more likely to hold up on new data.
pch.quizShowAnswer
B — Degree 2 — the simplest model within one standard error of the best — Differences inside one standard error are noise. The one-standard-error rule takes the simplest model that is statistically indistinguishable from the best, which is more likely to hold up on new data.
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – Expand features by hand
Section titled “Exercise 1 – Expand features by hand”Exercise 2 – Solve the quadratic fit
Section titled “Exercise 2 – Solve the quadratic fit”Exercise 3 – Build a polynomial pipeline
Section titled “Exercise 3 – Build a polynomial pipeline”Exercise 4 – Watch overfitting appear
Section titled “Exercise 4 – Watch overfitting appear”Exercise 5 – Extrapolate and watch it explode
Section titled “Exercise 5 – Extrapolate and watch it explode”- Adding as columns lets ordinary least squares fit curves — the model stays linear in its parameters.
- The hand-worked quadratic on five points gives with .
PolynomialFeaturesproduces terms; 10 features at degree 3 is 286 columns.- Choose the degree with cross-validation, then apply the one-standard-error rule to prefer the simplest model in the tie.
- Learning curves distinguish the two failure modes: converged-and-high is bias, wide-gap is variance.
- Scale after expanding, and never extrapolate.
Exercise 6 – Apply the one-standard-error rule
Section titled “Exercise 6 – Apply the one-standard-error rule”Continue to Cost Functions - Mean Squared Error (MSE) — a closer look at the loss every fit on this page was silently minimising, and what changes when you pick a different one.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading