AI Knowledge Tree
C1 · Linear & GLMs · lesson 10

Poisson and Gamma GLMs

Use Poisson regression for counts and Gamma regression for positive continuous durations.

⏱ 26 minPrerequisites:Generalized linear models

The GLM framework opens up two regression flavors that Gaussian linear regression handles badly: counts (Poisson) and positive continuous durations (Gamma). Both use a log link so the predicted mean stays positive, and both have a variance structure tied to the mean.

Poisson regression for counts

When the response yy is a count (number of events in a time window, clicks per session, citations per paper), the Gaussian model is wrong on two grounds: counts are nonnegative integers, and their variance grows with the mean. The Poisson model handles both.

Setup:

  • Random component: yi∣xi∼Pois(μi)y_i \mid x_i \sim \text{Pois}(\mu_i), so E[y]=Var⁡(y)=μ\mathbb{E}[y] = \operatorname{Var}(y) = \mu.
  • Link: log link, log⁡μi=xi⊤β\log \mu_i = x_i^\top \beta, hence μi=exi⊤β\mu_i = e^{x_i^\top\beta}.

The log link guarantees the mean stays positive. Coefficients are multiplicative effects on the rate: eβje^{\beta_j} is the factor by which the expected count multiplies when xjx_j increases by one unit[1].

Log-likelihood:

ℓ(β)=∑iyi xi⊤β−exi⊤β−log⁡yi!.\ell(\beta) = \sum_i y_i\, x_i^\top \beta - e^{x_i^\top \beta} - \log y_i!.

Concave in β\beta. The MLE has no closed form; IRLS converges quickly with W=diag⁡(μi)W = \operatorname{diag}(\mu_i) and working response zi=xi⊤β+(yi−μi)/μiz_i = x_i^\top\beta + (y_i - \mu_i)/\mu_i.

Overdispersion

The Poisson assumes Var⁡(y)=μ\operatorname{Var}(y) = \mu. Real count data often has variance larger than the mean (overdispersion), because individual rates themselves vary across units. Two standard remedies:

  • Quasi-Poisson: scale the variance by a dispersion parameter ϕ\phi estimated from residuals, Var⁡(y)=ϕμ\operatorname{Var}(y) = \phi\mu. The point estimates are the same as Poisson; the standard errors widen.
  • Negative Binomial: a Poisson-gamma mixture where each unit has its own rate drawn from a Gamma distribution, allowing Var⁡(y)=μ+μ2/k\operatorname{Var}(y) = \mu + \mu^2/k[2].

A back-of-envelope check: if the sample variance of yy is much larger than the sample mean, expect overdispersion.

Gamma regression for positive continuous responses

When y>0y > 0 is a continuous duration or amount (waiting time, insurance claim, network latency), the Gaussian linear model can predict negative means and assumes constant variance. The Gamma GLM handles both.

Setup:

  • Random component: yi∣xi∼Gamma(mean μi, shape α)y_i \mid x_i \sim \text{Gamma}(\text{mean } \mu_i,\ \text{shape } \alpha). The variance grows as Var⁡(y)=μ2/α\operatorname{Var}(y) = \mu^2/\alpha, so the coefficient of variation is constant.
  • Link: usually log link log⁡μ=x⊤β\log \mu = x^\top \beta in practice (canonical is the reciprocal 1/μ1/\mu, but the log link is preferred because it cannot produce negative means).

Like Poisson, coefficients are multiplicative effects on the mean. The shape parameter α\alpha is estimated from the data (or set by the user).

How to choose

A short decision guide:

  • yy is a count, Var⁡(y)≈μ\operatorname{Var}(y) \approx \mu: Poisson.
  • yy is a count, Var⁡(y)>μ\operatorname{Var}(y) > \mu: Negative Binomial.
  • y>0y > 0 is continuous with constant coefficient of variation: Gamma.
  • y>0y > 0 is continuous with constant variance: linear regression on log⁡y\log y is the lazy alternative.
