Leçon 3 sur 8
Unité · Des données qui gardent leur sens
Les fichiers Stata et SPSS gardent leurs étiquettes
haven, la classe labelled, et pourquoi le .dta d'un institut de sondage doit être lu sous R même si l'analyse se fait ailleurs — plus les deux façons de perdre un dictionnaire de codes sans s'en apercevoir.
Ce qu’un CSV jette
Un institut de sondage livre un .dta ou un .sav. À l’intérieur, une colonne
n’est pas seulement des valeurs — elle porte une étiquette de variable (« Le
chef de ménage a consenti à l’entretien ») et des étiquettes de valeurs
(1 = Oui, 2 = Non, 9 = Ne sait pas).
Exportez-le en CSV et les deux disparaissent. Vous obtenez une colonne de 1,
2 et 9, un dictionnaire de codes en PDF que quelqu’un doit conserver, et une
moyenne de 1,7 qui ne signifie rien.
library(haven)
survey <- read_dta(here("data", "raw", "household-survey.dta"))
haven lit les étiquettes en même temps que les valeurs. C’est la raison d’être
de cette leçon, et la raison de lire un fichier d’enquête sous R même si
l’analyse se fera en Python : R préserve davantage du fichier que pandas, et
un CSV accompagné d’un dictionnaire que vous avez écrit vous-même est un moins
bon artefact que l’original.
La classe labelled
read_dta() vous donne des colonnes de classe haven_labelled : les valeurs
sous-jacentes, avec leurs étiquettes attachées.
class(survey$consent)
#> [1] "haven_labelled" "vctrs_vctr" "double"
attr(survey$consent, "labels")
#> Oui Non Ne sait pas
#> 1 2 9
attr(survey$consent, "label")
#> [1] "Le chef de menage a consenti a l'entretien"
Les valeurs sont toujours là — l’arithmétique fonctionne — et c’est justement le
piège. Une colonne étiquetée est un nombre du point de vue de mean() :
mean(survey$consent, na.rm = TRUE)
#> [1] 1.7
Ce chiffre est la moyenne de 1, 2 et 9. Il n’a aucun sens et il n’avertit
de rien.
Convertir délibérément
haven propose trois gestes, et le bon dépend de ce qu’est la colonne.
Une variable catégorielle devient un facteur.
library(dplyr)
survey <- survey |>
mutate(consent = as_factor(consent))
levels(survey$consent)
#> [1] "Oui" "Non" "Ne sait pas"
as_factor() — celui de haven, non le as.factor() de base — utilise les
étiquettes de valeurs comme niveaux, dans l’ordre que le fichier déclare plutôt
qu’alphabétiquement. Cet ordre est un cadeau : celui du dictionnaire de codes est
généralement celui qu’un rapport attend.
Un véritable nombre perd ses étiquettes.
survey <- survey |> mutate(hh_size = zap_labels(hh_size))
zap_labels() retire l’étiquetage et laisse les valeurs. À employer là où la
colonne est réellement une quantité qui portait accessoirement une étiquette.
Tout d’un coup, quand le fichier est uniformément catégoriel :
survey <- read_dta(path) |> as_factor()
Appliquer as_factor() au tibble entier convertit chaque colonne étiquetée.
Rapide, et faux pour tout fichier mêlant catégories et quantités — vérifiez avec
glimpse() avant d’y recourir.
Les deux façons de perdre un dictionnaire de codes
Lire avec les étiquettes déjà appliquées
survey <- read_dta(path, .name_repair = "unique")
survey <- as_factor(survey) # les valeurs ont disparu, restent les etiquettes
Les codes sous-jacents sont désormais indisponibles. Cela compte plus qu’il n’y
paraît : le dictionnaire, le questionnaire, le plan de tabulation et toute
analyse antérieure renvoient tous à 1 et 2. À un collègue qui demande
« combien ont été codés 9 ? », un facteur ne répond pas.
Gardez les deux :
survey <- read_dta(path)
codes <- survey |> mutate(across(where(is.labelled), zap_labels))
labels <- survey |> as_factor()
Écrire le CSV et passer à autre chose
readr::write_csv(as_factor(survey), "survey.csv")
Le CSV porte désormais "Oui" et "Non", ce qui est lisible et perd dans une
autre direction : les codes ont disparu, et avec eux la distinction entre « Ne
sait pas » et une véritable valeur manquante.
Si vous devez remettre un CSV à une analyse Python, remettez un dictionnaire avec lui, généré depuis le fichier plutôt que saisi :
codebook <- tibble::tibble(
variable = names(survey),
label = vapply(survey, function(x) attr(x, "label") %||% NA_character_, character(1))
)
readr::write_csv(codebook, "survey-codebook.csv")
Les étiquettes de valeurs demandent une ligne par niveau, ce que
labelled::look_for() produit directement si vous disposez de ce paquet. Dans
tous les cas, le dictionnaire est dérivé, non transcrit — un dictionnaire
transcrit s’écarte du fichier qu’il décrit.
Les valeurs manquantes définies par l’utilisateur
Stata et SPSS distinguent des sortes de manquant : refus, sans objet, non posé.
haven les préserve sous forme de NA étiquetés plutôt que de les fondre en
un seul.
survey$income
#> <labelled<double>[3]>
#> [1] 4500 NA(a) NA(b)
NA(a) et NA(b) restent des NA pour is.na() : rien ne devient donc
silencieusement un nombre. Mais la distinction survit, et elle est souvent le
constat — une question refusée par 40 % des répondants est un problème différent
d’une question qui ne les concernait pas.
survey <- survey |> mutate(income = zap_missing(income))
zap_missing() les aplatit en NA ordinaires une fois que vous les avez
comptés. Comptez d’abord.
SPSS et les autres formats
read_sav(path) # SPSS .sav
read_por(path) # SPSS portable
read_dta(path) # Stata
read_xpt(path) # SAS transport
Tous renvoient la même structure étiquetée : tout ce qui précède s’applique tel quel.
L’écriture est symétrique, et parfois nécessaire quand un ministère attend un
.dta :
write_dta(survey, "cleaned.dta")
Stata impose aux noms de variables des règles que R n’a pas — pas de points,
32 caractères — et write_dta() lèvera une erreur plutôt que de les déformer
silencieusement. C’est le comportement souhaitable.
Où cela rejoint le cours Python
Le cours Python pour les données de programme dit, dans sa leçon sur les codes et les sentinelles, que c’est le seul endroit où la réponse est « utilisez l’autre langage ». C’est cette leçon-ci qu’il désigne.
pandas lit un .dta et vous donne soit les codes, soit les étiquettes, jamais
les deux, et ne préserve pas du tout les valeurs manquantes étiquetées. Si les
étiquettes comptent — et sur un fichier d’enquête, c’est toujours le cas — lisez
sous R, convertissez délibérément, et exportez à la fois les codes et un
dictionnaire dérivé.
Ce qui vient ensuite
Les étiquettes sont attachées et converties en facteurs. La leçon suivante porte
sur ce qu’est réellement un facteur en R, et sur le problème d’ordre que
as_factor() a résolu ici pour vous et ne résoudra pas pour une colonne que vous
construisez vous-même.