import whittaker as wk
data = wk.load_dataset("mcycle")
# Fit convex-constrained and unconstrained models
m_convex = wk.GAM("accel ~ s(times, bs='cx')").fit(data)
m_free = wk.GAM("accel ~ s(times, bs='tp')").fit(data)Fit a Convex Smooth
A convex function curves upward and so its slope is non-decreasing from left to right. This shape arises naturally in physics and engineering: the motorcycle helmet acceleration data from mcycle shows a sharp drop followed by a recovery, a pattern with a pronounced convex region. The bs="cx" basis imposes convexity across the entire range of the predictor, which prevents the smooth from developing the extra wiggles that a flexible unconstrained basis sometimes fits to noise.
Fit both models
Load the mcycle dataset and fit a convex-constrained model alongside an unconstrained baseline. Both use the same formula; only the basis changes.
Summarize the convex model
The EDF for the convex smooth reflects the constraint: it cannot be lower than 1 (linear) but the curvature is restricted to a single direction, so very high EDF values are suppressed.
m_convex.summary()GAM fit summary
============================================================
Formula: accel ~ s(times, bs='cx')
Family: Gaussian(link='identity')
Inference: GCV
Observations: 133
Coefficients: 20
Parametric coefficients:
Term Estimate Std.Err t value p-value
------------------------ ---------- ---------- ---------- ----------
(Intercept) -29.0127 4.1901 -6.924 2.128e-10
Approximate significance of smooth terms:
Term EDF Ref.df Chi.sq p-value
------------------------ ------ ------ ---------- ----------
s(times, bs='cx') 8.58 9 139.823 < 1e-16
Total EDF: 9.58
Scale est: 1783.561548
Deviance: 220128.6540
Null dev: 357878.4929
Dev. expl: 38.5%
GCV score: 1921.990623
AIC: 1382.70
BIC: 1410.39
Compare the smooths
Let’s plot the partial effect from each model. The convex smooth follows a bowl-shaped trajectory and the unconstrained smooth is free to develop local reversals that may or may not be physically meaningful.
m_convex.plot()m_free.plot()Check diagnostics
Run the four-panel diagnostic check on the convex model. If the residuals vs fitted plot shows a systematic arch, the convexity constraint may be too strong for this region of the data.
wk.check(m_convex)When convexity is appropriate
The convex basis is most useful when theory dictates the shape (cost curves in economics, voltage–discharge curves in battery modeling, or recovery curves in biomechanics). Imposing the constraint when it matches reality reduces variance without introducing meaningful bias. When shape is uncertain, bs="tp" remains the safer default.