A GAM prediction is an intercept plus a sum of smooth functions. predict(type="terms") exposes each smooth’s contribution separately, so you can see how much each predictor is adding to the linear predictor at any covariate value. This is useful for explaining which variable is driving a high or low fitted value.
Fit
Fit a two-smooth Gaussian GAM on the wages dataset.
# Import libraries
import whittaker as wk
import numpy as np
# Load data and fit two-smooth GAM
data = wk.load_dataset("wages")
model = wk.GAM("wage ~ s(age) + s(experience)").fit(data)
model.summary()
GAM fit summary
============================================================
Formula: wage ~ s(age) + s(experience)
Family: Gaussian(link='identity')
Inference: GCV
Observations: 800
Coefficients: 19
Parametric coefficients:
Term Estimate Std.Err t value p-value
------------------------ ---------- ---------- ---------- ----------
(Intercept) 45.2167 0.4174 108.341 < 1e-16
Approximate significance of smooth terms:
Term EDF Ref.df Chi.sq p-value
------------------------ ------ ------ ---------- ----------
s(age) 4.03 5 1363.371 < 1e-16
s(experience) 3.16 4 184.291 < 1e-16
Total EDF: 8.19
Scale est: 139.348765
Deviance: 110337.5740
Null dev: 374733.5230
Dev. expl: 70.6%
GCV score: 140.790323
AIC: 6228.08
BIC: 6266.45
Inspect each smooth’s contribution
Access each term’s contribution array using its label.
preds_terms.terms["s(age)"].round(3)
array([-21.521, -9.109, 5.311, 14.86 ])
preds_terms.terms["s(experience)"].round(3)
array([-4.84 , 4.884, 12.298, 15.613])
Each value is the smooth’s contribution to the linear predictor at the corresponding row in new_data, centered so that it integrates to zero over the data. A large positive value means that covariate value pushes the prediction upward.
Sum of contributions
The .values property sums all term contributions, giving the linear predictor minus the intercept.
preds_terms.values.round(3)
array([-26.361, -4.225, 17.609, 30.473])
To compare with the full link-scale prediction (which adds the intercept), call predict() with type="link".
# Predict on the link scale
link_preds = model.predict(new_data, type="link")
link_preds.values.round(3)
array([18.856, 40.992, 62.825, 75.69 ])
The difference between the two is the intercept: a single scalar added to every prediction.
(link_preds.values - preds_terms.values).round(4)
array([45.2167, 45.2167, 45.2167, 45.2167])
The constant difference confirms the identity: link prediction = intercept + sum(term contributions).
Compare contributions across observations
Stack the two smooth contributions side by side to see which predictor dominates at each point.
# Stack both smooth contributions side by side
np.column_stack([
preds_terms.terms["s(age)"].round(3),
preds_terms.terms["s(experience)"].round(3),
])
array([[-21.521, -4.84 ],
[ -9.109, 4.884],
[ 5.311, 12.298],
[ 14.86 , 15.613]])
Rows correspond to the four observations. The first column is the age contribution; the second is experience. When the age value is large and experience is near zero, age is the primary driver of that prediction (and vice versa). This decomposition is more informative than inspecting the overall fitted value alone.