---
title: "TD 3 : Régression multinomiale (R)"
subtitle: "Années universitaires 2025–2026 et 2026–2027 • Parcours Économie de la santé & Développement durable"
description: "Modèles logit multinomiaux (nominal et ordinal): traitement de l’exemple cancer de l’endomètre"
author: "Pierre Beaucoral"
format:
html:
theme: [cosmo, ../edu.scss]
slide-number: true
progress: true
hash: true
center: false
transition: slide
toc: true
number-sections: true
code-fold: true
code-tools: true
code-overflow: scroll
smaller: true
self-contained: true
pdf: default
editor:
markdown:
wrap: 72
---
## Objectifs du TD
- Manipuler un **modèle logit multinomial nominal** (`SUBTYPE`).
- Manipuler un **modèle logit ordinal** (`GRADE`).
- Savoir :
- préparer les données (codage en facteurs / variables ordinales)
;
- estimer, simplifier et interpréter un modèle ;
- tester :
- l’ajustement (Hosmer–Lemeshow multinomial) ;
- des interactions via test de rapport de vraisemblance (LR).
Données : *cancer.dta* (288 femmes avec cancer de l’endomètre).
------------------------------------------------------------------------
## Introduction
Un **modèle de régression multinomiale** est un modèle Logit ou Probit
dans lequel la variable à expliquer $Y$ est une variable qualitative à
$k > 2$ modalités. Cette variable peut être qualitative **nominale** ou
**ordinale**.
### Cas d'une variable expliquée nominale
Dans le cas d'une variable expliquée **nominale**, on prend n'importe
quelle modalité comme modalité de référence (modalité 0), et on estime
des *pseudo-côtes*, c’est-à-dire :
- $\displaystyle \frac{\Pr(Y = 1)}{\Pr(Y = 0)}$
- $\displaystyle \frac{\Pr(Y = 2)}{\Pr(Y = 0)}$
- etc.
Par exemple, dans le cas $k = 3$ modalités de $Y$, on a :
$\Pr(Y = 0) + \Pr(Y = 1) + \Pr(Y = 2) = 1$
MAIS $\Pr(Y = 0) + \Pr(Y = 1) < 1$ et $\Pr(Y = 0) + \Pr(Y = 2) < 1$
On estime alors les paramètres $\beta_g$ tels que :
$$
\ln \left(\frac{\Pr(Y = g)}{\Pr(Y = 0)}\right)
= \beta_{g0} + \sum_{j=1}^{p} \beta_{gj} X_j
$$
avec $g = 1, \dots, k-1$.
On estime donc :
- $(k - 1)$ paramètres pour chaque variable explicative quantitative ;
- $(k - 1)(q - 1)$ paramètres pour une variable explicative
qualitative à $q$ modalités.
### Cas d'une variable expliquée ordinale
Dans le cas d'une variable expliquée **ordinale**, $Y = 0$ ou $1$ ou
$2$, etc. représente une réponse graduée.
La résolution suppose l’existence d’une variable continue sous-jacente
$Y^*$, et de $(k - 1)$ bornes $c_j$ telles que :
- si $y_i^* < c_1$ alors $y_i = 1$
- si $c_{j-1} < y_i^* < c_j$ alors $y_i = j$
- si $y_i^* > c_{k-1}$ alors $y_i = k$
On a :
$$
y_i^* = X_i B + \varepsilon_i
$$
et on estime conjointement :
- les paramètres $\beta_j$ correspondant à chaque variable explicative
;
- les seuils $c_g$ ($g = 1, \dots, k - 1$).
On prédit alors l'appartenance de chaque individu à chaque classe par
les formules :
$$\Pr(Y_i = 0) = \Phi(c_1 - X_i B)$$
$$\Pr(Y_i = g) = \Phi(c_g - X_i B) - \Phi(c_{g-1} - X_i B)$$
où $\Phi$ est :
- la fonction de répartition d'une loi gaussienne centrée réduite dans
le cas du **modèle Probit multivarié** ;
- l'inverse de la fonction Logit dans le cas du **Logit multivarié**.
## Présentation de l'étude et des données
Les données étudiées proviennent de *Hill et al.* (1995) et sont
utilisées comme exemple dans l’ouvrage de Kleinbaum et Klein.
- 288 femmes avec un cancer de l’endomètre participent à l’étude.
### Dictionnaire des variables
- **ID** : identifiant individuel.
- **GRADE** : variable ordinale indiquant le stade de la tumeur
- 0 : bien différenciée
- 1 : modérément différenciée
- 2 : peu différenciée
- **RACE** : variable indicatrice à deux modalités
- 1 : peau noire
- 0 : peau blanche
- **ESTROGEN** : variable indicatrice à deux modalités
- 1 : la femme a déjà pris des œstrogènes
- 0 : sinon
- **SUBTYPE** : variable qualitative à trois modalités codant le
sous-type de tissu cancéreux
- 0 : Adénocarcinome
- 1 : Adenosquamous
- 2 : Autre
- **AGE** : âge recodé en deux classes
- 0 : 50–64 ans
- 1 : 65–79 ans
- **SMK** : variable binaire indiquant le statut tabagique au moment
de l’étude
- 1 : fumeuse
- 0 : non-fumeuse
### Références
- Hill, H.A., Coates, R.J., Austin, H., Correa, P., Robboy, S.J.,
Chen, V., Click, L.A., Barrett, R.J., Boyce, J.G., Kotz, H.L., and
Harlan, L.C., *Racial differences in tumor grade among women with
endometrial cancer*, Gynecol. Oncol. 56: 154–163, 1995.
- David G. Kleinbaum, Mitchel Klein, *Logistic Regression – A
Self‐Learning Text*, Third Edition, Springer, 2010.
------------------------------------------------------------------------
## Packages utilisés
```{r}
#| label: setup
#| echo: true
#| message: false
#| warning: false
library(tidyverse)
library(janitor)
library(haven)
library(broom)
library(dplyr)
library(gt)
library(nnet) # multinom (logit multinomial nominal)
library(generalhoslem) # logitgof : test de Hosmer–Lemeshow multinomial
library(MASS) # polr : logit ordinal
theme_set(theme_minimal())
```
------------------------------------------------------------------------
## Import des données et préparation
On suppose que le fichier `cancer.dta` se trouve dans le dossier
`./data/`.
```{r}
#| label: import-data
#| echo: true
cancer_raw <- read_dta("./data/cancer.dta") |>
clean_names()
glimpse(cancer_raw)
```
On dispose notamment des variables :
- `grade` (3 modalités ordonnées),
- `race`,
- `estrogen`,
- `subtype`,
- `age`,
- `smoking`.
------------------------------------------------------------------------
## Recodage des variables
On crée des facteurs explicites pour la régression, en choisissant des
**références** cohérentes avec l’énoncé :
```{r}
#| label: recode-vars
cancer <- cancer_raw |>
mutate(
# convertir les labels Stata en facteurs R
grade_f = as_factor(grade),
subtype_f = as_factor(subtype),
race_f = as_factor(race),
estrogen_f = as_factor(estrogen),
age_f = as_factor(age),
smk_f = as_factor(smoking),
# forcer l'ordre pour l'ordinal (adapter les noms à ce que tu vois)
grade_ord = fct_relevel(
grade_f,
"bien différencié",
"moyennement différencié",
"peu différencié"
)
)
cancer |>
dplyr::select(grade, grade_ord, subtype, subtype_f,
race_f, estrogen_f, age_f, smk_f) |>
head()
```
------------------------------------------------------------------------
## Modèle multinomial pour expliquer SUBTYPE
Variables explicatives : `RACE`, `ESTROGEN`, `SMK`, `AGE`.
------------------------------------------------------------------------
### Estimation du premier modèle (logit multinomial nominal)
```{r}
#| label: mod-subtype-full
mod_sub_full <- multinom(
subtype_f ~ race_f + estrogen_f + smk_f + age_f,
data = cancer
)
res_sub_ful <- tidy(mod_sub_full,
exponentiate = TRUE, # passe en OR
conf.int = TRUE) # ajoute IC 95%
res_sub_ful
```
------------------------------------------------------------------------
```{r}
summary(mod_sub_full)
```
On obtient, pour chaque modalité $g$ $\neq$ référence, une équation :
$\log \frac{P(\text{SUBTYPE}=g)}{P(\text{SUBTYPE}=\text{adenocarcinomous})} = \beta{g0} + \beta{g,\text{race}} + \dots$
> À caractéristiques identiques (race, oestrogènes, âge), les
> **fumeuses** ont une **cote (odds)** d’avoir un cancer *adenosquamous*
> plutôt que le sous-type de référence environ **2,4 fois plus élevée**
> que les **non fumeuses** (odds-ratio ≈ 2,4). Attention : cela ne
> signifie pas que leur probabilité est 2,4 fois plus grande.
------------------------------------------------------------------------
### Résumé et odds-ratios
```{r}
#| label: tidy-subtype-full
res_sub_full <- tidy(mod_sub_full, exponentiate = TRUE, conf.int = TRUE)
res_sub_full
```
Ici, `estimate` = **odds-ratio**, `conf.low` / `conf.high` = IC 95 %.
------------------------------------------------------------------------
### Probabilités prédites
On génère les probabilités prédites pour chaque modalité de `SUBTYPE` :
```{r}
#| label: predict-subtype-probs
phat_sub <- predict(mod_sub_full, type = "probs")
head(phat_sub)
colMeans(phat_sub) # moyennes des proba par modalité
```
------------------------------------------------------------------------
On recolle ces probabilités aux données :
```{r}
#| label: bind-preds
# 1) Sous-échantillon sans NA sur les variables du modèle
cancer_complete <- cancer |>
drop_na(subtype_f, race_f, estrogen_f, smk_f, age_f)
# 2) Modèle multinomial sur cancer_complete
mod_sub_full <- multinom(
subtype_f ~ race_f + estrogen_f + smk_f + age_f,
data = cancer_complete
)
```
------------------------------------------------------------------------
```{r}
# 3) Probabilités prédites (286 x 3)
phat_sub <- predict(mod_sub_full, type = "probs")
colMeans(phat_sub)
```
```{r}
# 4) On transforme en tibble et on renomme
phat_sub_tbl <- as_tibble(phat_sub)
names(phat_sub_tbl) <- paste0("p_", levels(cancer_complete$subtype_f))
# ex : "p_adenocarcinomous" "p_adenosquamous" "p_other"
# 5) On colle aux données *complètes* (286 lignes)
cancer_sub <- bind_cols(cancer_complete, phat_sub_tbl)
cancer_sub |>
dplyr::select(subtype_f, starts_with("p_")) |>
slice(1:5)
```
------------------------------------------------------------------------
### Test d’ajustement (Hosmer–Lemeshow multinomial)
On utilise `logitgof()` du package **generalhoslem**.
- `obs` : modalités de `SUBTYPE` sous forme numérique (1, 2, 3).
- `exp` : matrice de probabilités prédites.
- `g` : nombre de groupes (à ajuster si nécessaire).
```{r}
#| label: gof-subtype
#| warning: false
y_num <- as.numeric(cancer_sub$subtype_f)
exp_mat <- as.matrix(phat_sub)
# Exemple avec 10 groupes
gof_10 <- logitgof(obs = y_num, exp = exp_mat, g = 10)
gof_10
```
> Le test de Hosmer–Lemeshow (généralisé au multinomial et implémenté
> par `logitgof()` dans `generalhoslem`) compare, dans des groupes de
> probabilités prédites similaires, les effectifs observés de chaque
> catégorie de la variable dépendante aux effectifs attendus selon le
> modèle. La statistique de test est de type χ². Une grande p-value
> indique que l’on ne détecte pas de mauvais ajustement global du modèle
> aux données ; une petite p-value suggère un manque d’adéquation
> (modèle mal calibré).
------------------------------------------------------------------------
Si le test ne passe pas (classes attendues trop petites), on réduit le
nombre de groupes :
```{r}
#| label: gof-subtype-multi-g
library(purrr)
map_df(4:10, ~{
out <- try(logitgof(y_num, exp_mat, g = .x), silent = TRUE)
tibble(
g = .x,
stat = if (inherits(out, "try-error")) NA_real_ else out$statistic,
p_value = if (inherits(out, "try-error")) NA_real_ else out$p.value
)
})
```
------------------------------------------------------------------------
**Lecture :**
- p-value **grande** → pas d’évidence de mauvais ajustement.
- p-value **petite** → modèle mal ajusté (au moins pour certains
groupes).
> En diminuant le nombre de groupes, le test Hosmer–Lemeshow devient
> applicable. Les p-values varient avec g, ce qui montre que le test est
> assez instable dans ce petit échantillon multinomial. Néanmoins, pour
> la plupart des partitions (g = 4, 5, 7, 8, 9, 10), on ne rejette pas
> l’hypothèse d’un bon ajustement (p-value \> 5 %). On peut donc
> conclure qu’**il n’y a pas de signe clair de mauvais ajustement du
> modèle aux données**, tout en rappelant que ce test doit être
> interprété avec prudence.
------------------------------------------------------------------------
### Simplification du modèle (tests LR)
On cherche un modèle **plus parcimonieux** en retirant les variables non
significatives.
Exemple : on teste si on peut retirer `smk_f` puis `age_f`
```{r}
#| label: mod-subtype-simplify
# Modèle sans SMK
mod_sub_nosmk <- multinom(
subtype_f ~ race_f + estrogen_f + age_f,
data = cancer
)
# Test LR : mod_sub_nosmk vs mod_sub_full
anova(mod_sub_nosmk, mod_sub_full)
```
**Test 1 : peut-on retirer `smk_f` ?**\
Modèles comparés :
1. Modèle réduit : `subtype_f ~ race_f + estrogen_f + age_f`
2. Modèle complet : `subtype_f ~ race_f + estrogen_f + smk_f + age_f`
Résultat du test LR :
- À 5 %, on **rejette** H₀ “on peut enlever `smk_f`” : **le tabagisme
(`smk_f`) apporte une information significative** pour expliquer le
sous-type de cancer → on **garde `smk_f`**.
------------------------------------------------------------------------
```{r}
# Modèle sans AGE (à partir du modèle sans SMK par exemple)
mod_sub_noage <- multinom(
subtype_f ~ race_f + estrogen_f + smk_f,
data = cancer
)
anova(mod_sub_noage, mod_sub_full)
```
------------------------------------------------------------------------
**Interprétation :**
- Si la p-value du test LR est **\> 5 %**, on ne rejette pas $H_0$ :
le modèle réduit n’est pas significativement pire → on peut
**retirer** la variable.
- On garde donc `smk_f` mais pas `age`
> Les degrés de liberté du test de rapport de vraisemblance sont égaux
> au **nombre de paramètres supprimés** entre le modèle complet et le
> modèle réduit.\
> Dans un modèle multinomial, retirer une variable facteur à $L$
> modalités enlève $(J-1)(L-1)$ coefficients, donc $(J-1)(L-1)$ degrés
> de liberté. $J$ catégories et $L$ modalités.
------------------------------------------------------------------------
### Test meilleur modèle
On teste sur les deux variables restantes
```{r}
#| label: helper-select-best
#| echo: true
# Modèle sans RACE (à partir du modèle sans SMK par exemple)
mod_sub_norace <- multinom(
subtype_f ~ age_f + estrogen_f + smk_f,
data = cancer
)
anova(mod_sub_norace, mod_sub_full)
```
------------------------------------------------------------------------
On retire la variable estrogen pour expliquer `SUBTYPE` :
```{r}
#| label: best-subtype-example
# 1) Construire un sous-échantillon complet pour toutes les variables en jeu
cancer_lr <- cancer |>
filter(
!is.na(subtype_f),
!is.na(age_f),
!is.na(race_f),
!is.na(smk_f),
!is.na(estrogen_f) # même si tu ne l'utilises pas dans tous les modèles
)
# 2) Re-estimer les modèles sur CE MÊME jeu de données
mod_sub_full <- multinom(
subtype_f ~ age_f + race_f + smk_f + estrogen_f,
data = cancer_lr
)
mod_sub_noestro <- multinom(
subtype_f ~ age_f + race_f + smk_f,
data = cancer_lr
)
# 3) Maintenant, le test LR fonctionne
anova(mod_sub_noestro, mod_sub_full, test = "Chisq")
```
> La fonction se base uniquement sur les **résultats passés** (modèles
> déjà estimés)\
> et sélectionne celui qui minimise l’AIC (ou le BIC).
------------------------------------------------------------------------
### Interprétation économique du modèle final
```{r}
mod_sub_final <- multinom(
subtype_f ~ age_f + smk_f,
data = cancer
)
res_sub <- tidy(mod_sub_final,
exponentiate = TRUE, # passe en OR
conf.int = TRUE) # ajoute IC 95%
res_sub
```
Principe d’interprétation :
- Un odds-ratio **\> 1** pour une modalité donnée signifie que la
variable augmente les **cotes** d’appartenir à ce type de cancer par
rapport à la référence (`adenocarcinomous`), toutes choses égales
par ailleurs.
- Un odds-ratio **\< 1** signifie au contraire une diminution des
cotes.
------------------------------------------------------------------------
### Tableau de contingence des individus bien / mal classés
#### (a) Effectifs par type de tumeur observée
```{r}
#| label: tab-subtype-obs
cancer_sub |>
count(subtype_f)
```
------------------------------------------------------------------------
#### (b) Classe prédite par « probabilité max » (règle du 1er choix)
On attribue à chaque individu la **classe prédite** correspondant à la
probabilité la plus élevée :
```{r}
#| label: pred-class-argmax
phat_sub <- predict(mod_sub_final, type = "probs")
colMeans(phat_sub)
phat_sub_tbl <- as_tibble(phat_sub)
names(phat_sub_tbl) <- paste0("p_", levels(cancer_complete$subtype_f))
# ex : "p_adenocarcinomous" "p_adenosquamous" "p_other"
cancer <- bind_cols(cancer, phat_sub_tbl)
# vecteur des probas sous forme de matrice
probs_mat <- as.matrix(
cancer[, c("p_adenocarcinomous", "p_adenosquamous", "p_other")]
)
# indices de la proba max par individu (1, 2 ou 3)
idx_max <- max.col(probs_mat)
# noms des modalités dans le bon ordre
lev <- c("adenocarcinomous", "adenosquamous", "other")
cancer <- cancer |>
mutate(
pred_subtype = factor(lev[idx_max],
levels = levels(subtype_f))
)
cancer |>
dplyr::select(subtype_f, pred_subtype, starts_with("p_")) |>
slice(1:5)
```
------------------------------------------------------------------------
```{r}
tab_sub <- table(
Observed = cancer$subtype_f,
Predicted = cancer$pred_subtype
)
tab_sub
prop_ok <- sum(diag(tab_sub)) / sum(tab_sub)
1-prop_ok
```
------------------------------------------------------------------------
## B. Modèle ordinal pour expliquer GRADE
On modélise le **stade de la tumeur** (bien / moyennement / peu
différenciée) en fonction de :
- `RACE`, `ESTROGEN`, `SUBTYPE`, `AGE`, `SMK`.
------------------------------------------------------------------------
### Modèle de base (logit ordinal)
On utilise `polr()` (MASS) avec `grade_ord` comme variable ordinale.
```{r}
#| label: mod-grade-base
mod_grade_base <- polr(
grade_ord ~ race_f + estrogen_f + subtype_f + age_f + smk_f,
data = cancer,
Hess = TRUE
)
summary(mod_grade_base)
```
Les sorties donnent :
- Les coefficients $\beta$ (effets sur le **score latent**),
- Les seuils (cutpoints) séparant les catégories de `GRADE`.
------------------------------------------------------------------------
### Test d’ajustement via interactions (LR tests)
#### Interaction ESTROGEN × SUBTYPE
On ajoute l’interaction `estrogen_f * subtype_f` :
```{r}
#| label: mod-grade-int-es
mod_grade_es <- polr(
grade_ord ~ race_f + estrogen_f * subtype_f + age_f + smk_f,
data = cancer,
Hess = TRUE
)
anova(mod_grade_base, mod_grade_es)
```
- `anova()` réalise un **test LR** entre modèle sans interaction
(`mod_grade_base`) et modèle avec interaction (`mod_grade_es`).
- On lit la **p-value** :
- si petite → l’interaction améliore le modèle ;
- si grande → on peut s’en passer.
------------------------------------------------------------------------
#### Autres interactions possibles
On peut tester d’autres interactions pertinentes, par exemple :
- `ESTROGEN × AGE` :
```{r}
#| label: mod-grade-int-eage
mod_grade_eage <- polr(
grade_ord ~ race_f + estrogen_f * age_f + subtype_f + smk_f,
data = cancer,
Hess = TRUE
)
anova(mod_grade_base, mod_grade_eage)
```
------------------------------------------------------------------------
- `SUBTYPE × AGE` :
```{r}
#| label: mod-grade-int-sage
mod_grade_sage <- polr(
grade_ord ~ race_f + estrogen_f + subtype_f * age_f + smk_f,
data = cancer,
Hess = TRUE
)
anova(mod_grade_base, mod_grade_sage)
```
Pour chaque interaction :
- Si la p-value LR est **faible** → interaction importante → à garder
;
- Sinon → pas de gain significatif → on privilégie le modèle sans
interaction.
------------------------------------------------------------------------
### Sélection de modèle
```{r}
summary(mod_grade_base)
```
------------------------------------------------------------------------
### Interprétation du modèle ordinal final
- **Race (noire vs non noire)**
- Coefficient = 0,60 (t ≈ 2,14) → **significatif**.
- Les femmes noires ont des **cotes ≈ 1,8 fois plus élevées**
d’avoir un grade plus mauvais (passer vers “moyennement/peu
différencié”), toutes choses égales par ailleurs.
- **Traitement aux œstrogènes (oui vs non)**
- Coefficient = −0,61 (t ≈ −2,41) → **significatif**.
- Les femmes ayant reçu un traitement œstrogénique ont des **cotes
≈ divisées par 2** d’avoir un grade plus mauvais.
→ Le traitement est associé à des **grades un peu meilleurs**.
- **Sous-type tumoral** (réf. = adenocarcinomous)
- *adenosquamous* : coef = 1,78 (t ≈ 5,48) → très significatif.\
→ Cotes ≈ 6 fois plus élevées d’avoir un **grade plus
défavorable** que l’adenocarcinome.
- *other* : effet faible et **non significatif**.
- **Âge (65–79 ans) et tabagisme (smk_fyes)**
- Coefficients proches de 0, t très faibles → **pas d’association
significative** avec le grade, une fois contrôlé pour race,
sous-type et œstrogènes.
------------------------------------------------------------------------
## Conclusion TD3
- On a estimé :
- un **logit multinomial nominal** pour `SUBTYPE` ;
- un **logit ordinal** pour `GRADE`.
- On a :
- testé l’ajustement du modèle nominal via un **Hosmer–Lemeshow
multinomial** ;
- utilisé des **tests de rapport de vraisemblance** pour :
- simplifier les modèles (variables non significatives),
- tester l’intérêt d’interactions dans le modèle ordinal ;
- Choisi les spécifications finales.
À retenir :
- Pour les variables **nominales**, on compare chaque modalité à une
**référence** via des odds-ratios.
- Pour les variables **ordinales**, le modèle logit/probit ordonné
repose sur une **variable latente** et des **seuils**, avec une
interprétation en termes de tendance vers des catégories plus
élevées ou plus basses.