Skill Tracer
Skills › Scientific Agent Skills › statsmodels › glm.md

glm.md

617 lines · skills/statsmodels/references/glm.md at 5f2bc03

Open on GitHub
1# Generalized Linear Models (GLM) Reference
2
3This document provides comprehensive guidance on generalized linear models in statsmodels, including families, link functions, and applications.
4

Generalized Linear Models (GLM) Reference

This document provides comprehensive guidance on generalized linear models in statsmodels, including families, link functions, and applications.

Raw lines 1 to 4
5## Overview
6
7GLMs extend linear regression to non-normal response distributions through:
81. **Distribution family**: Specifies the conditional distribution of the response
92. **Link function**: Maps the conditional mean to the linear predictor; the inverse link maps back
103. **Variance function**: Relates variance to the mean
12**General form**: g(μ) = Xβ, where g is the link function and μ = E(Y|X)

Overview

GLMs extend linear regression to non-normal response distributions through:

  1. Distribution family: Specifies the conditional distribution of the response
  2. Link function: Maps the conditional mean to the linear predictor; the inverse link maps back
  3. Variance function: Relates variance to the mean

General form: g(μ) = Xβ, where g is the link function and μ = E(Y|X)

Raw lines 5 to 13
14## When to Use GLM
16- **Binary outcomes**: Logistic regression (Binomial family with logit link)
17- **Count data**: Poisson or Negative Binomial regression
18- **Positive continuous data**: Gamma or Inverse Gaussian
19- **Conditional mean/variance structure**: Choose family from the response mechanism, not a normality test on the marginal response
20- **Link functions**: Need non-linear relationship between predictors and response scale
22Formula predictions take raw named columns; array predictions take the exact training design. Do not reuse a result from a different family in a later fragment. Use positive finite exposure, pass `exposure=e` or `offset=np.log(e)` (not both), and supply new exposure/offset when predicting new rows. Binomial proportions need trial counts (`var_weights=n`) or a two-column `[successes, failures]` response; arbitrary proportions are not Bernoulli observations.

When to Use GLM

  • Binary outcomes: Logistic regression (Binomial family with logit link)
  • Count data: Poisson or Negative Binomial regression
  • Positive continuous data: Gamma or Inverse Gaussian
  • Conditional mean/variance structure: Choose family from the response mechanism, not a normality test on the marginal response
  • Link functions: Need non-linear relationship between predictors and response scale

Formula predictions take raw named columns; array predictions take the exact training design. Do not reuse a result from a different family in a later fragment. Use positive finite exposure, pass exposure=e or offset=np.log(e) (not both), and supply new exposure/offset when predicting new rows. Binomial proportions need trial counts (var_weights=n) or a two-column [successes, failures] response; arbitrary proportions are not Bernoulli observations.

Raw lines 14 to 23
24## Distribution Families

Distribution Families

Raw lines 24 to 25
26### Binomial Family
28For binary outcomes (0/1) or proportions (k/n).
30**When to use:**
31- Binary classification
32- Success/failure outcomes
33- Proportions or rates
35**Common links:**
36- Logit (default): log(μ/(1-μ))
37- Probit: Φ⁻¹(μ)
38- Log: log(μ)
40```python
41import statsmodels.api as sm
42import statsmodels.formula.api as smf
44# Binary logistic regression
45model = sm.GLM(y, X, family=sm.families.Binomial())
46results = model.fit()
48# Formula API
49results = smf.glm('success ~ x1 + x2', data=df,
50 family=sm.families.Binomial()).fit()
52# Access predictions (probabilities)
53probs = results.predict(new_df) # Raw named predictors after a formula fit
55# Classification (0.5 threshold)
56predictions = (probs > 0.5).astype(int)
57```
59**Interpretation:**
60```python
61import numpy as np
63# Odds ratios (for logit link)
64odds_ratios = np.exp(results.params)
65print("Odds ratios:", odds_ratios)
67# For 1-unit increase in x, odds multiply by exp(beta)
68```

Binomial Family

For binary outcomes (0/1) or proportions (k/n).

When to use:

  • Binary classification
  • Success/failure outcomes
  • Proportions or rates

Common links:

  • Logit (default): log(μ/(1-μ))
  • Probit: Φ⁻¹(μ)
  • Log: log(μ)
import statsmodels.api as sm
import statsmodels.formula.api as smf

# Binary logistic regression
model = sm.GLM(y, X, family=sm.families.Binomial())
results = model.fit()

# Formula API
results = smf.glm('success ~ x1 + x2', data=df,
                  family=sm.families.Binomial()).fit()

# Access predictions (probabilities)
probs = results.predict(new_df)  # Raw named predictors after a formula fit

# Classification (0.5 threshold)
predictions = (probs > 0.5).astype(int)

Interpretation:

import numpy as np

# Odds ratios (for logit link)
odds_ratios = np.exp(results.params)
print("Odds ratios:", odds_ratios)

