TD 1 : Modèle de Poisson (R)

Années universitaires 2025–2026 et 2026–2027 • Parcours Économie de la santé & Développement durable

Régression log-linéaire : Tabac & cancer du poumon
Author

Pierre Beaucoral

Contexte: On étudie d’anciennes données reliant tabagisme et décès par cancer du poumon. Variables : age (classes), smoking status (4 classes), population (centaines de milliers), deaths (décès annuels).

1 Préparation

1.1 Objectifs de ce TD

  • Importer et préparer un tableau comptages + exposition (population à risque).
  • Ajuster un GLM Poisson avec offset (log-exposition).
  • Évaluer l’ajustement : déviance (vs modèle saturé) & Pearson.
  • Comparer des modèles via tests de rapport de vraisemblance (LR).
  • Interpréter en ratios de taux d’incidence (IRR) et produire des comptes attendus.

1.2 Packages

Code
# installer si nécessaire : install.packages(c("readxl","dplyr","tidyr","janitor","ggplot2","broom","gt","performance","DescTools"))
library(readxl)
library(dplyr)
library(tidyr)
library(janitor)
library(ggplot2)
library(broom)
library(gt)
library(performance)   # check_overdispersion
library(DescTools)     # PChisq for GOF (si besoin)

# Formatage des nombres dans le texte (virgule décimale, p-values)
fr    <- function(x, d = 2) formatC(x, format = "f", digits = d, decimal.mark = ",")
fmt_p <- function(p) if (p < 0.001) "< 0,001" else paste("=", fr(p, 3))
NoteRappel de cours : Régression de Poisson

La régression de Poisson est un modèle linéaire généralisé (GLM) adapté aux données de comptage (ex. nombre de décès, d’accidents, de visites).

Formulation :

  • Variable dépendante : un comptage \(Y_i \in \{0,1,2,\dots\}\). - Loi supposée : \(Y_i \sim \text{Poisson}(\mu_i)\) avec \(\mathbb{E}[Y_i] = \mu_i\).

  • Lien log : \[ \log(\mu_i) = \beta_0 + \beta_1 X_{1i} + \dots + \beta_p X_{pi} + \log(\text{exposition}_i) \] où l’offset \(\log(\text{exposition}_i)\) tient compte de la taille de la population ou du temps d’observation.

Pourquoi utiliser ce modèle ?

  • Les données sont des comptages positifs (non négatifs).

  • La variance est proportionnelle à la moyenne (\(\text{Var}(Y)=\mu\)).

  • On cherche à modéliser des taux d’incidence (décès/population, accidents/temps).

Interprétation des coefficients :

  • Les \(\beta_j\) s’interprètent via l’Incidence Rate Ratio (IRR) :
    \[ IRR_j = e^{\beta_j} \] → \(IRR_j > 1\) : le taux est plus élevé que la référence.
    → \(IRR_j < 1\) : le taux est plus faible.

Diagnostics courants :

  • Tests de qualité d’ajustement (déviance, Pearson).

  • Vérification de la sur-dispersion (si \(\text{Var}(Y) > \mu\), préférer quasi-Poisson ou binomiale négative).

Extensions :

  • Modèle de Poisson avec offset (exposition).

  • Quasi-Poisson pour corriger la variance.

  • Binomiale négative pour sur-dispersion forte.

2 Import et manipulation des données

2.1 Importer les données smoking_dat.xlsx

Télécharger smoking_dat.xlsx

Dans l’énoncé, les données à importer sont smoking_dat.xlsx et les variables age, smoking status, population, deaths.

Note

Dictionnaire des variables :
• age: en classes (40-44, 45-49, 50-54, 55-59, 60-64, 65-69, 70-74, 75-79, 80+).
• smoking status: 4 classes (ne fume pas / fume le cigare ou la pipe / fume la cigarette et le cigare ou la pipe ; fume seulement la cigarette)
• population: en centaine de milliers de personnes
• deaths: comptage des décès par cancer du poumon en un an.

Code
# Chemin suggéré : placez le fichier dans data/smoking_dat.xlsx
# Si vous avez un CSV, remplacez read_excel par read.csv(...)
data_path <- "data/smoking_dat.xlsx"

df <- read_excel(data_path) |>
  clean_names()