A Poisson GLM uses a log link so its mean stays positive and grows multiplicatively with the predictor, fitting count data that a straight-line regression cannot.
A Poisson GLM uses a log link so its mean stays positive and grows multiplicatively with the predictor, fitting count data that a straight-line regression cannot.

Check it on arrays

python · numpy
import numpy as np

rng = np.random.default_rng(0)

# Poisson regression on simulated counts
n, d = 500, 3
X = np.column_stack([np.ones(n), rng.normal(size=(n, d - 1))])
beta_true = np.array([0.5, 0.4, -0.3])
mu_true = np.exp(X @ beta_true)
y = rng.poisson(mu_true)

def fit_glm_log(X, y, var_fn, steps=20):
    """IRLS with log link."""
    mu = (y + 1.0).astype(float)
    for _ in range(steps):
        eta = np.log(mu)
        z = eta + (y - mu) / mu                       # canonical-link work resp
        W = mu**2 / var_fn(mu)                        # for log link: W = mu^2/Var(y)
        beta = np.linalg.solve(X.T @ (W[:, None] * X), X.T @ (W * z))
        mu = np.exp(X @ beta)
    return beta, mu

beta_pois, mu_pois = fit_glm_log(X, y, var_fn=lambda m: m)
print("Poisson coefficients:", np.round(beta_pois, 3), " (true ", beta_true, ")")

# Multiplicative interpretation: e^beta gives the rate multiplier
for j, b in enumerate(beta_pois):
    print(f"  e^beta_{j} = {np.exp(b):.3f}  -> rate multiplies by this per +1 unit")

# Gamma regression on positive continuous response with log link
beta_g_true = np.array([1.0, 0.5, -0.2])
mu_g_true = np.exp(X @ beta_g_true)
shape = 4.0
scale = mu_g_true / shape                              # so that mean = shape * scale = mu_g_true
y_g = rng.gamma(shape=shape, scale=scale)

beta_gamma, mu_gamma = fit_glm_log(X, y_g, var_fn=lambda m: m**2 / shape)
print("\nGamma coefficients:  ", np.round(beta_gamma, 3), " (true ", beta_g_true, ")")

# Check overdispersion: Poisson has Var(y) = mu
print(f"\nempirical Var/mean for Poisson data: {y.var() / y.mean():.2f}  (Poisson = 1)")

Both GLMs recover their true coefficients, the multiplicative interpretation of eβe^\beta holds for the log link, and the Poisson sample variance is close to the sample mean.

Exercises

Exercise 1. A Poisson regression for daily store visits fits intercept β0=2.0\beta_0 = 2.0 and a "weekend" coefficient β1=0.7\beta_1 = 0.7 (with x1=1x_1 = 1 on weekends, 00 otherwise). Compute the expected count on a weekday and on a weekend, and state the multiplicative effect of the weekend.

Solution

Step 1. The log link gives μ=ex⊤β\mu = e^{x^\top\beta}. On a weekday x1=0x_1 = 0, so the linear predictor is β0=2.0\beta_0 = 2.0 and

μweekday=e2.0≈7.389.\mu_{\text{weekday}} = e^{2.0} \approx 7.389.

Step 2. On a weekend x1=1x_1 = 1, so the predictor is β0+β1=2.7\beta_0 + \beta_1 = 2.7 and

μweekend=e2.7≈14.880.\mu_{\text{weekend}} = e^{2.7} \approx 14.880.

Step 3. The ratio of the two means is e2.7/e2.0=e0.7≈2.014e^{2.7}/e^{2.0} = e^{0.7} \approx 2.014. The weekend multiplies the expected count by eβ1≈2.01e^{\beta_1} \approx 2.01, the multiplicative reading of a log-link coefficient: each unit of the predictor scales the rate by eβje^{\beta_j} rather than adding to it.

Exercise 2. Show that the Poisson log-likelihood ℓ(β)=∑i(yixi⊤β−exi⊤β−log⁡yi!)\ell(\beta) = \sum_i \big(y_i x_i^\top\beta - e^{x_i^\top\beta} - \log y_i!\big) is concave in β\beta, so any stationary point is the global maximum.

