When two continuous predictors interact (meaning the effect of one depends on the level of the other) a tensor product smooth te(x1, x2) captures that joint surface without assuming the predictors operate on the same scale. Unlike s(x1, x2), a tensor product is invariant to the units of each predictor, making it the right choice when x1 and x2 are measured in different units or have very different ranges.
Load data
Load the wages dataset, which contains age, experience, and hourly wage for survey respondents.
import whittaker as wk
data = wk.load_dataset("wages", as_frame=True)
data.head()
|
age |
experience |
wage |
| 0 |
62.323637 |
9.216817 |
80.231223 |
| 1 |
42.032395 |
2.487841 |
40.024189 |
| 2 |
63.883454 |
7.251912 |
60.292852 |
| 3 |
21.799293 |
3.799293 |
20.232940 |
| 4 |
46.545724 |
1.738119 |
62.383214 |
The columns are age (years), experience (years in the workforce), and wage (hourly rate in dollars). Age and experience are correlated but not identical as some workers have more experience relative to their age, others have little.
Fit
Fit a Gamma GAM with a tensor product of age and experience. The Gamma family is appropriate for wages because the response is strictly positive and often right-skewed.
# Fit tensor product interaction surface
model = wk.GAM(
"wage ~ te(age, experience)",
family=wk.Gamma(),
).fit(data)
model.summary()
/opt/hostedtoolcache/Python/3.12.14/x64/lib/python3.12/site-packages/scipy/optimize/_optimize.py:2358: RuntimeWarning: invalid value encountered in scalar subtract
p = (xf - fulc) * q - (xf - nfc) * r
/opt/hostedtoolcache/Python/3.12.14/x64/lib/python3.12/site-packages/scipy/optimize/_optimize.py:2359: RuntimeWarning: invalid value encountered in scalar subtract
q = 2.0 * (q - r)
/opt/hostedtoolcache/Python/3.12.14/x64/lib/python3.12/site-packages/scipy/optimize/_optimize.py:2357: RuntimeWarning: invalid value encountered in scalar multiply
q = (xf - fulc) * (fx - fnfc)
GAM fit summary
============================================================
Formula: wage ~ te(age, experience)
Family: Gamma(link='log')
Inference: GCV
Observations: 800
Coefficients: 100
Parametric coefficients:
Term Estimate Std.Err t value p-value
------------------------ ---------- ---------- ---------- ----------
(Intercept) 6878617665819.6689 0.0000 7504768711412710412182880256.000 < 1e-16
Approximate significance of smooth terms:
Term EDF Ref.df Chi.sq p-value
------------------------ ------ ------ ---------- ----------
te(age, experience) 22.93 23 135575690797148592.000 < 1e-16
Total EDF: 23.93
Scale est: 320845457739210.875000
Deviance: 248999919535812480.0000
Null dev: 205.9584
Dev. expl: -120898139560873168.0%
GCV score: 329915952163046.062500
AIC: 60159.48
BIC: 60271.57
The summary reports a single EDF for the tensor product term. A high EDF indicates that the interaction surface has substantial curvature: the wage effect of additional experience differs across age groups.
Partial effects
wk.partial_effects(model)
The partial effects plot renders the tensor product as a two-dimensional surface or contour. Look for regions where the wage surface curves differently along age versus experience: for example, high experience may carry a larger wage premium for mid-career workers than for very young or very old ones.
Diagnostics
For a Gamma model, pay attention to the Q-Q plot. Heavy tails or a systematic arc indicate the distributional assumption may need revisiting. Clean residuals here confirm the tensor product captured the main interaction structure.