Smoothing¶
Splines¶
Spline basis constructors.
- class aurora.smoothing.splines.BSplineBasis(knots, degree=3, cyclic=False)[source]¶
Bases:
objectB-spline basis functions via Cox-de Boor recursion.
B-splines are piecewise polynomials with compact support, defined recursively using the Cox-de Boor formula. They form a basis for the space of splines of a given degree with specified knots.
- Parameters:
knots (array-like) – Knot vector (should include boundary knots with multiplicity = degree + 1). For open B-splines, repeat boundary knots degree+1 times.
degree (int, default=3) – Degree of the B-spline basis (3 for cubic B-splines).
cyclic (bool, default=False) – Whether to use cyclic (periodic) boundary conditions.
- Variables:
Notes
For degree p and n_basis basis functions, the knot vector must have length n_basis + p + 1.
References
de Boor, C. (1978). A Practical Guide to Splines. Springer-Verlag. Eilers, P.H.C. & Marx, B.D. (1996). Flexible smoothing with B-splines and
penalties. Statistical Science, 11(2), 89-121.
- basis_matrix(x, sparse=False)[source]¶
Compute B-spline basis matrix using Cox-de Boor recursion.
- Parameters:
- Returns:
B – Basis matrix where B[i,j] = B_j^p(x_i), the j-th B-spline of degree p evaluated at x_i.
- Return type:
Notes
- Uses the Cox-de Boor recursion formula:
B_i^0(x) = 1 if t_i ≤ x < t_{i+1}, else 0 B_i^p(x) = w_i^p(x) B_i^{p-1}(x) + (1 - w_{i+1}^p(x)) B_{i+1}^{p-1}(x)
where w_i^p(x) = (x - t_i) / (t_{i+p} - t_i)
Sparse output exploits the compact support property: only (degree + 1) basis functions are non-zero at any point. For degree=3, each row has at most 4 non-zero entries, regardless of n_basis. This reduces: - Memory: O(n_samples × n_basis) → O(n_samples × degree) - Computation: O(n_samples × n_basis × degree²) → O(n_samples × degree²)
Sparse output is only available for NumPy backend. PyTorch and JAX backends always return dense arrays (sparse support is experimental).
- penalty_matrix(order=2)[source]¶
Compute difference penalty matrix.
For B-splines, we typically use a difference penalty that approximates the integrated squared derivative penalty.
- Parameters:
order (int, default=2) – Order of differences (2 for approximating second derivative).
- Returns:
S – Penalty matrix where β’Sβ approximates ∫ [f^(m)(x)]^2 dx.
- Return type:
Notes
- The difference penalty is:
S = D’D
where D is the order-th difference matrix.
For order=2: D_ij penalizes (β_i - 2β_{i+1} + β_{i+2})² This approximates the integrated squared second derivative.
- static create_knots(x, n_basis=10, degree=3, method='quantile')[source]¶
Create knot vector for B-splines from data.
- Parameters:
- Returns:
knots – Full knot vector with repeated boundary knots.
- Return type:
ndarray
Notes
For open B-splines, boundary knots are repeated (degree + 1) times. This ensures the spline interpolates at boundaries.
- class aurora.smoothing.splines.CubicSplineBasis(knots, boundary_knots=None)[source]¶
Bases:
objectNatural cubic spline basis for smooth function representation.
Natural cubic splines provide a flexible basis for representing smooth functions. They are especially useful in GAMs for modeling non-linear relationships.
- Parameters:
- Variables:
References
Wood, S.N. (2017). Generalized Additive Models: An Introduction with R (2nd ed.)
- basis_matrix(x)[source]¶
Compute the basis matrix B where B[i,j] = b_j(x_i).
- Parameters:
x (array-like, shape (n_samples,)) – Points at which to evaluate the basis functions.
- Returns:
B – Basis matrix. Uses the same array backend as input x.
- Return type:
Notes
The basis functions are constructed to satisfy: - Cubic polynomials between knots - Continuous second derivatives at knots - Zero second derivatives at boundaries (natural condition)
- penalty_matrix()[source]¶
Compute the penalty matrix S for integrated squared second derivative.
The penalty matrix satisfies: β’Sβ = ∫ [f’’(x)]² dx
Notes
For natural cubic splines, the penalty can be computed analytically. The first two basis functions (constant and linear) receive zero penalty since their second derivatives are zero.
For the ESL (5.4-5.5) basis N_{j+2} = d_j - d_{K-1} with d_j(x) = [(x - xi_j)_+^3 - (x - xi_K)_+^3] / (xi_K - xi_j), the second derivative is d_j’’(x) = 6[(x - xi_j)_+ - (x - xi_K)_+]/(xi_K - xi_j). Since (x - xi_K)_+ = 0 on [xi_1, xi_K] and every N_j’’ vanishes outside the boundary knots, the integral over the real line reduces to
∫ d_i’’ d_j’’ dx = J(xi_i, xi_j) / [(xi_K - xi_i)(xi_K - xi_j)]
with J(t, s) = ∫ 6(x - t)_+ 6(x - s)_+ dx, and
S[i+2, j+2] = D(i,j) - D(i,K-1) - D(K-1,j) + D(K-1,K-1).
- static create_knots(x, n_knots=10, method='quantile')[source]¶
Create knot locations from data.
- Parameters:
x (array-like) – Data values used to determine knot positions.
n_knots (int, default=10) – Number of interior knots to place.
method (str, default='quantile') –
Knot placement method:
'quantile': Place knots at quantiles of x, adapting to the empirical data distribution.'uniform': Place knots uniformly between min(x) and max(x).
- Returns:
knots – Sorted interior knot locations. May contain fewer than n_knots entries if duplicate quantiles are merged.
- Return type:
ndarray, shape (n_knots,) or fewer
- Raises:
ValueError – If n_knots is less than 1 or method is unknown.
Examples
>>> x = np.random.randn(500) >>> knots = CubicSplineBasis.create_knots(x, n_knots=8) >>> len(knots) <= 8 True
- class aurora.smoothing.splines.PSplineBasis(n_basis=20, degree=3, penalty_order=2, domain=None)[source]¶
Bases:
objectP-Spline basis: B-splines with equally-spaced knots and difference penalty.
P-Splines use a large number of B-spline basis functions with a difference penalty on coefficients to achieve smooth fits. The amount of smoothing is controlled by a single parameter λ.
- Parameters:
n_basis (int, default=20) – Number of B-spline basis functions. More basis functions allow more flexibility but require larger penalty. Typically 15-40 is sufficient.
degree (int, default=3) – B-spline degree. Cubic (degree=3) is standard and provides C² continuity.
penalty_order (int, default=2) – Order of difference penalty: - 1: Penalizes first differences (roughness of coefficients) - 2: Penalizes second differences (curvature, most common) - 3: Penalizes third differences (rate of curvature change)
domain (tuple of float, optional) – (min, max) domain for the basis. If None, determined from data.
- Variables:
Examples
>>> import numpy as np >>> from aurora.smoothing.splines import PSplineBasis >>> >>> # Create basis >>> basis = PSplineBasis(n_basis=25, degree=3, penalty_order=2) >>> >>> # Generate data >>> x = np.linspace(0, 2*np.pi, 100) >>> y = np.sin(x) + np.random.normal(0, 0.2, 100) >>> >>> # Fit with automatic smoothing selection >>> result = basis.fit(x, y, lambda_='gcv') >>> y_smooth = result.fitted_values_
Notes
The P-spline approach separates the basis complexity from smoothness control: - Use enough basis functions (n_basis) to capture the true function - Control smoothness entirely via the penalty parameter λ
For most applications, 20-30 basis functions with cubic degree and second-order penalty works well. The optimal λ is typically selected via GCV, AIC, or REML.
- penalty_matrix()[source]¶
Return the difference penalty matrix S = D’D.
- Returns:
S – Penalty matrix for penalized regression
- Return type:
- Raises:
ValueError – If basis has not been set up yet (call basis_matrix first)
- fit(x, y, lambda_='gcv', weights=None, lambda_range=(1e-6, 1e6))[source]¶
Fit P-spline to data.
- Parameters:
x (array-like) – Predictor values
y (array-like) – Response values
lambda (float or {'gcv', 'aic', 'reml'}) – Smoothing parameter. If string, automatically select via: - ‘gcv’: Generalized Cross-Validation (default, fast) - ‘aic’: Akaike Information Criterion - ‘reml’: Restricted Maximum Likelihood (better for small samples)
weights (array-like, optional) – Observation weights. If None, uniform weights.
lambda_range (tuple of float) – Search range for automatic lambda selection
- Returns:
Fitted P-spline with coefficients, fitted values, etc.
- Return type:
Examples
>>> # Automatic smoothing selection >>> result = basis.fit(x, y, lambda_='gcv') >>> >>> # Fixed smoothing parameter >>> result = basis.fit(x, y, lambda_=100.0) >>> >>> # With observation weights >>> result = basis.fit(x, y, weights=w, lambda_='gcv')
- class aurora.smoothing.splines.PSplineResult(coef_, fitted_values_, lambda_, basis, edf_, gcv_, sigma2_, x_, y_, weights_)[source]¶
Bases:
objectResults from P-spline fitting.
- Variables:
coef (ndarray) – Fitted spline coefficients
fitted_values (ndarray) – Fitted values at training points
lambda (float) – Smoothing parameter used
basis (PSplineBasis) – The basis object used for fitting
edf (float) – Effective degrees of freedom (trace of hat matrix)
gcv (float) – Generalized Cross-Validation score
sigma2 (float) – Estimated residual variance
x (ndarray) – Training predictor values
y (ndarray) – Training response values
weights (ndarray) – Observation weights used
- Parameters:
coef_ (NDArray)
fitted_values_ (NDArray)
lambda_ (float)
basis (PSplineBasis)
edf_ (float)
gcv_ (float)
sigma2_ (float)
x_ (NDArray)
y_ (NDArray)
weights_ (NDArray)
- coef_: NDArray¶
- fitted_values_: NDArray¶
- basis: PSplineBasis¶
- x_: NDArray¶
- y_: NDArray¶
- weights_: NDArray¶
- predict(x_new)[source]¶
Predict at new points.
- Parameters:
x_new (array-like) – Points at which to predict
- Returns:
y_pred – Predicted values
- Return type:
ndarray
- derivative(x=None, order=1)[source]¶
Evaluate derivative of fitted spline.
- Parameters:
x (array-like, optional) – Points at which to evaluate. If None, uses training points.
order (int) – Order of derivative (1, 2, …)
- Returns:
deriv – Derivative values
- Return type:
ndarray
- confidence_band(x=None, level=0.95)[source]¶
Compute pointwise confidence band.
- Parameters:
x (array-like, optional) – Points at which to compute band. If None, uses training points.
level (float) – Confidence level (e.g., 0.95 for 95%)
- Returns:
lower, upper – Lower and upper confidence bounds
- Return type:
ndarray
- aurora.smoothing.splines.fit_pspline(x, y, n_basis=20, degree=3, penalty_order=2, lambda_='gcv', weights=None, **kwargs)[source]¶
Fit P-spline smoother to data.
Convenience function that creates a PSplineBasis and fits it to data.
- Parameters:
x (array-like) – Predictor values
y (array-like) – Response values
n_basis (int, default=20) – Number of B-spline basis functions
degree (int, default=3) – B-spline degree (cubic by default)
penalty_order (int, default=2) – Order of difference penalty
lambda (float or str, default='gcv') – Smoothing parameter or selection method (‘gcv’, ‘aic’, ‘reml’)
weights (array-like, optional) – Observation weights
**kwargs – Additional arguments passed to PSplineBasis.fit()
- Returns:
Fitted P-spline
- Return type:
Examples
>>> import numpy as np >>> from aurora.smoothing.splines import fit_pspline >>> >>> # Generate noisy sine data >>> x = np.linspace(0, 2*np.pi, 100) >>> y = np.sin(x) + np.random.normal(0, 0.2, 100) >>> >>> # Fit P-spline with automatic smoothing >>> result = fit_pspline(x, y, n_basis=25) >>> print(f"EDF: {result.edf_:.2f}, λ: {result.lambda_:.2f}") >>> >>> # Predict at new points >>> x_new = np.linspace(0, 2*np.pi, 200) >>> y_smooth = result.predict(x_new)
- class aurora.smoothing.splines.bspline.BSplineBasis(knots, degree=3, cyclic=False)[source]¶
Bases:
objectB-spline basis functions via Cox-de Boor recursion.
B-splines are piecewise polynomials with compact support, defined recursively using the Cox-de Boor formula. They form a basis for the space of splines of a given degree with specified knots.
- Parameters:
knots (array-like) – Knot vector (should include boundary knots with multiplicity = degree + 1). For open B-splines, repeat boundary knots degree+1 times.
degree (int, default=3) – Degree of the B-spline basis (3 for cubic B-splines).
cyclic (bool, default=False) – Whether to use cyclic (periodic) boundary conditions.
- Variables:
Notes
For degree p and n_basis basis functions, the knot vector must have length n_basis + p + 1.
References
de Boor, C. (1978). A Practical Guide to Splines. Springer-Verlag. Eilers, P.H.C. & Marx, B.D. (1996). Flexible smoothing with B-splines and
penalties. Statistical Science, 11(2), 89-121.
- basis_matrix(x, sparse=False)[source]¶
Compute B-spline basis matrix using Cox-de Boor recursion.
- Parameters:
- Returns:
B – Basis matrix where B[i,j] = B_j^p(x_i), the j-th B-spline of degree p evaluated at x_i.
- Return type:
Notes
- Uses the Cox-de Boor recursion formula:
B_i^0(x) = 1 if t_i ≤ x < t_{i+1}, else 0 B_i^p(x) = w_i^p(x) B_i^{p-1}(x) + (1 - w_{i+1}^p(x)) B_{i+1}^{p-1}(x)
where w_i^p(x) = (x - t_i) / (t_{i+p} - t_i)
Sparse output exploits the compact support property: only (degree + 1) basis functions are non-zero at any point. For degree=3, each row has at most 4 non-zero entries, regardless of n_basis. This reduces: - Memory: O(n_samples × n_basis) → O(n_samples × degree) - Computation: O(n_samples × n_basis × degree²) → O(n_samples × degree²)
Sparse output is only available for NumPy backend. PyTorch and JAX backends always return dense arrays (sparse support is experimental).
- penalty_matrix(order=2)[source]¶
Compute difference penalty matrix.
For B-splines, we typically use a difference penalty that approximates the integrated squared derivative penalty.
- Parameters:
order (int, default=2) – Order of differences (2 for approximating second derivative).
- Returns:
S – Penalty matrix where β’Sβ approximates ∫ [f^(m)(x)]^2 dx.
- Return type:
Notes
- The difference penalty is:
S = D’D
where D is the order-th difference matrix.
For order=2: D_ij penalizes (β_i - 2β_{i+1} + β_{i+2})² This approximates the integrated squared second derivative.
- static create_knots(x, n_basis=10, degree=3, method='quantile')[source]¶
Create knot vector for B-splines from data.
- Parameters:
- Returns:
knots – Full knot vector with repeated boundary knots.
- Return type:
ndarray
Notes
For open B-splines, boundary knots are repeated (degree + 1) times. This ensures the spline interpolates at boundaries.
- class aurora.smoothing.splines.cubic.CubicSplineBasis(knots, boundary_knots=None)[source]¶
Bases:
objectNatural cubic spline basis for smooth function representation.
Natural cubic splines provide a flexible basis for representing smooth functions. They are especially useful in GAMs for modeling non-linear relationships.
- Parameters:
- Variables:
References
Wood, S.N. (2017). Generalized Additive Models: An Introduction with R (2nd ed.)
- basis_matrix(x)[source]¶
Compute the basis matrix B where B[i,j] = b_j(x_i).
- Parameters:
x (array-like, shape (n_samples,)) – Points at which to evaluate the basis functions.
- Returns:
B – Basis matrix. Uses the same array backend as input x.
- Return type:
Notes
The basis functions are constructed to satisfy: - Cubic polynomials between knots - Continuous second derivatives at knots - Zero second derivatives at boundaries (natural condition)
- penalty_matrix()[source]¶
Compute the penalty matrix S for integrated squared second derivative.
The penalty matrix satisfies: β’Sβ = ∫ [f’’(x)]² dx
Notes
For natural cubic splines, the penalty can be computed analytically. The first two basis functions (constant and linear) receive zero penalty since their second derivatives are zero.
For the ESL (5.4-5.5) basis N_{j+2} = d_j - d_{K-1} with d_j(x) = [(x - xi_j)_+^3 - (x - xi_K)_+^3] / (xi_K - xi_j), the second derivative is d_j’’(x) = 6[(x - xi_j)_+ - (x - xi_K)_+]/(xi_K - xi_j). Since (x - xi_K)_+ = 0 on [xi_1, xi_K] and every N_j’’ vanishes outside the boundary knots, the integral over the real line reduces to
∫ d_i’’ d_j’’ dx = J(xi_i, xi_j) / [(xi_K - xi_i)(xi_K - xi_j)]
with J(t, s) = ∫ 6(x - t)_+ 6(x - s)_+ dx, and
S[i+2, j+2] = D(i,j) - D(i,K-1) - D(K-1,j) + D(K-1,K-1).
- static create_knots(x, n_knots=10, method='quantile')[source]¶
Create knot locations from data.
- Parameters:
x (array-like) – Data values used to determine knot positions.
n_knots (int, default=10) – Number of interior knots to place.
method (str, default='quantile') –
Knot placement method:
'quantile': Place knots at quantiles of x, adapting to the empirical data distribution.'uniform': Place knots uniformly between min(x) and max(x).
- Returns:
knots – Sorted interior knot locations. May contain fewer than n_knots entries if duplicate quantiles are merged.
- Return type:
ndarray, shape (n_knots,) or fewer
- Raises:
ValueError – If n_knots is less than 1 or method is unknown.
Examples
>>> x = np.random.randn(500) >>> knots = CubicSplineBasis.create_knots(x, n_knots=8) >>> len(knots) <= 8 True
- class aurora.smoothing.splines.pspline.PSplineBasis(n_basis=20, degree=3, penalty_order=2, domain=None)[source]¶
Bases:
objectP-Spline basis: B-splines with equally-spaced knots and difference penalty.
P-Splines use a large number of B-spline basis functions with a difference penalty on coefficients to achieve smooth fits. The amount of smoothing is controlled by a single parameter λ.
- Parameters:
n_basis (int, default=20) – Number of B-spline basis functions. More basis functions allow more flexibility but require larger penalty. Typically 15-40 is sufficient.
degree (int, default=3) – B-spline degree. Cubic (degree=3) is standard and provides C² continuity.
penalty_order (int, default=2) – Order of difference penalty: - 1: Penalizes first differences (roughness of coefficients) - 2: Penalizes second differences (curvature, most common) - 3: Penalizes third differences (rate of curvature change)
domain (tuple of float, optional) – (min, max) domain for the basis. If None, determined from data.
- Variables:
Examples
>>> import numpy as np >>> from aurora.smoothing.splines import PSplineBasis >>> >>> # Create basis >>> basis = PSplineBasis(n_basis=25, degree=3, penalty_order=2) >>> >>> # Generate data >>> x = np.linspace(0, 2*np.pi, 100) >>> y = np.sin(x) + np.random.normal(0, 0.2, 100) >>> >>> # Fit with automatic smoothing selection >>> result = basis.fit(x, y, lambda_='gcv') >>> y_smooth = result.fitted_values_
Notes
The P-spline approach separates the basis complexity from smoothness control: - Use enough basis functions (n_basis) to capture the true function - Control smoothness entirely via the penalty parameter λ
For most applications, 20-30 basis functions with cubic degree and second-order penalty works well. The optimal λ is typically selected via GCV, AIC, or REML.
- penalty_matrix()[source]¶
Return the difference penalty matrix S = D’D.
- Returns:
S – Penalty matrix for penalized regression
- Return type:
- Raises:
ValueError – If basis has not been set up yet (call basis_matrix first)
- fit(x, y, lambda_='gcv', weights=None, lambda_range=(1e-6, 1e6))[source]¶
Fit P-spline to data.
- Parameters:
x (array-like) – Predictor values
y (array-like) – Response values
lambda (float or {'gcv', 'aic', 'reml'}) – Smoothing parameter. If string, automatically select via: - ‘gcv’: Generalized Cross-Validation (default, fast) - ‘aic’: Akaike Information Criterion - ‘reml’: Restricted Maximum Likelihood (better for small samples)
weights (array-like, optional) – Observation weights. If None, uniform weights.
lambda_range (tuple of float) – Search range for automatic lambda selection
- Returns:
Fitted P-spline with coefficients, fitted values, etc.
- Return type:
Examples
>>> # Automatic smoothing selection >>> result = basis.fit(x, y, lambda_='gcv') >>> >>> # Fixed smoothing parameter >>> result = basis.fit(x, y, lambda_=100.0) >>> >>> # With observation weights >>> result = basis.fit(x, y, weights=w, lambda_='gcv')
Tensor Products¶
Tensor product smooths for modeling interactions between variables.
Tensor product smooths allow for flexible modeling of interactions between two or more variables. They construct a smooth surface by taking the tensor product of marginal basis functions.
References
- Wood, S.N. (2017). Generalized Additive Models: An Introduction with R.
Chapman and Hall/CRC, 2nd edition. Chapter 5.
- aurora.smoothing.tensor.tensor_product_basis(X1, X2, basis1, basis2)[source]¶
Compute tensor product basis matrix.
Creates a tensor product basis by multiplying basis functions from two marginal bases: B(x₁, x₂) = B₁(x₁) ⊗ B₂(x₂)
- Parameters:
- Returns:
B – Tensor product basis matrix.
- Return type:
ndarray, shape (n, n_basis1 * n_basis2)
Notes
- For each observation i, the tensor product basis is:
B[i, :] = vec(B₁[i, :] ⊗ B₂[i, :])
where ⊗ denotes the outer product.
Examples
>>> import numpy as np >>> from aurora.smoothing.splines.bspline import BSplineBasis >>> from aurora.smoothing.tensor import tensor_product_basis >>> # Create marginal bases >>> x1 = np.linspace(0, 1, 100) >>> x2 = np.linspace(0, 1, 100) >>> knots1 = BSplineBasis.create_knots(x1, n_basis=8, degree=3) >>> knots2 = BSplineBasis.create_knots(x2, n_basis=8, degree=3) >>> basis1 = BSplineBasis(knots1, degree=3) >>> basis2 = BSplineBasis(knots2, degree=3) >>> # Compute tensor product >>> B_tensor = tensor_product_basis(x1, x2, basis1, basis2) >>> B_tensor.shape (100, 64)
- aurora.smoothing.tensor.tensor_product_penalty(S1, S2, p1, p2)[source]¶
Compute tensor product penalty matrices.
For tensor products, we need two penalty matrices: - S_x1: penalizes variation in x₁ direction - S_x2: penalizes variation in x₂ direction
- Parameters:
- Returns:
S_x1 (ndarray, shape (p1*p2, p1*p2)) – Penalty matrix for variation in x₁ direction.
S_x2 (ndarray, shape (p1*p2, p1*p2)) – Penalty matrix for variation in x₂ direction.
- Return type:
Notes
- The tensor product penalty is:
S_x1 = S1 ⊗ I2 (penalizes wiggliness in x₁) S_x2 = I1 ⊗ S2 (penalizes wiggliness in x₂)
where ⊗ is the Kronecker product.
The full penalty is: λ₁ β’ S_x1 β + λ₂ β’ S_x2 β
Examples
>>> import numpy as np >>> from aurora.smoothing.tensor import tensor_product_penalty >>> # Create marginal penalties >>> S1 = np.eye(5) >>> S2 = np.eye(6) >>> # Compute tensor penalties >>> S_x1, S_x2 = tensor_product_penalty(S1, S2, 5, 6) >>> S_x1.shape (30, 30) >>> S_x2.shape (30, 30)
- aurora.smoothing.tensor.fit_tensor_product(X1, X2, y, basis1, basis2, S1, S2, lambda1=1.0, lambda2=1.0, weights=None)[source]¶
Fit a tensor product smooth.
- Parameters:
X1 (ndarray, shape (n,)) – First variable values.
X2 (ndarray, shape (n,)) – Second variable values.
y (ndarray, shape (n,)) – Response variable.
basis1 (object) – First marginal basis.
basis2 (object) – Second marginal basis.
S1 (ndarray, shape (p1, p1)) – Penalty matrix for first marginal basis.
S2 (ndarray, shape (p2, p2)) – Penalty matrix for second marginal basis.
lambda1 (float, default=1.0) – Smoothing parameter for x₁ direction.
lambda2 (float, default=1.0) – Smoothing parameter for x₂ direction.
weights (ndarray, shape (n,), optional) – Observation weights.
- Returns:
result – Dictionary containing: - ‘coefficients’: Coefficient estimates - ‘fitted_values’: Fitted values - ‘basis_matrix’: Tensor product basis matrix - ‘edf’: Effective degrees of freedom
- Return type:
Notes
Minimizes: ||W^(1/2)(y - Bβ)||² + λ₁ β’ S_x1 β + λ₂ β’ S_x2 β
where B is the tensor product basis matrix.
Examples
>>> import numpy as np >>> from aurora.smoothing.splines.bspline import BSplineBasis >>> from aurora.smoothing.tensor import fit_tensor_product >>> # Generate data >>> n = 200 >>> x1 = np.random.uniform(0, 1, n) >>> x2 = np.random.uniform(0, 1, n) >>> y = np.sin(2*np.pi*x1) * np.cos(2*np.pi*x2) + 0.1*np.random.randn(n) >>> # Create bases >>> knots1 = BSplineBasis.create_knots(x1, n_basis=10, degree=3) >>> knots2 = BSplineBasis.create_knots(x2, n_basis=10, degree=3) >>> basis1 = BSplineBasis(knots1, degree=3) >>> basis2 = BSplineBasis(knots2, degree=3) >>> S1 = basis1.penalty_matrix(order=2) >>> S2 = basis2.penalty_matrix(order=2) >>> # Fit tensor product >>> result = fit_tensor_product(x1, x2, y, basis1, basis2, S1, S2) >>> result['fitted_values'].shape (200,)
Penalties¶
Penalties for smoothing parameter estimation.
Smoothing Parameter Selection¶
Smoothing parameter selection strategies.
- aurora.smoothing.selection.gcv_score(y, X, S, lambda_, weights=None)[source]¶
Compute GCV score for given smoothing parameter.
- The GCV score is:
GCV(λ) = (n_eff * RSS) / (n_eff - tr(H))²
where RSS is residual sum of squares and H is the hat matrix.
- Parameters:
- Returns:
score – GCV score (lower is better).
- Return type:
Notes
- The penalized least squares problem is:
min_β ||y - Xβ||² + λ β’Sβ
Solution: β̂ = (X’WX + λS)⁻¹ X’Wy Hat matrix: H = X(X’WX + λS)⁻¹X’W
- GCV approximates leave-one-out CV by:
GCV = RSS / (1 - tr(H)/n)²
With observation weights, n is replaced by the effective sample size n_eff = #{i : w_i > 0} (Wood 2017, §4.5): zero-weight observations contribute neither to RSS nor to tr(H), so counting them in n would bias the score towards undersmoothing.
References
- Craven, P. & Wahba, G. (1978). Smoothing noisy data with spline functions.
Numerische Mathematik, 31, 377-403. https://doi.org/10.1007/BF01404567
Wood, S.N. (2017). Generalized Additive Models: An Introduction with R.
- aurora.smoothing.selection.select_smoothing_parameter(y, X, S, weights=None, lambda_min=1e-6, lambda_max=1e6, method='golden')[source]¶
Select optimal smoothing parameter using GCV.
- Parameters:
y (ndarray, shape (n,)) – Response variable.
X (ndarray, shape (n, p)) – Design matrix (basis matrix).
S (ndarray, shape (p, p)) – Penalty matrix.
weights (ndarray, shape (n,), optional) – Observation weights.
lambda_min (float, default=1e-6) – Minimum smoothing parameter to consider.
lambda_max (float, default=1e6) – Maximum smoothing parameter to consider.
method (str, default='golden') – Optimization method: - ‘golden’: Golden section search (robust, no derivatives) - ‘brent’: Brent’s method (faster, still no derivatives)
- Returns:
result – Dictionary containing: - ‘lambda_opt’: Optimal smoothing parameter - ‘gcv_score’: GCV score at optimum - ‘edf’: Effective degrees of freedom at optimum - ‘coefficients’: Fitted coefficients at optimum - ‘fitted_values’: Fitted values at optimum
- Return type:
Notes
GCV tends to undersmooth slightly compared to REML/ML, but is computationally simpler and doesn’t require distributional assumptions.
For multiple smoothing parameters (multi-dimensional λ), use REML instead.
Examples
>>> from aurora.smoothing.splines.bspline import BSplineBasis >>> x = np.linspace(0, 1, 100) >>> y = np.sin(2 * np.pi * x) + 0.1 * np.random.randn(100) >>> knots = BSplineBasis.create_knots(x, n_basis=10, degree=3) >>> basis = BSplineBasis(knots, degree=3) >>> X = basis.basis_matrix(x) >>> S = basis.penalty_matrix(order=2) >>> result = select_smoothing_parameter(y, X, S) >>> print(f"Optimal λ: {result['lambda_opt']:.4f}") >>> print(f"Effective DoF: {result['edf']:.2f}")
- aurora.smoothing.selection.reml_score(y, X, S, lambda_, weights=None)[source]¶
Compute REML score for given smoothing parameter.
The REML criterion (Wood 2011, eq. 4; Wood 2017, §4.6), profiled over the scale parameter and up to additive constants in lambda, is:
-2 log(REML) = (n - M_p) log(D) + log|X’WX + λS| - r log(λ) - log|S|_+
where D is the penalised deviance
||W^{1/2}(y - Xβ̂)||² + λβ̂'Sβ̂,r = rank(S),M_p = dim null(S), andlog|S|_+is the log pseudo-determinant of S.- Parameters:
- Returns:
reml – REML score (lower is better).
- Return type:
Notes
The REML criterion penalizes both overfitting (via the deviance) and model complexity (via determinant terms). It is invariant to fixed effects and provides unbiased estimates of variance components.
The
-r log(λ)term acts as a barrier preventing λ → 0 (interpolation); omitting it collapses the optimum onto the lower search bound.REML is generally preferred over GCV for: - Multiple smoothing parameters - Small sample sizes - Models with fixed effects
- aurora.smoothing.selection.select_smoothing_parameter_reml(y, X, S, weights=None, lambda_min=1e-6, lambda_max=1e6, method='brent')[source]¶
Select smoothing parameter via REML optimization.
Minimizes the REML criterion over the smoothing parameter λ.
- Parameters:
y (ndarray, shape (n,)) – Response variable.
X (ndarray, shape (n, p)) – Design matrix (basis functions).
S (ndarray, shape (p, p)) – Penalty matrix.
weights (ndarray, shape (n,), optional) – Observation weights.
lambda_min (float, default=1e-6) – Minimum smoothing parameter to search.
lambda_max (float, default=1e6) – Maximum smoothing parameter to search.
method ({'brent', 'golden'}, default='brent') –
Optimization method: - ‘brent’: Bounded Brent search on the log(λ) interval
(
scipy.optimize.minimize_scalar(method='bounded'); recommended)’golden’: Golden section search over a bracket spanning the bounds
- Returns:
result – Dictionary containing: - ‘lambda_opt’: Optimal smoothing parameter - ‘reml_score’: REML score at optimum - ‘coefficients’: Coefficient estimates - ‘fitted_values’: Fitted values - ‘edf’: Effective degrees of freedom
- Return type:
Examples
>>> import numpy as np >>> from aurora.smoothing.selection.reml import select_smoothing_parameter_reml >>> from aurora.smoothing.splines.bspline import BSplineBasis >>> # Generate data >>> x = np.linspace(0, 1, 100) >>> y = np.sin(2 * np.pi * x) + 0.1 * np.random.randn(100) >>> # Create basis >>> knots = BSplineBasis.create_knots(x, n_basis=10, degree=3) >>> basis = BSplineBasis(knots, degree=3) >>> X = basis.basis_matrix(x) >>> S = basis.penalty_matrix(order=2) >>> # Select lambda via REML >>> result = select_smoothing_parameter_reml(y, X, S) >>> print(f"Optimal lambda: {result['lambda_opt']:.4f}") >>> print(f"EDF: {result['edf']:.2f}")
Notes
REML is preferred over GCV when: - You have multiple smoothing parameters - Sample size is small - You want theoretical justification (ML-based)
The optimization is performed on log(λ) scale for numerical stability.
References
- Wood, S.N. (2017). Generalized Additive Models: An Introduction with R.
Chapman and Hall/CRC, 2nd edition.
- aurora.smoothing.selection.select_multiple_smoothing_parameters_reml(y, X_list, S_list, weights=None, lambda_init=None, lambda_min=1e-6, lambda_max=1e6, max_iter=20, tol=1e-4)[source]¶
Select multiple smoothing parameters via REML optimization.
Uses alternating optimization: fix all λⱼ except one, optimize that one, repeat until convergence.
- Parameters:
y (ndarray, shape (n,)) – Response variable.
X_list (list of ndarray) – List of design matrices, one per smooth term.
S_list (list of ndarray) – List of penalty matrices, one per smooth term.
weights (ndarray, shape (n,), optional) – Observation weights.
lambda_init (list of float, optional) – Initial smoothing parameters. If None, starts with all 1.0.
lambda_min (float, default=1e-6) – Minimum smoothing parameter.
lambda_max (float, default=1e6) – Maximum smoothing parameter.
max_iter (int, default=20) – Maximum number of alternating iterations.
tol (float, default=1e-4) – Convergence tolerance for relative change in λ.
- Returns:
result – Dictionary containing: - ‘lambda_opt’: List of optimal smoothing parameters - ‘reml_score’: REML score at optimum - ‘coefficients’: Full coefficient vector - ‘fitted_values’: Fitted values - ‘edf_values’: List of EDF per smooth term - ‘converged’: Whether optimization converged - ‘n_iter’: Number of iterations
- Return type:
Notes
This implements a simple alternating optimization scheme. More sophisticated methods exist (e.g., Newton-Raphson on all λ simultaneously) but are more complex to implement.
The alternating scheme is: 1. Fix λ₁, …, λⱼ₋₁, λⱼ₊₁, …, λₘ 2. Optimize λⱼ via REML 3. Cycle through j = 1, …, m 4. Repeat until convergence
References
- Wood, S.N. (2011). Fast stable restricted maximum likelihood and marginal
likelihood estimation of semiparametric generalized linear models.