Retour à la leçon·Leçon 3 sur 8·Faire entrer les données
Lire un export sans le corrompre
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.
Ce que couvre cette leçon
- Les dégâts précèdent le regard
- Les codes de valeur manquante deviennent des nombres
- Les identifiants ne sont pas des nombres
- Les dates
- Les catégories à vocabulaire fixe
- Une lecture défensive, de bout en bout
- Lire les autres formats
- La suite
Notes du présentateur
Inférence de types, zéros initiaux, dates, et le code de valeur manquante qui devient un nombre. Les dégâts se produisent à l'import, avant même que vous ayez regardé quoi que ce soit.Les dégâts précèdent le regard
read_csv(chemin)tient sur une ligne et prend une demi-douzaine de décisions à votre place.
Notes du présentateur
read_csv(chemin)tient sur une ligne et prend une demi-douzaine de décisions à votre place. La plupart sont justes. Celles qui ne le sont pas le sont silencieusement, et le temps que vous vous en aperceviez, la valeur corrompue est passée dans trois synthèses et un graphique. Cette leçon consiste à reprendre ces décisions.Les codes de valeur manquante deviennent des nombres — En Python
import pandas as pd naif = pd.read_csv("data/raw/muac-screening-artibonite-2024.v1.csv") print(naif["muac_mm"].mean())Notes du présentateur
Le registre PB code les mesures manquantes-99, et non par une cellule vide. Il y a une raison à cela — une case vide sur un registre papier est ambiguë entre « non mesuré » et « l'enquêteur a sauté la page », alors qu'une valeur sentinelle ne l'est pas — mais un lecteur de CSV n'a aucun moyen de savoir que-99n'est pas une mesure.Les codes de valeur manquante deviennent des nombres — En Python
muac = pd.read_csv( "data/raw/muac-screening-artibonite-2024.v1.csv", na_values={"muac_mm": ["-99"]}, ) print(muac["muac_mm"].mean()) print(muac["muac_mm"].isna().sum())Notes du présentateur
Ce chiffre est inférieur de plusieurs millimètres à la réalité. Pire, il est plausible : rien n'y ressemble à une erreur, et une moyenne de PB n'est pas une valeur que la plupart des lecteurs peuvent vérifier d'un coup d'œil. Déclarez la sentinelle à la lecture et elle n'entre jamais dans un calcul :Les codes de valeur manquante deviennent des nombres — En R
library(readr) muac <- read_csv( "data/raw/muac-screening-artibonite-2024.v1.csv", na = c("", "NA", "-99") ) mean(muac$muac_mm, na.rm = TRUE) sum(is.na(muac$muac_mm))Les codes de valeur manquante deviennent des nombres
Traiter une sentinelle n'est pas la même chose que décider quoi faire de la valeur manquante. Cette décision relève de la leçon 6. Ici, vous vous assurez seulement que le code cesse de se faire passer pour une mesure.
Notes du présentateur
Notez la différence. pandas permet de restreindre la sentinelle à une colonne, ce qui est souhaitable :-99est un PB manquant, mais si une colonne portait légitimement une valeur négative, un traitement global la détruirait. Leread_csvde R appliquenaà tout le fichier ; restreignez ensuite la portée lorsque cela importe.Les identifiants ne sont pas des nombres — En Python
muac = pd.read_csv( chemin, dtype={"child_id": "string", "commune": "string"}, na_values={"muac_mm": ["-99"]}, )Notes du présentateur
child_ids'écrit iciCH00854et survit intact à l'import. Beaucoup de vrais registres utilisent des identifiants purement numériques —00854— et un lecteur les transformera obligeamment en l'entier854. Le zéro initial a disparu, la jointure vers le fichier ménage échoue exactement pour les identifiants qui en portaient un, et l'échec ressemble à des ménages manquants plutôt qu'à une erreur de type.Les identifiants ne sont pas des nombres — En R
muac <- read_csv( chemin, col_types = cols( child_id = col_character(), commune = col_character() ), na = c("", "NA", "-99") )Notes du présentateur
La règle se généralise : si vous ne ferez jamais d'arithmétique dessus, ce n'est pas un nombre. Codes de formation sanitaire, numéros de téléphone, identifiants de grappe, numéros de ménage et codes administratifs sont tous du texte qui s'écrit avec des chiffres.Les dates — En Python
muac["screening_date"] = pd.to_datetime( muac["screening_date"], format="%Y-%m-%d", errors="raise" )Notes du présentateur
screening_datearrive sous la forme2024-01-15. C'est de l'ISO 8601, c'est sans ambiguïté, et les deux lecteurs l'interpréteront correctement. Sachez que vous avez de la chance. De vrais exports produisent15/01/2024,01/15/2024,15-janv-24et des numéros de série Excel comme45306, parfois dans la même colonne, parce que trois personnes ont saisi sur trois machines configurées différemment.01/02/2024est soit le 1er février, soit le 2 janvier, et le fichier ne vous dira pas lequel.Les dates — En R
muac <- muac |> dplyr::mutate( screening_date = as.Date(screening_date, format = "%Y-%m-%d") ) stopifnot(!any(is.na(muac$screening_date)))Les dates
- Énoncez explicitement le format plutôt que de laisser l'analyseur le déduire. Un format déduit peut changer d'un…
- Utilisez
errors="raise". L'alternative,errors="coerce", convertit toute date illisible en valeur manquante —…
Notes du présentateur
Deux habitudes valent la peine d'être prises :Les catégories à vocabulaire fixe — En Python
issues = pd.CategoricalDtype( ["no-action", "referred-tsfp", "referred-otp", "referred-sc"], ordered=False ) muac["outcome"] = muac["outcome"].astype(issues) # Toute valeur hors vocabulaire est désormais NaN — comptez-les avant de continuer. print(muac["outcome"].isna().sum())Notes du présentateur
outcomeprend quatre valeurs etsexen prend deux. Les déclarer comme catégorielles apporte deux choses : un objet plus léger et — plus utile — une erreur lorsqu'une valeur hors vocabulaire apparaît.Les catégories à vocabulaire fixe — En R
muac <- muac |> dplyr::mutate( outcome = factor( outcome, levels = c("no-action", "referred-tsfp", "referred-otp", "referred-sc") ) ) sum(is.na(muac$outcome))Notes du présentateur
C'est un vrai piège dans les deux langages : une valeur hors des niveaux déclarés devient manquante au lieu de déclencher une erreur. Comptez toujours les valeurs manquantes immédiatement après la conversion. Un passage de zéro à neuf signifie que neuf lignes portaient une valeur dont vous ignoriez l'existence, et il vaut mieux la voir maintenant que la découvrir sous forme de trou dans un tableau. La colonneoedemade ce registre est exactement ce cas. Ennery et Desdunes l'ont renseignée de manière incohérente au premier trimestre, avecYetNplutôt quetrueetfalse. Lue naïvement, ces lignes deviennent manquantes sans un mot.Une lecture défensive, de bout en bout — En Python (suite)
import pandas as pd CHEMIN = "data/raw/muac-screening-artibonite-2024.v1.csv" muac = pd.read_csv( CHEMIN, dtype={"child_id": "string", "commune": "string", "sex": "string"}, na_values={"muac_mm": ["-99"]}, keep_default_na=True, ) muac["screening_date"] = pd.to_datetime( muac["screening_date"], format="%Y-%m-%d", errors="raise" ) assert len(muac) == 4218, f"4218 lignes attendues, {len(muac)} obtenues"Notes du présentateur
Assemblons le tout — en affirmant ce que l'on attend, pour que le script échoue sur un mauvais fichier au lieu de produire un mauvais chiffre.Une lecture défensive, de bout en bout — En Python (suite)
assert muac["child_id"].notna().all(), "chaque ligne exige un identifiant" print(muac.dtypes) print(muac.isna().sum())Une lecture défensive, de bout en bout — En R (suite)
library(readr) library(dplyr) CHEMIN <- "data/raw/muac-screening-artibonite-2024.v1.csv" muac <- read_csv( CHEMIN, col_types = cols( child_id = col_character(), commune = col_character(), screening_date = col_date(format = "%Y-%m-%d"), age_months = col_integer(), sex = col_character(), muac_mm = col_integer(), oedema = col_character(), outcome = col_character()Une lecture défensive, de bout en bout — En R (suite)
), na = c("", "NA", "-99") ) stopifnot(nrow(muac) == 4218) stopifnot(!any(is.na(muac$child_id))) glimpse(muac) colSums(is.na(muac))Notes du présentateur
Les assertions sont la partie que l'on saute et celle qui rapporte. Un fichier qui arrive avec 4 190 lignes au lieu de 4 218 a perdu quelque chose entre le serveur et vous, et le script doit le dire plutôt que de rapporter discrètement une charge de cas plus faible.Lire les autres formats
Source Python R CSV pd.read_csvreadr::read_csvExcel pd.read_excelreadxl::read_excelStata pd.read_statahaven::read_dtaSPSS pd.read_spsshaven::read_savParquet pd.read_parquetarrow::read_parquetNotes du présentateur
Les mêmes principes s'appliquent, avec d'autres noms de fonctions. Deux remarques propres au secteur. Les fichiers Stata et SPSS portent des étiquettes de valeurs —1 = Oui,2 = Non— quehavenconserve alors que pandas les perd le plus souvent ; si un institut de sondage vous remet un.dta, lisez-le en R même si vous l'analyserez en Python. Et les exports Excel comportent fréquemment une ligne de titre fusionnée au-dessus de l'en-tête, que les deux lecteurs prendront pour l'en-tête si vous ne leur demandez pas de la sauter.La suite
- Le registre est maintenant en mémoire, avec ses types intacts.
Notes du présentateur
Le registre est maintenant en mémoire, avec ses types intacts. La leçon suivante le restructure — ainsi que l'export aplati à la CommCare sous lequel il aurait pu arriver — en une ligne par observation.