# Harmonisation de noms
# On s'attend à des colonnes: age (classes), smoking_status (4 classes), population, deaths
df <- df |>
  rename(
    age = matches("^age$|^age_class|^agecat"),
    smoking_status = matches("^smoking|^smoke"),
    population = matches("^pop|^population"),
    deaths = matches("^deaths|^dead")
  )

glimpse(df)
Rows: 36
Columns: 4
$ age            <chr> "40-44", "45-59", "50-54", "55-59", "60-64", "65-69", "…
$ smoking_status <chr> "no", "no", "no", "no", "no", "no", "no", "no", "no", "…
$ population     <dbl> 656, 359, 249, 632, 1067, 897, 668, 361, 274, 145, 104,…
$ deaths         <dbl> 18, 22, 19, 55, 117, 170, 179, 120, 120, 2, 4, 3, 38, 1…

2.2 Coder les deux variables enregistrées en texte avec des chiffres

En Stata on ferait encode + i.variable.
En R, il suffit de déclarer les variables comme factor.

Code
df <- df |>
  mutate(
    age = factor(age, ordered = FALSE),
    smoking_status = factor(smoking_status, ordered = FALSE)
  )

# Vérification des niveaux
levels(df$age); levels(df$smoking_status)
[1] "40-44" "45-59" "50-54" "55-59" "60-64" "65-69" "70-74" "75-79" "80+"  
[1] "cigarPipeOnly"  "cigarretteOnly" "cigarrettePlus" "no"            
Note

Rappel : Les facteurs indiquent à R qu’il s’agit de variables qualitatives. Chaque modalité sera transformée en variable indicatrice (dummy) dans la régression.

2.2.1 Unité d’exposition

L’énoncé précise que population est en centaines de milliers. Pour une interprétation plus intuitive, on peut ramener l’exposition à l’unité personne (facultatif) :

Code
# Ici, on transforme 'population' en nombre de personnes si besoin.
# Exemple: si population = 2.3 signifie 2.3 * 100 000 personnes :
expo_personnes <- TRUE
scale_factor <- 1e5

df <- df |>
  mutate(
    exposure = if (expo_personnes) population * scale_factor else population
  )
summary(df$exposure)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
  9800000  36925000  85850000 155894444 230550000 605200000 

3 Estimations

3.1 Appliquer un premier modèle de régression log-linéaire

Modèle Poisson log-linéaire avec effets de smoking_status et age, et offset log(exposure) : c’est l’équivalent de Stata poisson deaths i.smokecod i.agecod, exposure(pop).

Tip

Pourquoi un modèle de Poisson ?
Les données sont des comptages (nombre de décès).
Le modèle de Poisson relie l’espérance de ces comptages à des variables explicatives par une fonction de lien log :
\[ \log(\mathbb{E}[Y]) = X\beta \]
Cette structure garantit que la prédiction est positive et que la variance est proportionnelle à la moyenne (hypothèse de Poisson).

Nous voulons expliquer le nombre de décès par l’âge et le statut tabagique, en tenant compte de l’exposition.

Code
mod1 <- glm(deaths ~ smoking_status + age + offset(log(exposure)), 
            family = poisson(link = "log"), data = df)

summary(mod1)

Call:
glm(formula = deaths ~ smoking_status + age + offset(log(exposure)), 
    family = poisson(link = "log"), data = df)

Coefficients:
                              Estimate Std. Error  z value Pr(>|z|)    
(Intercept)                  -15.14514    0.06783 -223.293  < 2e-16 ***
smoking_statuscigarretteOnly   0.36915    0.03791    9.737  < 2e-16 ***
smoking_statuscigarrettePlus   0.17015    0.03643    4.671 3.00e-06 ***
smoking_statusno              -0.04781    0.04699   -1.017    0.309    
age45-59                       0.55388    0.07999    6.924 4.38e-12 ***
age50-54                       0.98039    0.07682   12.762  < 2e-16 ***
age55-59                       1.37946    0.06526   21.138  < 2e-16 ***
age60-64                       1.65423    0.06257   26.439  < 2e-16 ***
age65-69                       1.99817    0.06279   31.824  < 2e-16 ***
age70-74                       2.27141    0.06435   35.296  < 2e-16 ***
age75-79                       2.55858    0.06778   37.746  < 2e-16 ***
age80+                         2.84692    0.07242   39.310  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for poisson family taken to be 1)

    Null deviance: 4055.984  on 35  degrees of freedom
