cassionAnalyse de données

Leçon 2 sur 8

Unité · L'échantillon n'est pas la population

Construire les pondérations à partir de la base

Probabilité de sélection à chaque degré, la pondération de base qu'elle implique, l'ajustement pour non-réponse, et le contrôle qui prouve l'ensemble — des pondérations totalisant 56 428, exactement ce que la base déclare.

PythonR120 minEnquête démographique et de santé (EDS)Enquête par grappes à indicateurs multiples (MICS)Enquête SMART

Une pondération est un nombre avec un seul sens

Une pondération de sondage, c’est le nombre d’unités de la population que représente l’unité tirée.

Cette phrase est toute la leçon. Le reste n’est que l’arithmétique pour y parvenir, et chaque contrôle est une façon de demander si les pondérations veulent toujours dire cela.

La pondération est l’inverse de la probabilité de sélection. Un ménage ayant une chance sur 60 d’être retenu représente 60 ménages ; un ménage ayant une chance sur 18 en représente 18. Rien de plus mystérieux.

Premier degré — probabilité proportionnelle à la taille

Vingt-cinq zones de dénombrement ont été tirées dans chaque strate, avec une probabilité proportionnelle aux ménages que chaque zone contient.

P(area i selected) = n_h * M_i / M_h

où n_h est le nombre de zones tirées dans la strate h (25), M_i les ménages de la zone i, et M_h les ménages de toute la strate.

import pandas as pd

frame = pd.read_csv("household-survey-frame-2025.v1.csv")

stratum_households = frame.groupby("stratum")["households"].sum()
selected = frame[frame["selected"] == True].copy()

selected["p_stage1"] = (
    25 * selected["households"] / selected["stratum"].map(stratum_households)
)
print(selected[["ea_id", "stratum", "households", "p_stage1"]].head())
library(dplyr)

stratum_households <- frame |> summarise(M_h = sum(households), .by = stratum)

selected <- frame |>
  filter(selected) |>
  left_join(stratum_households, by = "stratum") |>
  mutate(p_stage1 = 25 * households / M_h)

Une zone plus grande a plus de chances d’être retenue. C’est tout l’objet du tirage proportionnel à la taille — il place les entretiens là où sont les gens, ce qui évite qu’une enquête dépense un tiers de son budget dans des hameaux.

Second degré — un tirage fixe

Quatorze ménages ont été tirés dans chaque zone retenue, à partir de la liste de cette zone.

P(household selected | area selected) = m / M_i

avec m = 14.

selected["p_stage2"] = 14 / selected["households"]
selected["p_overall"] = selected["p_stage1"] * selected["p_stage2"]
print(selected.groupby("stratum")["p_overall"].describe()[["min", "max"]])
selected <- selected |>
  mutate(p_stage2 = 14 / households,
         p_overall = p_stage1 * p_stage2)

selected |> summarise(min = min(p_overall), max = max(p_overall), .by = stratum)

Les deux probabilités se compensent

Développez le produit et M_i disparaît.

P(household) = (n_h * M_i / M_h) * (m / M_i) = n_h * m / M_h

Tout ménage d’une strate a la même probabilité globale, quelle que soit la taille de sa zone. Le plan est autopondéré à l’intérieur d’une strate.

Exécutez le code et le minimum et le maximum de p_overall sont identiques dans chaque strate, ce qui est l’arithmétique qui le confirme. Ce n’est ni une coïncidence ni une commodité — c’est précisément pourquoi le tirage proportionnel à la taille va de pair avec un tirage fixe, dans les EDS, les MICS et la méthodologie SMART. La mesure de taille qui décide des zones visitées est annulée par celle qui décide du nombre de ménages tirés.

Deux conséquences à retenir. Une pondération qui varie à l’intérieur d’une strate dans un plan proportionnel à la taille signale un problème — en général une base dont la mesure de taille est périmée. Et la pondération de base est une propriété de la strate, non du ménage, si bien qu’elle se calcule en trois nombres.

base_weight = stratum_households / (25 * 14)
print(base_weight.round(1))
stratum_households |> mutate(base_weight = M_h / (25 * 14))
Strate Ménages dans la base Pondération de base
Urbain 21 270 60,8
Rural accessible 28 958 82,7
Rural reculé 6 200 17,7

Un ménage rural reculé en représente dix-huit ; un ménage rural accessible en représente quatre-vingt-trois. Ce rapport est toute la différence entre 32,8 % et 29,1 %.

L’ajustement pour non-réponse

Quatorze ménages ont été tirés par zone. Moins ont été enquêtés — 996 des 1 050 tirés. Les ménages enquêtés doivent porter ceux qui ne l’ont pas été.

survey = pd.read_csv("household-survey-2025.v1.csv")

interviewed = selected.set_index("ea_id")["households_interviewed"]

survey["base_weight"] = survey["stratum"].map(base_weight)
survey["nr_adjustment"] = 14 / survey["ea_id"].map(interviewed)
survey["weight"] = survey["base_weight"] * survey["nr_adjustment"]

print(survey["weight"].describe()[["min", "max"]].round(1))
survey <- survey |>
  left_join(select(selected, ea_id, households_interviewed), by = "ea_id") |>
  left_join(mutate(stratum_households, base_weight = M_h / (25 * 14)), by = "stratum") |>
  mutate(weight = base_weight * 14 / households_interviewed)