Solution

Step 1. Write ηi=xi⊤β\eta_i = x_i^\top\beta and μi=eηi\mu_i = e^{\eta_i}. The term log⁡yi!\log y_i! does not depend on β\beta, so it drops out of every derivative. Differentiate one example's contribution with respect to β\beta:

∇β(yiηi−eηi)=yixi−eηixi=(yi−μi) xi,\nabla_\beta\big(y_i \eta_i - e^{\eta_i}\big) = y_i x_i - e^{\eta_i} x_i = (y_i - \mu_i)\, x_i,

using ∇βηi=xi\nabla_\beta \eta_i = x_i and ddηeη=eη\frac{d}{d\eta} e^\eta = e^\eta.

Step 2. Differentiate again to get the Hessian. Since ∇βμi=μixi\nabla_\beta \mu_i = \mu_i x_i,

∇β2(yiηi−eηi)=−μi xixi⊤.\nabla^2_\beta\big(y_i \eta_i - e^{\eta_i}\big) = -\mu_i\, x_i x_i^\top.

Summing over examples, ∇2ℓ=−∑iμixixi⊤=−X⊤WX\nabla^2\ell = -\sum_i \mu_i x_i x_i^\top = -X^\top W X with W=diag⁡(μi)W = \operatorname{diag}(\mu_i) and every μi=eηi>0\mu_i = e^{\eta_i} > 0.

Step 3. For any vector vv, v⊤(−X⊤WX)v=−∑iμi(xi⊤v)2≤0v^\top(-X^\top W X)v = -\sum_i \mu_i (x_i^\top v)^2 \le 0, because each μi>0\mu_i > 0 and each squared term is nonnegative. The Hessian is negative semidefinite, so ℓ\ell is concave and any stationary point is a global maximum.

Exercise 3 (code). Overdispersion is the signature that count data violates the Poisson mean-equals-variance assumption. Simulate a negative-binomial-like process (a Poisson whose rate is itself Gamma-distributed across units) and compare its variance-to-mean ratio against a plain Poisson sample with the same mean. Assert the mixed process is overdispersed (ratio above 1.5) while the plain Poisson sits near 1.

Solution
python · numpy
import numpy as np

rng = np.random.default_rng(0)
n = 200_000
mean_rate = 5.0

# Plain Poisson: Var/mean should be ~1.
y_pois = rng.poisson(mean_rate, size=n)
ratio_pois = y_pois.var() / y_pois.mean()

# Poisson-Gamma mixture: each unit's rate is Gamma with mean mean_rate.
# This is the negative-binomial construction; it inflates the variance.
shape = 1.0
rates = rng.gamma(shape=shape, scale=mean_rate / shape, size=n)
y_mix = rng.poisson(rates)
ratio_mix = y_mix.var() / y_mix.mean()

print(f"plain Poisson Var/mean: {ratio_pois:.3f}")
print(f"Poisson-Gamma Var/mean: {ratio_mix:.3f}")

assert abs(ratio_pois - 1.0) < 0.05        # Poisson: variance equals mean
assert ratio_mix > 1.5                     # mixture is overdispersed

The plain Poisson sample has a variance-to-mean ratio near 1, matching Var⁡(y)=μ\operatorname{Var}(y) = \mu. The Poisson-Gamma mixture, where each unit draws its own rate, has a ratio well above 1: this is the overdispersion that pushes the modeler from Poisson to Quasi-Poisson or Negative Binomial.

This closes the C1 linear-models track; the next subsection turns to a different geometric story: max-margin classifiers and kernels.


References

  1. [1]McCullagh, P., & Nelder, J. A. (1989). Generalized Linear Models (2nd ed.), §6 (Poisson), §8 (Gamma). Chapman & Hall.↩
  2. [2]Nelder, J. A., & Wedderburn, R. W. M. (1972). "Generalized linear models". JRSS-A, 135(3), 370-384. doi:10.2307/2344614. The original GLM treatment of Poisson and Gamma responses.↩