Shape-constrained P-spline: convex or concave.
ConvexPSpline(
k=20,
degree=3,
m=2,
concave=False,
)
Uses the same B-spline basis and difference penalty as ~whittaker.smooths.pspline.PSpline, but additionally requires the fitted curve f(x) = \sum_j \beta_j B_j(x) to be convex (or, with concave=True, concave) over the whole domain. As with MonotonePSpline, this is enforced as a linear inequality on the coefficients: for an equally-spaced B-spline basis, the curve is convex whenever the second differences of the coefficients, \Delta^2 \beta_j = \beta_j - 2\beta_{j-1} + \beta_{j-2}, are all non-negative. Use ConvexPSpline (via s(x, bs="cx") for convex or s(x, bs="cv") for concave in a formula) when the relationship is known to have a single bend of consistent curvature — e.g. a cost curve, a learning curve, or a concave production function — and an unconstrained smooth would otherwise produce spurious inflection points from noise.
Parameters
k: int = 20
-
Number of B-spline basis functions. The default is 20.
degree: int = 3
-
B-spline polynomial degree. The default is 3 (cubic).
m: int = 2
-
Difference penalty order. The default is 2.
concave: bool = False
-
If
True, enforce concavity. The default is False (convex).
Notes
As with MonotonePSpline, the constraint is enforced during P-IRLS fitting by projecting the ordinary coefficient update onto the convex (or concave) cone after each iteration: smooth terms whose basis is a ConvexPSpline have their coefficient block passed through project_convex before the next iteration’s linear predictor is formed (see whittaker.fitting.pirls). The projection extends the monotone PAVA projection by one order of differencing: convexity requires the first differences of the coefficients, d_j = \beta_j - \beta_{j-1}, to form a non-decreasing sequence (equivalently, that the second differences of \beta are non-negative), so project_convex computes the first differences, projects them onto the monotone cone with PAVA (see the Notes on MonotonePSpline for the algorithm), and then reconstructs \beta by cumulatively summing the projected differences back up from \beta_0. Concavity is handled by negating the differences before and after the PAVA projection, mirroring decreasing=True for MonotonePSpline.
Examples
import numpy as np
import whittaker as wt
rng = np.random.default_rng(0)
x = np.sort(rng.uniform(-1, 1, 200))
y = x**2 + rng.normal(scale=0.1, size=200)
model = wt.GAM("y ~ s(x, bs='cx')").fit({"x": x, "y": y})
new_x = np.linspace(-1, 1, 50)
fitted = model.predict({"x": new_x}).values
second_diff = np.diff(fitted, n=2)
# Second differences are non-negative up to fitting/projection tolerance.
np.all(second_diff >= -1e-2)
Attributes
constraint_direction
Sign convention used by the second-difference PAVA projection.
constraint_direction: int
Returns -1 when concave=True (the first differences of the coefficients are negated before and after the PAVA projection, yielding non-positive second differences) or 1 for the default convex case. Consulted by project_convex and by whittaker.fitting.pirls when projecting this term’s coefficient block each iteration.
constraint_order
Order of the difference constraint enforced by projection.
Always 2: convexity/concavity is a constraint on the second differences of the coefficients (equivalently, the first differences must form a monotone sequence), as opposed to MonotonePSpline’s first-order (1) constraint.
Methods
null_space_dimension()
Return the dimension of the basis’s unpenalized null space.
As with MonotonePSpline, the shape constraint is enforced by post-hoc projection of the coefficients rather than by removing degrees of freedom from the penalty, so this basis reports 0 unpenalized dimensions.