Retour à la leçon·Leçon 6 sur 8·Des 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.
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.Le tableau à six lignes pour trois districts
district Ménages Sous 15 L/personne/jour NORD-OUEST 33 21,2 % nord-ouest 20 20,0 % Sud-Est 803 16,4 % Nord-Ouest 727 14,7 % Nord Ouest 22 9,1 % Centre 798 8,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.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.La liste administrative fait autorité — En R
admin <- tibble::tribble( ~admin1_pcode, ~admin1_name, "HT07", "Nord-Ouest", "HT05", "Centre", "HT09", "Sud-Est" )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.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.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")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 OuestetNord-Ouestse normalisent tous ennord 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.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.Le reliquat, c'est le vrai travail — En R
matched |> filter(is.na(admin1_pcode)) |> count(district, sort = TRUE)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.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)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") }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.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.
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 unreplaceou uncase_when. Résistez-y.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"))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_byetdecided_ontransforment 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.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.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.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.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.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.