# For 1-unit increase in x, odds multiply by exp(beta)
Raw lines 26 to 69
70### Poisson Family
72For count data (non-negative integers).
74**When to use:**
75- Count outcomes (number of events)
76- Rare events
77- Rate modeling (with offset)
79**Common links:**
80- Log (default): log(μ)
81- Identity: μ
82- Sqrt: √μ
84```python
85# Poisson regression
86model = sm.GLM(y, X, family=sm.families.Poisson())
87results = model.fit()
89# With exposure/offset for rates
90# If modeling rate = counts/exposure
91model = sm.GLM(y, X, family=sm.families.Poisson(),
92 offset=np.log(exposure))
93results = model.fit()
95# Interpretation: exp(beta) = multiplicative effect on expected count
96import numpy as np
97rate_ratios = np.exp(results.params)
98print("Rate ratios:", rate_ratios)
99```
101**Overdispersion check:**
102```python
103# Pearson dispersion is a screening statistic, not a calibrated universal test
104overdispersion = results.pearson_chi2 / results.df_resid
105print(f"Overdispersion: {overdispersion}")
107# If >> 1, consider Negative Binomial
108if overdispersion > 1.5:
109 print("Consider Negative Binomial model for overdispersion")
110```

Poisson Family

For count data (non-negative integers).

When to use:

  • Count outcomes (number of events)
  • Rare events
  • Rate modeling (with offset)

Common links:

  • Log (default): log(μ)
  • Identity: μ
  • Sqrt: √μ
# Poisson regression
model = sm.GLM(y, X, family=sm.families.Poisson())
results = model.fit()

# With exposure/offset for rates
# If modeling rate = counts/exposure
model = sm.GLM(y, X, family=sm.families.Poisson(),
               offset=np.log(exposure))
results = model.fit()

# Interpretation: exp(beta) = multiplicative effect on expected count
import numpy as np
rate_ratios = np.exp(results.params)
print("Rate ratios:", rate_ratios)

Overdispersion check:

# Pearson dispersion is a screening statistic, not a calibrated universal test
overdispersion = results.pearson_chi2 / results.df_resid
print(f"Overdispersion: {overdispersion}")

# If >> 1, consider Negative Binomial
if overdispersion > 1.5:
    print("Consider Negative Binomial model for overdispersion")
Raw lines 70 to 111
112### Negative Binomial Family
114For overdispersed count data.
116**When to use:**
117- Count data with variance > mean
118- Excess zeros or large variance
119- Poisson model shows overdispersion
121```python
122# Negative Binomial GLM with fixed alpha
123model = sm.GLM(y, X, family=sm.families.NegativeBinomial(alpha=1.0))
124results = model.fit()
126# Use the discrete count model when alpha should be estimated
127from statsmodels.discrete.discrete_model import NegativeBinomial
128nb_model = NegativeBinomial(y, X)
129nb_results = nb_model.fit()
131print(f"Dispersion parameter alpha: {np.asarray(nb_results.params)[-1]}")
132```

Negative Binomial Family

For overdispersed count data.

When to use:

  • Count data with variance > mean
  • Excess zeros or large variance
  • Poisson model shows overdispersion
# Negative Binomial GLM with fixed alpha
model = sm.GLM(y, X, family=sm.families.NegativeBinomial(alpha=1.0))
results = model.fit()

# Use the discrete count model when alpha should be estimated
from statsmodels.discrete.discrete_model import NegativeBinomial
nb_model = NegativeBinomial(y, X)
nb_results = nb_model.fit()

print(f"Dispersion parameter alpha: {np.asarray(nb_results.params)[-1]}")
Raw lines 112 to 133
134### Gaussian Family
136Equivalent to OLS but fit via IRLS (Iteratively Reweighted Least Squares).
138**When to use:**
139- Want GLM framework for consistency
140- Need robust standard errors
141- Comparing with other GLMs
143**Common links:**
144- Identity (default): μ
145- Log: log(μ)
146- Inverse: 1/μ
148```python
149# Gaussian GLM (equivalent to OLS)
150model = sm.GLM(y, X, family=sm.families.Gaussian())
151results = model.fit()
153# Verify equivalence with OLS
154ols_results = sm.OLS(y, X).fit()
155print("Parameters close:", np.allclose(results.params, ols_results.params))
156```

Gaussian Family

Equivalent to OLS but fit via IRLS (Iteratively Reweighted Least Squares).

When to use:

  • Want GLM framework for consistency
  • Need robust standard errors
  • Comparing with other GLMs

Common links:

  • Identity (default): μ
  • Log: log(μ)
  • Inverse: 1/μ
# Gaussian GLM (equivalent to OLS)
model = sm.GLM(y, X, family=sm.families.Gaussian())
results = model.fit()

