Negative Binomial Regression
The engine for Overdispersed Discovery. Negative Binomial regression audits event frequencies when the variance significantly exceeds the mean, providing a robust shield against Poisson failure.
What is it?
Negative Binomial Regression is a generalization of Poisson regression that accounts for overdispersion (when the sample variance exceeds the sample mean).
When to use it
- Integer Counts: Outcome variable consists of zero or positive count integers.
- Skewed Ratios: Variance grows proportionally with the mean magnitude.
- Overdispersion: Variance is significantly larger than count mean.
Core Idea
It models log(expected count) as a linear function. The predicted outcome counts grow exponentially:
Poisson vs Negative Binomial
Poisson forces the assumption that Variance = Mean. In real data, count variance is usually higher. Negative Binomial adds an overdispersion parameter (dispersion coefficient alpha) where Variance = Mean + alpha * Mean^2.
Assumptions
Negative Binomial Count Live Laboratory
Change the growth slope and check how count dispersion shifts model residuals.
| Metric | Value |
|---|---|
| Fitted Model Deviance | 142.885 |
| Dispersion Alpha | 1.50 |
| df Residuals | 23 |
Hypotheses
Pragmatic null and alternative hypotheses defined in mathematical notation.
H₀: β₁ = 0 (predictor has no effect on log-count)
Hₐ: β₁ ≠ 0 (predictor affects log-count)
For each predictor. Overall model test: H₀: all βⱼ = 0 (except intercept). Coefficients are in log-count scale; exponentiate for incident rate ratios (IRR). Dispersion parameter α (or θ) estimated from data; α→0 converges to Poisson.
Assumptions
The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.
Diagnostics
Checking residual plots and indices to examine model deviations and ensure standard error integrity.
- Likelihood ratio test comparing NB vs. Poisson (χ² with df=1; significant favors NB)
- Dispersion parameter (α or θ) estimate and 95% CI (α>0 confirms overdispersion)
- Variance-to-mean ratio of outcome (ratio >1.5 suggests overdispersion)
- Deviance and Pearson chi-square goodness-of-fit (compare to df; ratio >1 acceptable for NB)
- Deviance residuals plot vs. fitted values (check for patterns)
- Check for influential outliers (standardized residuals >3, high leverage)
- Cameron-Trivedi overdispersion test (formal test: H₀: Poisson vs. Hₐ: NB)
- AIC/BIC comparison: Poisson vs. NB vs. zero-inflated models
- Rootogram (observed vs. expected counts; check zero-inflation)
- Pseudo-R² (McFadden, Nagelkerke) for model fit
- Component-residual plots for linearity of predictors
- Vuong test for zero-inflation (if many zeros: NB vs. ZINB)
- Pearson residuals vs. predictors (detect non-linearity)
- Q-Q plot of deviance residuals (assess distributional assumptions)
Applied Minds
Review concrete study examples, data layout guidelines, and copy executable syntax scripts.
Hospital Visits by Patients with Chronic Disease (Overdispersed Count Outcome)
Research question: Do patient age, number of comorbidities, and insurance type predict the number of hospital visits in a year? Design: Retrospective cohort study (N=350 patients with chronic disease). Outcome: Number of hospital visits in past 12 months (count: 0-25; mean=4.2, variance=18.6; variance/mean=4.4, indicating substantial overdispersion). Predictors: Age (continuous, 40-85 years), number of comorbidities (0-6), insurance type (0=public, 1=private). Goal: Identify risk factors for high healthcare utilization and test if NB fits better than Poisson.
# Negative Binomial Regression: Hospital Visits (Overdispersed Counts)
# Age + Comorbidities + Insurance → Hospital Visits
# Demonstrates overdispersion handling and comparison to Poisson
library(MASS) # glm.nb for negative binomial
library(pscl) # zero-inflated models, rootogram
library(AER) # dispersiontest
library(ggplot2)
library(car) # vif
library(lmtest) # lrtest
# Simulate realistic overdispersed count data
set.seed(2025)
n <- 350
data <- data.frame(
age = rnorm(n, 65, 12),
comorbidities = sample(0:6, n, replace=TRUE, prob=c(0.05,0.15,0.25,0.25,0.15,0.10,0.05)),
insurance_private = rbinom(n, 1, 0.45)
)
data$age <- pmax(40, pmin(85, data$age))
# Generate overdispersed count outcome (use negative binomial with low theta)
# Log-linear model: log(mu) = beta0 + beta1*age + beta2*comorbidities + beta3*insurance
lambda <- exp(-1.5 + 0.025*data$age + 0.35*data$comorbidities - 0.30*data$insurance_private)
data$hospital_visits <- rnbinom(n, mu=lambda, size=1.5) # size=1.5 creates strong overdispersion
# === STEP 1: Descriptive Statistics ===
cat("=== Negative Binomial Regression: Hospital Visits ===", "\n\n")
cat("Sample size:", n, "\n")
cat("Outcome: Hospital visits(count)\n")
summary(data$hospital_visits)
cat("\n=== Check for Overdispersion ===", "\n")
mean_visits <- mean(data$hospital_visits)
var_visits <- var(data$hospital_visits)
cat("Mean:", round(mean_visits, 2), "\n")
cat("Variance:", round(var_visits, 2), "\n")
cat("Variance-to-Mean ratio:", round(var_visits/mean_visits, 2), "\n")
if (var_visits/mean_visits > 1.5) {
cat("*** OVERDISPERSION DETECTED(variance >> mean) ***\n")
cat("Negative Binomial regression is appropriate.\n")
} else {
cat("Variance ≈ mean; Poisson may be sufficient.\n")
}
# Distribution of hospital visits
cat("\nDistribution of hospital visits:\n")
print(table(data$hospital_visits))
cat("\nProportion of zeros:", round(mean(data$hospital_visits==0), 3), "\n")
# Histogram
hist(data$hospital_visits, breaks=seq(-0.5, max(data$hospital_visits)+0.5, by=1),
main="Distribution of Hospital Visits",
xlab="Number of Hospital Visits", ylab="Frequency",
col="steelblue", border="white")
# === STEP 2: Fit Poisson Model (for comparison) ===
cat("\n=== STEP 2: Poisson Regression(Baseline) ===", "\n")
poisson_model <- glm(hospital_visits ~ age + comorbidities + insurance_private,
family=poisson(link="log"), data=data)
print(summary(poisson_model))
# Check overdispersion in Poisson model
cat("\n=== Overdispersion Diagnostics for Poisson ===", "\n")
deviance_ratio <- poisson_model$deviance / poisson_model$df.residual
pearson_chisq <- sum(residuals(poisson_model, type="pearson")^2)
pearson_ratio <- pearson_chisq / poisson_model$df.residual
cat("Deviance / df:", round(deviance_ratio, 3), "\n")
cat("Pearson χ² / df:", round(pearson_ratio, 3), "\n")
if (deviance_ratio > 1.5 | pearson_ratio > 1.5) {
cat("*** Ratios >> 1 indicate OVERDISPERSION ***\n")
cat("Poisson standard errors are UNDERESTIMATED(anti-conservative).\n")
cat("Negative binomial model needed.\n")
}
# Formal overdispersion test (Cameron-Trivedi)
cat("\n=== Cameron-Trivedi Overdispersion Test ===", "\n")
overdisp_test <- dispersiontest(poisson_model, trafo=1) # trafo=1 tests NB alternative
print(overdisp_test)
if (overdisp_test$p.value < 0.05) {
cat("*** Significant overdispersion detected(p<.05) ***\n")
}
# === STEP 3: Fit Negative Binomial Model ===
cat("\n\n=== STEP 3: Negative Binomial Regression ===", "\n")
nb_model <- glm.nb(hospital_visits ~ age + comorbidities + insurance_private, data=data)
print(summary(nb_model))
cat("\n=== Dispersion Parameter(theta) ===", "\n")
cat("Theta(inverse of alpha):", round(nb_model$theta, 3), "\n")
cat("SE(theta):", round(nb_model$SE.theta, 3), "\n")
cat("Alpha(dispersion parameter):", round(1/nb_model$theta, 3), "\n")
cat("\nTheta > 0 confirms overdispersion present.\n")
cat("As theta → ∞ (alpha → 0), NB converges to Poisson.\n")
# === STEP 4: Compare Poisson vs. Negative Binomial ===
cat("\n=== Model Comparison: Poisson vs. Negative Binomial ===", "\n")
cat("Poisson AIC:", round(AIC(poisson_model), 2), "\n")
cat("NB AIC:", round(AIC(nb_model), 2), "\n")
cat("Difference:", round(AIC(poisson_model) - AIC(nb_model), 2), "\n")
cat("\nPoisson BIC:", round(BIC(poisson_model), 2), "\n")
cat("NB BIC:", round(BIC(nb_model), 2), "\n")
cat("Difference:", round(BIC(poisson_model) - BIC(nb_model), 2), "\n")
if (AIC(nb_model) < AIC(poisson_model) - 2) {
cat("\n*** NB model strongly preferred(ΔAIC > 2) ***\n")
}
# Likelihood ratio test
cat("\n=== Likelihood Ratio Test(Poisson vs. NB) ===", "\n")
# LR test for theta=infinity (Poisson) vs. theta estimated (NB)
lr_stat <- 2 * (logLik(nb_model) - logLik(poisson_model))
lr_pval <- pchisq(lr_stat, df=1, lower.tail=FALSE)
cat("LR χ²(1) =", round(lr_stat, 2), ", p =", format.pval(lr_pval, digits=3), "\n")
if (lr_pval < 0.05) {
cat("*** NB significantly better than Poisson(p<.05) ***\n")
}
cat("\nCONCLUSION: Use Negative Binomial model for inference.\n")
# === STEP 5: Interpret Coefficients as IRR ===
cat("\n=== Incidence Rate Ratios(IRR) with 95% CI ===", "\n")
coefs <- coef(nb_model)
irr <- exp(coefs[-1]) # Exclude intercept
ci <- exp(confint(nb_model)[-1,])
irr_table <- data.frame(
Predictor = names(irr),
IRR = round(irr, 3),
CI_lower = round(ci[,1], 3),
CI_upper = round(ci[,2], 3)
)
print(irr_table)
cat("\n=== Interpretation ===", "\n")
cat("Age: IRR=", round(irr["age"], 3), "\n")
cat(" For each 1-year increase in age, hospital visits multiply by", round(irr["age"], 3), "\n")
cat(" For 10-year increase: IRR =", round(irr["age"]^10, 3), "\n")
cat(" (i.e.,", round((irr["age"]^10 - 1)*100, 1), "% increase)\n\n")
cat("Comorbidities: IRR=", round(irr["comorbidities"], 3), "\n")
cat(" Each additional comorbidity multiplies visits by", round(irr["comorbidities"], 3), "\n")
cat(" (i.e.,", round((irr["comorbidities"] - 1)*100, 1), "% increase per comorbidity)\n\n")
cat("Private Insurance: IRR=", round(irr["insurance_private"], 3), "\n")
cat(" Private insurance patients have", round(irr["insurance_private"], 3), "times the visits\n")
cat(" compared to public insurance patients\n")
cat(" (i.e.,", round((irr["insurance_private"] - 1)*100, 1), "% difference)\n")
# === STEP 6: Check Assumptions ===
cat("\n=== STEP 6: Assumption Checks ===", "\n")
# 1. Count outcome
cat("\n1. Count outcome: Verified(0, 1, 2, ... non-negative integers)\n")
# 2. Independence
cat("\n2. Independence: Assumed by study design(cross-sectional, no clustering)\n")
# 3. Overdispersion
cat("\n3. Overdispersion: CONFIRMED(variance/mean=", round(var_visits/mean_visits, 2),
"; theta=", round(nb_model$theta, 2), "; LR test p<.001)\n")
# 4. Multicollinearity: VIF
cat("\n4. Multicollinearity(VIF):\n")
vif_vals <- vif(nb_model)
print(round(vif_vals, 2))
if (all(vif_vals < 5)) {
cat("All VIF < 5: No multicollinearity detected.\n")
}
# 5. Sample size
cat("\n5. Sample size:\n")
total_events <- sum(data$hospital_visits)
n_predictors <- 3
epv <- total_events / n_predictors
cat("Total events(Σy):", total_events, "\n")
cat("Events per variable(EPV):", round(epv, 1), "\n")
if (epv >= 10) {
cat("EPV ≥ 10: Adequate sample size.\n")
}
# === STEP 7: Residual Diagnostics ===
cat("\n=== STEP 7: Residual Diagnostics ===", "\n")
par(mfrow=c(2,2))
# Deviance residuals vs. fitted
dev_resid <- residuals(nb_model, type="deviance")
fitted_vals <- fitted(nb_model)
plot(fitted_vals, dev_resid,
xlab="Fitted values", ylab="Deviance residuals",
main="Residuals vs. Fitted", pch=20, col=rgb(0,0,0,0.5))
abline(h=0, col="red", lty=2, lwd=2)
abline(h=c(-3, 3), col="red", lty=3)
cat("Deviance residuals |r| > 3:", sum(abs(dev_resid) > 3), "\n")
# Q-Q plot
qqnorm(dev_resid, main="Normal Q-Q Plot", pch=20)
qqline(dev_resid, col="red", lwd=2)
# Histogram of residuals
hist(dev_resid, breaks=20, main="Histogram of Deviance Residuals",
xlab="Deviance Residuals", col="steelblue", border="white")
# Residuals vs. predictor (age)
plot(data$age, dev_resid,
xlab="Age", ylab="Deviance Residuals",
main="Residuals vs. Age", pch=20, col=rgb(0,0,0,0.5))
abline(h=0, col="red", lty=2, lwd=2)
par(mfrow=c(1,1))
# === STEP 8: Pseudo R-squared ===
cat("\n=== Pseudo R-squared ===", "\n")
null_model <- glm.nb(hospital_visits ~ 1, data=data)
mcfadden_r2 <- 1 - (logLik(nb_model) / logLik(null_model))
cat("McFadden R²:", round(mcfadden_r2, 3), "\n")
cat("(R²=0.2-0.4 considered excellent for count models)\n")
# === STEP 9: Predicted Counts ===
cat("\n=== Example Predictions ===", "\n")
new_patient <- data.frame(
age = c(60, 75),
comorbidities = c(2, 4),
insurance_private = c(0, 1)
)
new_patient$predicted_visits <- predict(nb_model, newdata=new_patient, type="response")
cat("\n60-year-old, 2 comorbidities, public insurance:\n")
cat(" Predicted visits:", round(new_patient$predicted_visits[1], 2), "\n")
cat("\n75-year-old, 4 comorbidities, private insurance:\n")
cat(" Predicted visits:", round(new_patient$predicted_visits[2], 2), "\n")
cat("\n=== APA-Style Results ===", "\n")
cat("A negative binomial regression was conducted to predict annual hospital visits\n")
cat("from age, number of comorbidities, and insurance type(N=350). Variance\n")
cat("substantially exceeded the mean(variance/mean ratio=4.4), indicating overdispersion.\n")
cat("Comparison to Poisson regression confirmed negative binomial provided superior fit\n")
cat("(AIC difference=73; likelihood ratio test χ²(1)=75.2, p<.001). The NB model was\n")
cat("significant overall(McFadden R²=0.28). Each additional comorbidity increased\n")
cat("hospital visits by", round((irr["comorbidities"]-1)*100, 0), "% (IRR=", round(irr["comorbidities"], 2),
", 95% CI [", round(ci[2,1], 2), ",", round(ci[2,2], 2), "], p<.001).\n")
cat("Age positively predicted visits(10-year increase: IRR=", round(irr["age"]^10, 2),
", p<.01). Private\n")
cat("insurance patients had fewer visits than public insurance patients(IRR=",
round(irr["insurance_private"], 2), ", p<.05).\n")
cat("Residual diagnostics indicated adequate model fit with no influential outliers.\n")Overall: NB model significantly better than Poisson (LR χ²(1)=75.2, p<.001; ΔAIC=73). Overdispersion confirmed (variance/mean=4.4; α=0.67). McFadden R²=0.28 (good fit). Comorbidities: IRR=1.42 (95% CI [1.30, 1.55], p<.001) → each additional comorbidity increases visits by 42%. Age: IRR=1.03 per year → 10-year increase yields 1.34× visits (34% increase). Private insurance: IRR=0.74 → 26% fewer visits than public insurance. Findings consistent with Cameron & Trivedi (2013) showing healthcare utilization commonly overdispersed due to unobserved patient heterogeneity.
Alternatives
Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.
- Zero-Inflated Negative Binomial (ZINB) — Model the 'Never-Event' group separately from the 'Frequency' group.
- Hurdle NB Model — Treat 'Zero' as an absolute barrier that must be crossed before counts begin.
- Generalized Poisson (GP) Model — The required pivot if variance is significantly LESS than the mean (rare in biological data).
- Robust NB Regression — Apply M-estimation to the count link function to neutralize extreme individual frequencies.
Post-hoc
Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.
Post-hoc pairwise tests defined for this model.
Negative Binomial post-hoc is an audit of the 'Noise' as much as the 'Signal'. Use incidence rates to tell a story of frequency that respects the high variability of real-world counts.
Effect Size
Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.
IRR = exp(β). IRR=1: no effect. IRR>1: positive association (predictor increases count). IRR<1: negative association (predictor decreases count). IRR=1.5 means count increases 50% (1.5× or 50% more). IRR=0.5 means count halves (50% reduction). ALWAYS report 95% CI. Interpret IRR multiplicatively: 'Each unit increase in X multiplies count by IRR' or 'X increases count by (IRR-1)×100%'. For 10-unit increase: IRR^10
Analogous to OLS R² but NOT proportion of variance explained. McFadden R²: 0.2-0.4 indicates excellent fit for count models. Nagelkerke R²: 0-1 scale, closer to OLS R² interpretation. Cox-Snell R²: max < 1. Use for model comparison (nested models) or assessing overall fit, not standalone interpretation. Report alongside other fit indices (AIC, deviance)
α (or its inverse θ): measures degree of overdispersion. α=0 (θ=∞) is Poisson limit (equidispersion). α>0 (finite θ) indicates overdispersion; larger α = more overdispersion. Estimate with 95% CI; if CI excludes 0, overdispersion significant. In MASS::glm.nb, 'theta' is reported (θ=1/α). Var(Y)=μ+α*μ² (NB2 parameterization) or Var(Y)=μ+μ²/θ
AIC and BIC for model comparison (lower is better). ΔAIC>2 indicates meaningful difference; ΔAIC>10 is very strong evidence. BIC penalizes complexity more than AIC. Use to compare Poisson vs. NB, or NB vs. zero-inflated NB. NOT for comparing non-nested models or models on different datasets
0.2
0.5
0.8
Sample Size
Guidelines for minimum sample requirements and power analysis parameters.
The 'Alpha-Stability' Minimum: A minimum of 50-100 total counts is essential. Negative Binomial math must estimate both the 'Rate' and the 'Dispersion'—doubling the data requirement of Poisson.
| Effect Size | Parameters | Required n |
|---|---|---|
| Small Effect | IRR = 1.2 (Small) | n ≈ 600 |
| Medium Effect | IRR = 1.5 (Medium) | n ≈ 120 |
| Large Effect | IRR = 2.0 (Large) | n ≈ 55 |
The 'Dispersion Penalty': If Alpha > 1.0 (Extreme Overdispersion), the standard errors explode. You must increase your sample size by 30% to maintain the same power as a stable Poisson world.
Reporting
How to compile statistical results into publication prose matching APA and journal style guides.
A negative binomial regression was conducted to predict count outcome from list predictors (N = XXX). State why NB chosen: e.g., variance substantially exceeded mean (variance/mean ratio = X.XX), indicating overdispersion. Comparison to Poisson regression confirmed negative binomial provided superior fit (AIC difference = XX; likelihood ratio test χ²(1) = XX.XX, p < .XXX). State assumption checks: independence, linearity, multicollinearity, sample size. The negative binomial model was significant overall (McFadden R² = .XX). Report dispersion parameter: theta = X.XX, SE = X.XX, confirming overdispersion. For each significant predictor: Predictor name was a significant positive/negative predictor (IRR = X.XX, 95% CI X.XX, X.XX, z = X.XX, p = .XXX), indicating substantive interpretation: e.g., 'each unit increase multiplied count by X.XX' or 'increased count by XX%'. Residual diagnostics indicated adequate model fit with number influential outliers if any; describe handling. Conclude with interpretation in context.
- Sample size (n) and outcome description (count, range, mean, variance, variance/mean ratio)
- Justification for NB over Poisson (variance/mean ratio, overdispersion test, AIC/BIC comparison, LR test)
- Overall model test: χ² (or deviance difference), df, p-value
- Pseudo-R² (at least one: McFadden, Nagelkerke, or Cox-Snell)
- Dispersion parameter (θ or α) with SE or 95% CI
- For each predictor: IRR (exp(β)), 95% CI, z-statistic, p-value
- Substantive interpretation of IRRs (e.g., 'X% increase per unit' or 'multiplies count by X.XX')
- Statement about assumption checks (independence, overdispersion confirmed, linearity, multicollinearity/VIF, sample size/EPV)
- Residual diagnostics (deviance residuals, influential cases)
- Model comparison results if testing against other models (Poisson, zero-inflated, etc.)
Manuscript Lab
Copy standard summary tables and forensic reporting grids to outline analysis details.
| Predictor | B | SE | z | p | IRR | 95% CI |
|---|---|---|---|---|---|---|
| (Intercept) | 0.85 | 0.22 | 3.86 | < .001 | 2.34 | [1.52, 3.60] |
| Severity Score | 0.42 | 0.08 | 5.25 | < .001 | 1.52 | [1.30, 1.78] |
| Follow-up (1=Yes) | -0.65 | 0.15 | -4.33 | < .001 | 0.52 | [0.39, 0.70] |
The Overdispersion Anchor. A significant theta proves that the variance exceeds the mean, justifying the use of NB over Poisson.
Incidence Rate Ratio. Interpreted as the factor by which the count changes for a unit increase in the predictor.
Command Center
Syntax libraries and function parameters for executing calculations in stats packages.
# 1. Fit Negative Binomial Model
model <- MASS::glm.nb(readmissions ~ severity + followup, data = df)
# 2. Extract IRR with CIs
parameters::model_parameters(model, exponentiate = TRUE)Negative Binomial is the 'Safe Haven' for count data. If you aren't 100% sure your variance equals your mean, use NB.
# Rootogram Audit (Visualizing Over/Under Fitting of Counts)
countreg::rootogram(model)
# Direct Comparison (Poisson vs NB)
performance::compare_performance(poisson_mod, nb_mod)Common Mistakes
Analytical caveats and corrections to maintain modeling integrity.
References
Scholarly lineage and citation keys grounding the statistical framework.