9  Échantillonnage, poids et objet de design

ImportantQuestion de départ

Dans une enquête où certaines personnes ont eu plus de chances d’être sélectionnées que d’autres, une ligne doit-elle nécessairement compter exactement autant qu’une autre ?

9.1 Objectifs d’apprentissage

À la fin de ce chapitre, vous saurez :

  • expliquer la différence entre échantillon observé et population cible ;
  • interpréter intuitivement une probabilité d’inclusion et un poids de sondage ;
  • reconnaître le rôle de la stratification et des grappes ;
  • distinguer poids de base, ajustement de non-réponse et calibration ;
  • créer un objet survey.design avec {survey} ;
  • comparer une statistique brute et une statistique pondérée ;
  • comprendre pourquoi pondérer une moyenne ne suffit pas toujours pour obtenir une erreur standard correcte ;
  • conserver ensemble données et informations de plan pour les analyses suivantes.

9.2 Pourquoi ce chapitre arrive avant les statistiques descriptives

Dans beaucoup de cours de R, les poids apparaissent à la fin, comme une option supplémentaire. Dans un livre consacré spécifiquement aux données d’enquête, ce serait trompeur. Le plan de sondage influence non seulement les estimations ponctuelles, mais également leur incertitude (Lumley 2010; Lumley et al. 2026).

Nous devons donc définir l’objet d’enquête avant de produire systématiquement des pourcentages, intervalles de confiance ou tests.

9.3 Échantillon et population

Supposons une population de 100 000 adultes. Une enquête interroge 2 000 personnes. Les 2 000 lignes décrivent d’abord les personnes observées. Pour utiliser ces données afin d’estimer une caractéristique de la population, il faut comprendre comment ces personnes sont entrées dans l’échantillon.

Si chaque adulte avait exactement la même probabilité d’être sélectionné, la situation est relativement simple. Mais de nombreuses enquêtes utilisent :

  • des probabilités différentes selon les groupes ;
  • une stratification ;
  • plusieurs degrés de tirage ;
  • des grappes géographiques ;
  • des ajustements de non-réponse ;
  • une calibration sur des marges de population.

9.4 Probabilité d’inclusion et poids

Si une unité a une probabilité d’inclusion \(\pi_i\), son poids de base est intuitivement :

\[ w_i = \frac{1}{\pi_i}. \]

Une personne sélectionnée avec une probabilité de 1/100 porte un poids de base 100 ; une personne sélectionnée avec une probabilité de 1/50 porte un poids 50.

Cela ne signifie pas que la première « vaut deux fois plus humainement ». Le poids représente sa contribution statistique dans le mécanisme d’estimation.

9.4.1 Poids normalisés

Dans le jeu pédagogique, poids_final a été normalisé autour de 1. Il est utile pour illustrer les différences relatives de pondération, mais sa somme n’est pas conçue pour représenter la taille réelle d’une population française.

Afficher le code R
resume_poids <- tibble::tibble(
  Minimum = min(enqueter$poids_final),
  Q1 = unname(quantile(enqueter$poids_final, .25)),
  Médiane = median(enqueter$poids_final),
  Moyenne = mean(enqueter$poids_final),
  Q3 = unname(quantile(enqueter$poids_final, .75)),
  Maximum = max(enqueter$poids_final)
) |>
  dplyr::mutate(dplyr::across(everything(), ~round(.x, 3)))

knitr::kable(resume_poids, align = rep("r", 6))
Minimum Q1 Médiane Moyenne Q3 Maximum
0.5 0.797 0.949 1 1.17 2.176

La moyenne est très proche de 1, avec des poids qui varient approximativement de 0,50 à 2,18.

NotePoids normalisé ou poids d’extrapolation

Deux jeux de poids peuvent produire les mêmes proportions pondérées tout en ayant des sommes différentes. Il faut consulter la documentation pour savoir si un poids est calibré pour totaliser une population, un échantillon, ou une autre constante.

9.5 Stratification

Une population peut être divisée en strates avant le tirage : par exemple grandes zones, catégories de communes ou classes définies pour assurer une représentation suffisante.

Dans EnquêteR, le plan pédagogique comporte six strates et plusieurs unités primaires d’échantillonnage (PSU) dans chacune d’elles. Une table compacte permet de voir cette architecture sans imprimer deux sorties séparées.

Afficher le code R
structure_plan <- enqueter |>
  dplyr::group_by(strate) |>
  dplyr::summarise(
    `Répondants` = dplyr::n(),
    `PSU distinctes` = dplyr::n_distinct(psu),
    .groups = "drop"
  )

