Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

〰️ Polynomial and Flexible Regression

Let us have a quick recap of the first session of the semester, where regression models were introduced. In linear regression, we assume that the relationship between a predictor xx and a response yy is a straight line:

y≈β0+β1x.y \approx \beta_0 + \beta_1 x.

In many real-world problems, this assumption is too restrictive. The true relationship may be curved, bend differently in different regions of xx, or vary smoothly but non-linearly.

In this chapter, we look at four families of methods that allow more flexible regression functions:

Let’s start by creating some example data. Imagine we have one predictor xx and a response yy generated as:

y=sin⁡(x)+ε,ε∼N(0,σ2).y = \sin(x) + \varepsilon, \quad \varepsilon \sim \mathcal{N}(0, \sigma^2).
<Figure size 640x480 with 1 Axes>

If we fit a straight line to such data, the model will clearly miss the oscillating pattern. Flexible regression methods aim to recover such non-linear patterns while still being based on the same least squares framework.


Polynomial Regression

Polynomial regression extends the linear model by including powers of xx as additional predictors:

Pros:

Cons:


We can conveniently generate polynomial features using PolynomialFeatures from sklearn.preprocessing and then fit a standard LinearRegression model.

<Figure size 640x480 with 1 Axes>

Things to observe:


Piecewise Constant Regression

The simple idea:

Divide the range of xx into intervals using cut points (knots) and fit a constant mean within each interval.

Concretely, choose cut points c1<c2<⋯<cKc_1 < c_2 < \dots < c_K and define indicator variables

I1(x)=1(x≤c1),I2(x)=1(c1<x≤c2),…,IK+1(x)=1(x>cK).I_1(x) = \mathbf{1}(x \le c_1), \quad I_2(x) = \mathbf{1}(c_1 < x \le c_2), \dots, I_{K+1}(x) = \mathbf{1}(x > c_K).

Then the model is

y≈β0+β1I1(x)+⋯+βK+1IK+1(x),y \approx \beta_0 + \beta_1 I_1(x) + \dots + \beta_{K+1} I_{K+1}(x),

which produces a piecewise constant (stepwise) regression function. This is equivalent to a B-spline of degree 0 (a “zero-order spline”).

Pros:

Cons:

We can build a 0th-order spline (step function) basis using patsy.dmatrix with bs(..., degree=0) and then fit a linear model with statsmodels:

Fit the model:

                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.757
Model:                            OLS   Adj. R-squared:                  0.752
Method:                 Least Squares   F-statistic:                     152.2
Date:                Mon, 26 Oct 2026   Prob (F-statistic):           8.05e-59
Time:                        12:42:05   Log-Likelihood:                -167.85
No. Observations:                 200   AIC:                             345.7
Df Residuals:                     195   BIC:                             362.2
Df Model:                           4                                         
Covariance Type:            nonrobust                                         
=================================================================================================================================
                                                                    coef    std err          t      P>|t|      [0.025      0.975]
---------------------------------------------------------------------------------------------------------------------------------
Intercept                                                         1.0069      0.090     11.227      0.000       0.830       1.184
bs(x, knots=cut_points, degree=0, include_intercept=False)[0]     0.0223      0.127      0.176      0.861      -0.228       0.272
bs(x, knots=cut_points, degree=0, include_intercept=False)[1]    -0.4055      0.127     -3.197      0.002      -0.656      -0.155
bs(x, knots=cut_points, degree=0, include_intercept=False)[2]     1.6288      0.127     12.843      0.000       1.379       1.879
bs(x, knots=cut_points, degree=0, include_intercept=False)[3]     2.0670      0.127     16.297      0.000       1.817       2.317
==============================================================================
Omnibus:                        0.227   Durbin-Watson:                   0.792
Prob(Omnibus):                  0.893   Jarque-Bera (JB):                0.357
Skew:                          -0.062   Prob(JB):                        0.837
Kurtosis:                       2.834   Cond. No.                         5.83
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Visualise the stepwise fit:

<Figure size 1000x600 with 1 Axes>

Spline Regression

The previous section introduced “stepwise regression” as 0th-order splines. However, when we talk about spline regression, we usually mean higher-order splines, which will smooth these steps into continuous and differentiable curves. Again, the key idea is the similar:

Approximate the regression function by piecewise polynomials that are smoothly joined at pre-defined points called knots.

For example, a cubic spline with knots at t1,…,tKt_1, \dots, t_K is a function that is:

We usually do not work with the piecewise form directly. Instead, we represent splines as a linear combination of basis functions:

f(x)=∑j=1MθjBj(x),f(x) = \sum_{j=1}^{M} \theta_j B_j(x),

where Bj(x)B_j(x) are spline basis functions (B-splines). This again gives a linear model in the parameters θj\theta_j.

Two common options:

Again, we use the convenient bs() function from patsy to create B-spline bases. We can then plug these into statsmodels for ordinary least squares:

Fit an OLS model:

                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.906
