cassionAnalyse de données

Leçon 5 sur 8

Unité · Estimer correctement

Déclarez le plan une seule fois

Un objet de plan, et toute estimation ultérieure hérite des pondérations, des strates et des grappes. Le paquet survey de R, l'arithmétique équivalente en Python, et les quatre erreurs qui produisent en silence une simple moyenne.

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

Cessez de porter le plan à la main

Tout ce qui précède a été calculé explicitement, et c’était voulu — il faut savoir ce qu’est l’arithmétique. À partir d’ici, le plan doit être déclaré une fois et hérité, car l’alternative consiste à penser à appliquer pondérations, strates et grappes à chaque estimation d’un script de cinquante lignes, et l’estimation que vous oublierez est celle qui ira dans le résumé.

L’objet de plan sous R

library(survey)

design <- svydesign(
  ids     = ~ea_id,      # the primary sampling unit
  strata  = ~stratum,    # the stratification
  weights = ~weight,     # the weight built in lesson 2
  data    = survey,
  nest    = TRUE         # cluster ids are nested within strata
)

design

Quatre arguments et chacun porte.

  • ids est l’unité primaire de sondage — la zone de dénombrement, non le ménage. Passer ~household_id ici est l’erreur la plus fréquente de ce cours et produit silencieusement une variance de sondage aléatoire simple.
  • strata retire la variance inter-strates, qui est le gain mesuré à la leçon précédente.
  • weights rend l’estimation ponctuelle sans biais.
  • nest = TRUE indique au paquet que les identifiants de zone ne sont uniques qu’à l’intérieur d’une strate. Omettez-le là où ils sont réutilisés entre strates et les zones seront fusionnées.

Ensuite, chaque estimation sort du plan.

svymean(~I(food_insecure == "true"), design, deff = TRUE)
svytotal(~I(food_insecure == "true"), design)
svyby(~I(food_insecure == "true"), ~stratum, design, svymean, vartype = c("se", "ci"))
svyciprop(~I(food_insecure == "true"), design, method = "logit")

svyby est le cheval de trait — n’importe quelle estimation, par n’importe quel regroupement, avec le plan propagé. Et svyciprop est ce qu’il faut pour une proportion : la leçon suivante explique pourquoi l’intervalle de svymean a la mauvaise forme près de 0 et de 1.

La même chose en Python

Python n’a pas d’équivalent de survey sur lequel ce secteur se soit fixé, si bien que les exemples calculent l’estimateur directement. C’est un coût et aussi un avantage — l’arithmétique reste visible.

import numpy as np
import pandas as pd


class SurveyDesign:
    """Stratified two-stage design, ultimate-cluster variance."""

    def __init__(self, data, ids, strata, weights):
        self.data, self.ids, self.strata, self.weights = data, ids, strata, weights

    def mean(self, indicator):
        d = self.data
        y = indicator(d).astype(float)
        w = d[self.weights]
        p = np.average(y, weights=w)

        variance, total_w = 0.0, w.sum()
        for _, stratum in d.groupby(self.strata):
            residual = stratum[self.weights] * (indicator(stratum).astype(float) - p)
            totals = residual.groupby(stratum[self.ids]).sum()
            n = len(totals)
            if n < 2:
                continue
            variance += n / (n - 1) * ((totals - totals.mean()) ** 2).sum()
        se = np.sqrt(variance) / total_w

        srs = np.sqrt(p * (1 - p) / len(d))
        return {"estimate": p, "se": se, "deff": (se / srs) ** 2,
                "ci_low": p - 1.96 * se, "ci_high": p + 1.96 * se}


design = SurveyDesign(survey, ids="ea_id", strata="stratum", weights="weight")
print(design.mean(lambda d: d["food_insecure"] == "true"))
# The R equivalent of the whole class above:
svymean(~I(food_insecure == "true"), design, deff = TRUE)

La classe Python fait trente lignes et l’appel R une seule. Cette asymétrie est réelle et c’est pourquoi l’analyse d’enquêtes se fait d’ordinaire en R dans ce secteur. Employez R pour l’estimation d’enquête quand vous avez le choix ; la voie Python est pour les équipes qui ne l’ont pas, et pour comprendre ce que fait l’appel R.

L’approximation par grappe ultime

Les deux implémentations emploient le même raccourci, et il vaut la peine de le nommer car il ressemble à une simplification sans en être une.

La variance se calcule à partir des totaux des unités primaires de sondage, en ignorant entièrement le second degré. C’est exact lorsque le premier degré est tiré avec remise, et légèrement conservateur sinon — cela surestime très légèrement la variance en ignorant la correction de population finie au second degré.