knitr::kable(structure_plan, align = c("c", "r", "r"))
strate Répondants PSU distinctes
1 595 12
2 747 14
3 736 14
4 588 12
5 542 12
6 384 8

La stratification peut améliorer la précision lorsqu’elle est bien conçue. Elle doit néanmoins être déclarée au logiciel : sinon, le calcul de variance suppose implicitement un autre mécanisme de sélection.

9.6 Grappes et PSU

Dans un sondage en plusieurs degrés, on peut d’abord tirer des zones, établissements, communes ou autres unités primaires d’échantillonnage (Primary Sampling Units, PSU), puis sélectionner des individus à l’intérieur.

Les personnes d’une même grappe peuvent se ressembler davantage que deux personnes tirées indépendamment dans toute la population. Cette dépendance affecte l’incertitude. Ici, les 72 couples strate–PSU laissent 66 degrés de liberté de plan (PSU − strates) pour une partie de l’inférence. Ce nombre, et pas seulement les 3 592 lignes du fichier, intervient dans certains tests et intervalles.

9.7 Pourquoi weighted.mean() ne suffit pas

Pour une moyenne pondérée, on pourrait écrire :

Afficher le code R
weighted.mean(
  enqueter$satisfaction_vie,
  w = enqueter$poids_final,
  na.rm = TRUE
)
[1] 6.811065

Cette commande peut donner une estimation ponctuelle utile. Mais elle ne sait rien de strate ni de psu. Elle ne suffit donc pas pour calculer une variance ou un intervalle de confiance tenant compte du plan complexe.

C’est précisément le rôle du package {survey} (Lumley et al. 2026).

9.8 Créer l’objet de design

Le code suivant construit explicitement l’objet afin de comprendre ses composantes. Pour reprendre directement les exemples des chapitres suivants dans une nouvelle session R, le projet fournit aussi un script d’initialisation :

source("R/04-initialiser-analyse.R")

Ce script charge enqueter puis crée design avec exactement les informations de plan utilisées ci-dessous. La syntaxe essentielle est :

design <- survey::svydesign(
  ids = ~psu,
  strata = ~strate,
  weights = ~poids_final,
  data = enqueter,
  nest = TRUE
)

Dans le rendu du livre, cet objet a déjà été créé par le chunk d’initialisation invisible :

Afficher le code R
resume_design <- tibble::tibble(
  Composante = c(
    "Observations", "Strates", "PSU distinctes", "Poids final (min–max)"
  ),
  Valeur = c(
    format(nrow(enqueter), big.mark = " "),
    dplyr::n_distinct(enqueter$strate),
    dplyr::n_distinct(paste(enqueter$strate, enqueter$psu, sep = "-")),
    paste0(
      fmt_num(min(enqueter$poids_final, na.rm = TRUE), .01),
      " – ",
      fmt_num(max(enqueter$poids_final, na.rm = TRUE), .01)
    )
  )
)
knitr::kable(resume_design, align = c("l", "r"))
Composante Valeur
Observations 3 592
Strates 6
PSU distinctes 72
Poids final (min–max) 0,50 – 2,18

Les principaux arguments sont :

  • ids : identifiants de grappes / PSU ;
  • strata : strates ;
  • weights : poids ;
  • data : tableau contenant les variables ;
  • nest = TRUE : indique ici que les identifiants de PSU doivent être considérés comme imbriqués dans les strates.

La documentation de svydesign() définit cet objet comme le conteneur des données et métadonnées nécessaires à l’analyse d’échantillons complexes (Lumley et al. 2026).

9.8.1 Un plan sans grappe

Pour une enquête pondérée mais sans information de stratification ou de grappe :

design_simple <- svydesign(
  ids = ~1,
  weights = ~poids_final,
  data = enqueter
)

Il ne faut pas choisir ~1 par simplicité si un véritable plan plus complexe est connu.

9.9 Première comparaison brute / pondérée

Avant de multiplier les tableaux, comparons deux statistiques simples. Le tableau juxtapose le résumé des répondants et l’estimation obtenue avec le plan d’enquête.

Afficher le code R
est_sat <- survey::svymean(~satisfaction_vie, design, na.rm = TRUE)
est_emp <- survey::svymean(~en_emploi, design, na.rm = TRUE)