Residual deviance:   21.487  on 24  degrees of freedom
AIC: 285.51

Number of Fisher Scoring iterations: 4
Important

L’argument offset(log(exposure)) ajoute le log de l’exposition avec un coefficient fixé à 1. Cela revient à modéliser un taux de décès (décès / population).

Quelle interprétation?

On peut utiliser \(\exp(\beta_i)\) pour retrouver l’Incidence Rate Ratio (IRR). On le lit comme le coefficient multiplicateur de l’occurence de \(Y\) (ici le décès) par rapport à la catégorie de référence.

3.2 Calculer la déviance du modèle ajusté. DEV1

Code
dev1 <- deviance(mod1)     # Deviance du modèle vs saturé
df_dev1 <- df.residual(mod1)
c(dev1 = dev1, df = df_dev1, p_value = pchisq(dev1, df_dev1, lower.tail = FALSE))
     dev1        df   p_value 
21.486738 24.000000  0.609872 

Réponse. La déviance du modèle ajusté vaut DEV1 = 21,49 pour 24 degrés de liberté. La déviance mesure l’écart entre notre modèle et le modèle saturé (un paramètre par cellule, qui reproduit exactement les données) : \(DEV = 2\,[\ell(\text{saturé}) - \ell(\text{modèle})]\). Une déviance proche de son nombre de degrés de liberté (ici 21,49 pour 24) indique déjà un bon ajustement ; on le teste formellement plus bas.

3.3 Quel est l’effet de l’âge sur la probabilité de décès par cancer du poumon ?

Code
irr1 <- exp(coef(mod1))
ci1  <- exp(confint.default(mod1))   # IC 95 % de Wald, exponentiés

tibble(age = levels(df$age)[-1],
       IRR = irr1[paste0("age", age)],
       ic_bas = ci1[paste0("age", age), 1],
       ic_haut = ci1[paste0("age", age), 2]) |>
  gt() |>
  fmt_number(columns = -age, decimals = 2) |>
  tab_header(title = "IRR de l'âge (référence : 40-44 ans)")
IRR de l'âge (référence : 40-44 ans)
age IRR ic_bas ic_haut
45-59 1.74 1.49 2.04
50-54 2.67 2.29 3.10
55-59 3.97 3.50 4.51
60-64 5.23 4.63 5.91
65-69 7.38 6.52 8.34
70-74 9.69 8.54 11.00
75-79 12.92 11.31 14.75
80+ 17.23 14.95 19.86

Réponse. L’âge est le déterminant le plus fort de la mortalité par cancer du poumon. Tous les coefficients d’âge sont positifs et hautement significatifs (p < 0,001), et les IRR croissent de façon monotone avec la classe d’âge. À statut tabagique donné, et par rapport aux 40-44 ans, le taux de décès est multiplié par :

  • 1,74 chez les 45-49 ans ;
  • 5,23 chez les 60-64 ans ;
  • 17,2 chez les 80 ans et plus (IC 95 % : 15,0 à 19,9).

Autrement dit, d’une classe quinquennale à la suivante, le taux augmente d’environ 30 à 50 % (+74 % entre les deux premières classes). Ce gradient reflète l’accumulation de l’exposition et le temps de latence du cancer du poumon.

Note

À noter. Le fichier nomme la 2e classe 45-59 : c’est une coquille pour 45-49, puisque la classe suivante est 50-54. Par ailleurs, l’ordonnée à l’origine dépend de l’unité choisie pour l’exposition (la constante vaut le log du taux de la cellule de référence), alors que les IRR n’en dépendent pas. Ce sont eux qu’on interprète.

3.4 Sauvegarde du modèle (utile pour LR tests)

Code
# En R, on garde l'objet en mémoire (mod1). Pas besoin d'"estimates store".
# On peut aussi l'ajouter à une liste si on veut gérer plusieurs modèles :
models <- list(mod1 = mod1)

3.5 Tester l’ajustement de ce modèle

Pour cela nous allons réaliser deux tests :

  • Deviance GOF (modèle ajusté vs saturé).

  • Pearson GOF (comparaison effectifs attendus vs observés). Les degrés de liberté correspondent ici au nombre de cellules moins le nombre de paramètres estimés (y compris l’ordonnée à l’origine). L’énoncé suggère 24 df pour chacun de ces tests (voir justification plus bas).

