Skip to content
Statistical RethinkingStatistical Rethinking

10.2 Poisson regression

Poisson regression models count data using a log link. It assumes that the logarithm of the expected count is a linear function of the predictors.

10.2.1 The Poisson model

log⁡(λi)=α+βxi,yi∼Poisson(λi)\log(\lambda_i) = \alpha + \beta x_i, \quad y_i \sim \text{Poisson}(\lambda_i)

The log link ensures λi>0\lambda_i > 0. The key assumption is that mean = variance (E[yi]=Var(yi)=λiE[y_i] = \text{Var}(y_i) = \lambda_i).

# Poisson regression: tool count and population
set.seed(42)
n <- 30

# Simulate: tool count increases with log population

log_pop <- runif(n, 7, 12)
lambda_true <- exp(-2 + 0.3 * log_pop)
total_tools <- rpois(n, lambda_true)

# Fit Poisson regression

m <- glm(total_tools ~ log_pop, family = poisson)

cat("Poisson regression:\n")
cat(" Intercept:", round(coef(m)[1], 3), "\n")
cat(" log_pop:", round(coef(m)[2], 3), "\n")

# Interpret: each unit increase in log_pop multiplies expected count by exp(beta)

cat("\n Multiplicative effect of log_pop:", round(exp(coef(m)[2]), 3), "\n")

# Predicted counts

cat("\nPredicted counts:\n")
for (lp in c(8, 10, 12)) {
cat(sprintf(" log_pop = %d: lambda = %.1f\n", lp, exp(coef(m)[1] + coef(m)[2] * lp)))
}

# Plot

x_seq <- seq(7, 12, length.out = 100)
log_pred <- coef(m)[1] + coef(m)[2] * x_seq
lambda_pred <- exp(log_pred)

plot(log_pop, total_tools, pch = 16, col = "steelblue",
xlab = "log(population)", ylab = "Total tools",
main = "Poisson regression: tools vs population")
lines(x_seq, lambda_pred, lwd = 2, col = "firebrick")
Click Run to execute this code in your browser with WebR

10.2.2 Exposure and offset

When observations have different exposure (e.g., different time periods or areas), we include an offset:

log⁡λiexposurei=α+βxi\log\frac{\lambda_i}{\text{exposure}_i} = \alpha + \beta x_i

log⁡(λi)=log⁡(exposurei)+α+βxi\log(\lambda_i) = \log(\text{exposure}_i) + \alpha + \beta x_i

# Poisson regression with offset (exposure)
set.seed(42)
n <- 50

# Different exposure times

exposure <- runif(n, 1, 10)

# Rate depends on x

x <- runif(n, 0, 5)
rate <- exp(-1 + 0.4 _ x) # events per unit time
counts <- rpois(n, rate _ exposure)

# Fit with offset

m_offset <- glm(counts ~ x + offset(log(exposure)), family = poisson)

cat("Poisson regression with offset:\n")
cat(" Intercept:", round(coef(m_offset)[1], 3), "\n")
cat(" x:", round(coef(m_offset)[2], 3), "\n")
cat("\n The offset adjusts for different exposure times.\n")
cat(" The coefficient on x is the rate ratio per unit x.\n")

# Compare without offset (wrong!)

m_no_offset <- glm(counts ~ x, family = poisson)
cat("\nWithout offset (wrong):\n")
cat(" x:", round(coef(m_no_offset)[2], 3), "(biased)\n")
Click Run to execute this code in your browser with WebR

10.2.3 Checking for overdispersion

The Poisson assumes mean = variance. Overdispersion (variance > mean) is common and leads to underestimated uncertainty.

# Check for overdispersion
set.seed(42)
n <- 100
x <- runif(n, 0, 10)

# Overdispersed counts: variance > mean

lambda <- exp(0.5 + 0.2 * x)

# Add extra variation

y <- rpois(n, lambda * rgamma(n, shape = 2, rate = 2))

# Fit Poisson

m <- glm(y ~ x, family = poisson)

# Check: mean vs variance

cat("Overdispersion check:\n")
cat(" Mean of y:", round(mean(y), 2), "\n")
cat(" Variance of y:", round(var(y), 2), "\n")
cat(" Ratio (var/mean):", round(var(y) / mean(y), 2), "\n")

if (var(y) / mean(y) > 1.5) {
cat("\n Overdispersion detected! Ratio > 1\n")
cat(" Consider negative-binomial or gamma-Poisson model.\n")
}

# Deviance-based overdispersion test

deviance_stat <- deviance(m) / df.residual(m)
cat("\n Deviance / df:", round(deviance_stat, 2), "\n")
cat(" (Values >> 1 indicate overdispersion)\n")
Click Run to execute this code in your browser with WebR
Rethinking

The Poisson assumption is strong. The Poisson distribution assumes that the mean equals the variance. This is a very specific assumption — it means that the count process has no unobserved heterogeneity. In practice, counts almost always have more variance than the Poisson allows, because there are unobserved factors that vary across observations. This is overdispersion, and it is the rule, not the exception. Always check for it. If present, use a model that allows extra variance (Section 10.3).

Summary of Section 10.2

  • Poisson regression: log⁡(λi)=α+βxi\log(\lambda_i) = \alpha + \beta x_i with yi∼Poisson(λi)y_i \sim \text{Poisson}(\lambda_i).
  • Coefficients are log rate ratios — eβe^\beta is the multiplicative effect.
  • Use an offset for varying exposure: log⁡(λi)=log⁡(exposurei)+α+βxi\log(\lambda_i) = \log(\text{exposure}_i) + \alpha + \beta x_i.
  • Check for overdispersion: if variance > mean, the Poisson underestimates uncertainty.
  • Overdispersion is the rule, not the exception — always check.