cassionAnalyse de données

Retour à la leçonLeçon 6 sur 8Des valeurs qui ne peuvent pas être vraies

Six districts, trois districts

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 / 24

    Ce que couvre cette leçon

    • Le tableau à six lignes pour trois districts
    • La liste administrative fait autorité
    • Normalisez, puis appariez à l'identique
    • Le reliquat, c'est le vrai travail
    • Les appariements qu'il faut refuser
    • La correspondance est un fichier, pas une suite de remplacements
    • Appariez une fois, joignez sur le code pour toujours
    • L'assertion qui attrape la cinquième graphie du prochain tour
    • La suite
    Notes du présentateur
    Des noms de lieux en texte libre face à une liste administrative — normaliser, apparier à l'identique, relire le reliquat, et stocker le résultat dans un fichier de correspondance plutôt que dans une suite de remplacements. Plus l'assertion qui arrête la cinquième graphie du prochain tour.
  2. Diapositive 2 / 24

    Le tableau à six lignes pour trois districts

    districtMénagesSous 15 L/personne/jour
    NORD-OUEST3321,2 %
    nord-ouest2020,0 %
    Sud-Est80316,4 %
    Nord-Ouest72714,7 %
    Nord Ouest229,1 %
    Centre7988,9 %
    Notes du présentateur
    Regroupez l'enquête ménage EAH par district et voici ce qui revient. Six lignes. Il y a trois districts. Une équipe d'enquêteurs a écrit Nord-Ouest de quatre façons différentes, et comme les variantes se trient séparément, les 802 ménages de ce district sont découpés en morceaux de 727, 33, 22 et 20. Lisez maintenant le tableau comme le ferait une réunion de coordination. Trié du pire au meilleur, NORD-OUEST est le district prioritaire à 21,2 % — un chiffre calculé sur trente-trois ménages, la plus petite cellule du tableau et la plus facilement déplacée par une poignée d'entretiens. Le vrai Nord-Ouest, ses 802 ménages, est à 15,0 % et arrive deuxième derrière Sud-Est. Personne ne vous dira que cela s'est produit. Rien n'échoue. Le tableau a l'air correct, il répond seulement à une autre question que celle posée.
  3. Diapositive 3 / 24

    La liste administrative fait autorité — En Python

    admin = pd.DataFrame([
        {"admin1_pcode": "HT07", "admin1_name": "Nord-Ouest"},
        {"admin1_pcode": "HT05", "admin1_name": "Centre"},
        {"admin1_pcode": "HT09", "admin1_name": "Sud-Est"},
    ])
    Notes du présentateur
    La première décision porte sur les noms qui font foi, et la réponse n'est jamais « ceux qui sont dans les données ». Chaque pays dispose d'une hiérarchie administrative officielle — admin 1, admin 2, admin 3 — publiée avec des codes. Dans les opérations humanitaires ce sont les p-codes, diffusés par le Humanitarian Data Exchange et utilisés par tous les clusters, et tout leur intérêt est qu'un code n'a pas de variantes orthographiques.
  4. Diapositive 4 / 24

    La liste administrative fait autorité — En R

    admin <- tibble::tribble(
      ~admin1_pcode, ~admin1_name,
      "HT07",        "Nord-Ouest",
      "HT05",        "Centre",
      "HT09",        "Sud-Est"
    )
  5. Diapositive 5 / 24

    La liste administrative fait autorité

    • Une valeur des données qui ne se résout pas dans la liste est non appariée, pas fausse. Ce peut être une variante…
    • Le produit de l'appariement est un code, et tout l'aval joint sur le code.
    • C'est la liste, et non les données, qui décide du nombre de districts. Un tableau à six lignes échoue à un contrôle au…
    Notes du présentateur
    Trois conséquences découlent du fait de traiter cette liste comme l'autorité, et elles donnent la forme du reste de la leçon.
  6. Diapositive 6 / 24

    Normalisez, puis appariez à l'identique — En Python (suite)

    def match_key(series):
        return (
            series.fillna("")
            .str.normalize("NFKD")
            .str.encode("ascii", "ignore").str.decode("ascii")
            .str.lower()
            .str.replace(r"[^a-z0-9]+", " ", regex=True)
            .str.strip()
        )
    
    
    wash["district_key"] = match_key(wash["district"])
    admin["key"] = match_key(admin["admin1_name"])
    
    matched = wash.merge(
        admin[["key", "admin1_pcode", "admin1_name"]],
    Notes du présentateur
    La plupart des variantes ne sont pas vraiment des noms différents. Retirez ce qui ne porte pas de sens et elles se rejoignent.
  7. Diapositive 7 / 24

    Normalisez, puis appariez à l'identique — En Python (suite)

        left_on="district_key", right_on="key", how="left",
    )
    print(matched["admin1_pcode"].isna().sum(), "rows unmatched")
  8. Diapositive 8 / 24

    Normalisez, puis appariez à l'identique — En R

    match_key <- function(x) {
      x |>
        tidyr::replace_na("") |>
        stringi::stri_trans_general("Latin-ASCII") |>
        tolower() |>
        stringr::str_replace_all("[^a-z0-9]+", " ") |>
        stringr::str_squish()
    }
    
    wash  <- wash  |> mutate(district_key = match_key(district))
    admin <- admin |> mutate(key = match_key(admin1_name))
    
    matched <- wash |> left_join(admin, by = c("district_key" = "key"))
    sum(is.na(matched$admin1_pcode))
    Notes du présentateur
    La casse, les accents et la distinction trait d'union / espace expliquent chacune des quatre variantes de Nord-Ouest ici. NORD-OUEST, nord-ouest, Nord Ouest et Nord-Ouest se normalisent tous en nord ouest, et la jointure est exacte. Zéro ligne non appariée. C'est le cas courant et il vaut la peine de l'intérioriser — l'appariement approximatif vient après la normalisation, pas à sa place. Une grande partie de ce qui ressemble à un problème d'appariement difficile est de la ponctuation.
  9. Diapositive 9 / 24

    Le reliquat, c'est le vrai travail — En Python

    unmatched = (
        matched[matched["admin1_pcode"].isna()]
        .groupby("district")
        .size()
        .sort_values(ascending=False)
    )
    print(unmatched)
    Notes du présentateur
    Quand l'appariement exact ne solde pas tout — et sur des noms de villages cela n'arrive jamais — le reste est ce qu'il faut traiter.
  10. Diapositive 10 / 24

    Le reliquat, c'est le vrai travail — En R

    matched |>
      filter(is.na(admin1_pcode)) |>
      count(district, sort = TRUE)
  11. Diapositive 11 / 24

    Le reliquat, c'est le vrai travail

    • Traitez la liste par nombre de lignes, pas par ordre alphabétique — Les valeurs non appariées forment presque toujours…
    Notes du présentateur
    Traitez la liste par nombre de lignes, pas par ordre alphabétique. Les valeurs non appariées forment presque toujours une tête très courte et une longue traîne — deux ou trois variantes couvrant l'essentiel des lignes, puis des cas isolés. Solder la tête prend dix minutes et récupère la majeure partie des données. Pour la traîne, produisez des candidats plutôt que des décisions.
  12. Diapositive 12 / 24

    Le reliquat, c'est le vrai travail — En Python

    from rapidfuzz import process, fuzz
    
    for value in unmatched.index:
        best = process.extract(
            match_key(pd.Series([value]))[0],
            admin["key"].tolist(),
            scorer=fuzz.ratio,
            limit=3,
        )
        print(value, "->", best)
  13. Diapositive 13 / 24

    Le reliquat, c'est le vrai travail — En R

    for (value in unique(unmatched$district)) {
      scores <- stringdist::stringsim(match_key(value), admin$key, method = "jw")
      cat(value, "->", admin$admin1_name[order(-scores)][1:3], "\n")
    }
  14. Diapositive 14 / 24

    Les appariements qu'il faut refuser

    • Contraignez par l'unité parente. Un village n'a besoin d'être apparié qu'aux villages de sa propre commune, et une…
    • Refusez l'acceptation automatique en dessous d'un seuil élevé. Une quasi correspondance sur un nom de deux syllabes…
    • Laissez non apparié ce qui n'est pas apparié. Une ligne sans district est honnête et apparaît dans le tableau de…
    Notes du présentateur
    L'appariement approximatif sur des noms de lieux est plus dangereux qu'il n'y paraît, car les unités administratives sont fréquemment nommées les unes d'après les autres, et d'après les mêmes saints, rivières et administrateurs coloniaux. Deux communes réelles et distinctes peuvent différer d'un seul caractère. Trois habitudes qui évitent l'erreur coûteuse.
  15. Diapositive 15 / 24

    Les appariements qu'il faut refuser

    Le mode de défaillance n'est pas que le script n'arrive pas à apparier. C'est que le script apparie quelque chose, et que le ménage finit compté dans un district où il n'est pas.
  16. Diapositive 16 / 24

    La correspondance est un fichier, pas une suite de remplacements — En Python

    mapping = pd.read_csv("reference/district-mapping.csv")   # raw_value, admin1_pcode, decided_by, decided_on
    wash = wash.merge(mapping, left_on="district", right_on="raw_value", how="left")
    Notes du présentateur
    Le réflexe est un replace ou un case_when. Résistez-y.
  17. Diapositive 17 / 24

    La correspondance est un fichier, pas une suite de remplacements — En R

    mapping <- readr::read_csv(here::here("reference", "district-mapping.csv"))
    wash <- wash |> left_join(mapping, by = c("district" = "raw_value"))
  18. Diapositive 18 / 24

    La correspondance est un fichier, pas une suite de remplacements

    • Il est relisable par quelqu'un qui ne lit pas le code — en général la personne qui connaît les districts.
    • Il est réutilisable par le notebook, le tableau de bord et le script du prochain tour.
    • Il est comparable, si bien qu'ajouter une variante apparaît en revue comme une seule ligne.
    • Il porte qui a décidé. decided_by et decided_on transforment une correspondance d'artefact technique en trace,…
    Notes du présentateur
    Un fichier de correspondance l'emporte sur une suite de remplacements pour quatre raisons, et c'est la dernière qui compte vraiment.
  19. Diapositive 19 / 24

    Appariez une fois, joignez sur le code pour toujours — En Python

    wash = wash.drop(columns=["district", "district_key"]).rename(
        columns={"admin1_pcode": "admin1"}
    )
    Notes du présentateur
    Après l'appariement, retirez le nom en texte libre de tout l'aval et emportez le p-code.
  20. Diapositive 20 / 24

    Appariez une fois, joignez sur le code pour toujours — En R

    wash <- wash |> select(-district, -district_key) |> rename(admin1 = admin1_pcode)
    Notes du présentateur
    C'est l'étape qui fait que le problème reste résolu. Les dénominateurs de population, l'enquête du tour précédent, la matrice 4W du cluster et les données géographiques de la carte se joignent tous sur le code sans jamais recomparer un nom. Deux jeux de données porteurs de p-codes se joignent correctement du premier coup, ce qui est toute la raison d'être de ces codes.
  21. Diapositive 21 / 24

    L'assertion qui attrape la cinquième graphie du prochain tour — En Python

    EXPECTED_DISTRICTS = {"HT05", "HT07", "HT09"}
    
    seen = set(wash["admin1"].dropna())
    assert seen <= EXPECTED_DISTRICTS, f"unexpected districts: {seen - EXPECTED_DISTRICTS}"
    assert wash["admin1"].isna().sum() == 0, "rows with no district after mapping"
    Notes du présentateur
    Tout ce qui précède nettoie le fichier que vous avez. Une ligne empêche le même défaut d'arriver inaperçu au trimestre suivant.
  22. Diapositive 22 / 24

    L'assertion qui attrape la cinquième graphie du prochain tour — En R

    EXPECTED_DISTRICTS <- c("HT05", "HT07", "HT09")
    
    stopifnot(
      all(wash$admin1 %in% EXPECTED_DISTRICTS),
      !any(is.na(wash$admin1))
    )
    Notes du présentateur
    Remarquez ce que cela fait lorsque le programme s'étend réellement à un quatrième district — cela échoue. C'est correct. Un nouveau district est une chose dont quelqu'un devrait vous informer, et un script qui l'absorbe en silence calculera en silence un taux de couverture contre un dénominateur qui ne l'inclut pas.
  23. Diapositive 23 / 24

    La suite

    • Vous disposez maintenant de règles pour les valeurs et d'une correspondance pour les noms, appliquées l'une et l'autre à la main ce trimestre.
    Notes du présentateur
    Vous disposez maintenant de règles pour les valeurs et d'une correspondance pour les noms, appliquées l'une et l'autre à la main ce trimestre. L'unité suivante les fait tourner toutes seules — une suite de validation qui s'exécute à chaque lecture, rapporte les échecs avec leurs comptes, et arrête la chaîne avant qu'un mauvais export n'atteigne un tableau de bord.
  24. Diapositive 24 / 24

    La suite

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