import numpy as np
from whittaker.smooths import TPRS
rng = np.random.default_rng(0)
x = rng.uniform(0, 1, 100)
basis = TPRS(k=10).fit(x)
B = basis.basis_matrix(x)
S = basis.penalty_matrix()
B.shape, S.shape((100, 10), (10, 10))
Abstract base class for all smooth basis types.
Usage
Every smooth term in a GAM — a thin plate spline, a cubic regression spline, a P-spline, a Gaussian process smooth, and so on — is represented internally as a linear basis expansion f(x) = B(x) @ beta together with a quadratic roughness penalty beta.T @ S @ beta. This class fixes the contract that every basis type must satisfy so that the fitting machinery (penalized least squares, smoothing-parameter selection, prediction) can treat all basis types uniformly. Concrete subclasses differ only in how B and S are constructed, not in how they are consumed.
Subclasses must implement fit(), basis_matrix(), penalty_matrix(), null_space_dimension(), and the n_basis property.
The typical workflow is::
basis = MyBasis(k=10)
basis.fit(x_train)
B_train = basis.basis_matrix(x_train) # (n_train, k)
B_new = basis.basis_matrix(x_new) # (n_new, k)
S = basis.penalty_matrix() # (k, k)
A penalized basis decomposes its k basis functions into an unpenalized null space of dimension M (typically low-order polynomials, for which the penalty contributes nothing — e.g. a straight line has zero roughness under a second-derivative penalty) and a penalized range space of dimension k - M. The total penalty for smoothing parameter lambda is
\lambda \, \boldsymbol{\beta}^\top \mathbf{S} \boldsymbol{\beta},
where S is symmetric positive semi-definite with null_space_dimension() zero eigenvalues. Basis types built on this contract may additionally supply identifiability constraints (via identifiability_constraints()) when the raw basis is not full rank on its own — for example when several smooth terms share an intercept and must each be constrained to have mean zero over the training data.
Any concrete subclass follows this pattern:
| Name | Description |
|---|---|
| is_fitted |
True after fit() has been called.
|
| n_basis |
Number of basis functions k.
|
True after fit() has been called.
is_fitted: bool
Used internally (via _check_fitted()) to guard methods such as basis_matrix() and penalty_matrix() that depend on state computed during fit() (e.g. knot locations or transformation matrices), raising a clear error instead of failing on missing attributes when called out of order.
Number of basis functions k.
n_basis: int
This is the number of columns returned by basis_matrix() and the size of the (square) penalty_matrix(). It is fixed at construction time (e.g. via a k argument) and does not change after fit() is called.
| Name | Description |
|---|---|
| basis_matrix() |
Evaluate basis functions at x.
|
| fit() | Fit the basis to training data. |
| identifiability_constraints() |
Return a constraint matrix C such that C @ beta = 0, or None.
|
| null_space_dimension() |
Return the dimension M of the penalty null space.
|
| penalty_matrix() |
Return the (k, k) roughness penalty matrix S.
|
Evaluate basis functions at x.
Usage
x: NDArray(n,) for univariate or (n, d) for multivariate.
NDArray(n, k) where k = self.n_basis.
Fit the basis to training data.
Usage
Stores any parameters required for later calls to basis_matrix() (e.g. knot locations, transformation matrices).
x: NDArray(n,) for univariate smooths or (n, d) for multivariate smooths.
selfReturn a constraint matrix C such that C @ beta = 0, or None.
Usage
Some bases are not identifiable on their own once combined with other terms in a model (for example, several smooths sharing an intercept), and must be constrained — typically to have mean zero over the training data — before fitting. Subclasses may override this method to supply such sum-to-zero or other linear identifiability constraints; the default implementation returns None, meaning no constraint is required.
NDArray or None(n_constraints, k), or None if the basis needs no identifiability constraint.
Return the dimension M of the penalty null space.
Usage
The null space is the subspace of basis coefficients for which the roughness penalty is exactly zero — typically the low-order polynomials (e.g. constants and straight lines under a second-derivative penalty). By convention the first M columns of the basis matrix span this null space, so S[:M, :M] and the corresponding rows/columns of S involving those columns vanish.
intM, satisfying 0 <= M < k.
Return the (k, k) roughness penalty matrix S.
Usage
S encodes the quadratic wiggliness penalty applied to the basis coefficients: the total penalty contribution for smoothing parameter lambda is lambda * beta.T @ S @ beta. S is always symmetric positive semi-definite, with null_space_dimension() zero eigenvalues corresponding to the unpenalized functions (e.g. low-order polynomials) that the basis can represent for free.
NDArray(k, k) where k = self.n_basis.