---
title: "Données manquantes et imputation multiple"
---
```{r}
#| label: setup-analyse
#| include: false
source("R/04-initialiser-analyse.R")
suppressPackageStartupMessages({
library(mice)
library(mitools)
})
```
::: {.callout-important title="Question de départ"}
Lorsque le revenu manque pour une partie des répondants, vaut-il mieux supprimer ces observations, remplir les cases par une valeur unique, ou représenter explicitement l'incertitude créée par ce que l'on n'a pas observé ?
:::
## Objectifs d'apprentissage
À la fin de ce chapitre, vous saurez :
- distinguer une non-réponse imputable d'une absence structurelle liée au questionnaire ;
- diagnostiquer **qui** manque avant de choisir une méthode ;
- expliquer les hypothèses MCAR, MAR et MNAR sans les transformer en diagnostics automatiques ;
- construire un modèle d'imputation cohérent avec l'analyse substantielle ;
- utiliser réellement le workflow `mice()` → `with()` → `pool()` ;
- comparer cas complets et imputations multiples avec estimations et intervalles ;
- articuler imputation multiple et plan d'enquête ;
- rédiger ce que l'imputation change — et ce qu'elle ne résout pas.
## Toutes les valeurs manquantes ne sont pas à imputer
L'imputation commence par le questionnaire, pas par `mice()`.
Dans une enquête, une cellule vide peut représenter un refus, « ne sait pas », une panne de collecte, une valeur supprimée lors du nettoyage, mais aussi une question **hors univers**. `heures_travail`, par exemple, n'a pas à être inventée pour une personne retraitée uniquement parce que la cellule est `NA`.
::: {.callout-warning title="Règle fondamentale"}
N'imputez jamais mécaniquement une valeur qui n'aurait pas dû exister selon le questionnaire. Une absence structurelle et une non-réponse à une question effectivement posée sont deux objets différents.
:::
## Commencer par mesurer le problème
Dans le jeu pédagogique, le revenu est l'une des variables les plus touchées par la non-réponse. Calculons un audit compact.
```{r}
#| label: tab-ch21-missing
tab_na <- enqueter |>
summarise(
Revenu = mean(is.na(revenu_mensuel)),
Âge = mean(is.na(age)),
Diplôme = mean(is.na(diplome_4)),
Santé = mean(is.na(sante_f)),
`Satisfaction de vie` = mean(is.na(satisfaction_vie))
) |>
tidyr::pivot_longer(everything(), names_to = "Variable", values_to = "Part") |>
mutate(`Part manquante` = scales::percent(Part, accuracy = 0.1, decimal.mark = ",")) |>
select(Variable, `Part manquante`)
knitr::kable(tab_na, align = c("l", "r"))
```
Le taux ne suffit pas. Il faut aussi demander si les personnes sans revenu diffèrent des autres sur des caractéristiques observées.
```{r}
#| label: tab-ch21-profile-missing
enqueter_na <- enqueter |>
mutate(
revenu_manquant = factor(
if_else(is.na(revenu_mensuel), "Revenu manquant", "Revenu observé"),
levels = c("Revenu observé", "Revenu manquant")
)
)
survey::svydesign(
ids = ~psu, strata = ~strate, weights = ~poids_final,
data = enqueter_na, nest = TRUE
) |>
gtsummary::tbl_svysummary(
by = revenu_manquant,
include = c(age, diplome_4, sexe_f, satisfaction_vie, sante_f),
missing = "no",
label = list(
age ~ "Âge",
diplome_4 ~ "Niveau de diplôme",
sexe_f ~ "Sexe / genre",
satisfaction_vie ~ "Satisfaction de vie",
sante_f ~ "État de santé"
)
) |>
gtsummary::bold_labels()
```
::: {.callout-note title="Ce que montre ce tableau"}
Une différence entre cas complets et incomplets n'identifie pas à elle seule un mécanisme MAR ou MNAR. Elle montre en revanche pourquoi supprimer automatiquement les lignes incomplètes peut modifier la composition de l'échantillon analytique.
:::
## MCAR, MAR et MNAR : trois hypothèses
**MCAR — Missing Completely At Random.** La probabilité d'absence ne dépend ni des informations observées ni de la valeur manquante.
**MAR — Missing At Random.** Conditionnellement aux variables observées incluses dans le modèle d'imputation, le mécanisme de manque ne dépend plus de la valeur non observée.
**MNAR — Missing Not At Random.** Même après conditionnement, l'absence dépend encore de la valeur non observée ou d'une information non disponible.
Un refus de revenu peut être associé à l'âge, au diplôme ou à l'emploi — informations observées — mais également à un revenu extrêmement élevé ou faible précisément parce que cette valeur est extrême. Les données observées ne permettent généralement pas de « tester MAR » de façon définitive.
::: {.callout-note title="Principe d'interprétation"}
L'imputation multiple ne prouve pas que MAR est vrai. Elle produit une analyse cohérente **sous un modèle et des hypothèses explicites**. Lorsque MNAR est plausible, une analyse de sensibilité est plus informative qu'une confiance excessive dans un seul scénario.
:::
## Pourquoi une valeur unique est une mauvaise représentation de l'incertitude
Imputer la moyenne est facile :
```{r}
revenu_moyen <- mean(enqueter$revenu_mensuel, na.rm = TRUE)
enqueter_simple <- enqueter |>
mutate(
revenu_imp_simple = if_else(
is.na(revenu_mensuel), revenu_moyen, revenu_mensuel
)
)
```
Mais toutes les valeurs imputées sont alors identiques. La distribution est artificiellement resserrée et l'incertitude liée au manque disparaît du calcul final.
L'imputation multiple adopte une autre logique : elle génère plusieurs valeurs plausibles, ajuste le même modèle dans plusieurs jeux complétés, puis combine **les estimations et leurs variances** [@vanbuuren2011mice].
## Construire un exemple reproductible
Pour que le rendu du livre reste raisonnable tout en exécutant réellement l'analyse, nous utilisons ici cinq imputations et cinq itérations. **Ce n'est pas une recommandation universelle.** Dans un projet de recherche, le nombre d'imputations doit être augmenté lorsque l'incertitude liée au manque est importante et la stabilité Monte-Carlo doit être vérifiée.
Nous concentrons l'imputation sur `revenu_mensuel`. Les autres variables utilisées comme prédicteurs sont requises comme observées dans ce mini-exemple. Cela permet d'isoler clairement ce que fait l'algorithme.
```{r}
#| label: ch21-prep-imp
data_imp <- enqueter |>
filter(
!is.na(age),
!is.na(sexe_f),
!is.na(diplome_4),
!is.na(satisfaction_vie),
!is.na(sante_f),
!is.na(statut_emploi_f)
) |>
mutate(strate_f = factor(strate)) |>
select(
id, satisfaction_vie, revenu_mensuel, age,
sexe_f, diplome_4, sante_f, statut_emploi_f,
territoire_f, mode_collecte_f, strate_f,
poids_final, psu, strate
)
ini <- mice::mice(data_imp, maxit = 0, printFlag = FALSE)
meth <- ini$method
pred <- ini$predictorMatrix
# Une seule cible d'imputation : le revenu.
meth[] <- ""
meth["revenu_mensuel"] <- "pmm"
# Les identifiants et variables de design ne sont pas des cibles.
pred[,] <- 0
pred[
"revenu_mensuel",
c(
"satisfaction_vie", "age", "sexe_f", "diplome_4",
"sante_f", "statut_emploi_f", "territoire_f",
"mode_collecte_f", "strate_f", "poids_final"
)
] <- 1
```
La méthode `pmm` (*predictive mean matching*) impute ici des valeurs de revenu à partir de valeurs effectivement observées chez des profils proches. Cela évite notamment de fabriquer directement des revenus hors du support empirique par une simple prédiction linéaire.
Nous incluons aussi des informations du dispositif (`strate_f`, mode, territoire, poids) parce qu'elles peuvent être liées à la non-réponse et à l'analyse finale. Nous n'introduisons pas ici 72 indicatrices de PSU dans le modèle d'imputation : ce serait disproportionné pour cet exemple pédagogique. Dans une enquête réelle fortement structurée en grappes, la manière de représenter cette structure dans l'imputation doit être discutée explicitement.
## Exécuter les imputations
```{r}
#| label: ch21-mice-run
set.seed(2026)
imp <- mice::mice(
data_imp,
m = 5,
maxit = 5,
method = meth,
predictorMatrix = pred,
printFlag = FALSE
)
resume_imp <- tibble::tibble(
Élément = c("Imputations", "Itérations", "Variables imputées", "Événements consignés"),
Valeur = c(
imp$m,
imp$iteration,
sum(imp$method != ""),
if (is.null(imp$loggedEvents)) 0 else nrow(imp$loggedEvents)
)
)
knitr::kable(resume_imp, align = c("l", "r"))
```
### Vérifier la convergence
**Figure — Trace des chaînes d'imputation pour le revenu**
```{r}
#| label: plot-ch21-trace
#| fig-alt: "Évolution des statistiques de la chaîne d'imputation du revenu au fil des itérations pour cinq imputations."
plot(imp)
```
Une trace n'est pas une certification automatique. On recherche surtout une absence de dérive persistante et un mélange raisonnable des chaînes [@vanbuuren2018fimd].
### Comparer valeurs observées et imputées
```{r}
#| label: plot-ch21-density
#| fig-alt: "Deux densités comparent les revenus observés aux revenus imputés pour les cas initialement manquants."
imp_long <- mice::complete(imp, "long", include = FALSE)
manquant_initial <- is.na(data_imp$revenu_mensuel)
imp_values <- imp_long |>
mutate(
ligne = as.integer(.id),
origine_manquante = manquant_initial[ligne]
) |>
filter(origine_manquante) |>
transmute(revenu_mensuel, Source = "Valeurs imputées")
obs_values <- data_imp |>
filter(!is.na(revenu_mensuel)) |>
transmute(revenu_mensuel, Source = "Valeurs observées")
bind_rows(obs_values, imp_values) |>
ggplot(aes(x = revenu_mensuel, colour = Source, fill = Source)) +
geom_density(alpha = 0.12, linewidth = 0.9) +
scale_colour_manual(values = palette_enqueter[c(1, 3)]) +
scale_fill_manual(values = palette_enqueter[c(1, 3)]) +
labs(
x = "Revenu mensuel (€)", y = "Densité",
title = "L'imputation doit rester plausible sur l'échelle substantielle",
subtitle = "Les valeurs imputées concernent uniquement les revenus initialement manquants",
caption = "Données synthétiques EnquêteR ; cinq imputations par predictive mean matching."
) +
theme_enqueter()
```
L'objectif n'est pas d'obtenir deux courbes identiques. Si les personnes sans revenu observé ont un profil différent, leurs valeurs imputées peuvent aussi avoir une distribution différente. En revanche, une masse de valeurs impossibles ou une distribution totalement étrangère aux données observées doit conduire à revoir le modèle.
## Analyser puis combiner : les règles de Rubin
Ajustons dans chaque jeu imputé le même modèle de satisfaction.
```{r}
fit_imp <- with(
imp,
lm(
satisfaction_vie ~ I(revenu_mensuel / 1000) + age +
sexe_f + diplome_4 + sante_f
)
)
res_pool <- mice::pool(fit_imp)
pool_table <- broom::tidy(res_pool, conf.int = TRUE)
pool_focus <- pool_table |>
dplyr::filter(
term %in% c(
"I(revenu_mensuel/1000)",
"age",
"diplome_4Supérieur long"
)
) |>
dplyr::mutate(
Terme = dplyr::recode(
term,
"I(revenu_mensuel/1000)" = "Revenu (+1 000 €)",
"age" = "Âge (+1 an)",
"diplome_4Supérieur long" = "Supérieur long vs inférieur au bac"
),
Estimation = fmt_num(estimate, .001),
`IC 95 %` = paste0("[", fmt_num(conf.low, .001), " ; ", fmt_num(conf.high, .001), "]"),
`Information manquante` = fmt_pct(fmi, .1)
) |>
dplyr::select(Terme, Estimation, `IC 95 %`, `Information manquante`)
knitr::kable(pool_focus, align = c("l", "r", "c", "r"))
```
`pool()` combine la variabilité moyenne **à l'intérieur** des imputations et la variabilité **entre** imputations. La colonne « information manquante » (`fmi`) indique, pour chaque paramètre, quelle part de l'incertitude est attribuable au manque sous le modèle d'imputation. Les cinq jeux complétés ne sont donc jamais traités comme cinq fois plus de personnes.
::: {.callout-warning title="Erreur très fréquente"}
Concaténer les jeux imputés puis ajuster un seul modèle produit une pseudo-taille d'échantillon artificielle. Les imputations représentent plusieurs versions plausibles des **mêmes observations**, pas de nouveaux répondants.
:::
## Cas complets versus imputations multiples
La comparaison doit porter sur des estimations, pas sur le nombre d'étoiles de significativité.
```{r}
fit_cc <- lm(
satisfaction_vie ~ I(revenu_mensuel / 1000) + age +
sexe_f + diplome_4 + sante_f,
data = data_imp
)
coef_cc <- coef(summary(fit_cc))["I(revenu_mensuel/1000)", ]
ci_cc <- confint(fit_cc)["I(revenu_mensuel/1000)", ]
ligne_mi <- pool_table |>
filter(term == "I(revenu_mensuel/1000)")
comparaison_imp <- tibble(
Analyse = c("Cas complets", "Imputation multiple"),
estimation = c(unname(coef_cc["Estimate"]), ligne_mi$estimate),
bas = c(ci_cc[1], ligne_mi$conf.low),
haut = c(ci_cc[2], ligne_mi$conf.high)
)
```
```{r}
#| label: plot-ch21-compare
#| fig-alt: "Deux estimations avec intervalles de confiance comparent l'effet d'une hausse de mille euros de revenu sur la satisfaction selon deux traitements des données manquantes."
ggplot(comparaison_imp, aes(x = estimation, y = forcats::fct_rev(Analyse))) +
geom_vline(xintercept = 0, linetype = 2, colour = couleurs_enqueter[["gris"]]) +
geom_errorbarh(aes(xmin = bas, xmax = haut), height = 0.12, linewidth = 0.8) +
geom_point(size = 3, colour = couleurs_enqueter[["principal"]]) +
labs(
x = "Variation de satisfaction associée à +1 000 € de revenu",
y = NULL,
title = "L'imputation est une analyse de sensibilité, pas une machine à créer de la significativité",
subtitle = "Modèle ajusté sur âge, sexe, diplôme et santé",
caption = "Données synthétiques EnquêteR."
) +
theme_enqueter()
```
::: {.callout-note title="Lecture du résultat"}
Si les deux estimations restent proches, la conclusion substantielle est relativement robuste à ce traitement des revenus manquants **sous le modèle d'imputation retenu**. Un écart marqué demanderait au contraire de comprendre quels profils ont été réintroduits par l'imputation et pourquoi.
:::
## Articuler imputation multiple et plan d'enquête
L'exemple précédent isolait la logique de `{mice}` avec `lm()`. Dans une enquête complexe, l'analyse finale doit également utiliser les poids, strates et grappes.
Une manière reproductible consiste à transformer les jeux complétés en `imputationList`, à construire un design identique dans chaque jeu, puis à combiner les modèles survey avec `MIcombine()`.
```{r}
#| label: ch21-survey-mi
imp_list <- mitools::imputationList(
lapply(seq_len(imp$m), function(i) mice::complete(imp, action = i))
)
designs_imp <- survey::svydesign(
ids = ~psu,
strata = ~strate,
weights = ~poids_final,
data = imp_list,
nest = TRUE
)
mods_svy_imp <- with(
designs_imp,
survey::svyglm(
satisfaction_vie ~ I(revenu_mensuel / 1000) + age +
sexe_f + diplome_4 + sante_f
)
)
design_cc_imp <- survey::svydesign(
ids = ~psu,
strata = ~strate,
weights = ~poids_final,
data = data_imp,
nest = TRUE
)
pool_svy <- mitools::MIcombine(
mods_svy_imp,
df.complete = survey::degf(design_cc_imp)
)
coef_svy_mi <- pool_svy$coefficients
se_svy_mi <- sqrt(diag(pool_svy$variance))
terme_revenu <- grep("revenu_mensuel", names(coef_svy_mi), value = TRUE)[1]
crit_svy_mi <- stats::qt(.975, df = pool_svy$df[terme_revenu])
resume_svy_mi <- tibble::tibble(
Terme = "Revenu (+1 000 €)",
Estimation = unname(coef_svy_mi[terme_revenu]),
`Erreur standard` = unname(se_svy_mi[terme_revenu]),
bas = Estimation - crit_svy_mi * `Erreur standard`,
haut = Estimation + crit_svy_mi * `Erreur standard`,
`Information manquante` = unname(pool_svy$missinfo[terme_revenu])
) |>
dplyr::transmute(
Terme,
Estimation = fmt_num(Estimation, .001),
`Erreur standard` = fmt_num(`Erreur standard`, .001),
`IC 95 %` = paste0("[", fmt_num(bas, .001), " ; ", fmt_num(haut, .001), "]"),
`Information manquante` = fmt_pct(`Information manquante`, .1)
)
knitr::kable(resume_svy_mi, align = c("l", "r", "r", "c", "r"))
```
Cette étape combine deux sources d'incertitude : celle créée par le **plan d'enquête** et celle créée par les **données manquantes**. `MIcombine()` reçoit ici les degrés de liberté du plan complet, ce qui est préférable à une approximation normale automatique dans une application survey. Poids et variables auxiliaires ne sont donc pas une décoration ajoutée après coup : ils font partie de la définition de l'analyse.
## Lecture sociologique : les absents font partie du problème de recherche
Dans les sciences sociales, la non-réponse n'est pas seulement une nuisance informatique. Refuser de déclarer son revenu, ne pas connaître précisément ses ressources ou interrompre un questionnaire peut être socialement structuré.
Pour une analyse réelle, on demanderait donc :
- la non-réponse au revenu varie-t-elle selon l'âge, la position sociale ou la situation d'emploi ?
- les variables utilisées dans le modèle d'imputation capturent-elles une partie de cette structuration ?
- les résultats substantiels changent-ils lorsque l'on modifie le modèle d'imputation ?
::: {.callout-warning title="Ce que nous ne pouvons pas conclure"}
Une stabilité entre cas complets et imputations multiples ne démontre ni que les données sont MAR, ni que le biais de non-réponse a disparu. Elle montre seulement que, sous les scénarios analysés, la conclusion étudiée change peu.
:::
## Mini-exercice
Parmi ces absences, lesquelles sont candidates à une imputation ?
1. `revenu_mensuel = NA` après un refus ;
2. `heures_travail = NA` pour une personne retraitée ;
3. `satisfaction_vie = NA` après non-réponse ;
4. `teletravail_jours = NA` pour une personne sans emploi.
::: {.callout-caution collapse="true" title="Correction"}
Les cas 1 et 3 peuvent être candidats à une imputation si le modèle et les hypothèses sont justifiés. Les cas 2 et 4 sont des absences structurelles : il serait incohérent de fabriquer une valeur pour une question hors univers.
:::
## Exercice de synthèse
### Niveau A — Appliquer
Reproduisez cinq imputations du revenu. Comparez graphiquement distribution observée et imputée, puis estimez le même modèle en cas complets et après imputation.
### Niveau B — Choisir
Un collègue propose de remplacer chaque revenu manquant par la médiane de son niveau de diplôme. Expliquez pourquoi cette solution ne représente pas correctement l'incertitude et proposez un modèle d'imputation plus cohérent.
### Niveau C — Analyser
Modifiez la matrice de prédicteurs de l'imputation. Comparez les estimations poolées du revenu et rédigez un paragraphe distinguant : résultat stable, hypothèse MAR, sensibilité au modèle et risque MNAR.
## À retenir
- Une cellule `NA` n'est pas automatiquement imputable.
- Il faut analyser **qui manque** avant de remplir les cases.
- MCAR, MAR et MNAR sont des hypothèses sur le mécanisme de manque.
- L'imputation multiple combine des estimations et des variances, pas des pseudo-observations supplémentaires.
- Les diagnostics portent sur convergence, plausibilité et sensibilité.
- Dans une enquête complexe, imputation et design doivent être articulés explicitement.
- Comparer cas complets et imputations multiples est une analyse de robustesse, pas un concours de p-values.
## Pour aller plus loin
La référence logicielle centrale pour `{mice}` est van Buuren et Groothuis-Oudshoorn [@vanbuuren2011mice]. Pour les diagnostics, la sélection de variables et les analyses de sensibilité, voir van Buuren [@vanbuuren2018fimd].