Code
# Deviance test (déjà calculé ci-dessus)
dev_stat  <- deviance(mod1)
dev_df    <- df.residual(mod1)
dev_p     <- pchisq(dev_stat, dev_df, lower.tail = FALSE)

# Pearson GOF (somme (y - mu)^2 / mu) et chi2 approx avec mêmes df résiduels)
mu_hat    <- fitted(mod1)         # comptages attendus
y_obs     <- df$deaths
pearson   <- sum((y_obs - mu_hat)^2 / mu_hat)
pearson_p <- pchisq(pearson, dev_df, lower.tail = FALSE)

tibble(
  test = c("Deviance GOF", "Pearson GOF"),
  statistic = c(dev_stat, pearson),
  df = c(dev_df, dev_df),
  p_value = c(dev_p, pearson_p)
) |>
  gt()
test statistic df p_value
Deviance GOF 21.48674 24 0.6098720
Pearson GOF 20.61936 24 0.6610658

Réponse. Les deux tests concluent au bon ajustement du modèle :

  • Déviance : \(G^2 =\) 21,49, 24 df, p = 0,610 ;
  • Pearson : \(X^2 =\) 20,62, 24 df, p = 0,661.

Les hypothèses testées sont :

  • H0 : le modèle reproduit les données aussi bien que le modèle saturé (les écarts observés − attendus sont dus au hasard) ;
  • H1 : le modèle est mal spécifié (il manque des effets, par exemple une interaction âge × tabac).

Les deux p-values sont largement supérieures à 0,05 : on ne rejette pas H0. Un modèle additif (effets de l’âge et du tabac qui se multiplient sur le taux) suffit donc à décrire les 36 cellules, et il n’est pas nécessaire d’ajouter une interaction âge × tabac.

Important

Attention au sens du test : ici, c’est une p-value élevée qui rassure. Ne pas rejeter H0 veut dire que les données ne contredisent pas le modèle ; cela ne prouve pas qu’il est « vrai ».

3.6 Justifiez pourquoi ces tests sont effectués avec le même df.

Code
c(cellules = nrow(df), parametres = length(coef(mod1)),
  df = nrow(df) - length(coef(mod1)),
  cellules_mu_inf_5 = sum(mu_hat < 5), mu_min = min(mu_hat))
         cellules        parametres                df cellules_mu_inf_5 
         36.00000          12.00000          24.00000           2.00000 
           mu_min 
          3.83633 

Réponse. Les deux statistiques comparent la même chose : les comptages observés \(y_i\) et les comptages ajustés \(\hat\mu_i\) du même modèle, sur les mêmes cellules. La déviance le fait par un rapport de vraisemblances, \(G^2 = 2\sum_i y_i \log(y_i/\hat\mu_i) - (y_i - \hat\mu_i)\), et Pearson par des écarts au carré normalisés, \(X^2 = \sum_i (y_i - \hat\mu_i)^2/\hat\mu_i\). Ce sont deux approximations asymptotiques équivalentes du même test (modèle contre modèle saturé), qui suivent sous H0 la même loi \(\chi^2(N - p)\) :

  • \(N =\) 36 cellules (9 classes d’âge × 4 statuts tabagiques), soit les paramètres du modèle saturé ;
  • \(p =\) 12 paramètres estimés (1 constante + 3 indicatrices tabac + 8 indicatrices âge) ;
  • d’où df = 36 − 12 = 24, ce qui correspond aux 24 df de l’énoncé.

Condition de validité : l’approximation χ² demande des effectifs attendus suffisants (règle usuelle \(\hat\mu_i \geq 5\)). Ici, seules 2 cellules sur 36 ont \(\hat\mu_i < 5\) (minimum 3,84) : ce sont les fumeurs de cigare/pipe de 40-44 et 45-49 ans, des cellules à faible effectif. On peut donc se fier à l’approximation, et les deux statistiques sont d’ailleurs très proches.

3.7 Ajuster un modèle sans la variable smoke, et effectuer un test de rapport de vraisemblance entre ce nouveau modèle et celui précédemment sauvegardé

On ajuste un modèle sans tabac et on compare à mod1 par LR test. En Stata : lrtest. En R : anova(mod0, mod1, test="Chisq").