premiere_comparaison <- tibble::tibble(
  Indicateur = c("Satisfaction de vie (moyenne)", "En emploi (proportion)"),
  Brut = c(
    mean(enqueter$satisfaction_vie, na.rm = TRUE),
    mean(enqueter$en_emploi, na.rm = TRUE)
  ),
  Pondéré = c(as.numeric(coef(est_sat)), as.numeric(coef(est_emp))),
  `Erreur standard survey` = c(as.numeric(SE(est_sat)), as.numeric(SE(est_emp)))
) |>
  dplyr::mutate(
    Brut = c(fmt_num(Brut[1], .01), fmt_pct(Brut[2], .1)),
    Pondéré = c(fmt_num(Pondéré[1], .01), fmt_pct(Pondéré[2], .1)),
    `Erreur standard survey` = c(
      fmt_num(`Erreur standard survey`[1], .001),
      fmt_pct(`Erreur standard survey`[2], .1)
    )
  )

knitr::kable(premiere_comparaison, align = c("l", "r", "r", "r"))
Indicateur Brut Pondéré Erreur standard survey
Satisfaction de vie (moyenne) 6,82 6,81 0,026
En emploi (proportion) 60,3% 59,3% 0,8%

La pondération peut déplacer peu le point estimé tout en modifiant son incertitude. Une petite différence brut–pondéré sur un indicateur ne permet donc pas de conclure que le plan est négligeable.

9.9.1 Voir simultanément brut et pondéré

Le même jeu de variables peut être résumé avec un tableau brut et un tableau survey. Cela rend visible la différence d’objet statistique.

Afficher le code R
tbl_brut <- enqueter |>
  gtsummary::tbl_summary(
    include = c(sexe_f, diplome_4, age_classe_f),
    missing = "no",
    label = list(
      sexe_f ~ "Sexe / genre",
      diplome_4 ~ "Niveau de diplôme",
      age_classe_f ~ "Classe d'âge"
    )
  ) |>
  gtsummary::bold_labels()

tbl_pond <- design |>
  gtsummary::tbl_svysummary(
    include = c(sexe_f, diplome_4, age_classe_f),
    missing = "no",
    label = list(
      sexe_f ~ "Sexe / genre",
      diplome_4 ~ "Niveau de diplôme",
      age_classe_f ~ "Classe d'âge"
    )
  ) |>
  gtsummary::bold_labels()

gtsummary::tbl_merge(
  list(tbl_brut, tbl_pond),
  tab_spanner = c("**Échantillon brut**", "**Estimation pondérée**")
)
Caractéristique
Échantillon brut
Estimation pondérée
N = 3 5921 N = 3 5931
Sexe / genre

    Homme 1 692 (48%) 1 737 (49%)
    Femme 1 814 (51%) 1 771 (50%)
    Autre / non-binaire 54 (1,5%) 52 (1,4%)
Niveau de diplôme

    Inférieur au bac 1 601 (45%) 1 623 (46%)
    Bac 703 (20%) 694 (20%)
    Bac+2 548 (16%) 543 (15%)
    Supérieur long 676 (19%) 668 (19%)
Classe d'âge

    18-29 537 (15%) 563 (16%)
    30-44 1 131 (32%) 1 093 (31%)
    45-59 1 212 (34%) 1 175 (33%)
    60-79 688 (19%) 737 (21%)
1 n (%)
NoteCe que montre la comparaison

La pondération ne transforme pas un échantillon en population réelle observée. Elle change la contribution des lignes afin d’estimer des paramètres sous le dispositif documenté. Le tableau brut reste utile pour connaître les répondants effectivement disponibles ; le tableau pondéré vise un autre objet.

9.10 Deux usages complémentaires des tableaux survey

Pour une vérification rapide, svytable() reste très pratique : prop.table(svytable(~diplome_4, design)) renvoie directement la composition pondérée par diplôme. Pour les tableaux destinés au lecteur, nous privilégierons ensuite svymean() pour les estimations et gtsummary::tbl_svysummary() pour la mise en forme (Sjoberg et al. 2021).

Cette distinction évite de confondre objet de calcul et objet de communication : le premier doit être facilement inspectable, le second doit rendre le dénominateur, les unités et l’incertitude compréhensibles.

9.11 Sous-populations : ne pas filtrer naïvement le design

Supposons que l’on souhaite analyser les personnes de 60 ans et plus. Avec un objet survey, la méthode recommandée est généralement de définir un domaine :

Afficher le code R
design_60plus <- subset(design, age >= 60)
svymean(~satisfaction_vie, design_60plus, na.rm = TRUE)
                   mean     SE