# Verify equivalence with OLS
ols_results = sm.OLS(y, X).fit()
print("Parameters close:", np.allclose(results.params, ols_results.params))
Raw lines 134 to 157
158### Gamma Family
160For positive continuous data, often right-skewed.
162**When to use:**
163- Positive outcomes (insurance claims, survival times)
164- Right-skewed distributions
165- Variance proportional to mean²
167**Common links:**
168- Inverse (default): 1/μ
169- Log: log(μ)
170- Identity: μ
172```python
173# Gamma regression (common for cost data)
174model = sm.GLM(y, X, family=sm.families.Gamma())
175results = model.fit()
177# Log link often preferred for interpretation
178model = sm.GLM(y, X, family=sm.families.Gamma(link=sm.families.links.Log()))
179results = model.fit()
181# With log link, exp(beta) = multiplicative effect
182import numpy as np
183effects = np.exp(results.params)
184```

Gamma Family

For positive continuous data, often right-skewed.

When to use:

  • Positive outcomes (insurance claims, survival times)
  • Right-skewed distributions
  • Variance proportional to mean²

Common links:

  • Inverse (default): 1/μ
  • Log: log(μ)
  • Identity: μ
# Gamma regression (common for cost data)
model = sm.GLM(y, X, family=sm.families.Gamma())
results = model.fit()

# Log link often preferred for interpretation
model = sm.GLM(y, X, family=sm.families.Gamma(link=sm.families.links.Log()))
results = model.fit()

# With log link, exp(beta) = multiplicative effect
import numpy as np
effects = np.exp(results.params)
Raw lines 158 to 185
186### Inverse Gaussian Family
188For positive continuous data with specific variance structure.
190**When to use:**
191- Positive skewed outcomes
192- Variance proportional to mean³
193- Alternative to Gamma
195**Common links:**
196- Inverse squared (default): 1/μ²
197- Log: log(μ)
199```python
200model = sm.GLM(y, X, family=sm.families.InverseGaussian())
201results = model.fit()
202```

Inverse Gaussian Family

For positive continuous data with specific variance structure.

When to use:

  • Positive skewed outcomes
  • Variance proportional to mean³
  • Alternative to Gamma

Common links:

  • Inverse squared (default): 1/μ²
  • Log: log(μ)
model = sm.GLM(y, X, family=sm.families.InverseGaussian())
results = model.fit()
Raw lines 186 to 203
204### Tweedie Family
206Flexible family covering multiple distributions.
208**When to use:**
209- Insurance claims (mixture of zeros and continuous)
210- Semi-continuous data
211- Need flexible variance function
213**Special cases (power parameter p):**
214- p=0: Normal
215- p=1: Poisson
216- p=2: Gamma
217- p=3: Inverse Gaussian
218- 1<p<2: Compound Poisson-Gamma (common for insurance)
220```python
221# Tweedie with power=1.5
222model = sm.GLM(y, X, family=sm.families.Tweedie(link=sm.families.links.Log(),
223 var_power=1.5))
224results = model.fit()
225```

Tweedie Family

Flexible family covering multiple distributions.

When to use:

  • Insurance claims (mixture of zeros and continuous)
  • Semi-continuous data
  • Need flexible variance function

Special cases (power parameter p):

  • p=0: Normal
  • p=1: Poisson
  • p=2: Gamma
  • p=3: Inverse Gaussian
  • 1<p<2: Compound Poisson-Gamma (common for insurance)
# Tweedie with power=1.5
model = sm.GLM(y, X, family=sm.families.Tweedie(link=sm.families.links.Log(),
                                                 var_power=1.5))
results = model.fit()
Raw lines 204 to 226
227## Link Functions
229Link functions connect the linear predictor to the mean of the response.

Link Functions

Link functions connect the linear predictor to the mean of the response.

Raw lines 227 to 230
231### Available Links
233```python
234from statsmodels.genmod import families
236# Identity: g(μ) = μ
237link = families.links.Identity()
239# Log: g(μ) = log(μ)
240link = families.links.Log()
242# Logit: g(μ) = log(μ/(1-μ))
243link = families.links.Logit()
245# Probit: g(μ) = Φ⁻¹(μ)
246link = families.links.Probit()
248# Complementary log-log: g(μ) = log(-log(1-μ))
249link = families.links.CLogLog()
251# Inverse: g(μ) = 1/μ
252link = families.links.InversePower()
254# Inverse squared: g(μ) = 1/μ²
255link = families.links.InverseSquared()
257# Square root: g(μ) = √μ
258link = families.links.Sqrt()
260# Power: g(μ) = μ^p
261link = families.links.Power(power=2)
262```

Available Links

from statsmodels.genmod import families

# Identity: g(μ) = μ
link = families.links.Identity()

# Log: g(μ) = log(μ)
link = families.links.Log()

# Logit: g(μ) = log(μ/(1-μ))
link = families.links.Logit()

# Probit: g(μ) = Φ⁻¹(μ)
link = families.links.Probit()