Model:                            OLS   Adj. R-squared:                  0.903
Method:                 Least Squares   F-statistic:                     308.2
Date:                Mon, 26 Oct 2026   Prob (F-statistic):           5.30e-96
Time:                        12:42:05   Log-Likelihood:                -73.555
No. Observations:                 200   AIC:                             161.1
Df Residuals:                     193   BIC:                             184.2
Df Model:                           6                                         
Covariance Type:            nonrobust                                         
=====================================================================================================================
                                                        coef    std err          t      P>|t|      [0.025      0.975]
---------------------------------------------------------------------------------------------------------------------
Intercept                                            -0.2490      0.155     -1.604      0.110      -0.555       0.057
bs(x, df=6, degree=3, include_intercept=False)[0]     1.8920      0.290      6.527      0.000       1.320       2.464
bs(x, df=6, degree=3, include_intercept=False)[1]     2.0838      0.189     11.049      0.000       1.712       2.456
bs(x, df=6, degree=3, include_intercept=False)[2]    -0.6812      0.231     -2.947      0.004      -1.137      -0.225
bs(x, df=6, degree=3, include_intercept=False)[3]     4.6825      0.214     21.838      0.000       4.260       5.105
bs(x, df=6, degree=3, include_intercept=False)[4]     3.3392      0.241     13.834      0.000       2.863       3.815
bs(x, df=6, degree=3, include_intercept=False)[5]     2.7045      0.216     12.516      0.000       2.278       3.131
==============================================================================
Omnibus:                        2.725   Durbin-Watson:                   1.790
Prob(Omnibus):                  0.256   Jarque-Bera (JB):                2.671
Skew:                           0.281   Prob(JB):                        0.263
Kurtosis:                       2.933   Cond. No.                         19.7
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Visualise the spline fit:

<Figure size 640x480 with 1 Axes>

Local Regression (LOWESS)

Polynomial and spline regression still use a global basis: one set of parameters applies to the whole range of xx. Local regression takes a different view:

Fit a separate regression in a neighbourhood around each target point, using only nearby observations (with weights).

For a target point x0x_0:

  1. Define a neighbourhood around x0x_0, e.g. the closest α⋅n\alpha \cdot n observations, where α\alpha is a smoothing parameter between 0 and 1.

  2. Assign weights to observations, typically higher for points closer to x0x_0.

  3. Fit a weighted least squares regression (often linear or quadratic) using those weighted points.

  4. The fitted value at x0x_0 is the prediction from this local model.

Repeat this for many x0x_0 to obtain a smooth curve.

Key parameters:

statsmodels provides a convenient implementation of LOWESS (locally weighted scatterplot smoothing).

<Figure size 640x480 with 1 Axes>

Observe:

Local regression methods are very useful for exploratory analysis: they give a flexible, data-driven summary of the trend without a strong global parametric assumption.


Interactive: how much flexibility is too much?

All four methods have exactly one knob that controls flexibility — the polynomial degree, the number of cut points, the spline degrees of freedom, and the LOWESS span. Below you can turn that knob for each method and watch the fit respond. The reported errors come from a 70/30 train/test split, so you can also see where the extra flexibility stops paying off.

Source
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
/tmp/ipykernel_2642/3514466390.py:33: SingularMatrixWarning: The design matrix is rank-deficient. The model parameters are not uniquely determined.
  model = sm.OLS(y_tr, basis_tr).fit()
Loading...

The slider walks through all four method families in turn (polynomial, then steps, then splines, then LOWESS). Watch the two error numbers as you drag: the train MSE decreases monotonically for every method, while the test MSE bottoms out and then climbs. That gap is the bias-variance tradeoff of ⚖️ Bias-Variance Tradeoff, now visible for four different notions of “flexibility”.


Summary and Quiz

MethodKey IdeaFlexibilityProsCons
Polynomial RegressionPowers of xx as predictors: x,x2,…,xdx, x^2, \dots, x^dControlled by degree ddSimple to fit with OLS; interpretableOscillates at boundaries; global changes affect entire curve
Piecewise Constant RegressionConstant values in intervals defined by knotsControlled by knotsSimple and interpretableDiscontinuous at knots; sensitive to knot placement
Spline RegressionPiecewise polynomials smoothly joined at knotsControlled by knots and degreeSmooth and flexible; mostly localRequires choosing knots; can overfit with many knots
Local Regression (LOWESS)Weighted regression in neighborhood of each pointControlled by span/fractionData-driven; no global assumptionsComputationally intensive; requires choosing span; harder to interpret

In the lecture, all methods except simple polynomial regression are considered local models. In piecewise constant regression, only the information within each bin contributes to the fitted value in that region. Spline regression is also largely local, but neighbouring intervals are coupled through smoothness constraints at the knots, which introduces a limited amount of global structure.

In practice, these methods are often combined with regularisation and cross-validation to control overfitting and to select tuning parameters such as the degree, number and location of knots, or the span.


Loading...
Loading...
Loading...
Loading...