satisfaction_vie 6.5262 0.0652

Cette opération conserve l’information du design nécessaire au calcul de variance dans le domaine. La logique des sous-populations sera reprise plus loin.

9.12 Poids de base, non-réponse et calibration : panorama

Un « poids final » peut résulter de plusieurs étapes.

9.12.1 Poids de base

Inverse de la probabilité d’inclusion issue du plan de tirage.

9.12.2 Ajustement de non-réponse

Certains groupes répondent moins que d’autres. Le poids peut être ajusté pour compenser partiellement ce déséquilibre, selon une méthode documentée.

9.12.3 Calibration / redressement

Les poids sont modifiés afin que certaines distributions pondérées concordent avec des totaux externes connus : âge, sexe, région, diplôme, etc.

Le chapitre 22 abordera ces techniques plus en détail. Ici, l’objectif est de comprendre que poids_final n’est pas un coefficient mystérieux que l’on applique sans lire sa documentation.

AvertissementUn poids ne répare pas tout

La pondération ne corrige pas automatiquement les erreurs de mesure, les questions mal formulées, une couverture déficiente sur des dimensions non observées ou toute forme de biais de non-réponse. Elle fait partie d’un dispositif d’estimation, pas d’une garantie générale de représentativité.

9.13 Effet de plan et taille effective : deux diagnostics à ne pas confondre

Deux idées différentes sont souvent mélangées.

La première concerne la dispersion des poids. Si quelques observations portent beaucoup plus de poids que les autres, l’information disponible est moins équilibrée. La taille effective de Kish résume uniquement cette perte liée aux poids :

\[ n_{eff,w} = \frac{(\sum_i w_i)^2}{\sum_i w_i^2}. \]

La seconde concerne le plan complet : stratification et corrélation à l’intérieur des PSU peuvent à leur tour augmenter ou réduire la variance. L’effet de plan (DEFF) est donc propre à une estimation donnée.

Comme poids_final est normalisé et n’est pas l’inverse brut d’une probabilité d’inclusion, nous demandons à {survey} le DEFF avec remise (deff = "replace"). La documentation du package recommande cette référence lorsque les poids ont été rescalés (Lumley et al. 2026).

Afficher le code R
n_eff_poids <- kish_neff(enqueter$poids_final)

sat_deff <- survey::svymean(
  ~satisfaction_vie, design, na.rm = TRUE, deff = "replace"
)
sport_deff <- survey::svymean(
  ~sport_regulier, design, na.rm = TRUE, deff = "replace"
)

diag_plan <- tibble::tibble(
  Diagnostic = c(
    "N observé",
    "N effectif — dispersion des poids seulement",
    "DEFF — satisfaction de vie",
    "DEFF — sport régulier"
  ),
  Valeur = c(
    fmt_num(nrow(enqueter), 1),
    fmt_num(n_eff_poids, 1),
    fmt_num(as.numeric(survey::deff(sat_deff)), .01),
    fmt_num(as.numeric(survey::deff(sport_deff)), .01)
  )
)

knitr::kable(diag_plan, align = c("l", "r"))
Diagnostic Valeur
N observé 3 592
N effectif — dispersion des poids seulement 3 350
DEFF — satisfaction de vie 0,95
DEFF — sport régulier 1,07

Ici, la dispersion des poids seule ramène la taille effective d’environ 3 592 à 3 350 observations. Ce nombre ne tient compte ni des strates ni des grappes : il ne doit donc pas être présenté comme « la » taille effective universelle de l’enquête.

NotePourquoi garder les deux diagnostics ?

La taille effective de Kish est une propriété des poids. Le DEFF de svymean() est une propriété de l’estimation sous le plan. Deux variables peuvent avoir le même jeu de poids mais des effets de plan différents parce que leur corrélation intra-grappe n’est pas la même.

Le chapitre 14 reviendra sur la variance, les degrés de liberté et l’effet de plan.

9.14 Vérifier les poids avant de les utiliser

Plutôt qu’une longue sortie, retenons cinq percentiles et la distribution graphique.

Afficher le code R
quantiles_poids <- tibble::tibble(
  Percentile = c("1 %", "5 %", "50 %", "95 %", "99 %"),
  Poids = as.numeric(
    quantile(enqueter$poids_final, probs = c(.01, .05, .5, .95, .99))
  )
) |>
  dplyr::mutate(Poids = round(Poids, 3))