# Complementary log-log: g(μ) = log(-log(1-μ))
link = families.links.CLogLog()

# Inverse: g(μ) = 1/μ
link = families.links.InversePower()

# Inverse squared: g(μ) = 1/μ²
link = families.links.InverseSquared()

# Square root: g(μ) = √μ
link = families.links.Sqrt()

# Power: g(μ) = μ^p
link = families.links.Power(power=2)
Raw lines 231 to 263
264### Choosing Link Functions
266**Common default links** (canonical up to conventional scaling for these families):
267- Binomial → Logit
268- Poisson → Log
269- Gamma → Inverse
270- Gaussian → Identity
271- Inverse Gaussian → Inverse squared
273The default Gamma inverse link and binomial log/identity links may violate their response domains for some designs. Inspect warnings, finite fitted means and support; a converged optimizer does not make an invalid mean valid. Use a positive-mean link when appropriate.
275**When to use non-canonical:**
276- **Log link with Binomial**: Risk ratios instead of odds ratios
277- **Identity link**: Direct additive effects (when sensible)
278- **Probit vs Logit**: Similar results, preference based on field
279- **CLogLog**: Asymmetric relationship, common in survival analysis
281```python
282# Example: Risk ratios with log-binomial model
283model = sm.GLM(y, X, family=sm.families.Binomial(link=sm.families.links.Log()))
284results = model.fit()
286# exp(beta) now gives risk ratios, not odds ratios
287risk_ratios = np.exp(results.params)
288```

Choosing Link Functions

Common default links (canonical up to conventional scaling for these families):

  • Binomial → Logit
  • Poisson → Log
  • Gamma → Inverse
  • Gaussian → Identity
  • Inverse Gaussian → Inverse squared

The default Gamma inverse link and binomial log/identity links may violate their response domains for some designs. Inspect warnings, finite fitted means and support; a converged optimizer does not make an invalid mean valid. Use a positive-mean link when appropriate.

When to use non-canonical:

  • Log link with Binomial: Risk ratios instead of odds ratios
  • Identity link: Direct additive effects (when sensible)
  • Probit vs Logit: Similar results, preference based on field
  • CLogLog: Asymmetric relationship, common in survival analysis
# Example: Risk ratios with log-binomial model
model = sm.GLM(y, X, family=sm.families.Binomial(link=sm.families.links.Log()))
results = model.fit()

# exp(beta) now gives risk ratios, not odds ratios
risk_ratios = np.exp(results.params)
Raw lines 264 to 289
290## Model Fitting and Results

Model Fitting and Results

Raw lines 290 to 291
292### Basic Workflow
294```python
295import statsmodels.api as sm
297# Add constant
298X = sm.add_constant(X_data)
300# Specify family and link
301family = sm.families.Poisson(link=sm.families.links.Log())
303# Fit model using IRLS
304model = sm.GLM(y, X, family=family)
305results = model.fit()
307# Summary
308print(results.summary())
309```

Basic Workflow

import statsmodels.api as sm

# Add constant
X = sm.add_constant(X_data)

# Specify family and link
family = sm.families.Poisson(link=sm.families.links.Log())

# Fit model using IRLS
model = sm.GLM(y, X, family=family)
results = model.fit()

# Summary
print(results.summary())
Raw lines 292 to 310
311### Results Attributes
313```python
314# Parameters and inference
315results.params # Coefficients
316results.bse # Standard errors
317results.tvalues # Z-statistics
318results.pvalues # P-values
319results.conf_int() # Confidence intervals
321# Predictions
322results.fittedvalues # Fitted values (μ)
323results.predict(X_new) # Predictions for new data
325# Model fit statistics
326results.aic # Akaike Information Criterion
327results.bic_llf # Likelihood-based BIC (GLM .bic can be deviance-based)
328results.deviance # Deviance
329results.null_deviance # Null model deviance
330results.pearson_chi2 # Pearson chi-squared statistic
331results.df_resid # Residual degrees of freedom
332results.llf # Log-likelihood
334# Residuals
335results.resid_response # Response residuals (y - μ)
336results.resid_pearson # Pearson residuals
337results.resid_deviance # Deviance residuals
338results.resid_anscombe # Anscombe residuals
339results.resid_working # Working residuals
340```

Results Attributes

# Parameters and inference
results.params              # Coefficients
results.bse                 # Standard errors
results.tvalues            # Z-statistics
results.pvalues            # P-values
results.conf_int()         # Confidence intervals

# Predictions
results.fittedvalues       # Fitted values (μ)
results.predict(X_new)     # Predictions for new data

# Model fit statistics
results.aic                # Akaike Information Criterion
results.bic_llf            # Likelihood-based BIC (GLM .bic can be deviance-based)
results.deviance           # Deviance
results.null_deviance      # Null model deviance
results.pearson_chi2       # Pearson chi-squared statistic
results.df_resid           # Residual degrees of freedom
results.llf                # Log-likelihood

