Smoothing¶
fdatools.smoothing: smooth() with GCV or degrees-of-freedom selection of λ,
positive / monotone / morph constraints, irregular designs, and the
scikit-learn Smoother estimator.
Penalised smoothing: turn discrete observations into functional data.
A single entry point, :func:smooth, replaces the twelve R fda smoothing
functions (smooth.basis, smooth.basisPar, Data2fd,
smooth.monotone, smooth.pos, smooth.morph, lambda2df,
lambda2gcv, df2lambda and friends). The unconstrained problem is the
penalised least-squares fit of Ramsay & Silverman chapter 5,
.. math::
(\Phi^{T} W \Phi + \lambda R)\,c = \Phi^{T} W y ,
\qquad R_{ij} = \int (L\varphi_i)(L\varphi_j)\,dt ,
whose hat matrix H = Φ (ΦᵀWΦ + λR)⁻¹ ΦᵀW gives the equivalent degrees of
freedom df = tr H and the generalised cross-validation criterion
gcv_i = (SSE_i / n) / (1 - df/n)² for curve i.
Constrained fits reuse the same penalty on a latent function W:
constraint="positive" fits x = exp W, constraint="monotone" fits
x(t) = β₀ + β₁ ∫ₐᵗ exp W(u) du, and constraint="morph" is the monotone
fit with β pinned so that the fitted curve maps the domain onto itself.
Their derivatives of any order are exact: with w_j = D^j W, Faà di Bruno's
formula gives Dⁿ exp W = exp(W) Bₙ(w_1, …, w_n) for the complete Bell
polynomial Bₙ (B₀ = 1, B_{n+1} = Σ_{i=0}^{n} C(n, i) B_{n-i} w_{i+1}),
and Dⁿ x = β₁ D^{n-1} exp W for a monotone fit.
Examples:
>>> import numpy as np
>>> from fdatools import BSpline
>>> from fdatools.smoothing import smooth
>>> t = np.linspace(0.0, 1.0, 21)
>>> y = np.sin(2 * np.pi * t)
>>> result = smooth(y, t, basis=BSpline(domain=(0.0, 1.0), n_basis=12), lam=1e-8)
>>> bool(np.max(np.abs(result.fd(t)[:, 0] - y)) < 1e-3)
True
SmoothResult
dataclass
¶
SmoothResult(
fd: FData,
df: float,
gcv: Array,
sse: float,
penalty_matrix: Array,
lam: float,
y2c_map: Array,
beta: Array | None = None,
constraint: str | None = None,
)
Outcome of a :func:smooth call.
Attributes:
| Name | Type | Description |
|---|---|---|
fd |
FData
|
The fitted curves. For a constrained fit this holds the latent
function |
df |
float
|
Equivalent degrees of freedom, |
gcv |
array
|
Generalised cross-validation score per curve, shaped like the curve axes
of |
sse |
float
|
Residual sum of squares of the fit, summed over every point and curve.
Unweighted even when |
penalty_matrix |
array
|
The roughness penalty |
lam |
float
|
The smoothing parameter actually used -- the value selected by GCV or by the degrees-of-freedom target when one of those was requested. |
y2c_map |
array or tuple of arrays
|
The linear map |
beta |
array or None
|
|
constraint |
str or None
|
The constraint that was applied, if any. |
Examples:
>>> import numpy as np
>>> from fdatools.smoothing import smooth
>>> t = np.linspace(0.0, 1.0, 15)
>>> result = smooth(t**2, t, lam=1e-8)
>>> result.constraint is None and result.beta is None
True
__call__ ¶
__call__(t: Any, deriv: int = 0) -> Array
Evaluate the fitted curves or one of their derivatives.
For a constrained fit the derivatives are exact to rounding error, for
any order: Dⁿ exp W = exp(W) Bₙ(DW, …, DⁿW) with the complete Bell
polynomial Bₙ (Faà di Bruno's formula), and a monotone curve has
Dⁿ x = β₁ D^{n-1} exp W for n >= 1. The value itself
(deriv=0) of a monotone curve is β₀ + β₁ ∫ₐᵗ exp W, integrated by
Gauss-Legendre quadrature between the basis break points.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
array
|
Points at which to evaluate. |
required |
deriv
|
int
|
Derivative order, |
0
|
Returns:
| Type | Description |
|---|---|
array
|
Fitted values with the shape of |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import numpy as np
>>> from fdatools.smoothing import smooth
>>> t = np.linspace(0.0, 1.0, 40)
>>> fit = smooth(np.exp(2 * t), t, constraint="positive", lam=1e-8)
>>> np.round(fit(np.array([0.5]), 3)[:, 0] / np.exp(1.0), 2) # D³ e^{2t} = 8 e^{2t}
array([8.])
Smoother ¶
Smoother(
basis: Basis | None = None,
*,
t: Any = None,
lam: float | str = "gcv",
penalty: int | LDO = 2,
weights: Any = None,
)
Bases: TransformerMixin, BaseEstimator
Penalised smoothing as a scikit-learn transformer.
Each row of X is one curve sampled on the shared grid t; transform
returns the basis coefficients, so a smoother can head a pipeline whose later
steps work on the coefficient representation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basis
|
Basis
|
Expansion basis. |
None
|
t
|
array
|
Sampling grid of length |
None
|
lam
|
float or str
|
Smoothing parameter, as in :func: |
'gcv'
|
penalty
|
int or LDO
|
Roughness operator, as in :func: |
2
|
weights
|
array
|
Observation weights of length |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
fd_ |
FData
|
The fitted curves, one per row of the training |
result_ |
SmoothResult
|
The full fit, including |
t_ |
ndarray
|
The grid actually used. |
n_features_in_ |
int
|
Number of sampling points seen during |
Examples:
>>> import numpy as np
>>> from fdatools.smoothing import Smoother
>>> rng = np.random.default_rng(0)
>>> t = np.linspace(0.0, 1.0, 25)
>>> X = np.sin(2 * np.pi * t) + 0.05 * rng.standard_normal((6, 25))
>>> coefs = Smoother(t=t, lam=1e-4).fit_transform(X)
>>> coefs.shape[0]
6
fit ¶
fit(X: Any, y: Any = None, *, t: Any = None) -> Smoother
Smooth every row of X and store the resulting curves.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
array of shape (n_samples, n_features)
|
One sampled curve per row. |
required |
y
|
ignored
|
Present for API compatibility. |
None
|
t
|
array
|
Sampling grid for this call, overriding the one given at construction. |
None
|
Returns:
| Type | Description |
|---|---|
Smoother
|
The fitted estimator. |
transform ¶
transform(X: Any) -> Array
Return the basis coefficients of every row of X.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
array of shape (n_samples, n_features)
|
Curves sampled on the grid seen during |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Coefficients of shape |
get_feature_names_out ¶
get_feature_names_out(input_features: Any = None) -> Array
Return the basis-function names produced by :meth:transform.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_features
|
ignored
|
Present for API compatibility; the output names come from the basis. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
One name per basis function. |
lambda_to_df ¶
lambda_to_df(
t: Any,
basis: Basis,
lam: float,
*,
penalty: int | LDO = 2,
weights: Any = None,
) -> float
Equivalent degrees of freedom of a smooth at a given lam.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
array
|
Observation points. |
required |
basis
|
Basis
|
Expansion basis. |
required |
lam
|
float
|
Smoothing parameter, on the linear scale. |
required |
penalty
|
int or LDO
|
Roughness operator; default the curvature penalty |
2
|
weights
|
array
|
Observation weights. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
|
Examples:
>>> import numpy as np
>>> from fdatools import BSpline
>>> from fdatools.smoothing import lambda_to_df
>>> t = np.linspace(0.0, 1.0, 30)
>>> round(lambda_to_df(t, BSpline(domain=(0.0, 1.0), n_basis=10), 0.0), 6)
10.0
df_to_lambda ¶
df_to_lambda(
t: Any,
basis: Basis,
df: float,
*,
penalty: int | LDO = 2,
weights: Any = None,
) -> float
Smoothing parameter whose smooth has df degrees of freedom.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
array
|
Observation points. |
required |
basis
|
Basis
|
Expansion basis. |
required |
df
|
float
|
Target equivalent degrees of freedom. |
required |
penalty
|
int or LDO
|
Roughness operator; default the curvature penalty |
2
|
weights
|
array
|
Observation weights. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The |
Examples:
>>> import numpy as np
>>> from fdatools import BSpline
>>> from fdatools.smoothing import df_to_lambda, lambda_to_df
>>> t = np.linspace(0.0, 1.0, 40)
>>> basis = BSpline(domain=(0.0, 1.0), n_basis=12)
>>> lam = df_to_lambda(t, basis, 6.0)
>>> round(lambda_to_df(t, basis, lam), 8)
6.0
gcv_curve ¶
gcv_curve(
y: Any,
t: Any,
basis: Basis,
lambdas: Any,
*,
penalty: int | LDO = 2,
weights: Any = None,
) -> Array
GCV score of every curve over a grid of smoothing parameters.
One eigendecomposition serves the whole grid, so the cost is
O(n_lambda · n_t · n_basis) rather than a factorisation per lambda.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
array
|
Observations of shape |
required |
t
|
array
|
Observation points, shape |
required |
basis
|
Basis
|
Expansion basis. |
required |
lambdas
|
array
|
Smoothing parameters on the linear scale. |
required |
penalty
|
int or LDO
|
Roughness operator; default the curvature penalty |
2
|
weights
|
array
|
Observation weights. |
None
|
Returns:
| Type | Description |
|---|---|
array
|
Shape |
Examples:
>>> import numpy as np
>>> from fdatools import BSpline
>>> from fdatools.smoothing import gcv_curve
>>> t = np.linspace(0.0, 1.0, 40)
>>> y = np.sin(2 * np.pi * t)
>>> scores = gcv_curve(y, t, BSpline(domain=(0.0, 1.0), n_basis=10), [1e-6, 1e-2])
>>> scores.shape
(2,)
smooth ¶
smooth(
y: Any,
t: Any,
*,
basis: Basis | None = None,
lam: float | str = "gcv",
penalty: int | LDO = 2,
constraint: str | None = None,
weights: Any = None,
df: float | None = None,
) -> SmoothResult
Fit smooth curves to discrete observations by penalised least squares.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
array or sequence of arrays
|
Observations of shape |
required |
t
|
array or sequence of arrays
|
Observation points shared by every curve, or one vector per curve for an
irregular design (in which case |
required |
basis
|
Basis
|
Expansion basis. Defaults to a cubic B-spline on |
None
|
lam
|
float or str
|
Smoothing parameter. A number is used directly; |
'gcv'
|
penalty
|
int or LDO
|
Roughness operator |
2
|
constraint
|
(None, 'positive', 'monotone', 'morph')
|
Shape constraint. |
None
|
weights
|
array
|
Observation weights of shape |
None
|
df
|
float
|
Target degrees of freedom; equivalent to |
None
|
Returns:
| Type | Description |
|---|---|
SmoothResult
|
The fit, its degrees of freedom, GCV scores and data-to-coefficient map. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the shapes of |
Examples:
>>> import numpy as np
>>> from fdatools.smoothing import smooth
>>> t = np.linspace(0.0, 1.0, 60)
>>> y = np.exp(np.sin(4 * t))
>>> fit = smooth(y, t, constraint="positive", lam=1e-6)
>>> bool(np.min(fit(t)) > 0.0)
True