Code
mod0 <- glm(deaths ~ age + offset(log(exposure)),
            family = poisson, data = df)

lr01 <- anova(mod0, mod1, test = "Chisq")
lr01
Analysis of Deviance Table

Model 1: deaths ~ age + offset(log(exposure))
Model 2: deaths ~ smoking_status + age + offset(log(exposure))
  Resid. Df Resid. Dev Df Deviance  Pr(>Chi)    
1        27    191.723                          
2        24     21.487  3   170.24 < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Réponse. Le test du rapport de vraisemblance compare deux modèles emboîtés : mod0 (âge seul) est un cas particulier de mod1 (âge + tabac), obtenu en imposant que les 3 coefficients du tabac soient nuls. La statistique est la différence des déviances, \(LR = DEV_0 - DEV_1 =\) 170,24, qui suit sous H0 un \(\chi^2(3)\). La p-value est < 0,001 : on rejette nettement H0. En plus, mod0 s’ajuste mal à lui seul (déviance 191,72 pour 27 df), alors que mod1 s’ajuste bien.

4 Conclure sur l’impact de l’usage du tabac sur la probabilité de décès par cancer du poumon.

Réponse. Le statut tabagique a un effet significatif sur la mortalité par cancer du poumon, même à âge donné. Le test LR indique que le tabac compte, mais pas dans quel sens : pour cela, il faut lire les coefficients.

Warning

Catégorie de référence. R prend la première modalité par ordre alphabétique, ici cigarPipeOnly (cigare ou pipe seulement), et non les non-fumeurs. Les IRR ci-dessous se lisent donc par rapport aux fumeurs de cigare/pipe. Pour prendre les non-fumeurs comme référence : df$smoking_status <- relevel(df$smoking_status, ref = "no").

À âge égal, par rapport aux fumeurs de cigare/pipe seulement :

  • cigarette seulement : taux multiplié par 1,45 (+ 45 %, IC 95 % 1,34 à 1,56) ;
  • cigarette + cigare/pipe : × 1,19 (IC 95 % 1,10 à 1,27) ;
  • non-fumeurs : × 0,95, différence non significative (p = 0,309).

Par rapport aux non-fumeurs, il suffit de faire le rapport des IRR : les fumeurs de cigarettes seules ont un taux 1,52 fois plus élevé (+ 52 %). Conclusion : fumer des cigarettes augmente nettement le risque de décès par cancer du poumon, alors que le cigare ou la pipe seuls ne se distinguent pas des non-fumeurs dans ces données.

4.1 Construire une nouvelle variable qui prend la valeur 1 si l’individu fume des cigarettes, 0 s’il n’en fume pas.

Créer une variable cigarette_user égale à 1 si l’individu fume des cigarettes, 0 sinon : l’énoncé demande de distinguer le type de produit et de concentrer l’attention sur la cigarette.

Code
# Adaptez le motif à vos libellés (ex.: "fume seulement la cigarette", "cigarette + cigare/pipe", etc.)
# On classera 1 si l'étiquette contient "cigarette", 0 sinon.
df <- df |>
  mutate(
    cigarette_user = as.integer(grepl("cigarrette", tolower(as.character(smoking_status)))))

table(df$cigarette_user, df$smoking_status)
   
    cigarPipeOnly cigarretteOnly cigarrettePlus no
  0             9              0              0  9
  1             0              9              9  0

Réponse. Le tableau croisé sert de vérification : cigarette_user vaut 1 pour les deux modalités qui contiennent la cigarette (cigarretteOnly, cigarrettePlus) et 0 pour no et cigarPipeOnly, soit 18 cellules de chaque côté. Le motif "cigarrette" reprend l’orthographe du fichier (avec deux r) : un motif "cigarette" ne trouverait aucune ligne, et tout le monde serait codé 0.

4.2 Ajuster un troisième modèle avec effet de l’âge et de cette variable d’usage de la cigarette.

Code
mod2 <- glm(deaths ~ cigarette_user + age + offset(log(exposure)),
            family = poisson, data = df)

summary(mod2)

Call:
glm(formula = deaths ~ cigarette_user + age + offset(log(exposure)), 
    family = poisson, data = df)

Coefficients:
                Estimate Std. Error  z value Pr(>|z|)    
