cassionAnalyse de données

Leçon 7 sur 8

Unité · Restructurer et répéter

Défaire un groupe répété aplati

pivot_longer() avec .value, l'appel unique qui retransforme un export CommCare ou Kobo en une ligne par personne — plus l'avertissement de pivot_wider() qui signifie que votre clé n'en est pas une.

R80 min

Ce qu’une plateforme de collecte fait d’un groupe répété

Un questionnaire ménage interroge sur chaque membre. Sous CommCare, KoboToolbox ou ODK, les réponses reviennent aplaties — une ligne par ménage, le groupe répété étalé sur des colonnes numérotées :

hh    member_1_age  member_1_sex  member_2_age  member_2_sex
H1              34  f                        8  m

Rien dans cette forme n’est analysable. Vous ne pouvez ni calculer un âge moyen, ni filtrer sur les enfants, ni joindre quoi que ce soit, parce qu’une personne n’est pas une ligne.

pivot_longer() avec .value

library(tidyr)

long <- wide |>
  pivot_longer(
    starts_with("member_"),
    names_to = c("member", ".value"),
    names_pattern = "member_(\\d+)_(.*)"
  )
# A tibble: 2 × 4
  hh    member   age sex
  <chr> <chr>  <dbl> <chr>
1 H1    1         34 f
2 H1    2          8 m

Une ligne par personne, et age et sex sont des colonnes distinctes avec leurs propres types. C’est la forme qu’attend tout ce qui suit.

La sentinelle .value est la pièce à comprendre. Dans names_to, un nom ordinaire — "member" — devient une nouvelle colonne portant cette partie de l’ancien nom de colonne. ".value" est différent : il dit cette partie du nom est le nom de la colonne de sortie. Ainsi member_1_age se scinde en member = "1" et une valeur appartenant à une colonne nommée age.

Sans .value, vous obtiendriez une unique colonne value mêlant nombres et texte, et une colonne name qu’il faudrait ensuite scinder et pivoter à nouveau.

Le motif

names_pattern est une expression régulière comportant un groupe de capture par entrée de names_to, dans l’ordre. "member_(\\d+)_(.*)" capture le numéro puis tout ce qui suit le second tiret bas.

Deux variantes lorsque les noms sont plus simples :

# Separateur fixe, sans expression reguliere.
pivot_longer(wide, starts_with("member_"),
             names_to = c("member", ".value"), names_sep = "_(?=[a-z]+$)")

# Une seule colonne de valeurs, nom conserve entier.
pivot_longer(wide, starts_with("crop_"),
             names_to = "slot", names_prefix = "crop_", values_to = "crop",
             values_drop_na = TRUE)

Le values_drop_na = TRUE de la seconde compte : un ménage avec trois emplacements de culture et deux cultures en a un vide, et une ligne NA par emplacement inutilisé est un bruit qui change tous les décomptes.

Vérifiez le nombre de lignes obtenu

Un pivot qui produit le mauvais nombre de lignes est l’erreur la plus facile à commettre et la plus difficile à voir, parce que le résultat paraît correct.

stopifnot(nrow(long) == sum(!is.na(wide$member_1_age)) + sum(!is.na(wide$member_2_age)))

Plus concrètement, comptez les personnes par ménage et comparez à la taille de ménage que le formulaire a enregistrée :

long |>
  count(hh, name = "members_found") |>
  left_join(households |> select(hh, hh_size), by = "hh") |>
  filter(members_found != hh_size)

Un résultat vide, c’est le contrôle qui passe. Un résultat non vide est une liste de ménages où la composition et la taille déclarée divergent — un constat de qualité des données avant d’être un problème de pivot.

pivot_wider() et l’avertissement qui veut dire quelque chose

L’opération inverse construit le tableau large qu’attend un rapport :

by_commune_month |>
  pivot_wider(names_from = month, values_from = cases, values_fill = 0)

Si plusieurs lignes partagent la même clé, tidyr ne lève pas d’erreur :

Warning: Values from `v` are not uniquely identified; output will contain
list-cols.

Cet avertissement signifie que votre clé n’en est pas une. La cellule contient désormais une liste des valeurs entrées en collision, et toute opération en aval échoue étrangement ou opère silencieusement sur une liste.

N’ajoutez pas values_fn = sum pour faire taire l’avertissement sans avoir d’abord cherché la cause des doublons. Il s’agit généralement de l’un de trois cas : un formulaire resoumis, une formation sanitaire rapportant deux fois dans le mois, ou une variable de regroupement oubliée. Seul le premier se somme sans risque.

by_commune_month |> count(commune, month) |> filter(n > 1)

Voilà le diagnostic, et il doit précéder le pivot plutôt que suivre l’avertissement.

values_fill

pivot_wider(..., values_fill = 0)

Sans risque pour un décompte, où une combinaison absente valait réellement zéro. Faux pour un taux, où l’absence signifie « non calculé » et non « zéro pour cent » — une commune sans dépistage en mars a un taux de MAG indéfini, non un taux de 0 %, et le remplir par zéro tire toutes les moyennes vers le bas.

Séparer et réunir des colonnes

tibble(x = c("2024-01", "2024-02")) |>
  separate_wider_delim(x, delim = "-", names = c("year", "month"))
# A tibble: 2 × 2
  year  month
  <chr> <chr>
1 2024  01
2 2024  02

separate_wider_delim() a remplacé l’ancien separate(), et le progrès tient à sa rigueur : une ligne au mauvais nombre de morceaux est une erreur plutôt qu’une troncature silencieuse. Là où une entrée irrégulière est attendue, dites-le :

separate_wider_delim(x, "-", names = c("year", "month"), too_few = "align_start")

L’opération inverse est unite(), utile pour bâtir une clé composite avant une jointure :

unite(df, "key", district, community, sep = "-", remove = FALSE)

remove = FALSE conserve les colonnes sources, dont vous avez presque toujours encore besoin.

L’imbrication, brièvement

Un groupe répété peut aussi rester imbriqué plutôt qu’aplati :

by_commune <- muac |>
  tidyr::nest(.by = commune)

by_commune$data[[1]]      # le tibble complet de la premiere commune

C’est la forme adaptée pour exécuter le même modèle ou le même résumé par commune sans boucle. Il vaut la peine de savoir que cela existe ; pour l’essentiel du rapportage de programme, le .by de summarise() y mène avec moins de machinerie.

Où cela rejoint le cours Python

Le cours Python couvre la même restructuration sous melt et pivot. Le vocabulaire diffère, les modes de défaillance non : un groupe répété qui doit devenir des lignes, une clé qui s’avère non unique, et une valeur de remplissage juste pour un décompte et fausse pour un taux.

Ce qui vient ensuite

Les données ont la bonne forme. La dernière leçon transforme tout ce qui précède en une fonction qu’un autre chargé pourra exécuter sur l’export du trimestre prochain sans rien modifier d’autre que ses arguments.

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.