CyclicCRS

Cyclic Cubic Regression Spline (periodic natural cubic spline).

Usage

Source

CyclicCRS(k=10)

A cyclic (periodic) cubic regression spline is the natural extension of CRS to covariates that wrap around, such as time of day, day of year, or wind direction, where the value and slope at the end of the range must match the value and slope at the start. Equivalent to mgcv’s bs="cc" basis. The spline is periodic over the range of the training data: f(x_min) = f(x_max), f'(x_min) = f'(x_max), and f''(x_min) = f''(x_max). Values outside the training range are mapped into the periodic domain via modular arithmetic, so predictions remain well-defined for any x. Choose CyclicCRS (rather than plain CRS) whenever the covariate is inherently circular; using a non-cyclic basis on such data would otherwise produce an artificial discontinuity at the wrap-around point.

The cyclic constraint absorbs one degree of freedom, so k knots produce k-1 basis functions. The penalty null space is 1-dimensional (constant functions only as linear functions are no longer unpenalized under periodicity).

Parameters

k: int = 10
Number of knots placed around the periodic domain. Must be at least 4, which yields at least 3 basis functions (n_basis = k - 1) after the cyclic constraint removes one degree of freedom. As with CRS, larger k gives more local flexibility, with overall smoothness controlled by the penalty and lambda rather than by k alone. The default is 10.

Notes

Knots are placed at evenly-spaced quantiles of the training data (falling back to an evenly-spaced grid when quantile ties would produce duplicates), exactly as in CRS, but the second-derivative relationship between the coefficient vector and the knot second derivatives is built on a circular tridiagonal system: _build_cyclic_Q() and _build_cyclic_R() construct the periodic analogues of CRS’s Q and R matrices, wrapping the sub/super-diagonal entries around modulo m = k - 1 (the number of free coefficients once f(x_min) = f(x_max) is imposed). Concretely, the periodic constraint identifies knot t_{k-1} with t_0, so \boldsymbol{\beta} has only m = k - 1 free entries, and second derivatives satisfy \mathbf{R}_c \mathbf{d} = \mathbf{Q}_c^\top \boldsymbol{\beta} with Q_c, R_c both m \times m and circulant-tridiagonal (each row’s neighbors wrap around index m). Unlike CRS, there are no natural boundary conditions — periodicity itself replaces them — so the penalty null space drops from dimension 2 (constant and linear) to dimension 1 (constant only): a periodic function cannot be a nonzero linear function of x, since a nonzero slope is incompatible with f(x_min) = f(x_max).

The penalty is the same quadratic form as in CRS, computed from the circular matrices,

\mathbf{S} = \mathbf{Q}_c \mathbf{R}_c^{-1} \mathbf{Q}_c^\top,

an m \times m (m = k - 1) positive semi-definite matrix of rank m - 1, whose one-dimensional null space is the constant sequence. At evaluation time, basis_matrix() maps any x into the periodic domain [x_min, x_max) via knots[0] + (x - knots[0]) % period before evaluating the same piecewise-cubic construction as CRS, so extrapolation is not linear (as in CRS) but exactly periodic.

Examples

import numpy as np
from whittaker.smooths import CyclicCRS

x = np.linspace(0, 2 * np.pi, 100)
basis = CyclicCRS(k=10).fit(x)
B = basis.basis_matrix(x)
S = basis.penalty_matrix()
B.shape, S.shape
((100, 9), (9, 9))

Attributes

Name Description
knots Knot locations used by the fitted basis.
n_basis Total number of basis functions.

knots

Knot locations used by the fitted basis.

knots: NDArray

The k locations, placed at evenly-spaced quantiles of the training data (or an evenly-spaced grid as a fallback), spanning one full period [x_min, x_max]. The last knot is identified with the first under the periodicity constraint, leaving k - 1 free coefficients.


n_basis

Total number of basis functions.

n_basis: int

Equal to k - 1: the periodicity constraint f(x_min) = f(x_max) identifies the last knot with the first, so only k - 1 of the k knot values are free coefficients.

Methods

Name Description
basis_matrix() Evaluate the cyclic CRS basis at x.
fit() Fit the cyclic 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 - 1) x (k - 1) cyclic penalty matrix S = Q_c R_c^{-1} Q_c'.

basis_matrix()

Evaluate the cyclic CRS basis at x.

Usage

Source

basis_matrix(x)

Any x outside the training range is first mapped into the periodic domain [x_min, x_max) via modular arithmetic, so the returned basis values are exactly periodic with period x_max - x_min.

Parameters

x: NDArray
1-D evaluation points, shape (n,) or (n, 1).

Returns

NDArray
Design matrix of shape (n, k - 1).

fit()

Fit the cyclic CRS to training data x.

Usage

Source

fit(x)

Places k knots at evenly-spaced quantiles of x (spanning one full period), then pre-computes the circular second-derivative operator and penalty matrix.

Parameters

x: NDArray
1-D training covariate, shape (n,) or (n, 1). The period is inferred as x.max() - x.min().

Returns

CyclicCRS
Returns self for method chaining.

Raises

ValueError
If n < k, or if x is not 1-D / (n, 1).

identifiability_constraints()

Return the sum-to-zero constraint row for the intercept.

Usage

Source

identifiability_constraints()

Evaluates the fitted basis at the free knots (excluding the duplicated wrap-around knot) and averages each column, giving a (1, k - 1) row C such that C @ beta == 0 constrains the smooth to have mean zero over the training data.

Returns

NDArray
Shape (1, k - 1) constraint matrix.

null_space_dimension()

Return the dimension of the penalty null space.

Usage

Source

null_space_dimension()

Always 1 for a cyclic CRS: only constant functions have zero penalty, since a nonzero linear term would violate the periodicity constraint f(x_min) = f(x_max).

Returns

int
Always 1.

penalty_matrix()

Return the (k - 1) x (k - 1) cyclic penalty matrix S = Q_c R_c^{-1} Q_c'.

Usage

Source

penalty_matrix()

S is positive semi-definite with rank k - 2. Its one-dimensional null space is spanned by the constant function (linear functions are no longer unpenalized under periodicity).

Returns

NDArray
Shape (k - 1, k - 1).