cassionAnalyse de données

Retour à la leçonLeçon 3 sur 8Faire 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.

Diapositives · PDFDiapositives · PowerPoint

  1. Diapositive 1 / 20

    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.
  2. Diapositive 2 / 20

    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.
  3. Diapositive 3 / 20

    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 -99 n'est pas une mesure.
  4. Diapositive 4 / 20

    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 :
  5. Diapositive 5 / 20

    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))
  6. Diapositive 6 / 20

    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 : -99 est un PB manquant, mais si une colonne portait légitimement une valeur négative, un traitement global la détruirait. Le read_csv de R applique na à tout le fichier ; restreignez ensuite la portée lorsque cela importe.
  7. Diapositive 7 / 20

    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_id s'écrit ici CH00854 et survit intact à l'import. Beaucoup de vrais registres utilisent des identifiants purement numériques — 00854 — et un lecteur les transformera obligeamment en l'entier 854. 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.
  8. Diapositive 8 / 20

    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.
  9. Diapositive 9 / 20

    Les dates — En Python

    muac["screening_date"] = pd.to_datetime(
        muac["screening_date"], format="%Y-%m-%d", errors="raise"
    )
    Notes du présentateur
    screening_date arrive sous la forme 2024-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 produisent 15/01/2024, 01/15/2024, 15-janv-24 et des numéros de série Excel comme 45306, parfois dans la même colonne, parce que trois personnes ont saisi sur trois machines configurées différemment. 01/02/2024 est soit le 1er février, soit le 2 janvier, et le fichier ne vous dira pas lequel.
  10. Diapositive 10 / 20

    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)))
  11. Diapositive 11 / 20

    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 :
  12. Diapositive 12 / 20

    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
    outcome prend quatre valeurs et sex en 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.
  13. Diapositive 13 / 20

    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 colonne oedema de ce registre est exactement ce cas. Ennery et Desdunes l'ont renseignée de manière incohérente au premier trimestre, avec Y et N plutôt que true et false. Lue naïvement, ces lignes deviennent manquantes sans un mot.
  14. Diapositive 14 / 20

    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.
  15. Diapositive 15 / 20

    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())
  16. Diapositive 16 / 20

    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()
  17. Diapositive 17 / 20

    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.
  18. Diapositive 18 / 20

    Lire les autres formats

    SourcePythonR
    CSVpd.read_csvreadr::read_csv
    Excelpd.read_excelreadxl::read_excel
    Statapd.read_statahaven::read_dta
    SPSSpd.read_spsshaven::read_sav
    Parquetpd.read_parquetarrow::read_parquet
    Notes 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 — que haven conserve 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.
  19. Diapositive 19 / 20

    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.
  20. Diapositive 20 / 20

    La suite

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