cassionAnalyse de données

Retour à la leçonLeçon 4 sur 8Identité et doublons

La même personne, deux fois, sous deux identifiants

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 doublon sans clé
    • Commencez par les attributs qui ne devraient pas coïncider
    • Regardez les faux avant les vrais
    • Le blocage, et pourquoi on ne peut pas s'en passer
    • Normalisez avant de comparer
    • Distance d'édition et choix d'un seuil
    • Notez plusieurs champs, n'enchaînez pas les filtres
    • Le produit est une file de relecture, pas un tableau propre
    • Ce qu'il ne faut pas automatiser
    • La suite
    Notes du présentateur
    L'appariement d'enregistrements quand rien ne joint — blocage, normalisation, distance d'édition et score. Douze paires candidates dans le registre de dépistage, six réelles, et quatre fabriquées par les âges manquants de la leçon 2.
  2. Diapositive 2 / 24

    Le doublon sans clé

    • Un enfant dépisté le matin, renvoyé chez lui, puis ramené l'après-midi par un autre accompagnant est enregistré deux fois sous deux identifiants.
    Notes du présentateur
    Un enfant dépisté le matin, renvoyé chez lui, puis ramené l'après-midi par un autre accompagnant est enregistré deux fois sous deux identifiants. De même pour un ménage visité par deux enquêteurs dans la même rue. De même pour un bénéficiaire figurant sur trois listes de distribution sous trois graphies du même nom. Aucun de ces cas ne partage de clé. duplicated() ne les trouvera jamais. Et ils comptent — dans une charge de cas ils gonflent le numérateur, dans un taux de couverture ils gonflent le numérateur sans le dénominateur, et dans un décompte de bénéficiaires ils sont précisément ce qu'un audit cherche. Les retrouver relève de l'appariement d'enregistrements, discipline différente du dédoublonnage. Le dédoublonnage supprime des lignes identiques. L'appariement décide si deux lignes qui ne sont pas identiques décrivent la même chose — et il peut se tromper dans les deux sens, raison pour laquelle le produit de cette leçon est une liste à faire relire par un humain, et non un tableau nettoyé.
  3. Diapositive 3 / 24

    Commencez par les attributs qui ne devraient pas coïncider — En Python

    CANDIDATE_KEY = ["commune", "screening_date", "age_months", "sex", "muac_mm"]
    
    candidates = (
        muac.groupby(CANDIDATE_KEY, dropna=False)
        .filter(lambda g: g["child_id"].nunique() > 1)
        .sort_values(CANDIDATE_KEY)
    )
    print(candidates[["child_id"] + CANDIDATE_KEY].to_string(index=False))
    Notes du présentateur
    Le registre de dépistage n'a ni nom ni ménage. Il a une combinaison qui devrait être quasi unique par accident — commune, date, âge, sexe et mesure.
  4. Diapositive 4 / 24

    Commencez par les attributs qui ne devraient pas coïncider — En R

    CANDIDATE_KEY <- c("commune", "screening_date", "age_months", "sex", "muac_mm")
    
    candidates <- muac |>
      group_by(across(all_of(CANDIDATE_KEY))) |>
      filter(n_distinct(child_id) > 1) |>
      ungroup() |>
      arrange(across(all_of(CANDIDATE_KEY)))
    Notes du présentateur
    Douze groupes reviennent. Six sont le même enfant réinscrit sous un nouvel identifiant. Six sont deux enfants différents qui se trouvent coïncider.
  5. Diapositive 5 / 24

    Regardez les faux avant les vrais

    CommuneDateÂgeSexePBIdentifiants
    Desdunes2024-01-2036f134CH03315, CH09241
    Gros-Morne2024-06-10manquantf131CH00576, CH02413
    Gros-Morne2024-06-10manquantm123CH01302, CH04050
    Gros-Morne2024-06-13manquantm-99CH00519, CH00755
    Gros-Morne2024-06-14manquantm137CH01558, CH02913
    Verrettes2024-11-0926f127CH02193, CH03129
  6. Diapositive 6 / 24

    Regardez les faux avant les vrais

    • Une colonne de blocage comportant des valeurs manquantes fabrique des appariements — Excluez les lignes manquantes du…
    Notes du présentateur
    Quatre des douze viennent de Gros-Morne, la semaine du 10 juin, et toutes ont un âge manquant. C'est la semaine dont le formulaire était mal configuré — la non-réponse de la leçon 2 qui revient dans une autre leçon sous un autre déguisement. Voici pourquoi. Regrouper sur une colonne manquante fait s'apparier chaque ligne manquante avec toutes les autres sur cette colonne. Le bloc s'effondre : au lieu de comparer des enfants du même âge, vous comparez tous les enfants d'âge inconnu. Dans une commune qui dépiste quatre-vingt-cinq enfants en une semaine, deux garçons ayant le même PB au millimètre n'a rien de surprenant. Une colonne de blocage comportant des valeurs manquantes fabrique des appariements. Excluez les lignes manquantes du bloc ou bloquez sur autre chose — ne laissez jamais un trou compter comme un accord.
  7. Diapositive 7 / 24

    Regardez les faux avant les vrais — En Python

    blocked = muac[muac["age_months"].notna() & muac["muac_mm"].notna()]
  8. Diapositive 8 / 24

    Regardez les faux avant les vrais — En R

    blocked <- muac |> filter(!is.na(age_months), !is.na(muac_mm))
    Notes du présentateur
    Faites-le et huit candidats subsistent — les six réinscriptions réelles et deux coïncidences authentiques à Verrettes et Saint-Marc. Huit, c'est une liste qu'un superviseur peut confronter au registre papier en une après-midi. Douze, dont quatre absurdes, c'est une liste qui apprend aux gens à ignorer les listes.
  9. Diapositive 9 / 24

    Le blocage, et pourquoi on ne peut pas s'en passer — En Python

    blocks = blocked.groupby(["commune", "sex"])
    print(sum(len(g) * (len(g) - 1) // 2 for _, g in blocks), "pairs to compare")
    Notes du présentateur
    Comparer chaque ligne à toutes les autres est quadratique. Sur 4 218 enregistrements cela fait 8,9 millions de paires — lent mais supportable. Sur un registre de 300 000 bénéficiaires, cela fait 45 milliards, ce qui ne l'est pas. Le blocage consiste à ne comparer que les enregistrements déjà d'accord sur quelque chose de simple et fiable — la commune, le site de distribution, le mois, la première lettre du nom de famille. Les comparaisons coûteuses n'ont ensuite lieu qu'à l'intérieur de chaque bloc.
  10. Diapositive 10 / 24

    Le blocage, et pourquoi on ne peut pas s'en passer — En R

    blocked |>
      count(commune, sex) |>
      summarise(pairs = sum(n * (n - 1) / 2))
    Notes du présentateur
    L'arbitrage est explicite : un bloc trop grossier est lent, et un bloc trop fin rate les paires en désaccord sur la colonne de blocage. Deux graphies d'un nom de village placées dans des blocs différents ne seront jamais comparées. C'est pourquoi la colonne de blocage doit être celle en laquelle vous avez le plus confiance, et pourquoi bloquer sur un champ en texte libre est généralement une erreur.
  11. Diapositive 11 / 24

    Normalisez avant de comparer — En Python (suite)

    import re
    import unicodedata
    
    
    def normalise(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.replace(r"\s+", " ", regex=True)
            .str.strip()
        )
    
    
    Notes du présentateur
    Pour tout ce qui relève du texte, l'essentiel du travail se fait avant le calcul de la moindre distance.
  12. Diapositive 12 / 24

    Normalisez avant de comparer — En Python (suite)

    households["head_key"] = normalise(households["head_of_household"])
  13. Diapositive 13 / 24

    Normalisez avant de comparer — En R

    normalise <- function(x) {
      x |>
        tidyr::replace_na("") |>
        stringi::stri_trans_general("Latin-ASCII") |>
        tolower() |>
        stringr::str_replace_all("[^a-z0-9 ]", " ") |>
        stringr::str_squish()
    }
    
    households$head_key <- normalise(households$head_of_household)
  14. Diapositive 14 / 24

    Normalisez avant de comparer

    • Ne retirez les accents que pour la clé de comparaison, jamais dans les données conservées — Étienne est le nom de la…
    Notes du présentateur
    Casse, accents, espaces doubles et ponctuation expliquent la grande majorité des noms « différents » dans les données de ce secteur. Retirez-les et une part étonnante de votre problème d'appariement approximatif devient de l'appariement exact. Ne retirez les accents que pour la clé de comparaison, jamais dans les données conservées. Étienne est le nom de la personne. etienne est un index.
  15. Diapositive 15 / 24

    Distance d'édition et choix d'un seuil — En Python

    from rapidfuzz import fuzz, process
    
    matches = process.extract(
        "jean baptiste pierre",
        households["head_key"].tolist(),
        scorer=fuzz.token_sort_ratio,
        score_cutoff=88,
        limit=10,
    )
    Notes du présentateur
    Pour ce qui survit à la normalisation, comparez avec une distance entre chaînes. N'importe laquelle des distances usuelles convient ; ce qui compte est de choisir un seuil délibérément et de dire lequel.
  16. Diapositive 16 / 24

    Distance d'édition et choix d'un seuil — En R

    scores <- stringdist::stringsim(
      "jean baptiste pierre",
      households$head_key,
      method = "jw"
    )
    which(scores > 0.88)
    Notes du présentateur
    token_sort_ratio et Jaro-Winkler sont deux valeurs par défaut raisonnables ici, pour une raison qu'il vaut la peine de connaître — dans ce secteur les noms arrivent avec leurs éléments permutés (Pierre Jean Baptiste contre Jean Baptiste Pierre) et avec une faute de frappe bien plus rarement dans les premiers caractères que dans les derniers. Le seuil est un curseur entre précision et rappel, pas un réglage. À 95 vous obtenez peu de paires et manquez de vrais doublons. À 80 vous en obtenez beaucoup, la plupart fausses, et le relecteur cesse de lire. Réglez-le en prenant un échantillon de cinquante paires à votre seuil proposé et en vérifiant combien sont réelles. Inscrivez le seuil retenu et le taux de justesse mesuré dans le journal de nettoyage.
  17. Diapositive 17 / 24

    Notez plusieurs champs, n'enchaînez pas les filtres — En Python

    def pair_score(a, b):
        name = fuzz.token_sort_ratio(a["head_key"], b["head_key"]) / 100
        same_site = 1.0 if a["community"] == b["community"] else 0.0
        size_gap = abs(a["household_size"] - b["household_size"])
        size = max(0.0, 1 - size_gap / 5)
        return 0.6 * name + 0.25 * same_site + 0.15 * size
    Notes du présentateur
    Un seul champ ne suffit jamais. Comparez-en une poignée et combinez-les, pour qu'un accord fort sur un champ compense un accord faible sur un autre.
  18. Diapositive 18 / 24

    Notez plusieurs champs, n'enchaînez pas les filtres — En R

    pair_score <- function(a, b) {
      name      <- stringdist::stringsim(a$head_key, b$head_key, method = "jw")
      same_site <- as.numeric(a$community == b$community)
      size      <- pmax(0, 1 - abs(a$household_size - b$household_size) / 5)
      0.6 * name + 0.25 * same_site + 0.15 * size
    }
    Notes du présentateur
    Les pondérations relèvent du jugement, et elles doivent être visibles plutôt qu'enfouies dans une chaîne de conditions. Un filtre enchaîné traite chaque critère comme absolu : une faute dans le nom de la communauté et la paire disparaît. Un score laisse les indices s'additionner, ce qui est de toute façon la manière dont un relecteur humain raisonne.
  19. Diapositive 19 / 24

    Le produit est une file de relecture, pas un tableau propre — En Python

    review = (
        pairs.sort_values("score", ascending=False)
        .loc[:, ["id_a", "id_b", "score", "head_a", "head_b", "community", "size_gap"]]
        .assign(decision="", reviewer="", reviewed_on="")
    )
    review.to_csv("outputs/review/duplicate-candidates.csv", index=False)
    Notes du présentateur
    C'est ce qui sépare un appariement utile d'un appariement qui provoque un incident.
  20. Diapositive 20 / 24

    Le produit est une file de relecture, pas un tableau propre — En R

    review <- pairs |>
      arrange(desc(score)) |>
      select(id_a, id_b, score, head_a, head_b, community, size_gap) |>
      mutate(decision = "", reviewer = "", reviewed_on = "")
    
    readr::write_csv(review, here::here("outputs", "review", "duplicate-candidates.csv"))
  21. Diapositive 21 / 24

    Le produit est une file de relecture, pas un tableau propre

    Un doublon fusionné est un enregistrement détruit. Si la fusion était fausse, la preuve qu'elle l'était a disparu avec lui.
    Notes du présentateur
    Trois colonnes vides, et c'est là tout l'intérêt. Le fichier part chez la personne qui tient le registre, revient avec une décision sur chaque ligne, et c'est ce fichier revenu que le script de nettoyage lit — pas le score, et pas un seuil appliqué automatiquement.
  22. Diapositive 22 / 24

    Ce qu'il ne faut pas automatiser

    • Une fausse fusion supprime quelqu'un. Deux ménages n'en font plus qu'un ; l'un cesse de recevoir l'assistance sans…
    • Une fausse scission compte double. C'est coûteux et embarrassant, et cela ne prive personne de rien.
    Notes du présentateur
    Dans les données de bénéficiaires, de protection et de gestion de cas, la fusion automatique n'est pas un raccourci technique à faible taux d'erreur. C'est une décision qui porte sur une personne, et les deux modes de défaillance ne sont pas symétriques. Cette asymétrie impose de sous-fusionner par défaut. Signalez, faites relire, et laissez décider la personne qui tient le registre. Lorsqu'une fusion a lieu, conservez les deux identifiants d'origine dans l'enregistrement fusionné pour pouvoir revenir en arrière — une table d'appariement avec kept_id, merged_id, score, reviewer et date à côté des données. Rien de tout cela n'est de la prudence gratuite. Les principes de gestion de l'information VBG qui régissent une partie des données de ce secteur le disent directement : le préjudice d'un enregistrement mal traité retombe sur la personne qu'il décrit, pas sur l'analyste.
  23. Diapositive 23 / 24

    La suite

    • Vous savez désormais quelles lignes désignent la même chose et quelles colonnes les identifient.
    Notes du présentateur
    Vous savez désormais quelles lignes désignent la même chose et quelles colonnes les identifient. L'unité suivante s'attaque aux valeurs elles-mêmes — les mesures qui ne peuvent pas être vraies, les codes de sortie qui contredisent leur propre mesure, et les seuils sectoriels qui transforment « cela semble faux » en une règle qu'un script applique.
  24. Diapositive 24 / 24

    La suite

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