TensorInteractionBasis

Tensor product interaction basis (ti()-style pure interaction smooth).

Usage

Source

TensorInteractionBasis(marginals)

TensorInteractionBasis builds a tensor product smooth like TensorProductBasis, but first projects each marginal basis onto its own penalty range space (removing the marginal’s null-space components, such as the constant and linear terms) before taking the tensor product. The result spans only the pure interaction between the covariates — none of the lower-order main-effect structure that a plain tensor product basis would otherwise reintroduce. This makes it the right building block for ANOVA-style decompositions of a smooth surface into orthogonal pieces, e.g. s(x1) + s(x2) + ti(x1, x2), where s(x1) and s(x2) already carry the main effects and ti(x1, x2) is meant to add only what a sum of the two one-dimensional smooths cannot represent. Use TensorInteractionBasis instead of TensorProductBasis whenever main effects are (or will be) modeled by separate marginal smooths and double-counting of the main-effect structure inside the interaction term must be avoided; use TensorProductBasis or TensorProductBasisT2 when the interaction term is meant to stand alone and include the main effects itself.

Parameters

marginals: list[SmoothBasis]
List of (unfitted) marginal basis objects, one per covariate dimension, e.g. [TPRS(k=10), TPRS(k=8)]. At least 2 marginals are required.

Notes

For each marginal basis with penalty S_j, fit() eigendecomposes S_j and keeps only the eigenvectors U_j with (numerically) positive eigenvalues — the penalized range space — while discarding the null-space eigenvectors (the unpenalized polynomials, dimension M_j). Each marginal’s basis matrix is then reprojected onto this reduced space, B_j' = B_j U_j, of dimension r_j = k_j - M_j rather than the original k_j, before the marginals are combined with the same row-wise Kronecker product used by TensorProductBasis:

\mathbf{B}[i, :] = \mathbf{B}_1'[i, :] \otimes \mathbf{B}_2'[i, :] \otimes \cdots \otimes \mathbf{B}_d'[i, :].

Because every marginal contributes only its range space, none of the columns of B correspond to a main-effect direction, and the total basis dimension is the product of the range-space dimensions, k = r_1 \cdot r_2 \cdots r_d, smaller than the k_1 \cdots k_d used by TensorProductBasis on the same marginals. In this reduced basis, each marginal’s penalty is already diagonal (it is expressed in its own eigenbasis), \operatorname{diag}(d_{j,1}, \ldots, d_{j,r_j}) for the retained eigenvalues d_{j,i}, and the per-direction penalty matrices are the Kronecker products

\mathbf{S}_j^{\text{ti}} = \mathbf{I}_{r_1} \otimes \cdots \otimes \operatorname{diag}(d_{j, 1}, \ldots, d_{j, r_j}) \otimes \cdots \otimes \mathbf{I}_{r_d},

exactly analogous to TensorProductBasis.penalty_matrices() but operating in the range-space coordinates. Since every retained coefficient direction is, by construction, in some marginal’s range space and therefore penalized by at least one of these matrices, null_space_dimension() is always 0 for the interaction basis itself. Note that the eigendecomposition used to find each marginal’s range space assumes the marginal penalty has a well-separated null space (a numerical tolerance of 1e-10 relative to the largest eigenvalue is used to distinguish “zero”); for marginal bases with unusual or nearly-singular penalties this tolerance may need revisiting.

Examples

import numpy as np
from whittaker.smooths.tensor import TensorInteractionBasis
from whittaker.smooths.tprs import TPRS

rng = np.random.default_rng(0)
x = rng.uniform(0, 1, (100, 2))

basis = TensorInteractionBasis([TPRS(k=6), TPRS(k=5)]).fit(x)
B = basis.basis_matrix(x)
penalties = basis.penalty_matrices()
B.shape, len(penalties)
((100, 12), 2)

Attributes

Name Description
marginals The list of marginal basis objects, fitted after fit() is called.
n_basis Total number of basis functions.

marginals

The list of marginal basis objects, fitted after fit() is called.

marginals: list[SmoothBasis]

Each marginal is fit independently to its own covariate column; its basis matrix is then projected onto its own penalty range space before being combined with the others via the row-wise Kronecker product to form basis_matrix().


n_basis

Total number of basis functions.

n_basis: int

Equal to the product r_1 * r_2 * ... * r_d of the marginal range-space dimensions (each marginal’s basis dimension minus its null-space dimension), since main-effect directions are projected out before the marginals are combined.

Methods

Name Description
basis_matrix() Evaluate the pure-interaction tensor basis at x.
fit() Fit each marginal basis, then decompose its penalty into null and range spaces.
identifiability_constraints() Return a sum-to-zero constraint on the interaction basis, if derivable.
null_space_dimension() Return the dimension of the basis’s unpenalized null space.
penalty_matrices() Return one penalty matrix per marginal direction, in range-space coordinates.
penalty_matrix() Sum of all per-direction penalty matrices, for single-penalty compatibility.

basis_matrix()

Evaluate the pure-interaction tensor basis at x.

Usage

Source

basis_matrix(x)

Projects each marginal’s basis matrix onto its own penalty range space (dropping the marginal’s null-space/main-effect columns) before combining them with the row-wise Kronecker product.

Parameters

x: NDArray
Covariate values, shape (n, d) where d matches the number of marginals.

Returns

NDArray
Design matrix of shape (n, k) where k = self.n_basis is the product of the marginal range-space dimensions.

fit()

Fit each marginal basis, then decompose its penalty into null and range spaces.

Usage

Source

fit(x)

For each marginal, fits the marginal basis to its column of x, eigendecomposes the marginal’s penalty matrix, and retains the eigenvectors with positive eigenvalues (the range space) for use by basis_matrix() and penalty_matrices().

Parameters

x: NDArray
Training covariates, shape (n, d) where d is the number of marginals.

Returns

TensorInteractionBasis
Returns self for method chaining.

Raises

ValueError
If the number of columns in x does not match the number of marginal bases.

identifiability_constraints()

Return a sum-to-zero constraint on the interaction basis, if derivable.

Usage

Source

identifiability_constraints()

Attempts to recover each marginal’s training covariate values (via a _x_train or _knots attribute) to build a constraint that forces the interaction surface to have mean zero over the training data. If any marginal does not expose its training data in a recognized form, no constraint can be derived and None is returned.

Returns

NDArray or None
A (1, k) matrix of the mean basis-function values over the training grid, or None if the training covariates could not be recovered from the marginals.

null_space_dimension()

Return the dimension of the basis’s unpenalized null space.

Usage

Source

null_space_dimension()

Because every marginal contributes only its penalty range space (the null-space components, such as constants and linear terms, were discarded during fit()), every retained coefficient direction is penalized by at least one of the matrices returned by penalty_matrices(). This is always 0 for the pure-interaction basis.

Returns

int
Always 0.

penalty_matrices()

Return one penalty matrix per marginal direction, in range-space coordinates.

Usage

Source

penalty_matrices()

Because each marginal’s basis has already been reprojected onto its penalty range space during fit(), the marginal penalty in that space is simply the diagonal matrix of its retained eigenvalues; this method Kronecker-expands that diagonal matrix into the full interaction basis exactly as TensorProductBasis.penalty_matrices() does for the ordinary (non-reprojected) basis.

Returns

list[NDArray]
List of d matrices, each of shape (k, k) where d is the number of marginals and k is the total (range-space) basis dimension.

penalty_matrix()

Sum of all per-direction penalty matrices, for single-penalty compatibility.

Usage

Source

penalty_matrix()

Returns

NDArray
The (k, k) sum of all matrices returned by penalty_matrices().