cassionAnalyse de données

Retour à la leçonLeçon 4 sur 8Une ligne par quoi ?

Long, large, et le groupe répété entre les deux

Le même diaporama que les téléchargements, rendu sous forme de page. Lancez le diaporama pour le présenter en plein écran — les flèches ou un clic avancent d'une diapositive, Échap quitte.

Diapositives · PDFDiapositives · PowerPoint

  1. Diapositive 1 / 24

    Ce que couvre cette leçon

    • Deux formes, et l'usage de chacune
    • Le groupe répété aplati
    • Les emplacements vides, et ceux qui veulent dire quelque chose
    • Du long au large — le tableau croisé DHIS2
    • La valeur de remplissage qui invente des données
    • Le tableau large que vous ne pouvez pas filtrer
    • Aller-retour, et son contrôle
    • La suite
    Notes du présentateur
    Pivoter parce que l'analyse a besoin de la forme, pas parce que l'export est arrivé ainsi. Le groupe répété aplati, le tableau croisé DHIS2, et la valeur de remplissage qui transforme « non rapporté » en zéro.
  2. Diapositive 2 / 24

    Deux formes, et l'usage de chacune

    • Long — une ligne par observation, une colonne par variable
    • Large — une ligne par unité, une colonne par mesure
    Notes du présentateur
    Les mêmes données, deux fois. Long — une ligne par observation, une colonne par variable. 2 736 lignes de formation sanitaire, période, antigène, doses. Large — une ligne par unité, une colonne par mesure. 456 lignes de formation sanitaire et période, avec six colonnes d'antigènes. Aucune n'est correcte dans l'absolu. La règle qui fonctionne vraiment :
  3. Diapositive 3 / 24

    Deux formes, et l'usage de chacune

    Le long sert à calculer. Le large sert à lire.
    Notes du présentateur
    Regroupez, filtrez, joignez et résumez sur des données longues, car chacun de ces verbes prend un nom de colonne et le format long a une colonne par concept. Pivotez vers le large en toute dernière étape avant qu'un humain le regarde, car une personne qui lit un tableau veut les six antigènes côte à côte. L'essentiel des ennuis de cette leçon vient de l'inverse — recevoir un export large et l'analyser large, ce qui transforme « calculer la couverture de chaque antigène » d'une ligne en six.
  4. Diapositive 4 / 24

    Le groupe répété aplati — Exemple

    household_id, member_1_age, member_1_sex, member_2_age, member_2_sex, member_3_age, ...
    Notes du présentateur
    Une plateforme de collecte pose une question répétée — chaque enfant du ménage, chaque symptôme observé, chaque source d'eau utilisée — et l'exporte en colonnes numérotées.
  5. Diapositive 5 / 24

    Le groupe répété aplati — En Python

    long = members_wide.melt(
        id_vars="household_id",
        var_name="field",
        value_name="value",
    )
    long[["slot", "attribute"]] = long["field"].str.extract(r"member_(\d+)_(\w+)")
    
    members = (
        long.dropna(subset=["slot"])
        .pivot(index=["household_id", "slot"], columns="attribute", values="value")
        .reset_index()
    )
    Notes du présentateur
    C'est l'une des formes les plus répandues dans ce secteur et elle est inexploitable telle quelle. Vous ne pouvez pas compter les membres, ni filtrer les enfants de moins de cinq ans, ni joindre à quoi que ce soit, parce que ce sur quoi vous voulez travailler est étalé en colonnes au lieu de descendre en lignes.
  6. Diapositive 6 / 24

    Le groupe répété aplati — En R

    members <- members_wide |>
      tidyr::pivot_longer(
        cols = tidyr::starts_with("member_"),
        names_to = c("slot", "attribute"),
        names_pattern = "member_(\\d+)_(\\w+)",
        values_to = "value"
      ) |>
      tidyr::pivot_wider(names_from = attribute, values_from = value)
  7. Diapositive 7 / 24

    Le groupe répété aplati

    • Deux pivots, pas un — Le premier transforme chaque colonne numérotée en lignes ; le second retransforme les noms…
    Notes du présentateur
    Deux pivots, pas un. Le premier transforme chaque colonne numérotée en lignes ; le second retransforme les noms d'attributs en colonnes. C'est en essayant de le faire d'un seul geste qu'on se bloque, car le nom de colonne porte deux faits — quel membre et quel attribut — qu'il faut séparer avant de pouvoir utiliser l'un ou l'autre. names_pattern en R et l'extraction par expression régulière en Python opèrent cette séparation. Mettez le motif au point face aux vrais noms de colonnes avant d'écrire quoi que ce soit d'autre ; un NA silencieux ici devient un membre qui disparaît.
  8. Diapositive 8 / 24

    Les emplacements vides, et ceux qui veulent dire quelque chose — En Python

    print(members["age"].isna().sum(), "empty slots")
    
    members = members[members["age"].notna() | members["sex"].notna()]
    Notes du présentateur
    Un formulaire prévu pour huit membres et un ménage de trois exporte cinq emplacements vides. La plupart ne sont rien. L'un d'eux ne l'est pas.
  9. Diapositive 9 / 24

    Les emplacements vides, et ceux qui veulent dire quelque chose — En R

    members |> summarise(empty = sum(is.na(age) & is.na(sex)))
    
    members <- members |> filter(!is.na(age) | !is.na(sex))
  10. Diapositive 10 / 24

    Les emplacements vides, et ceux qui veulent dire quelque chose — En Python

    counted = members.groupby("household_id").size().rename("members_found")
    check = households.join(counted, on="household_id")
    mismatch = check[check["members_found"] != check["household_size"]]
    print(len(mismatch), "households where the roster does not match household_size")
    Notes du présentateur
    La distinction — un emplacement dont tous les attributs sont manquants est du remplissage et doit partir. Un emplacement dont l'âge manque mais dont le sexe est renseigné est un membre réel avec un âge manquant, et le supprimer retire une personne du ménage. Filtrez sur « toute la ligne est vide », jamais sur une seule colonne. Confrontez le résultat à quelque chose que le fichier sait déjà.
  11. Diapositive 11 / 24

    Les emplacements vides, et ceux qui veulent dire quelque chose — En R

    check <- households |>
      left_join(count(members, household_id, name = "members_found"), by = "household_id") |>
      filter(members_found != household_size)
    Notes du présentateur
    La taille de ménage déclarée et le nombre de lignes de membres doivent concorder. Là où ce n'est pas le cas, soit le dépliage a perdu quelqu'un, soit l'entretien.
  12. Diapositive 12 / 24

    Du long au large — le tableau croisé DHIS2 — En Python

    wide = vax.pivot_table(
        index=["facility_id", "period"],
        columns="antigen",
        values="doses_administered",
        aggfunc="sum",
    ).reset_index()
    
    print(len(vax), "->", len(wide))
    Notes du présentateur
    L'extraction vaccinale arrive en long — une ligne par formation sanitaire, période et antigène — et c'est la bonne forme pour calculer. Pour la poser devant un responsable sanitaire de district, pivotez.
  13. Diapositive 13 / 24

    Du long au large — le tableau croisé DHIS2 — En R

    wide <- vax |>
      tidyr::pivot_wider(
        id_cols = c(facility_id, period),
        names_from = antigen,
        values_from = doses_administered
      )
    
    cat(nrow(vax), "->", nrow(wide), "\n")
  14. Diapositive 14 / 24

    Du long au large — le tableau croisé DHIS2

    • Le pivot de pandas lève une erreur sur les doublons ; pivot_table les agrège — Prenez pivot d'abord, précisément…
    Notes du présentateur
    2 736 lignes deviennent 456 — 38 formations sanitaires sur 12 mois — avec six colonnes d'antigènes. L'arithmétique mérite d'être vérifiée à chaque fois : 2 736 divisé par 6 antigènes fait 456, donc rien n'a été agrégé au passage. Quand la division ne tombe pas juste, la table longue avait des doublons sur la clé de pivot, et pivot_table les aura silencieusement sommés. Le pivot de pandas lève une erreur sur les doublons ; pivot_table les agrège. Prenez pivot d'abord, précisément parce que vous voulez l'erreur. Le pivot_wider de tidyr avertit et produit des colonnes-listes, ce qui est plus laid et tout aussi informatif.
  15. Diapositive 15 / 24

    La valeur de remplissage qui invente des données — En Python

    wide = vax.pivot_table(
        index=["facility_id", "period"], columns="antigen",
        values="doses_administered", aggfunc="sum", fill_value=0,
    )
  16. Diapositive 16 / 24

    La valeur de remplissage qui invente des données — En R

    wide <- vax |>
      tidyr::pivot_wider(names_from = antigen, values_from = doses_administered,
                         values_fill = 0)
  17. Diapositive 17 / 24

    La valeur de remplissage qui invente des données — En Python

    wide = wide.assign(reported=wide.notna().sum(axis=1))
    Notes du présentateur
    fill_value=0 remplit les combinaisons absentes des données longues. Pour des doses administrées c'est souvent juste — une formation sanitaire sans ligne penta3 n'a réellement administré aucune dose de penta3. Pour un taux, c'est toujours faux. Une cellule de couverture manquante signifie « non calculée », et la remplir par zéro publie une formation sanitaire à 0 % de couverture qui n'a simplement pas rapporté. Le cours de nettoyage faisait ce constat à propos du rapportage ; ici il revient par une autre porte, comme un argument de pivot que personne ne perçoit comme une décision. Le réflexe sûr est de la laisser manquante et de la traiter explicitement.
  18. Diapositive 18 / 24

    La valeur de remplissage qui invente des données — En R

    wide <- wide |> mutate(reported = rowSums(!is.na(across(bcg:penta3))))
  19. Diapositive 19 / 24

    Le tableau large que vous ne pouvez pas filtrer — En Python

    low = vax[vax["doses_administered"] < 10]
    Notes du présentateur
    Une fois en large, une question comme « quels couples formation-mois ont eu moins de dix doses d'un antigène quelconque » demande six comparaisons reliées par ou, et ajouter un septième antigène oblige à toutes les modifier. La même question sur des données longues tient en une ligne.
  20. Diapositive 20 / 24

    Le tableau large que vous ne pouvez pas filtrer — En R

    low <- vax |> filter(doses_administered < 10)
    Notes du présentateur
    C'est le test qui dit si vous avez pivoté trop tôt. Si votre opération suivante nomme plus d'une colonne contenant le même type de valeur, revenez au long.
  21. Diapositive 21 / 24

    Aller-retour, et son contrôle — En Python

    back = wide.melt(id_vars=["facility_id", "period"],
                     var_name="antigen", value_name="doses_administered").dropna()
    
    assert len(back) == len(vax)
    assert back["doses_administered"].sum() == vax["doses_administered"].sum()
    Notes du présentateur
    Toute restructuration digne de confiance est réversible. Affirmez-le une fois, pendant que vous écrivez le code.
  22. Diapositive 22 / 24

    Aller-retour, et son contrôle — En R

    back <- wide |>
      tidyr::pivot_longer(-c(facility_id, period),
                          names_to = "antigen", values_to = "doses_administered") |>
      filter(!is.na(doses_administered))
    
    stopifnot(nrow(back) == nrow(vax),
              sum(back$doses_administered) == sum(vax$doses_administered))
    Notes du présentateur
    Nombre de lignes et total. Deux lignes, et elles attrapent un antigène silencieusement perdu, un doublon qui s'est fait sommer, et une valeur de remplissage qui a inventé des lignes.
  23. Diapositive 23 / 24

    La suite

    • Vous savez désormais donner à une table la forme qu'exige la question.
    Notes du présentateur
    Vous savez désormais donner à une table la forme qu'exige la question. L'unité suivante porte sur les tables qu'il faut faire venir de l'extérieur — la base de population qui fournit un dénominateur de couverture, et le calendrier qui dit quelles périodes auraient dû exister.
  24. Diapositive 24 / 24

    La suite

    Lire la leçon complète, avec le code exécutable Retour à la leçon