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.
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.