(Intercept)    -15.15590    0.06378 -237.625  < 2e-16 ***
cigarette_user   0.26910    0.02757    9.762  < 2e-16 ***
age45-59         0.55342    0.07999    6.919 4.56e-12 ***
age50-54         0.98480    0.07682   12.820  < 2e-16 ***
age55-59         1.37640    0.06526   21.092  < 2e-16 ***
age60-64         1.64629    0.06256   26.317  < 2e-16 ***
age65-69         1.99023    0.06277   31.708  < 2e-16 ***
age70-74         2.26143    0.06432   35.161  < 2e-16 ***
age75-79         2.54560    0.06766   37.626  < 2e-16 ***
age80+           2.82907    0.07215   39.211  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for poisson family taken to be 1)

    Null deviance: 4055.984  on 35  degrees of freedom
Residual deviance:   92.237  on 26  degrees of freedom
AIC: 352.26

Number of Fisher Scoring iterations: 4
Code
anova(mod2, test = "Chisq")
Analysis of Deviance Table

Model: poisson, link: log

Response: deaths

Terms added sequentially (first to last)

               Df Deviance Resid. Df Resid. Dev  Pr(>Chi)    
NULL                              35     4056.0              
cigarette_user  1     58.3        34     3997.6 2.195e-14 ***
age             8   3905.4        26       92.2 < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
irr2 <- exp(coef(mod2))["cigarette_user"]
ci2  <- exp(confint.default(mod2))["cigarette_user", ]

Réponse. À âge égal, les fumeurs de cigarettes (seules ou avec cigare/pipe) ont un taux de décès multiplié par 1,31 par rapport aux autres (IC 95 % 1,24 à 1,38, p < 0,001). L’effet est significatif, mais ce modèle s’ajuste mal : sa déviance résiduelle vaut 92,24 pour 26 df (p < 0,001), contre 21,49 pour 24 df pour mod1. En regroupant les modalités, on perd de l’information. Le test suivant le confirme.

5 Comparer ce modèle avec le modèle initial par un test de rapport de vraisemblance

On compare le modèle multiniveaux par type de tabac (mod1) avec le modèle binaire cigarette vs non (mod2).

Code
lr21 <- anova(mod2, mod1, test = "Chisq")
lr21
Analysis of Deviance Table

Model 1: deaths ~ cigarette_user + age + offset(log(exposure))
Model 2: deaths ~ smoking_status + age + offset(log(exposure))
  Resid. Df Resid. Dev Df Deviance  Pr(>Chi)    
1        26     92.237                          
2        24     21.487  2    70.75 4.334e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Réponse. mod2 est emboîté dans mod1 : il revient à imposer \(\beta_{\text{cigOnly}} = \beta_{\text{cigPlus}}\) et \(\beta_{\text{no}} = \beta_{\text{cigarPipe}} = 0\), soit 2 contraintes (mod1 a 12 paramètres, mod2 en a 10). La statistique vaut \(LR =\) 70,75 pour 2 df, avec p < 0,001 : on rejette le modèle simplifié. La variable binaire « cigarette oui/non » est trop grossière : les deux groupes qu’elle fusionne n’ont pas le même risque.

6 Le type de produit fumé semble-t-il influencer la probabilité de décès par cancer du poumon ?

En Poisson log-linéaire : IRR = exp(coef). On reporte aussi des IC 95% exponentiés.

Code
tidy(mod1, conf.int = TRUE, exponentiate = TRUE) |>
  select(term, estimate, conf.low, conf.high, p.value) |>
  arrange(desc(estimate)) |>
  gt() |>
  tab_header(title = "IRR (modèle 1) : exp(coef) avec IC 95%")
