---
title: "TD 2 : Régression logistique (R)"
subtitle: "Années universitaires 2025–2026 et 2026–2027 • Parcours Économie de la santé & Développement durable"
description: "Régression logistique – traitement de l’exemple vu en cours"
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
---
> Ce TD reprend l'exemple et le dictionnaire de variables
> (`age, ed, employ, address, income, debtinc, credebt, othdebt, default`)
> décrits dans la ressource d’origine.\
> Données à récupérer: **`bankloanT.xls`** (ENT).
**Packages utilisés** : `tidyverse`, `readxl`, `janitor`, `broom`,
`ResourceSelection`, `pROC`, `gt`, `ggplot2`.
```{r}
#| label: setup
#| echo: false
#| warning: false
library(tidyverse)
library(readxl)
library(janitor)
library(broom)
library(ResourceSelection)
library(pROC)
library(gt)
theme_set(theme_minimal())
```
# Rappel de cours : Modèles Logit et Probit
## Quand utiliser ces modèles ?
La **régression logistique** et la **régression probit** sont utilisées
lorsque la variable à expliquer est **binaire** :
$Y_i \in {0,1}$
- Exemple : défaut de paiement / pas de défaut\
- Objectif : estimer la **probabilité** $P(Y_i = 1 \mid X_i)$ en
fonction de caractéristiques $X_i$
Les variables explicatives peuvent être **quantitatives ou
qualitatives**.
------------------------------------------------------------------------
## Pourquoi ne pas utiliser une régression linéaire ?
Une régression linéaire classique :
$Y_i = \beta_0 + \beta_1 X_i + \varepsilon_i$
peut donner des valeurs **prédites hors de \[0, 1\]**, ce qui est
absurde pour une probabilité.
Il faut donc introduire une **fonction de lien** qui transforme
l’intervalle (0, 1) en $\mathbb{R}$.
------------------------------------------------------------------------
## Fonction de lien du modèle Logit
$\text{logit}(p) = \ln\left(\frac{p}{1 - p}\right) \quad \text{et} \quad p = \frac{e^{x}}{1 + e^{x}} = \frac{1}{1 + e^{-x}}$
Le modèle s’écrit :
$\text{logit}(P(Y_i = 1)) = \beta_0 + \beta1 X{i1} + \dots + \beta_p X{ip}$
Les coefficients $\beta_j$ s’interprètent via les **odds-ratios** :
$OR_j = e^{\beta_j}$
------------------------------------------------------------------------
## Fonction de lien du modèle Probit
Le modèle Probit suppose qu’il existe une **variable latente** (Y_i\^\*)
telle que :
$Y_i^* = \beta_0 + \beta1 X{i1} + \dots + \beta_p X{ip} + \varepsilon_i \quad \text{avec} \quad \varepsilon_i \sim \mathcal{N}(0,1)$
et on observe :
$Y_i = 1 \text{ si } Y_i^* > 0$
d’où :
$P(Y_i = 1) = \Phi(\beta_0 + \beta_1 X_i)$ où $\Phi$ est la fonction de
répartition de la loi normale centrée réduite.
------------------------------------------------------------------------
## Comparaison graphique des liens Logit et Probit
```{r}
#| label: fig-logit-probit
#| fig-cap: "Comparaison des fonctions de lien Logit et Probit"
library(ggplot2)
x <- seq(-6, 6, length.out = 200)
df_link <- data.frame( x = x, Logit = 1 / (1 + exp(-x)), Probit = pnorm(x) )
ggplot(df_link, aes(x = x)) + geom_line(aes(y = Logit, color = "Logit")) + geom_line(aes(y = Probit, color = "Probit"), linetype = 2) + scale_color_manual(values = c("Logit" = "steelblue", "Probit" = "firebrick")) + labs( title = "Comparaison des liens Logit et Probit", x = "Score linéaire (Xβ)", y = "Probabilité prédite", color = "Modèle" ) + theme_minimal()
```
**Observation :**
- Les deux courbes sont très proches ; la principale différence réside
dans la forme légèrement plus aplatie du Probit aux extrémités.
- En pratique, les résultats logit et probit sont très similaires
(seuls les coefficients changent d’échelle :
$\beta_{\text{logit}} ≈ 1.6 β_{\text{probit}}$.
------------------------------------------------------------------------
### Interprétation économique
- **Logit** : privilégié quand on interprète les coefficients en
termes d’odds-ratios (très courant en santé et sciences sociales).
- **Probit** : privilégié en économie microéconométrique, car il se
relie naturellement à un modèle de variable latente et au Tobit.
------------------------------------------------------------------------
```{r}
#| label: fig-logit-example
#| fig-cap: "Exemple : probabilité prédite de défaut selon le ratio dette/revenu"
x <- seq(0, 40, length.out = 200)
beta0 <- -2.5; beta1 <- 0.12
p_hat <- 1 / (1 + exp(-(beta0 + beta1 * x)))
df_ex <- data.frame(debt_income = x, p_hat = p_hat)
ggplot(df_ex, aes(debt_income, p_hat)) +
geom_line(color = "seagreen4", linewidth = 1.2) +
labs(
title = "Effet du ratio dette/revenu sur la probabilité de défaut (modèle logit)",
x = "Ratio dette / revenu (%)",
y = "Probabilité prédite de défaut"
) +
theme_minimal()
```
**Interprétation :**\
→ Lorsque le ratio dette/revenu augmente, la probabilité prédite de
défaut croît de manière sigmoïde : faible au départ, elle augmente
rapidement autour de la zone moyenne, puis se stabilise.
------------------------------------------------------------------------
## Résumé comparatif
| Caractéristique | Logit | Probit |
|----|----|----|
| Fonction de lien | logit(p) = log(p/(1−p)) | Φ⁻¹(p) |
| Distribution implicite des erreurs | Logistique | Normale centrée réduite |
| Interprétation des coefficients | Odds-ratios | Z-scores latents |
| Usages typiques | Santé, sociologie | Économie, finance |
| Résultats empiriques | Quasi identiques (β_logit ≈ 1.6 β_probit) | |
> **À retenir :** Logit et Probit sont deux manières voisines de
> modéliser une probabilité binaire. Le choix entre les deux est souvent
> une question de convention disciplinaire ou d’interprétabilité des
> coefficients.
------------------------------------------------------------------------
# Questions TD
## Télécharger le fichier bankloanT.xls depuis l'ENT, puis importer les données dans R
```{r}
df0 <- read_excel("./data/bankloanT.xls") |> clean_names()
df0 |> glimpse()
```
## Etudier la distribution de la variable ed. Créer une variable catégorielle, puis une variable ne contenant que 4 classes.
```{r}
df0 |> count(ed) |> arrange(desc(n))
df1 <- df0 |>
mutate(
ed5 = factor(ed,
levels = c(
"Did not complete high school",
"High school degree",
"Some college",
"College degree",
"Post-undergraduate degree"
),
ordered = TRUE),
ed4 = fct_collapse(ed5,
"College or above" = c("College degree", "Post-undergraduate degree"))
)
df1 |> count(ed4)
ggplot(df1, aes(x = ed4)) +
geom_bar(fill = "steelblue") +
labs(x = "Niveau d'éducation (4 classes)", y = "Effectif",
title = "Distribution de l'éducation dans l'échantillon") +
theme_minimal()
```
## Etudier la matrice des corrélations entre les 7 variables quantitatives présentes.
```{r}
num_vars <- c("age","employ","address","income","debtinc","creddebt","othdebt")
df1 <- df1 |>
mutate(
debtinc = as.numeric(debtinc),
creddebt = as.numeric(creddebt),
othdebt = as.numeric(othdebt)
)
cor_mat <- df1 |>
select(all_of(num_vars)) |>
drop_na() |>
cor()
library(ggcorrplot)
ggcorrplot(
cor_mat,
hc.order = TRUE, # ordonne les variables par similarité
lab = TRUE, # affiche les coefficients
lab_size = 3,
colors = c("tomato2", "white", "seagreen3"),
title = "Corrélogramme des variables quantitatives",
ggtheme = theme_minimal()
)
```
## Recoder la variable à expliquer (default) en variable quantitative puis réaliser une première régression logistique incluant toutes les variables explicatives quantitatives.
```{r}
df2 <- df1 |>
mutate(
default_num = case_when(
default == "Yes" ~ 1,
default == "No" ~ 0,
TRUE ~ NA_real_ # conserve les NA existants
)
)
form1 <- default_num ~ age + employ + address + income + debtinc + creddebt + othdebt
mod1 <- glm(form1, data=df2, family=binomial("logit"))
summary(mod1)
```
## Réaliser une deuxième régression logistique incluant aussi la variable educ en classe. Enregistrer aussi les résultats de ce modèle, qui est le modèle le plus complexe que nous appliquerons à ces données.
```{r}
form2 <- update(form1, . ~ . + ed4)
mod2 <- glm(form2, data=df2, family=binomial("logit"))
summary(mod2)
```
## Tester l'ajustement de ce modèle complet, grâce à la commande hoslem.test, où g est le nombre de groupes de niveaux différents de fonction prédictive utilisé pour le test. Varier les valeurs de g pour vérifier la robustesse du résultat.
```{r}
hoslem.test(mod2$y, fitted(mod2), g=4)
hoslem.test(mod2$y, fitted(mod2), g=5)
hoslem.test(mod2$y, fitted(mod2), g=6)
hoslem.test(mod2$y, fitted(mod2), g=7)
hoslem.test(mod2$y, fitted(mod2), g=8)
hoslem.test(mod2$y, fitted(mod2), g=9)
hoslem.test(mod2$y, fitted(mod2), g=10)
```
## Grâce à la commande anova, réaliser un test de rapport de vraisemblance entre les deux modèles ajustés, et conclure sur la significativité de la variable educ.
```{r}
anova(mod1, mod2, test="Chisq")
```
- La p-value = **0.54** → on **ne rejette pas** $H_0$.
- Autrement dit :
> **l’ajout de la variable d’éducation `ed4` n’améliore pas
> significativement la qualité du modèle**.
## Ôter la variable la moins significative du modèle retenu. Vérifier que la P-value du test
de rapport de vraisemblance entre les modèles avec et sans cette
variable est égale ou très proche de la P-value du test bilatéral de
nullité du coefficient associé à la variable.
```{r}
form3 <- update(form1, . ~ . - othdebt)
mod3 <- glm(form3, data=df2, family=binomial("logit"))
summary(mod3)
anova(mod2, mod3, test="Chisq")
```
## Une variable est à nouveau très peu significative. Ajuster un nouveau modèle sans cette variable. Sauvegarder les valeurs prédites par ce modèle.
```{r}
form4 <- update(form3, . ~ . - income)
mod4 <- glm(form4, data=df2, family=binomial("logit"))
summary(mod4)
anova(mod3, mod4, test="Chisq")
```
## Etablir le tableau de contingence des individus bien ou mal classés avec une règle de coupure à 0,5
```{r}
# Probabilités prédites
df2_nona <- df2 |> drop_na(age, employ, address, income, debtinc, creddebt, othdebt, default_num, ed4)
p_hat <- predict(mod4, newdata = df2_nona, type = "response")
df2_nona <- df2_nona |>
mutate(
p_hat = p_hat,
pred_class = if_else(p_hat >= 0.5, 1, 0)
)
table(Predicted = df2_nona$pred_class, Observed = df2_nona$default_num)
mean(df2_nona$pred_class == df2_nona$default_num, na.rm = TRUE)
```
## Etablir la courbe ROC pour le « meilleur » modèle, puis calculer les "probabilités prédites" et étudier leur distribution
```{r}
roc_obj <- roc(df2_nona$default_num, df2_nona$p_hat)
plot(roc_obj, main="Courbe ROC : Modèle logit 2025")
auc(roc_obj)
ggplot(df2_nona, aes(x = p_hat, fill = factor(default_num))) +
geom_histogram(alpha = 0.6, position = "identity", bins = 30) +
labs(title = "Distribution des probabilités prédites par classe réelle",
x = "p̂(default = 1)", fill = "Défaut observé") +
theme_minimal()
```
### Interprétation du graphe
- L’axe des abscisses montre les **probabilités prédites de défaut**
$\hat{p} = P(\text{default}=1|X)$ .
- L’axe des ordonnées montre le **nombre d’individus** dans chaque
intervalle de probabilité.
- La couleur rouge (0) représente les **emprunteurs qui n’ont pas fait
défaut**.
- La couleur bleue (1) représente les **emprunteurs qui ont fait
défaut**.
## Refaire la même modélisation avec un modèle Probit et observer les différences et ressemblances avec la modélisation Logit.
```{r}
mod_probit <- glm(formula(mod4), data=df2, family=binomial("probit"))
summary(mod_probit)
```
## Pour aller plus loin…
Supposant qu'un individu ne remboursant pas son emprunt coûte en moyenne
100000\$, et qu'un individu payant son emprunt rapporte en moyenne
40000\$, on peut calculer (…) qu'il est optimal de n'accorder un prêt
qu'aux individus ayant une probabilité de rembourser estimée à 0,7 ou
plus.
## Règle de décision (probabilité de remboursement \>= 0.7)
```{r}
df2_nona <- df2_nona |> mutate(p_repay = 1 - p_hat, grant = as.numeric(p_repay >= 0.7))
mean(df2_nona$grant, na.rm = TRUE)
```
## Etablir le tableau de contingence des individus bien ou mal classés en considérant comme défaillants potentiels tous les individus ayant moins de 70% de chances de rembourser. Commenter ce tableau.
```{r}
thr_default <- 0.3
table(Predicted = df2_nona$p_hat >= thr_default, Observed = df2_nona$default_num)
tab <- table(Predicted = df2_nona$p_hat >= thr_default,
Observed = df2_nona$default_num)
accuracy <- sum(diag(tab)) / sum(tab)
accuracy
prop.table(tab, 2) # pourcentage par classe observée
```
> En fixant le seuil à 0,3 (c’est-à-dire en considérant comme «
> défaillant potentiel » tout individu ayant moins de 70 % de chances de
> rembourser), le modèle devient **plus prudent** : il classe davantage
> d’individus comme risqués. Le nombre de vrais positifs (défaillants
> correctement détectés) augmente, mais au prix d’une hausse des faux
> positifs (bons payeurs injustement rejetés).
>
> Autrement dit, la règle minimise les pertes dues aux impayés, mais
> réduit le volume de prêts accordés. C’est un **compromis classique
> entre risque de crédit et rentabilité** :\
>
> Plus le seuil est bas, plus la banque protège son portefeuille, mais
> plus elle refuse de bons clients.