knitr::kable(quantiles_poids, align = c("l", "r"))
Percentile Poids
1 % 0.565
5 % 0.641
50 % 0.949
95 % 1.507
99 % 1.716
Afficher le code R
ggplot(enqueter, aes(x = poids_final)) +
  geom_histogram(
    bins = 30,
    fill = couleurs_enqueter[["principal"]],
    colour = "white",
    linewidth = 0.2
  ) +
  geom_vline(xintercept = 1, linetype = 2, colour = couleurs_enqueter[["accent"]]) +
  labs(
    x = "Poids final", y = "Nombre d'observations",
    title = "Les poids ne sont pas tous égaux à 1",
    subtitle = "La ligne verticale repère le poids 1",
    caption = "Données synthétiques EnquêteR."
  ) +
  theme_enqueter()

Histogramme des poids finaux, concentrés autour de un mais avec une dispersion visible.

Des poids très dispersés peuvent augmenter la variance. Leur diagnostic est une étape méthodologique, pas uniquement technique.

9.15 Conserver l’objet de design dans le workflow

L’objet design devient la porte d’entrée des chapitres 10 à 19 :

enqueter_clean.rds
        ↓
   svydesign()
        ↓
     design
   ↙    ↓     ↘
svymean svytable svyglm ...

Cette architecture réduit le risque d’appliquer les poids à certaines analyses mais de les oublier dans d’autres.

9.16 Mini-exercices

9.16.1 Mini-exercice 1 — Probabilité et poids

Deux individus ont des probabilités d’inclusion respectives de 0,02 et 0,01. Quels sont leurs poids de base ?

Les poids sont 1/0,02 = 50 et 1/0,01 = 100. Le second individu représente davantage d’unités de population dans l’estimation parce qu’il avait une probabilité d’inclusion plus faible.

9.16.2 Mini-exercice 2 — Point estimé versus variance

Pourquoi une moyenne calculée avec weighted.mean() n’est-elle pas équivalente à une analyse complète avec svymean() lorsque l’enquête est stratifiée et en grappes ?

weighted.mean() utilise les poids pour le point estimé mais ne connaît pas la structure des strates et grappes. svymean() appliqué à un survey.design utilise les informations de design pour l’estimation de variance et l’erreur standard.

9.17 Exercice de synthèse

9.17.1 Niveau A — Appliquer

Créez l’objet design du livre et calculez :

  1. la moyenne pondérée de satisfaction_vie ;
  2. la proportion pondérée de personnes en emploi ;
  3. la distribution pondérée de diplome_4.

9.17.2 Niveau B — Choisir

Vous recevez un fichier avec une colonne poids, mais aucune information sur la façon dont elle a été construite. Quelles questions posez-vous avant de l’utiliser ?

9.17.3 Niveau C — Analyser

Comparez, pour sexe, diplome_4 et en_emploi, les distributions brutes et pondérées. Identifiez les catégories les plus modifiées par la pondération et rédigez une courte interprétation qui n’assimile pas automatiquement écart de pondération et « biais corrigé ».

9.17.4 Correction détaillée

Il faut au minimum savoir : population cible ; plan de sondage ; probabilité d’inclusion ; éventuelle normalisation ; ajustement de non-réponse ; variables de calibration ; présence de strates et grappes ; objectif du poids ; éventuels poids de réplication. Sans ces informations, la colonne ne peut pas être interprétée correctement.

9.18 À retenir

  • Une ligne d’enquête n’a pas nécessairement la même probabilité d’inclusion qu’une autre.
  • Le poids de base est lié à l’inverse de la probabilité d’inclusion.
  • Stratification et grappes concernent aussi l’estimation de l’incertitude.
  • weighted.mean() ne remplace pas un objet de plan complexe.
  • {survey} rassemble données, poids, strates et PSU dans un survey.design (Lumley et al. 2026).
  • Les analyses descriptives et modèles peuvent ensuite utiliser ce même objet.
  • Un poids final peut combiner plusieurs corrections ; sa construction doit être documentée.
  • Pondération et représentativité ne sont pas synonymes.
  • Les poids doivent eux-mêmes être diagnostiqués.

9.19 Pour aller plus loin

L’ouvrage de Lumley fournit une présentation complète de l’analyse de sondages complexes avec R (Lumley 2010). Le package {survey} implémente les principales statistiques, tests, modèles et méthodes de calibration pour ce type de données (Lumley et al. 2026).

Nous pouvons maintenant décrire les variables. À partir du chapitre suivant, nous distinguerons systématiquement description brute de l’échantillon et estimation pondérée associée au plan d’enquête.