Être légèrement conservateur est le bon sens d’erreur, et c’est pourquoi tous les grands paquets font cela par défaut. survey l’appelle l’approximation par grappe ultime, et tous les rapports EDS aussi.

Les quatre façons d’obtenir une simple moyenne par accident

Chacune produit un nombre d’allure correcte qui est une estimation de sondage aléatoire simple.

Passer le ménage comme grappe. ids = ~household_id dit que chaque ménage est sa propre unité primaire, donc qu’il n’y a pas de mise en grappes à prendre en compte, et l’erreur type s’effondre.

Oublier les pondérations. svydesign(..., weights = NULL) donne à chaque ménage le même poids, ce qui est le 32,8 % de la leçon 1.

Filtrer le tableau au lieu de sous-ensembler le plan. Celle-ci est subtile et fréquente.

# WRONG: the design object no longer knows about the dropped strata
rural <- svydesign(ids = ~ea_id, strata = ~stratum, weights = ~weight,
                   data = filter(survey, stratum != "urban"), nest = TRUE)

# RIGHT
rural <- subset(design, stratum != "urban")
# Same rule: subset the design, keeping the full strata structure in view
rural = SurveyDesign(survey[survey["stratum"] != "urban"],
                     ids="ea_id", strata="stratum", weights="weight")

Sous-ensembler un plan préserve la structure des strates et des grappes pour l’estimation de variance. Le reconstruire depuis un tableau filtré jette les zones sans ménage restant, ce qui change les degrés de liberté et peut produire en silence une strate à une seule grappe — où la contribution à la variance est indéfinie et discrètement écartée.

Calculer sur un tableau joint. Joignez l’enquête à quoi que ce soit en un-à-plusieurs et les pondérations deviennent fausses, car un ménage apparaît plusieurs fois. Agrégez au ménage avant d’estimer : c’est la leçon sur la granularité du cours de jointures qui revient dans un autre décor.

Des estimations autres que des proportions

svymean(~household_size, design, na.rm = TRUE)
svytotal(~I(food_insecure == "true"), design)
svyquantile(~minutes_to_water, design, quantiles = c(0.5, 0.9), na.rm = TRUE)
svyratio(~I(food_insecure == "true"), ~I(children_under5 > 0), design)
print(design.mean(lambda d: d["household_size"] > 7))

Deux remarques. Un total pondéré est l’estimation que l’on veut le plus souvent et qu’on ne peut le plus souvent pas obtenir, car elle exige des pondérations à l’échelle de la population — les pondérations normalisées de la leçon 2 donneraient un total de 996. Et na.rm = TRUE sur une moyenne d’enquête est une décision et non une commodité : cela suppose que les ménages manquants ressemblent aux ménages observés de leur strate, hypothèse identique à celle de l’ajustement pour non-réponse, et à énoncer une fois pour les deux.

Le plan du sous-échantillon

Les enfants mesurés forment un sous-échantillon avec sa propre pondération, et ils exigent leur propre objet de plan — non un filtre sur celui des ménages.

children <- subset(design, !is.na(child_muac_mm))
children <- update(children, child_weight = weight * children_under5)

child_design <- svydesign(ids = ~ea_id, strata = ~stratum,
                          weights = ~child_weight,
                          data = subset(survey, !is.na(child_muac_mm)), nest = TRUE)

svyciprop(~I(child_muac_mm < 125), child_design, method = "logit")
measured = survey[survey["child_muac_mm"].notna()].copy()
measured["child_weight"] = measured["weight"] * measured["children_under5"]

child_design = SurveyDesign(measured, ids="ea_id", strata="stratum",
                            weights="child_weight")
print(child_design.mean(lambda d: d["child_muac_mm"] < 125))

Les grappes sont les mêmes zones, donc la mise en grappes s’applique toujours. Seuls la pondération et la population changent.

Écrivez le plan à côté des estimations

Design           Stratified two-stage cluster
Strata           urban, rural-accessible, rural-remote
PSU              enumeration area (75 sampled, 25 per stratum)
Stage 1          PPS, size measure = households at frame listing
Stage 2          SRS, 14 households per area
Weights          base = M_h / (25 * 14), non-response adjusted within area
Variance         ultimate cluster, Taylor linearisation
Software         R survey 4.x / equivalent direct computation in Python

Sept lignes. Tout lecteur peut désormais reproduire vos erreurs types, et tout relecteur peut voir en dix secondes si vous avez fait les choses correctement — ce qu’on ne peut pas dire de la plupart des annexes d’enquête.

La suite

L’objet de plan suppose que tous les ménages tirés ont été enquêtés et que toutes les zones tirées ont été visitées. Ni l’un ni l’autre n’est vrai ici. La leçon suivante traite les 54 ménages non enquêtés et les deux zones qu’il a fallu remplacer.

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.