# Model Count Data with Poisson

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 data

Load the built-in fish dataset as a DataFrame and inspect its columns.


``` python
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).


``` python
data.head()
```


|     | 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

Fit a Poisson GAM with a smooth term for temperature and a linear term for depth.


``` python
model = wk.GAM(
    "count ~ s(temperature) + depth",
    family=wk.Poisson(),
).fit(data)

model.summary()
```


    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.


# Partial effects

Plotting the partial effects lets you see each smooth on its own scale.


``` python
wk.partial_effects(model)
```


<style>
  #altair-viz-1da057eb5cd148679a44e138567fef79.vega-embed {
    width: 100%;
    display: flex;
  }

  #altair-viz-1da057eb5cd148679a44e138567fef79.vega-embed details,
  #altair-viz-1da057eb5cd148679a44e138567fef79.vega-embed details summary {
    position: relative;
  }
</style>


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.


# On the response scale

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.


``` python
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.


# Overdispersion

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.
