Generalised linear models

statsmodels · Practical · 12 min

Many outcomes are not normal; GLMs model them correctly instead of forcing them into OLS.

A generalised linear model has three parts: a linear predictor (b0 + b1*x1 + ...), a link function connecting it to the mean of y, and a family describing y's distribution. In statsmodels: smf.glm("y ~ x", data=df, family=sm.families.Poisson()).fit().

Common choices:

  • Binomial() (logit link): binary outcomes or proportions,
  • Poisson() (log link): counts; coefficients exponentiate to rate ratios,
  • NegativeBinomial(alpha=...) (or smf.negativebinomial): counts with more variance than the mean (overdispersion),
  • Gamma(link=sm.families.links.Log()): positive, right-skewed amounts such as claim sizes,
  • Tweedie(var_power=1.5): zero-inflated continuous data such as insurance losses.

For rates, include exposure: claims per policy-year or clicks per impression. Pass exposure=df["years"] (or offset=np.log(df["years"])) so the model estimates a rate, not a raw count.

Check overdispersion for Poisson models: res.pearson_chi2 / res.df_resid well above 1 means standard errors are too small. Switch to negative binomial, or use scale="X2" in fit to rescale them.

GLM results include deviance, aic, pearson_chi2, and predict returns values on the response scale (counts, probabilities) by default.

Example

import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf

rng = np.random.default_rng(6)
n = 1000
df = pd.DataFrame({"young": rng.integers(0, 2, n), "years": rng.uniform(0.5, 3, n)})
rate = np.exp(-2.0 + 0.6 * df["young"])            # claims per policy-year
df["claims"] = rng.poisson(rate * df["years"])

res = smf.glm("claims ~ young", data=df, family=sm.families.Poisson(),
              exposure=df["years"]).fit()
print(res.params.round(2).to_dict())         # {'Intercept': -1.99, 'young': 0.58}
print(np.exp(res.params["young"]).round(2))  # 1.78 (young drivers: 78% higher claim rate)
print(round(res.pearson_chi2 / res.df_resid, 2))   # 0.89 (no overdispersion)

Remember

  • Pick the family from the outcome type and the link from how effects combine
  • Use exposure= or offset= to model rates
  • Check Poisson overdispersion via Pearson chi² / df

Watch out for

  • Modelling counts per varying exposure without an offset, which confuses rate with exposure.

Check yourself

  1. You model claim counts where policies were active for different lengths of time. What should you add?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.statsmodels.org/stable/glm.html