import whittaker as wk
data = wk.load_dataset("fish", as_frame=True)
# column names
data.columns.tolist()['temperature', 'depth', 'count']
Use the Poisson family whenever your response is a non-negative integer count (e.g., fish caught per survey, page views per hour, defects per batch). The log link means that each covariate has a multiplicative effect on the expected count, so the coefficients are interpretable as log-rate ratios. The smooth term s(temperature) captures any non-linear relationship without you having to guess the functional form.
Load the built-in fish dataset as a DataFrame and inspect its columns.
import whittaker as wk
data = wk.load_dataset("fish", as_frame=True)
# column names
data.columns.tolist()['temperature', 'depth', 'count']
The fish dataset has three columns: temperature (water temperature in degrees Celsius), depth (sampling depth in meters), and count (fish observed per transect).
| temperature | depth | count | |
|---|---|---|---|
| 0 | 13.232243 | 20.213893 | 7.0 |
| 1 | 13.969823 | 20.798765 | 14.0 |
| 2 | 24.284515 | 36.965959 | 3.0 |
| 3 | 9.838319 | 30.989895 | 2.0 |
| 4 | 20.002011 | 45.968733 | 4.0 |
Each row is one transect observation. Temperature has a curved relationship with count that a linear term would miss.
Fit a Poisson GAM with a smooth term for temperature and a linear term for depth.
GAM fit summary
============================================================
Formula: count ~ s(temperature) + depth
Family: Poisson(link='log')
Observations: 300
Coefficients: 11
Parametric coefficients:
Term Estimate Std.Err z value p-value
------------------------ ---------- ---------- ---------- ----------
(Intercept) 2.1998 0.0479 45.901 < 1e-16
depth -0.0249 0.0018 -13.916 < 1e-16
Approximate significance of smooth terms:
Term EDF Ref.df Chi.sq p-value
------------------------ ------ ------ ---------- ----------
s(temperature) 4.24 5 294.821 < 1e-16
Total EDF: 6.24
Deviance: 321.0387
Null dev: 843.4578
Dev. expl: 61.9%
GCV score: 1.116056
Scale est: 1.000000
AIC: 1304.77
BIC: 1327.88
The summary reports the effective degrees of freedom (EDF) for s(temperature). An EDF near 1 indicates a near-linear smooth. A higher EDF signals a more wiggly curve. The depth term enters as a parametric linear predictor because depth effects are often close to linear once temperature is accounted for.
Plotting the partial effects lets you see each smooth on its own scale.
Each panel shows the contribution of one term to the linear predictor, holding all other terms at their mean. The shaded band is a 95% pointwise confidence interval. A flat band crossing zero means the term is not contributing meaningfully.
Predictions are returned on the count scale automatically (there’s no need to apply exp() yourself). Sweep temperature across its observed range at a fixed depth to trace the fitted count curve.
import numpy as np
# Build grid over temperature at fixed depth
new_data = {
"temperature": np.linspace(8, 28, 50),
"depth": np.full(50, 10.0),
}
# Predict counts on the response scale
preds = model.predict(new_data).values
# first five predicted counts
preds[:5]array([2.11545194, 2.38988486, 2.69989897, 3.04998022, 3.44464172])
Sweeping temperature from 8 to 28 at a fixed depth of 10 m gives a count curve that tracks the non-linear partial effect you saw in the plot above.
If the residual deviance is much larger than the residual degrees of freedom, the Poisson assumption of equal mean and variance may be too restrictive. In that case swap the family for wk.NegativeBinomial(), which adds a dispersion parameter and absorbs the extra variation without changing the formula.