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
The log link ensures . The key assumption is that mean = variance ().
# 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")10.2.2 Exposure and offset
When observations have different exposure (e.g., different time periods or areas), we include an offset:
# 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")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")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: with .
- Coefficients are log rate ratios — is the multiplicative effect.
- Use an offset for varying exposure: .
- Check for overdispersion: if variance > mean, the Poisson underestimates uncertainty.
- Overdispersion is the rule, not the exception — always check.