import numpy as np
from whittaker.smooths import CRS
x = np.linspace(0, 1, 100)
basis = CRS(k=10).fit(x)
B = basis.basis_matrix(x)
S = basis.penalty_matrix()
B.shape, S.shape((100, 10), (10, 10))
Cubic Regression Splines (natural cubic splines with quantile knots).
Usage
A cubic regression spline is a piecewise-cubic function that is twice continuously differentiable, with a roughness penalty based on the integrated squared second derivative (classical smoothing-spline theory). Equivalent to mgcv’s bs="cr" basis. Unlike TPRS, which avoids explicit knot placement, CRS parameterizes the spline directly by its values at k knots placed at evenly-spaced quantiles of the training data, so it is cheaper to fit for a single covariate and gives a basis whose coefficients have a direct interpretation as function values at the knots. Choose CRS over TPRS when you have exactly one covariate, want knot-based interpretability, or need a classical natural-cubic-spline basis for compatibility with other software; choose TPRS for multivariate smooths or when knot placement is undesirable. Only univariate covariates are supported.
The first two columns of the basis matrix correspond to the linear null space of the penalty ({1, x}); the remaining k - 2 columns are penalized.
Note that unlike TPRS, the null-space columns are not stored as the leading columns. The full k-column basis is used directly, with the penalty having a 2-dimensional null space.
k: int = 103 (fewer knots cannot support a cubic spline with a nontrivial penalized part). Larger k places more knots and allows more local flexibility at the cost of more parameters to estimate; as with all penalized bases, the penalty (not k) is what ultimately controls the smoothness of the fitted curve once lambda is chosen. The default is 10.
Knots t_0 < t_1 < \cdots < t_{k-1} are placed at evenly-spaced quantiles of the training data x (falling back to an evenly-spaced grid over [min(x), max(x)] if quantile ties would otherwise produce duplicate knots). Basis construction follows the classical natural cubic spline parameterization by function values at the knots (de Boor; Wood, Generalized Additive Models, 2nd ed., §5.3.1):
\mathbf{d} = (f''(t_1), \ldots, f''(t_{k-2}))^\top are related to the coefficient vector \boldsymbol{\beta} (the spline values at the knots) by a tridiagonal linear system \mathbf{R} \mathbf{d} = \mathbf{Q}^\top \boldsymbol{\beta}, where Q (the k \times (k-2) matrix built by _build_Q()) and R (the (k-2) \times (k-2) symmetric positive-definite tridiagonal matrix built by _build_R()) are determined entirely by the knot spacings h_l = t_{l+1} - t_l.f''(t_0) = f''(t_{k-1}) = 0) pad \mathbf{d} with zeros at both ends to give the full k \times k second-derivative operator \mathbf{A} such that \mathbf{d}_{\text{full}} = \mathbf{A} \boldsymbol{\beta}.[t_0, t_{k-1}] the natural boundary conditions force the spline to continue linearly, so CRS extrapolates linearly rather than polynomially beyond the training range.The roughness penalty is the integral of squared second derivative, \int f''(x)^2 \, dx = \boldsymbol{\beta}^\top \mathbf{S} \boldsymbol{\beta}, with
\mathbf{S} = \mathbf{Q} \mathbf{R}^{-1} \mathbf{Q}^\top,
a k \times k positive semi-definite matrix of rank k - 2. Its two-dimensional null space is spanned by the constant and linear functions evaluated at the knots (any straight line has zero second derivative everywhere, hence zero penalty). Because S is built from tridiagonal Q and R, it is well-conditioned and cheap to compute even for large k; the main numerical caveat is that near-duplicate knots (from heavily tied data) can make R ill-conditioned, which is why fit() falls back to an evenly-spaced knot grid when quantile knots would collide.
| Name | Description |
|---|---|
| is_fitted | Whether the basis has been fitted. |
| knots | Knot locations used by the fitted basis. |
| n_basis | Total number of basis functions. |
Whether the basis has been fitted.
is_fitted: bool
Knot locations used by the fitted basis.
knots: NDArray
These are the k locations, placed at evenly-spaced quantiles of the training data (or an evenly-spaced grid as a fallback), at which the coefficient vector beta gives the spline’s function values.
Total number of basis functions.
n_basis: int
Equal to k, the number of knots, since the CRS basis uses the full set of knot-value coefficients with no dropped columns.
| Name | Description |
|---|---|
| basis_matrix() |
Evaluate the CRS basis at x.
|
| fit() |
Fit the CRS to training data x.
|
| identifiability_constraints() | Return the sum-to-zero constraint row for the intercept. |
| null_space_dimension() | Return the dimension of the penalty null space. |
| penalty_matrix() |
Return the k × k penalty matrix S = Q R⁻¹ Q'.
|
Evaluate the CRS basis at x.
Usage
x: NDArray(n,) or (n, 1).
NDArray(n, k).
Fit the CRS to training data x.
Usage
Places k knots at evenly-spaced quantiles of x and pre-computes the second-derivative operator and penalty matrix.
x: NDArray(n,) or (n, 1).
CRSself for method chaining.
ValueErrorn < k, or if x is not 1-D/(n, 1).
Return the sum-to-zero constraint row for the intercept.
Usage
Evaluates the fitted basis at the training knots and averages each column, giving a (1, k) row C such that C @ beta == 0 constrains the smooth to have mean zero over the knots. This is the constraint typically imposed when a smooth term must be identifiable alongside a separate model intercept.
NDArray(1, k) constraint matrix whose product with the coefficient vector equals zero when the smooth has mean zero over the training data.
Return the dimension of the penalty null space.
Usage
For CRS this is always 2: the constant and linear functions of x both have zero second derivative everywhere, so they are unpenalized regardless of k or the knot locations.
int2.
Return the k × k penalty matrix S = Q R⁻¹ Q'.
Usage
S is positive semi-definite with rank k - 2. Its null space is spanned by constant and linear functions of the knots.
NDArray(k, k).