range(survey$weight)

Les pondérations vont désormais de 17,7 à 96,5.

Ajustez à l’intérieur de la zone, pas de la strate. Les ménages qui n’ont pas répondu dans une zone donnée ressemblent bien plus aux ménages ayant répondu dans cette zone qu’à la moyenne de la strate. Ajuster au niveau de la strate est plus simple et jette précisément l’information qui rend l’ajustement utile.

L’hypothèse est énoncée et elle n’est pas gratuite — on suppose que les non-répondants ressemblent aux répondants de la même zone. Là où vous avez des raisons d’en douter — des concessions fermées dans un quartier, un incident de sécurité un jour donné — dites-le dans les limites, car aucune pondération n’y remédie.

Le contrôle qui prouve toute la construction

total = survey["weight"].sum()
frame_total = frame["households"].sum()
print(f"weights sum to {total:,.0f}; frame holds {frame_total:,}")
assert abs(total - frame_total) < 1
c(weights = sum(survey$weight), frame = sum(frame$households))

56 428 et 56 428. Les pondérations totalisent exactement le nombre de ménages que la base déclare, ce que « combien d’unités celle-ci représente » doit signifier pour signifier quelque chose.

Faites ce contrôle à chaque fois. Il attrape une strate dont la pondération de base a employé le mauvais dénominateur, un ajustement pour non-réponse appliqué deux fois, et une jointure qui a perdu des zones — trois erreurs autrement invisibles, puisque l’estimation obtenue ressemble toujours à un pourcentage.

Pondérations normalisées, et quand s’en servir

Certains logiciels veulent des pondérations de moyenne un plutôt que totalisant la population.

survey["weight_norm"] = survey["weight"] / survey["weight"].mean()
survey <- survey |> mutate(weight_norm = weight / mean(weight))

Proportions et moyennes sont identiques dans les deux cas — la mise à l’échelle s’annule. Les totaux, non. Une pondération normalisée ne peut pas estimer « combien de ménages sont en insécurité alimentaire », seulement « quelle part ». Gardez la pondération à l’échelle de la population comme référence et dérivez la normalisée là où un outil l’exige.

Pondérations par personne et de sous-échantillon

Deux autres pondérations découlent de la même logique, et les deux sont régulièrement fausses.

Une pondération par personne est la pondération du ménage multipliée par sa taille, car un ménage de huit représente huit fois plus de personnes que de ménages.

people = survey[survey["household_size"].notna()].copy()
people["person_weight"] = people["weight"] * people["household_size"]
print(f"estimated population {people['person_weight'].sum():,.0f}")
people <- survey |>
  filter(!is.na(household_size)) |>
  mutate(person_weight = weight * household_size)

sum(people$person_weight)

Cela donne 333 336 contre 328 127 dans la liste de base — 1,6 % d’écart. Vingt ménages n’ont pas de taille enregistrée et sortent, et une liste de base est elle-même une estimation faite des mois plus tôt. Un écart de cet ordre est normal et mérite d’être énoncé ; un écart de 20 % signale un problème.

Une pondération de sous-échantillon s’applique quand une unité est choisie parmi plusieurs. Un enfant de moins de cinq ans a été mesuré par ménage, si bien qu’un enfant mesuré dans un ménage comptant trois enfants éligibles en représente trois.

children = survey[survey["child_muac_mm"].notna()].copy()
children["child_weight"] = children["weight"] * children["children_under5"]
children <- survey |>
  filter(!is.na(child_muac_mm)) |>
  mutate(child_weight = weight * children_under5)

Sautez cette multiplication et les enfants des grands ménages sont sous-représentés — or les grands ménages sont systématiquement plus pauvres, donc le biais va dans un sens prévisible. Sur cette enquête, la malnutrition aiguë par PB est de 13,3 % parmi les enfants mesurés et de 11,4 % une fois les pondérations enfant appliquées.

Enregistrez les pondérations à côté des données

survey[["household_id", "ea_id", "stratum", "base_weight",
        "nr_adjustment", "weight"]].to_csv("outputs/weights.csv", index=False)
survey |>
  select(household_id, ea_id, stratum, base_weight, households_interviewed, weight) |>
  readr::write_csv(here::here("outputs", "weights.csv"))

Conservez les composantes, pas seulement la pondération finale. Quand quelqu’un demandera dans un an pourquoi un ménage compte pour 96 et un autre pour 18, la réponse est dans les colonnes et non dans votre souvenir d’un script.

La suite

Vous savez désormais produire une estimation de population. L’unité suivante demande ce qu’elle a coûté — combien de ménages exige une précision donnée, et pourquoi la formule de taille d’échantillon des manuels donne à peu près la moitié de ce qu’une enquête en grappes réclame réellement.

Animer cette leçon

La leçon en diaporama, la prose étant reléguée dans les notes du présentateur plutôt que projetée. Produit à partir de cette page, dont il ne peut donc pas s'écarter.

Lancer le diaporamaLire les diapositives

Le PDF ne requiert aucun logiciel et se projette depuis n'importe quel poste. Le fichier PowerPoint est fait pour être modifié : appliquez la charte de votre organisation, retirez une section pour une séance plus courte, ou fusionnez deux leçons en atelier.