IRR (modèle 1) : exp(coef) avec IC 95%
term estimate conf.low conf.high p.value
age80+ 1.723470e+01 1.496968e+01 1.988626e+01 0.000000e+00
age75-79 1.291740e+01 1.132626e+01 1.477500e+01 8.819244e-312
age70-74 9.693017e+00 8.558760e+00 1.101560e+01 6.852385e-273
age65-69 7.375556e+00 6.533324e+00 8.357251e+00 2.978913e-222
age60-64 5.229045e+00 4.633991e+00 5.922596e+00 4.943059e-154
age55-59 3.972749e+00 3.501347e+00 4.522490e+00 3.581870e-99
age50-54 2.665487e+00 2.294116e+00 3.100656e+00 2.658489e-37
age45-59 1.739985e+00 1.487796e+00 2.036040e+00 4.376858e-12
smoking_statuscigarretteOnly 1.446509e+00 1.343418e+00 1.558691e+00 2.099342e-22
smoking_statuscigarrettePlus 1.185481e+00 1.104252e+00 1.273769e+00 3.001586e-06
smoking_statusno 9.533182e-01 8.692799e-01 1.045138e+00 3.090007e-01
(Intercept) 2.645745e-07 2.312255e-07 3.016715e-07 0.000000e+00

Réponse. Oui, le type de produit compte. Trois constats (référence : cigare/pipe seulement, à âge égal) :

  1. La cigarette est le produit le plus nocif. Les fumeurs de cigarettes seules ont le taux le plus élevé (IRR = 1,45, soit 1,52 fois celui des non-fumeurs).
  2. Cigare et pipe seuls ne se distinguent pas des non-fumeurs. L’IRR des non-fumeurs par rapport au cigare/pipe (0,95) n’est pas significativement différent de 1, et son IC contient 1.
  3. Les fumeurs mixtes (cigarette + cigare/pipe) ont un risque intermédiaire (IRR = 1,19), significativement plus faible que celui des fumeurs de cigarettes seules : les IC ne se recouvrent pas. Une explication possible : ces fumeurs remplacent une partie de leurs cigarettes par le cigare ou la pipe, et consomment donc moins de cigarettes.

C’est pour cela que le test précédent rejette mod2 : il met dans le même groupe les fumeurs de cigarettes seules et les fumeurs mixtes, dont les risques diffèrent, et dans un autre groupe les non-fumeurs et les fumeurs de cigare/pipe, dont les risques sont proches. Le second regroupement est acceptable, le premier ne l’est pas.

Note

Ces données sont observationnelles : les IRR décrivent des associations ajustées sur l’âge. On ne contrôle ni l’intensité de la consommation (nombre de cigarettes par jour), ni sa durée, ni d’autres facteurs de confusion.

7 Extensions

7.1 Comptes attendus & tableau Observé vs Attendu

Code
df_preds <- df |>
  mutate(
    y_obs = deaths,
    mu_hat = fitted(mod1)
  )

df_preds |>
  select(age, smoking_status, exposure, y_obs, mu_hat) |>
  arrange(age, smoking_status) |>
  gt() |>
  fmt_number(columns = c(exposure, y_obs, mu_hat), decimals = 2)
age smoking_status exposure y_obs mu_hat
40-44 cigarPipeOnly 14,500,000.00 2.00 3.84
40-44 cigarretteOnly 341,000,000.00 124.00 130.50
40-44 cigarrettePlus 453,100,000.00 149.00 142.11
40-44 no 65,600,000.00 18.00 16.55
45-59 cigarPipeOnly 10,400,000.00 4.00 4.79
45-59 cigarretteOnly 223,900,000.00 140.00 149.10
45-59 cigarrettePlus 303,000,000.00 169.00 165.36
45-59 no 35,900,000.00 22.00 15.76
50-54 cigarPipeOnly 9,800,000.00 3.00 6.91
50-54 cigarretteOnly 185,100,000.00 187.00 188.82
50-54 cigarrettePlus 226,700,000.00 193.00 189.53
50-54 no 24,900,000.00 19.00 16.74
55-59 cigarPipeOnly 37,200,000.00 38.00 39.10
55-59 cigarretteOnly 327,000,000.00 514.00 497.17
55-59 cigarrettePlus 468,200,000.00 576.00 583.40
55-59 no 63,200,000.00 55.00 63.33
60-64 cigarPipeOnly 84,600,000.00 113.00 117.04
60-64 cigarretteOnly 379,100,000.00 778.00 758.66
60-64 cigarrettePlus 605,200,000.00 1,001.00 992.58
60-64 no 106,700,000.00 117.00 140.73
65-69 cigarPipeOnly 94,900,000.00 173.00 185.19
65-69 cigarretteOnly 242,100,000.00 689.00 683.37
65-69 cigarrettePlus 388,000,000.00 901.00 897.57
65-69 no 89,700,000.00 170.00 166.87
70-74 cigarPipeOnly 82,400,000.00 212.00 211.32
70-74 cigarretteOnly 119,500,000.00 432.00 443.30
70-74 cigarrettePlus 203,300,000.00 613.00 618.07
70-74 no 66,800,000.00 179.00 163.31
75-79 cigarPipeOnly 66,700,000.00 243.00 227.95
75-79 cigarretteOnly 43,600,000.00 214.00 215.54
75-79 cigarrettePlus 87,100,000.00 337.00 352.89
75-79 no 36,100,000.00 120.00 117.62
80+ cigarPipeOnly 53,700,000.00 253.00 244.86
80+ cigarretteOnly 11,300,000.00 63.00 74.53
80+ cigarrettePlus 34,500,000.00 189.00 186.49
80+ no 27,400,000.00 120.00 119.11
Code
res_pearson <- residuals(mod1, type = "pearson")

