---
title: "Calibration, redressement et poids avancés"
---
```{r}
#| label: setup-analyse
#| include: false
source("R/04-initialiser-analyse.R")
suppressPackageStartupMessages(library(gtsummary))
```
::: {.callout-important title="Question de départ"}
Si l'on connaît la structure de la population selon l'âge, le sexe et le territoire, comment utiliser cette information pour améliorer les poids — et comment vérifier que le redressement n'a pas créé une nouvelle source d'instabilité ?
:::
## Objectifs d'apprentissage
À la fin de ce chapitre, vous saurez :
- distinguer poids de base, ajustement de non-réponse et calibration ;
- expliquer ce que post-stratification et raking imposent aux poids ;
- définir clairement le domaine auquel la calibration s'applique ;
- construire des contraintes de population compatibles avec `{survey}` ;
- auditer numériquement l'atteinte des marges ;
- comparer des estimations avant et après redressement ;
- diagnostiquer dispersion, coefficient de variation et taille effective de Kish ;
- traiter le trimming comme une analyse de sensibilité ;
- expliquer le compromis potentiel entre correction de composition et perte de précision.
## Un poids final est le résultat d'une chaîne de décisions
Une écriture schématique est :
$$
w_i^{final}=w_i^{base}\times a_i^{NR}\times g_i,
$$
où le poids de base reflète le mécanisme de sélection, $a_i^{NR}$ un ajustement de non-réponse et $g_i$ un facteur de calibration. Les enquêtes réelles peuvent utiliser des procédures plus complexes, mais cette décomposition aide à comprendre qu'un « poids final » n'est pas une donnée brute.
```{r}
#| label: tab-ch22-weight-summary
tab_poids_init <- bind_rows(
resume_poids(enqueter$poids_base) |> mutate(Type = "Poids de base", .before = 1),
resume_poids(enqueter$poids_final) |> mutate(Type = "Poids final fourni", .before = 1)
) |>
select(Type, Minimum, Médiane, Moyenne, Maximum, `CV des poids`, `N effectif de Kish`) |>
mutate(across(where(is.numeric), ~round(.x, 2)))
knitr::kable(tab_poids_init)
```
::: {.callout-warning title="Ne jamais recalibrer à l'aveugle un poids officiel"}
Si un producteur fournit un poids final documenté, il faut d'abord comprendre ce qu'il incorpore. Recalibrer sans connaître les étapes déjà appliquées peut corriger deux fois la même dimension et rendre l'estimation difficile à interpréter.
:::
## Calibration : quelle information externe ajoute-t-on ?
La calibration utilise des informations auxiliaires connues sur la population. Une variable auxiliaire est particulièrement utile lorsqu'elle est :
- mesurée de façon comparable dans l'enquête et dans la source externe ;
- disponible pour la grande majorité des répondants ;
- liée au mécanisme de sélection ou de non-réponse ;
- et/ou liée aux variables d'intérêt dont on souhaite améliorer l'estimation.
La dernière condition explique pourquoi la calibration est **substantive** : calibrer sur âge et territoire n'a pas le même intérêt selon que l'on étudie emploi, santé, vote ou pratique culturelle.
Dans `enqueter`, les marges sont entièrement synthétiques. Elles servent à apprendre la méthode, pas à reconstruire une population réelle.
## Définir le domaine de calibration
Les contraintes ne peuvent pas être appliquées à une unité pour laquelle l'auxiliaire est absent. Commençons par documenter la perte d'observations.
```{r}
#| label: tab-ch22-completeness
n_total <- nrow(enqueter)
n_cal <- sum(
!is.na(enqueter$sexe_f) &
!is.na(enqueter$age_classe_f) &
!is.na(enqueter$territoire_f)
)
tibble::tibble(
Élément = c("Total", "Sexe manquant", "Âge manquant", "Territoire manquant", "Domaine complet"),
N = c(
n_total,
sum(is.na(enqueter$sexe_f)),
sum(is.na(enqueter$age_classe_f)),
sum(is.na(enqueter$territoire_f)),
n_cal
)
) |>
mutate(`Part du fichier` = fmt_pct(N / n_total, .1)) |>
knitr::kable()
```
```{r}
#| label: ch22-design-cal
design_cal <- subset(
design,
!is.na(sexe_f) & !is.na(age_classe_f) & !is.na(territoire_f)
)
```
::: {.callout-note title="Le domaine de calibration devient une partie de l'estimand"}
Dans cet exemple, les analyses avant/après calibration portent sur les répondants dont les trois auxiliaires sont observés. Si ces personnes diffèrent des autres, le changement de domaine peut lui-même modifier l'estimation. Dans une enquête réelle, le traitement des auxiliaires manquantes doit être documenté : imputation, catégorie explicite, autre jeu d'auxiliaires ou restriction justifiée.
:::
## Post-stratification : une distribution connue
Lorsque l'on connaît une distribution de population pour une variable ou un croisement de variables, `postStratify()` ajuste les poids pour reproduire ces totaux [@survey2026].
```{r}
#| label: ch22-poststrat
pop_sexe <- data.frame(
sexe_f = factor(
c("Homme", "Femme", "Autre / non-binaire"),
levels = levels(enqueter$sexe_f)
),
Freq = c(49000, 49500, 1500)
)
design_post <- survey::postStratify(
design_cal,
~sexe_f,
pop_sexe
)
```
```{r}
#| label: tab-ch22-poststrat-check
prop.table(survey::svytable(~sexe_f, design_post)) |>
as.data.frame() |>
transmute(
`Sexe / genre` = sexe_f,
`Part après post-stratification` = fmt_pct(Freq, .1)
) |>
knitr::kable()
```
Atteindre cette marge ne garantit rien sur diplôme, revenu, santé ou opinions. La post-stratification ne « rend pas l'échantillon représentatif » au sens général ; elle impose une structure précise sur les auxiliaires retenus.
## Raking : plusieurs marges séparées
Lorsque l'on connaît plusieurs distributions marginales mais pas leur croisement complet, le raking ajuste itérativement les poids pour respecter ces marges [@survey2026].
```{r}
#| label: ch22-rake-pop
pop_age <- data.frame(
age_classe_f = factor(
c("18-29", "30-44", "45-59", "60-79"),
levels = levels(enqueter$age_classe_f)
),
Freq = c(18000, 27000, 27000, 28000)
)
pop_territoire <- data.frame(
territoire_f = factor(
c("Île-de-France", "Nord-Ouest", "Nord-Est", "Sud-Ouest", "Sud-Est"),
levels = levels(enqueter$territoire_f)
),
Freq = c(19000, 21000, 18000, 18000, 24000)
)
design_rake <- survey::rake(
design = design_cal,
sample.margins = list(~sexe_f, ~age_classe_f, ~territoire_f),
population.margins = list(pop_sexe, pop_age, pop_territoire)
)
```
## Auditer les marges : le calcul doit être vérifiable
Construisons une table unique : structure avant, structure après, cible et écart résiduel.
```{r}
#| label: tab-ch22-margins
prop_design <- function(des, variable) {
tab <- prop.table(
survey::svytable(stats::as.formula(paste0("~", variable)), des)
)
tibble(modalite = names(tab), proportion = as.numeric(tab))
}
cibles <- bind_rows(
tibble(variable = "Sexe / genre", modalite = as.character(pop_sexe$sexe_f), cible = pop_sexe$Freq / sum(pop_sexe$Freq)),
tibble(variable = "Classe d'âge", modalite = as.character(pop_age$age_classe_f), cible = pop_age$Freq / sum(pop_age$Freq)),
tibble(variable = "Territoire", modalite = as.character(pop_territoire$territoire_f), cible = pop_territoire$Freq / sum(pop_territoire$Freq))
)
avant <- bind_rows(
prop_design(design_cal, "sexe_f") |> mutate(variable = "Sexe / genre"),
prop_design(design_cal, "age_classe_f") |> mutate(variable = "Classe d'âge"),
prop_design(design_cal, "territoire_f") |> mutate(variable = "Territoire")
) |>
rename(avant = proportion)
apres <- bind_rows(
prop_design(design_rake, "sexe_f") |> mutate(variable = "Sexe / genre"),
prop_design(design_rake, "age_classe_f") |> mutate(variable = "Classe d'âge"),
prop_design(design_rake, "territoire_f") |> mutate(variable = "Territoire")
) |>
rename(apres = proportion)
marges_compare <- cibles |>
left_join(avant, by = c("variable", "modalite")) |>
left_join(apres, by = c("variable", "modalite")) |>
mutate(ecart = apres - cible)
marges_compare |>
transmute(
Variable = variable,
Modalité = modalite,
Avant = fmt_pct(avant, .1),
Après = fmt_pct(apres, .1),
Cible = fmt_pct(cible, .1),
`Écart après-cible` = fmt_pp(ecart, .01)
) |>
knitr::kable()
```
```{r}
#| label: ch22-audit-marges
#| include: false
max_ecart_marge <- max(abs(marges_compare$ecart), na.rm = TRUE)
```
L'écart maximal aux marges après raking est ici de `r fmt_pp(max_ecart_marge, .01)`. Ce contrôle est plus informatif que « la fonction n'a pas renvoyé d'erreur ».
::: {.callout-tip title="Contrôle indispensable"}
Après toute calibration, vérifier : niveaux des facteurs, totaux externes, marges atteintes, distribution des poids et estimations substantives. La réussite informatique n'est qu'une étape.
:::
## Ce que le redressement change — et ce qu'il ne change pas
Comparons quelques paramètres sur **le même domaine** avant et après raking.
```{r}
#| label: tab-ch22-estimations
resume_estim <- function(des, nom) {
a <- survey::svymean(
~sport_regulier + confiance_science + satisfaction_vie,
des,
na.rm = TRUE
)
tibble::tibble(
Pondération = nom,
`Sport régulier` = as.numeric(coef(a)["sport_regulier"]),
`Confiance science` = as.numeric(coef(a)["confiance_science"]),
Satisfaction = as.numeric(coef(a)["satisfaction_vie"])
)
}
estim_compare <- bind_rows(
resume_estim(design_cal, "Avant raking"),
resume_estim(design_rake, "Après raking")
)
estim_compare |>
transmute(
Pondération,
`Sport régulier` = fmt_pct(`Sport régulier`, .1),
`Confiance science (1–5)` = fmt_num(`Confiance science`, .2),
`Satisfaction (0–10)` = fmt_num(Satisfaction, .2)
) |>
knitr::kable()
```
Une estimation peut changer beaucoup, peu ou pratiquement pas selon son association avec les auxiliaires corrigés. **Le fait qu'elle bouge n'est pas une preuve qu'elle devient vraie ; le fait qu'elle bouge peu n'est pas une preuve que la calibration était inutile.**
::: {.callout-note title="Lecture sociologique"}
Calibrer sur âge, sexe et territoire revient à imposer une certaine représentation de la structure démographique et territoriale. Si ces dimensions sont liées à une pratique sociale, l'estimation de cette pratique peut se déplacer. Le redressement formalise donc une hypothèse sur les dimensions observées de la sélection ; il ne reconstruit pas les dimensions absentes du modèle de réponse.
:::
## Diagnostiquer la dispersion des poids
Deux jeux de poids peuvent atteindre les mêmes marges tout en ayant des propriétés de variance très différentes.
```{r}
#| label: tab-ch22-diagnostic-poids
w_avant <- weights(design_cal)
w_apres <- weights(design_rake)
diagnostic_poids <- bind_rows(
resume_poids(w_avant) |> mutate(Poids = "Avant raking", .before = 1),
resume_poids(w_apres) |> mutate(Poids = "Après raking", .before = 1)
) |>
select(Poids, Minimum, Médiane, Moyenne, Maximum, `CV des poids`, `N effectif de Kish`) |>
mutate(across(where(is.numeric), ~round(.x, 2)))
knitr::kable(diagnostic_poids)
```
```{r}
#| label: plot-ch22-weights
#| fig-alt: "Deux densités comparent la distribution des poids avant et après raking."
tibble(`Avant raking` = w_avant, `Après raking` = w_apres) |>
pivot_longer(everything(), names_to = "Type", values_to = "Poids") |>
ggplot(aes(x = Poids, colour = Type, fill = Type)) +
geom_density(alpha = .10, linewidth = .8) +
scale_fill_manual(values = palette_enqueter[c(2, 3)]) +
scale_colour_manual(values = palette_enqueter[c(2, 3)]) +
labs(
x = "Valeur du poids",
y = "Densité",
title = "Atteindre les marges peut modifier fortement la dispersion des poids",
subtitle = "L'alignement externe et la précision doivent être diagnostiqués ensemble",
caption = "Données et marges entièrement synthétiques."
) +
theme_enqueter()
```
Le coefficient de variation et la taille effective de Kish donnent une intuition de la concentration des poids. Ils ne remplacent pas les erreurs standards calculées avec le design.
## Trimming : une analyse de sensibilité, pas une finition esthétique
Le trimming borne certains poids extrêmes. Il peut réduire la variance, mais il peut aussi éloigner les marges et modifier l'estimation.
```{r}
#| label: ch22-trim
bornes <- quantile(weights(design_rake), probs = c(.01, .99))
design_trim <- survey::trimWeights(
design_rake,
lower = unname(bornes[1]),
upper = unname(bornes[2]),
strict = TRUE
)
```
Comparons raking et trimming sur les diagnostics de poids :
```{r}
#| label: tab-ch22-trim-poids
bind_rows(
resume_poids(weights(design_rake)) |> mutate(Poids = "Raking", .before = 1),
resume_poids(weights(design_trim)) |> mutate(Poids = "Raking + trimming", .before = 1)
) |>
select(Poids, Minimum, Médiane, Maximum, `CV des poids`, `N effectif de Kish`) |>
mutate(across(where(is.numeric), ~round(.x, 2))) |>
knitr::kable()
```
Puis les estimations :
```{r}
#| label: tab-ch22-sensibilite
sensibilite_cal <- bind_rows(
resume_estim(design_cal, "Avant raking"),
resume_estim(design_rake, "Raking"),
resume_estim(design_trim, "Raking + trimming")
)
sensibilite_cal |>
transmute(
Pondération,
`Sport régulier` = fmt_pct(`Sport régulier`, .1),
`Confiance science` = fmt_num(`Confiance science`, .2),
Satisfaction = fmt_num(Satisfaction, .2)
) |>
knitr::kable()
```
Le trimming doit ensuite être suivi d'un **nouvel audit des marges**. Une baisse du CV des poids ne suffit pas à conclure qu'il s'agit d'une meilleure pondération.
## `calibrate()` : formulation plus générale
`survey::calibrate()` permet de travailler avec des totaux de population et différentes fonctions de calibration [@survey2026]. Lorsque les contraintes deviennent plus complexes, la correspondance entre la matrice du modèle et les totaux externes doit être contrôlée explicitement.
```{r, eval=FALSE}
# Logique générale : inspecter d'abord la matrice.
# X <- model.matrix(
# ~ sexe_f + age_classe_f + territoire_f,
# data = design_cal$variables
# )
# colnames(X)
#
# Les totaux fournis à calibrate() doivent correspondre exactement
# aux colonnes de cette matrice, intercept et références compris.
```
## Trois questions à poser avant de redresser
### 1. Quel problème de sélection voulons-nous réduire ?
Le choix des auxiliaires doit être relié au mécanisme de sélection ou de non-réponse, pas seulement à la disponibilité de marges publiques.
### 2. Quel prix payons-nous en variance ?
Une calibration très agressive peut concentrer le poids sur peu d'observations et réduire l'information effective.
### 3. Quelle conclusion reste hors de portée ?
Même une calibration parfaite sur plusieurs marges ne corrige pas automatiquement :
- une mauvaise mesure ;
- une sous-couverture non expliquée par les auxiliaires ;
- une non-réponse conditionnelle à des variables non observées ;
- une mauvaise définition du champ.
::: {.callout-warning title="Reproduire quelques marges n'est pas être représentatif de tout"}
La calibration améliore la cohérence avec des informations externes précises. Elle n'autorise pas une déclaration générale de « représentativité » indépendante des variables auxiliaires, du mécanisme de réponse et de la question étudiée.
:::
## Exercice de synthèse
### Niveau A — Appliquer
Calibrez le design uniquement sur l'âge. Vérifiez les marges et comparez la prévalence de pratique sportive avant/après.
### Niveau B — Choisir
Comparez une calibration âge seul à une calibration âge + sexe + territoire. Discutez simultanément évolution des estimations, CV des poids et taille effective de Kish.
### Niveau C — Analyser
Rédigez une note méthodologique pour une enquête réelle sur la confiance institutionnelle. Pour chaque auxiliaire proposé, justifiez : comparabilité avec la source externe, lien plausible avec la réponse, lien avec l'indicateur d'intérêt et risque de poids extrêmes. Ajoutez un plan de sensibilité au trimming.
## À retenir
- Un poids final est une construction méthodologique.
- La calibration doit être reliée à une information externe et à un mécanisme plausible de sélection/non-réponse.
- Le domaine de calibration doit être documenté, notamment lorsque des auxiliaires sont manquants.
- Post-stratification et raking doivent être suivis d'un audit numérique des marges.
- Il faut comparer les estimations **et** la dispersion des poids avant/après.
- La taille effective de Kish est un diagnostic de concentration des poids, pas un substitut au calcul de variance du design.
- Le trimming est une analyse de sensibilité biais–variance ; il peut dégrader les marges.
- Reproduire quelques marges ne garantit pas une représentativité générale.
## Pour aller plus loin
Les fonctions `postStratify()`, `rake()`, `calibrate()` et `trimWeights()` sont documentées dans `{survey}` [@survey2026]. Dans une application réelle, la documentation de l'enquête et de la source externe des marges reste aussi importante que le choix de la fonction R.