# Import libraries
import whittaker as wk
import numpy as np
# Load data and add synthetic noise predictor
data = wk.load_dataset("wages", as_frame=True)
rng = np.random.default_rng(23)
data["noise"] = rng.normal(0, 1, len(data))Use Select Smoothing to Shrink Away a Term
Standard GAM smooths are penalised to control wiggliness, but they cannot shrink entirely to zero — a term always contributes at least a linear effect. Select smoothing adds a second penalty that pushes the entire smooth toward zero when it has no explanatory power. This lets the fitting process perform automatic variable selection, analogous to LASSO in linear models.
Enable it globally with select=True, or per-term with the shrinkage cubic spline basis bs='cs'.
Set up the data
Load the wages dataset and add a synthetic noise column — a predictor that is genuinely unrelated to wages. The goal is to show that select smoothing identifies and discards it.
Fit without select smoothing
First, fit the model in the default way so you have a baseline for comparison. All three terms will receive some EDF regardless of whether they are truly useful.
# Fit standard GAM as baseline
m_standard = wk.GAM("wage ~ s(age) + s(experience) + s(noise)").fit(data)
m_standard.edf[4.027847821294607, 3.163582450809665, 1.0005439269452188]
Each smooth is allocated some EDF even if its contribution is negligible. The noise term will have a non-zero EDF here simply because the standard penalty cannot reach zero.
Fit with select=True
Now refit using select=True. The extra penalty allows any smooth to be shrunk completely out of the model.
# Refit with select=True to enable extra shrinkage penalty
m_select = wk.GAM(
"wage ~ s(age) + s(experience) + s(noise)",
).fit(data, select=True)
m_select.edf[3.994041131322576, 3.1619520817917213, 0.0036464502279951265]
The EDF values are returned in the order the terms appear in the formula: age, experience, noise. The noise term’s EDF should be close to zero, indicating that the penalty has effectively removed it.
Read the summary
m_select.summary()GAM fit summary
============================================================
Formula: wage ~ s(age) + s(experience) + s(noise)
Family: Gaussian(link='identity')
Inference: GCV
Observations: 800
Coefficients: 28
Parametric coefficients:
Term Estimate Std.Err t value p-value
------------------------ ---------- ---------- ---------- ----------
(Intercept) 45.2167 0.4174 108.339 < 1e-16
Approximate significance of smooth terms:
Term EDF Ref.df Chi.sq p-value
------------------------ ------ ------ ---------- ----------
s(age) 3.99 4 1362.498 < 1e-16
s(experience) 3.16 4 184.423 < 1e-16
s(noise) 0.00 1 0.000 0.9851
Total EDF: 8.16
Scale est: 139.354698
Deviance: 110346.6741
Null dev: 374733.5230
Dev. expl: 70.6%
GCV score: 140.790700
AIC: 6228.08
BIC: 6266.30
In the smooth terms table, look at the EDF column for s(noise). An EDF near 0 means the term has been shrunk away and contributes almost nothing to the predictions. The real predictors — age and experience — retain meaningful EDF.
Alternative: per-term shrinkage basis
Instead of the global flag, you can apply a shrinkage cubic spline (bs='cs') to individual terms. This is useful when you want select behaviour on only some smooths.
# Fit with per-term shrinkage cubic spline basis
m_cs = wk.GAM(
"wage ~ s(age, bs='cs') + s(experience, bs='cs') + s(noise, bs='cs')"
).fit(data)
m_cs.edf[3.985475625883009, 2.097999483251983, 8.306894703760463e-05]
The result should be comparable to select=True. Choose bs='cs' when you want fine-grained control over which terms are eligible for shrinkage.
Compare EDF across approaches
# Compare total EDF across all three approaches
m_standard.edf_total, m_select.edf_total, m_cs.edf_total(9.19197419904949, 8.159639663342293, 7.08355817808203)
The select models should have lower total EDF because the noise term has been shrunk out. A lower total EDF with similar or better deviance explained is a sign the extra penalty is doing useful work.
When to use select smoothing
| Situation | Recommendation |
|---|---|
| Many candidate predictors, unsure which matter | select=True for a single pass |
| Mixed confidence in predictors | bs='cs' on uncertain terms only |
| All predictors known to be relevant | Standard smooths; select adds overhead |
Select smoothing is most valuable in exploratory modeling where you have more candidate predictors than you expect to need. It does not replace domain knowledge, but it provides a principled way to let the data confirm or dismiss a term.