# Residuals
results.resid_response     # Response residuals (y - μ)
results.resid_pearson      # Pearson residuals
results.resid_deviance     # Deviance residuals
results.resid_anscombe     # Anscombe residuals
results.resid_working      # Working residuals
Raw lines 311 to 341
342### Pseudo R-squared
344```python
345# Likelihood-based McFadden measure: appropriate for discrete outcomes
346pseudo_r2 = results.pseudo_rsquared(kind="mcf")
347print(f"McFadden pseudo R²: {pseudo_r2:.4f}")
348# Cox-Snell is available for discrete or continuous likelihoods
349print("Cox-Snell:", results.pseudo_rsquared(kind="cs"))
350# 1 - deviance/null_deviance is a different measure; do not label it McFadden.
351```

Pseudo R-squared

# Likelihood-based McFadden measure: appropriate for discrete outcomes
pseudo_r2 = results.pseudo_rsquared(kind="mcf")
print(f"McFadden pseudo R²: {pseudo_r2:.4f}")
# Cox-Snell is available for discrete or continuous likelihoods
print("Cox-Snell:", results.pseudo_rsquared(kind="cs"))
# 1 - deviance/null_deviance is a different measure; do not label it McFadden.
Raw lines 342 to 352
353## Diagnostics
355### Goodness of Fit
357```python
358# Chi-square goodness-of-fit approximations need adequate expected cell counts;
359# these are not generally calibrated for sparse counts or ungrouped Bernoulli data
360from scipy import stats
362deviance_pval = stats.chi2.sf(results.deviance, results.df_resid)
363print(f"Deviance test p-value: {deviance_pval}")
365# Pearson chi-squared test
366pearson_pval = stats.chi2.sf(results.pearson_chi2, results.df_resid)
367print(f"Pearson chi² test p-value: {pearson_pval}")
369# Check for overdispersion/underdispersion
370dispersion = results.pearson_chi2 / results.df_resid
371print(f"Dispersion: {dispersion}")
372# For a Poisson model with an adequate mean, >1 flags extra-Poisson dispersion.
373# Gaussian/Gamma/InverseGaussian estimate scale; this ratio need not be 1.
374```

Goodness of Fit

# Chi-square goodness-of-fit approximations need adequate expected cell counts;
# these are not generally calibrated for sparse counts or ungrouped Bernoulli data
from scipy import stats

deviance_pval = stats.chi2.sf(results.deviance, results.df_resid)
print(f"Deviance test p-value: {deviance_pval}")

# Pearson chi-squared test
pearson_pval = stats.chi2.sf(results.pearson_chi2, results.df_resid)
print(f"Pearson chi² test p-value: {pearson_pval}")

# Check for overdispersion/underdispersion
dispersion = results.pearson_chi2 / results.df_resid
print(f"Dispersion: {dispersion}")
# For a Poisson model with an adequate mean, >1 flags extra-Poisson dispersion.
# Gaussian/Gamma/InverseGaussian estimate scale; this ratio need not be 1.
Raw lines 355 to 375
376### Residual Analysis
378```python
379import matplotlib.pyplot as plt
381# Deviance residuals vs fitted
382plt.figure(figsize=(10, 6))
383plt.scatter(results.fittedvalues, results.resid_deviance, alpha=0.5)
384plt.xlabel('Fitted values')
385plt.ylabel('Deviance residuals')
386plt.axhline(y=0, color='r', linestyle='--')
387plt.title('Deviance Residuals vs Fitted')
388plt.show()
390# Q-Q plot of deviance residuals
391from statsmodels.graphics.gofplots import qqplot
392qqplot(results.resid_deviance, line='s')
393plt.title('Q-Q Plot of Deviance Residuals')
394plt.show()
396# For binary outcomes: binned residual plot
397if isinstance(results.model.family, sm.families.Binomial):
398 from statsmodels.graphics.gofplots import qqplot
399 # Group predictions and compute average residuals
400 # (custom implementation needed)
401 pass
402```

Residual Analysis

import matplotlib.pyplot as plt

# Deviance residuals vs fitted
plt.figure(figsize=(10, 6))
plt.scatter(results.fittedvalues, results.resid_deviance, alpha=0.5)
plt.xlabel('Fitted values')
plt.ylabel('Deviance residuals')
plt.axhline(y=0, color='r', linestyle='--')
plt.title('Deviance Residuals vs Fitted')
plt.show()

# Q-Q plot of deviance residuals
from statsmodels.graphics.gofplots import qqplot
qqplot(results.resid_deviance, line='s')
plt.title('Q-Q Plot of Deviance Residuals')
plt.show()

# For binary outcomes: binned residual plot
if isinstance(results.model.family, sm.families.Binomial):
    from statsmodels.graphics.gofplots import qqplot
    # Group predictions and compute average residuals
    # (custom implementation needed)
    pass
