This document outlines the theoretical background for the scatter_smooth package, synthesizing concepts from The Elements of Statistical Learning (Hastie, Tibshirani, Friedman), specifically Chapter 5. It also discusses the implementation details (Reinsch form vs. Basis form) and compares the features with R’s smooth.spline.
1. The Smoothing Spline Problem¶
The goal of smoothing splines is to find a function that fits the data well while remaining smooth. This is formulated as the minimization of a penalized residual sum of squares:
where is a smoothing parameter and are weights.
: becomes an interpolating spline (passes through every point).
: approaches the weighted linear least squares fit.
The Solution: Natural Cubic Spline¶
Remarkably, the solution to this infinite-dimensional optimization problem is a finite-dimensional Natural Cubic Spline with knots at the unique values of .
A natural cubic spline is a piecewise cubic polynomial that is continuous up to its second derivative, and linear beyond the boundary knots (i.e., its second derivative is zero at the boundaries).
2. Matrix Formulation¶
Since the solution is a linear combination of basis functions, we can write:
where are the natural cubic spline basis functions.
The optimization problem reduces to:
where:
is the basis matrix.
is the weight matrix.
is the penalty matrix.
The solution is a generalized ridge regression:
Reinsch Form vs. Basis Form¶
There are three primary ways to compute the solution in our package:
A. The Natural Spline Basis Form (engine='natural')¶
We explicitly construct the basis matrix and the penalty matrix .
We use the property that a natural cubic spline is determined by its values and second derivatives at the knots.
Our C++ implementation constructs efficiently by solving the tridiagonal system that relates second derivatives to values.
We then solve the linear system .
Use of this is not recommended as it is much slower. It was written as part of the development process.
Why Basis Form?
Allows for Regression Splines: We can choose fewer knots than data points (). The math remains identical, but the matrices are smaller ( instead of ).
Easier to extend to weighted least squares or other loss functions.
B. The Reinsch Form (engine='reinsch')¶
If knots are placed at every data point (), the solution vector can be found directly without forming .
where .
is an tridiagonal matrix of second differences.
is an tridiagonal matrix.
This allows solving the system in time using banded solvers, making it extremely fast for large . This matches the algorithm used in R’s smooth.spline.
C. The B-Spline Basis Form (engine='bspline')¶
Uses the B-spline basis with compact support.
Constructs banded matrices for the normal equations and .
Solves using LAPACK’s
dpbsvviascipy. This is the only partscipyis used so direct calls to LAPACK could in theory be used.Efficient for both and .
Also allows regression splines.
D. Automatic (engine='auto')¶
Uses Reinsch form if possible, else uses the B-Spline form.
3. Degrees of Freedom ()¶
The smoothing parameter is abstract. A more intuitive measure of model complexity is the effective degrees of freedom ().
where is the smoother matrix such that .
For the Reinsch form, we compute the trace in time using Takahashi’s equations (calculating selected elements of the inverse of a banded matrix via Cholesky decomposition).
For the Basis form (Natural), we compute the trace of the dense matrix inverse.
For the B-spline form, we also use Takahashi’s equations on the banded system to compute the trace efficiently in time.
3.1. Details on Degrees of Freedom Computation¶
Trace of Inverse via Takahashi’s Algorithm¶
Computing the degrees of freedom requires the trace of the smoother matrix . In the basis form:
Thus, . Using the cyclic property of the trace:
Let . We need to compute .
For banded matrices (like those in our B-spline implementation), we can compute this trace efficiently in time without inverting the full matrix.
Let . We need . Since and are banded, is banded. Let be the Cholesky decomposition of , where is an upper triangular banded matrix. We need elements of specifically those where is non-zero. Since is banded, we only need the elements of within the bandwidth of .
Takahashi’s Equations provide a way to compute the elements of within the band of (which includes the band of ) using a backward recurrence starting from the bottom-right corner.
Compute .
Iterate backwards from to 1:
Compute off-diagonal elements inside the band using previously computed values and .
Compute diagonal element .
Once we have the band of , we can compute the trace of the product efficiently:
Since is symmetric and banded, the sum only involves indices where , which are exactly the elements we computed.
Handling Natural Boundary Conditions (The Projection Matrix )¶
The “natural” boundary conditions (zero second derivative at endpoints) imply linear constraints on the B-spline coefficients .
These constraints reduce the number of free parameters from to . We can express the full parameter vector as a linear transformation of the reduced parameters :
where is an sparse projection matrix. is mostly an identity matrix (shifted), with the first row and last row having non-zero entries in the first two and last two columns respectively.
The optimization problem in terms of becomes:
The normal equations are:
Let and . These matrices remain banded (with slightly modified corners). We solve for and then reconstruct .
Our bspline engine implements this by:
Constructing the full banded matrices and .
Applying the projection in-place to the bands (modifying the top-left and bottom-right blocks).
Solving the reduced banded system.
Using Takahashi’s algorithm on the reduced system to compute the degrees of freedom efficiently.
4. Comparison with R’s smooth.spline¶
| Feature | smooth.spline (R) | scatter_smooth (Python) |
|---|---|---|
| Algorithm | Reinsch Form () | engine='auto' (selects best), engine='reinsch' (), engine='natural' (), engine='bspline' (Banded) |
| Knots | All unique (default) or | All unique or specified n_knots |
| Input | Via spar or lambda | Via lamval |
| Input | Supported | Supported (via root finding) |
| Automatic Tuning | GCV / CV | GCV |
| Weights | Supported | Supported |
| Derivatives | Built-in | Exposed via predict(deriv=...) |
| Extrapolation | Linear (via predict) | Linear (explicitly handled) |
Key Differences¶
Speed: For ,
smooth.splineis faster because it exploits the band structure of the full system. Our implementation allowsn_knots < N(Regression Splines), which restores speed () and reduces overfitting risk, a featuresmooth.splinesupports vianknots.engine='reinsch': Fast implementation for when knots = unique . Matchessmooth.splineperformance.engine='natural': Uses the natural cubic spline basis. Good for general use and when .engine='bspline': Uses B-spline basis. Efficient and stable, good for sparse matrices.
Spar (R specific): R uses a scaling parameter
sparrelated to . We use raw (scaled by -range cubed for numerical stability).
5. References¶
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning: Data Mining, Inference, and Prediction. 2nd Edition. Springer. (Chapter 5).
Green, P. J., & Silverman, B. W. (1994). Nonparametric Regression and Generalized Linear Models: A Roughness Penalty Approach. Chapman and Hall/CRC.