Lecture. Les comptages attendus \(\hat\mu_{ij}\) sont très proches des comptages observés dans toutes les cellules. Les résidus de Pearson, \((y - \hat\mu)/\sqrt{\hat\mu}\), sont tous compris entre -2,00 et 1,57. Seule la cellule des non-fumeurs de 60-64 ans atteint tout juste −2 (un peu moins de décès que prévu), ce qui est attendu par hasard sur 36 cellules : pas de cellule franchement mal prédite. Le modèle additif âge + tabac ne présente donc pas d’écart systématique, ce qui est cohérent avec les tests d’ajustement.

7.2 Vérification d’éventuelle sur-dispersion

Le GLM Poisson suppose Var(Y) = E[Y]. Si Var(Y) >> E[Y], la sur-dispersion peut invalider les tests usuels (SE sous-estimés).

Code
check_overdispersion(mod1)
# Overdispersion test

       dispersion ratio =  0.859
  Pearson's Chi-Squared = 20.619
                p-value =  0.661

Lecture. Le rapport de dispersion vaut \(X^2/df =\) 0,86, proche de 1 et même légèrement inférieur (p = 0,661) : pas de sur-dispersion. L’hypothèse \(\text{Var}(Y) = \mathbb{E}[Y]\) est compatible avec les données, donc les erreurs standards et les tests du modèle de Poisson sont fiables. Il n’est pas nécessaire de passer à un modèle quasi-Poisson ou binomial négatif.

En cas de sur-dispersion marquée, envisagez Quasi-Poisson (family = quasipoisson) ou Négative Binomiale (MASS::glm.nb) et comparez l’ajustement.

7.3 Graphiques (facultatifs)

Code
shapes_9 <- c(16, 17, 15, 3, 7, 8, 0, 1, 2)  # à ta convenance
ggplot(df_preds, aes(mu_hat, y_obs, color = smoking_status, shape = age)) +
  geom_point(size = 2) +
  geom_abline(intercept = 0, slope = 1, linetype = 2) +
  scale_shape_manual(values = shapes_9) +
  labs(x = "Comptes attendus (mod1)", y = "Comptes observés",
       title = "Observé vs Attendu : GLM Poisson (mod1)")

7.4 Bilan

  • Le modèle de Poisson avec offset permet d’estimer des taux de décès en fonction du tabagisme et de l’âge.

  • Le modèle additif âge + tabac (mod1) s’ajuste bien (déviance 21,49 pour 24 df, p = 0,610), sans sur-dispersion.

  • L’âge est le facteur dominant : le taux est multiplié par 17,2 entre 40-44 ans et 80 ans et plus.

  • Le tabac a un effet significatif (LR = 170,24, 3 df), qui tient à la cigarette : à âge égal, les fumeurs de cigarettes seules ont un taux 1,52 fois plus élevé que les non-fumeurs, alors que cigare et pipe seuls ne s’en distinguent pas.

  • La variable binaire « cigarette oui/non » est trop grossière (rejetée par le test LR contre mod1) : le type de produit compte.

  • Les IRR offrent une interprétation intuitive : par rapport à la catégorie de référence, combien de fois le taux de décès est-il multiplié. Il faut toujours vérifier quelle est cette référence.

Conseil pratique : en recherche appliquée, vérifiez toujours la sur-dispersion et documentez les hypothèses de variance (Poisson vs quasi-Poisson vs binomiale négative).