Raw lines 376 to 403
404### Influence and Outliers
406```python
407from statsmodels.stats.outliers_influence import GLMInfluence
409influence = results.get_influence() # Supplies the GLM-specific leverage and scale
411# Leverage
412leverage = influence.hat_matrix_diag
414# Cook's distance
415cooks_d = influence.cooks_distance[0]
417# Standardized approximate change in fitted means (not OLS DFFITS)
418d_fitted_scaled = influence.d_fittedvalues_scaled
420# Find influential observations
421influential = np.where(cooks_d > 4/len(y))[0]
422print(f"Influential observations: {influential}")
423```

Influence and Outliers

from statsmodels.stats.outliers_influence import GLMInfluence

influence = results.get_influence()  # Supplies the GLM-specific leverage and scale

# Leverage
leverage = influence.hat_matrix_diag

# Cook's distance
cooks_d = influence.cooks_distance[0]

# Standardized approximate change in fitted means (not OLS DFFITS)
d_fitted_scaled = influence.d_fittedvalues_scaled

# Find influential observations
influential = np.where(cooks_d > 4/len(y))[0]
print(f"Influential observations: {influential}")
Raw lines 404 to 424
425## Hypothesis Testing
427```python
428# Wald test for single parameter (automatically in summary)
430# Likelihood ratio test for nested models
431# Fit reduced model
432model_reduced = sm.GLM(y, X_reduced, family=family).fit()
433model_full = sm.GLM(y, X_full, family=family).fit()
435# LR statistic
436lr_stat = 2 * (model_full.llf - model_reduced.llf)
437df_diff = model_full.df_model - model_reduced.df_model
439from scipy import stats
440lr_pval = stats.chi2.sf(lr_stat, df_diff)
441print(f"LR test p-value: {lr_pval}")
443# Wald test for multiple parameters
444# Test beta_1 = beta_2 = 0
445R = [[0, 1, 0, 0], [0, 0, 1, 0]]
446wald_test = results.wald_test(R)
447print(wald_test)
448```

Hypothesis Testing

# Wald test for single parameter (automatically in summary)

# Likelihood ratio test for nested models
# Fit reduced model
model_reduced = sm.GLM(y, X_reduced, family=family).fit()
model_full = sm.GLM(y, X_full, family=family).fit()

# LR statistic
lr_stat = 2 * (model_full.llf - model_reduced.llf)
df_diff = model_full.df_model - model_reduced.df_model

from scipy import stats
lr_pval = stats.chi2.sf(lr_stat, df_diff)
print(f"LR test p-value: {lr_pval}")

# Wald test for multiple parameters
# Test beta_1 = beta_2 = 0
R = [[0, 1, 0, 0], [0, 0, 1, 0]]
wald_test = results.wald_test(R)
print(wald_test)
Raw lines 425 to 449
450## Robust Standard Errors
452```python
453# Heteroscedasticity-robust (sandwich estimator)
454results_robust = results.model.fit(cov_type='HC0')
456# Cluster-robust
457results_cluster = results.model.fit(cov_type='cluster',
458 cov_kwds={'groups': cluster_ids})
460# Compare standard errors
461print("Regular SE:", results.bse)
462print("Robust SE:", results_robust.bse)
463```
465Robust covariance addresses the variance calculation under its asymptotic assumptions. It does not repair separation, an incorrect mean model, few independent clusters, or endogeneity. GLM influence approximations are experimental, especially with exposure/offset.

Robust Standard Errors

# Heteroscedasticity-robust (sandwich estimator)
results_robust = results.model.fit(cov_type='HC0')

# Cluster-robust
results_cluster = results.model.fit(cov_type='cluster',
                                    cov_kwds={'groups': cluster_ids})

# Compare standard errors
print("Regular SE:", results.bse)
print("Robust SE:", results_robust.bse)

Robust covariance addresses the variance calculation under its asymptotic assumptions. It does not repair separation, an incorrect mean model, few independent clusters, or endogeneity. GLM influence approximations are experimental, especially with exposure/offset.

Raw lines 450 to 466
467## Model Comparison
469```python
470# AIC/BIC for non-nested models
471models = [model1_results, model2_results, model3_results]
472for i, res in enumerate(models, 1):
473 print(f"Model {i}: AIC={res.aic:.2f}, BIC={res.bic_llf:.2f}")
475# Likelihood ratio test for nested models (as shown above)
477# Cross-validation for predictive performance
478from sklearn.model_selection import StratifiedKFold
479from sklearn.metrics import log_loss
481# Binary independent observations only; X already includes the intercept.
482X_array, y_array = np.asarray(X), np.asarray(y)
483family = sm.families.Binomial()
484kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
485cv_scores = []
487for train_idx, val_idx in kf.split(X_array, y_array):
488 X_train, X_val = X_array[train_idx], X_array[val_idx]
489 y_train, y_val = y_array[train_idx], y_array[val_idx]
491 model_cv = sm.GLM(y_train, X_train, family=family).fit()
492 pred_probs = model_cv.predict(X_val)
494 score = log_loss(y_val, pred_probs)
495 cv_scores.append(score)
497print(f"CV Log Loss: {np.mean(cv_scores):.4f} ± {np.std(cv_scores):.4f}")
498```

Model Comparison

# AIC/BIC for non-nested models
models = [model1_results, model2_results, model3_results]
for i, res in enumerate(models, 1):
    print(f"Model {i}: AIC={res.aic:.2f}, BIC={res.bic_llf:.2f}")

# Likelihood ratio test for nested models (as shown above)

# Cross-validation for predictive performance
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import log_loss

# Binary independent observations only; X already includes the intercept.
X_array, y_array = np.asarray(X), np.asarray(y)
family = sm.families.Binomial()
kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = []

for train_idx, val_idx in kf.split(X_array, y_array):
    X_train, X_val = X_array[train_idx], X_array[val_idx]
    y_train, y_val = y_array[train_idx], y_array[val_idx]

    model_cv = sm.GLM(y_train, X_train, family=family).fit()
    pred_probs = model_cv.predict(X_val)

    score = log_loss(y_val, pred_probs)
    cv_scores.append(score)

print(f"CV Log Loss: {np.mean(cv_scores):.4f} ± {np.std(cv_scores):.4f}")
Raw lines 467 to 499
500## Prediction
502```python
503# Point predictions
504predictions = results.predict(X_new)
506# For classification: get probabilities and convert
507if isinstance(family, sm.families.Binomial):
508 probs = predictions
509 class_predictions = (probs > 0.5).astype(int)
511# For counts: predictions are expected counts
512if isinstance(family, sm.families.Poisson):
513 expected_counts = predictions
515# Conditional mean confidence intervals, not intervals for a new observation
516mean_prediction = results.get_prediction(X_new, which="mean")
517mean_ci = mean_prediction.conf_int(alpha=0.05)
518mean_table = mean_prediction.summary_frame()
519# A bootstrap of fitted means alone also omits future outcome noise.
520# Outcome prediction intervals need the fitted family/scale plus parameter uncertainty.
521```

Prediction

# Point predictions
predictions = results.predict(X_new)

# For classification: get probabilities and convert
if isinstance(family, sm.families.Binomial):
    probs = predictions
    class_predictions = (probs > 0.5).astype(int)

# For counts: predictions are expected counts
if isinstance(family, sm.families.Poisson):
    expected_counts = predictions

# Conditional mean confidence intervals, not intervals for a new observation
mean_prediction = results.get_prediction(X_new, which="mean")
mean_ci = mean_prediction.conf_int(alpha=0.05)
mean_table = mean_prediction.summary_frame()
# A bootstrap of fitted means alone also omits future outcome noise.
# Outcome prediction intervals need the fitted family/scale plus parameter uncertainty.
Raw lines 500 to 522
523## Common Applications

Common Applications

Raw lines 523 to 524
525### Logistic Regression (Binary Classification)
527```python
528import statsmodels.api as sm
530# Fit logistic regression
531X = sm.add_constant(X_data)
532model = sm.GLM(y, X, family=sm.families.Binomial())
533results = model.fit()
535# Odds ratios
536odds_ratios = np.exp(results.params)
537odds_ci = np.exp(results.conf_int())
539# Classification metrics
540from sklearn.metrics import classification_report, roc_auc_score
542probs = results.predict(X)
543predictions = (probs > 0.5).astype(int)
545print(classification_report(y, predictions))
546print(f"AUC: {roc_auc_score(y, probs):.4f}")
548# ROC curve
549from sklearn.metrics import roc_curve
550import matplotlib.pyplot as plt
552fpr, tpr, thresholds = roc_curve(y, probs)
553plt.plot(fpr, tpr)
554plt.plot([0, 1], [0, 1], 'k--')
555plt.xlabel('False Positive Rate')
556plt.ylabel('True Positive Rate')
557plt.title('ROC Curve')
558plt.show()
559```

Logistic Regression (Binary Classification)

import statsmodels.api as sm

# Fit logistic regression
X = sm.add_constant(X_data)
model = sm.GLM(y, X, family=sm.families.Binomial())
results = model.fit()

# Odds ratios
odds_ratios = np.exp(results.params)
odds_ci = np.exp(results.conf_int())

# Classification metrics
from sklearn.metrics import classification_report, roc_auc_score

probs = results.predict(X)
predictions = (probs > 0.5).astype(int)

print(classification_report(y, predictions))
print(f"AUC: {roc_auc_score(y, probs):.4f}")

# ROC curve
from sklearn.metrics import roc_curve
import matplotlib.pyplot as plt

fpr, tpr, thresholds = roc_curve(y, probs)
plt.plot(fpr, tpr)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.show()
Raw lines 525 to 560
561### Poisson Regression (Count Data)
563```python
564# Fit Poisson model
565X = sm.add_constant(X_data)
566model = sm.GLM(y_counts, X, family=sm.families.Poisson())
567results = model.fit()
569# Rate ratios
570rate_ratios = np.exp(results.params)
571print("Rate ratios:", rate_ratios)
573# Check overdispersion
574dispersion = results.pearson_chi2 / results.df_resid
575if dispersion > 1.5:
576 print(f"Elevated Pearson dispersion ({dispersion:.2f}); investigate mean, dependence and count family.")
577```

Poisson Regression (Count Data)

# Fit Poisson model
X = sm.add_constant(X_data)
model = sm.GLM(y_counts, X, family=sm.families.Poisson())
results = model.fit()

# Rate ratios
rate_ratios = np.exp(results.params)
print("Rate ratios:", rate_ratios)

# Check overdispersion
dispersion = results.pearson_chi2 / results.df_resid
if dispersion > 1.5:
    print(f"Elevated Pearson dispersion ({dispersion:.2f}); investigate mean, dependence and count family.")
Raw lines 561 to 578
579### Gamma Regression (Cost/Duration Data)
581```python
582# Fit Gamma model with log link
583X = sm.add_constant(X_data)
584model = sm.GLM(y_cost, X,
585 family=sm.families.Gamma(link=sm.families.links.Log()))
586results = model.fit()
588# Multiplicative effects
589effects = np.exp(results.params)
590print("Multiplicative effects on mean:", effects)
591```

Gamma Regression (Cost/Duration Data)

# Fit Gamma model with log link
X = sm.add_constant(X_data)
model = sm.GLM(y_cost, X,
               family=sm.families.Gamma(link=sm.families.links.Log()))
results = model.fit()

# Multiplicative effects
effects = np.exp(results.params)
print("Multiplicative effects on mean:", effects)
Raw lines 579 to 592
593## Best Practices
5951. **Check conditional assumptions**: Examine response support, mean structure, exposure and residual patterns; marginal response normality does not choose a GLM
5962. **Verify link function**: Use canonical links unless there's a reason not to
5973. **Examine residuals**: Discrete deviance residuals need not be normal; evaluate calibration and simulation-based diagnostics appropriate to the family
5984. **Test for overdispersion**: Especially for Poisson models
5995. **Use offsets appropriately**: For rate modeling with varying exposure
6006. **Consider robust SEs**: When variance assumptions questionable
6017. **Compare models**: Use AIC/BIC for non-nested, LR test for nested
6028. **Interpret on original scale**: Transform coefficients (e.g., exp for log link)
6039. **Check influential observations**: Use Cook's distance
60410. **Validate predictions**: Use cross-validation or holdout set

Best Practices

  1. Check conditional assumptions: Examine response support, mean structure, exposure and residual patterns; marginal response normality does not choose a GLM
  2. Verify link function: Use canonical links unless there's a reason not to
  3. Examine residuals: Discrete deviance residuals need not be normal; evaluate calibration and simulation-based diagnostics appropriate to the family
  4. Test for overdispersion: Especially for Poisson models
  5. Use offsets appropriately: For rate modeling with varying exposure
  6. Consider robust SEs: When variance assumptions questionable
  7. Compare models: Use AIC/BIC for non-nested, LR test for nested
  8. Interpret on original scale: Transform coefficients (e.g., exp for log link)
  9. Check influential observations: Use Cook's distance
  10. Validate predictions: Use cross-validation or holdout set
Raw lines 593 to 605
606## Common Pitfalls
6081. **Forgetting to add constant**: No intercept term
6092. **Using wrong family**: Check distribution of response
6103. **Ignoring overdispersion**: Use Negative Binomial instead of Poisson
6114. **Misinterpreting coefficients**: Remember link function transformation
6125. **Not checking convergence**: IRLS may not converge; check warnings
6136. **Complete separation in logistic**: Some categories perfectly predict outcome
6147. **Using identity link with bounded outcomes**: May predict outside valid range
6158. **Comparing models with different samples**: Use same observations
6169. **Forgetting offset in rate models**: Must use log(exposure) as offset
61710. **Not considering alternatives**: Mixed models, zero-inflation for complex data

Common Pitfalls

  1. Forgetting to add constant: No intercept term
  2. Using wrong family: Check distribution of response
  3. Ignoring overdispersion: Use Negative Binomial instead of Poisson
  4. Misinterpreting coefficients: Remember link function transformation
  5. Not checking convergence: IRLS may not converge; check warnings
  6. Complete separation in logistic: Some categories perfectly predict outcome
  7. Using identity link with bounded outcomes: May predict outside valid range
  8. Comparing models with different samples: Use same observations
  9. Forgetting offset in rate models: Must use log(exposure) as offset
  10. Not considering alternatives: Mixed models, zero-inflation for complex data
Raw lines 606 to 617