Leçon 8 sur 8
Faire en sorte que cela tourne encore au trimestre suivant
Transformer une analyse qui fonctionne en une analyse qui se relance sur un nouvel export sans modification, échoue bruyamment quand l'entrée change, et se transmet à quelqu'un qui n'a rien de votre contexte.
Le test
L’export du trimestre suivant arrive. Combien d’éléments devez-vous modifier avant que le rapport ne se régénère ?
Si la réponse dépasse un — le chemin du fichier d’entrée — l’analyse n’est pas reproductible : c’est le compte rendu d’un après-midi. Cette leçon ramène la réponse à un.
Découpez l’analyse en étapes qui se passent des fichiers
Trois scripts, chacun avec une seule mission, chacun écrivant un fichier que le suivant lit.
scripts/
01-lecture.py export brut -> data/interim/muac-type.parquet
02-nettoyage.py typé -> data/processed/muac-propre.parquet
output/tables/journal-nettoyage.csv
03-indicateurs.py propre -> output/tables/mag-par-commune.csv
L’intérêt n’est pas le rangement. C’est que, lorsque le tableau d’indicateurs paraît faux, vous puissiez relancer la troisième étape en deux secondes au lieu de réexécuter un notebook de quinze minutes, et qu’un collègue qui débogue la deuxième n’ait pas à comprendre la troisième.
Utilisez Parquet plutôt que CSV entre les étapes. Ce format préserve les types —
autrement dit, le travail fait en leçon 3 pour que child_id soit lu comme du
texte n’est pas défait à la première écriture-relecture.
muac.to_parquet("data/interim/muac-type.parquet", index=False)
arrow::write_parquet(muac, "data/interim/muac-type.parquet")
Rassemblez les paramètres en un seul endroit
Chaque seuil, chemin et valeur de coupure figure en tête de fichier ou dans une configuration, jamais enfoui au milieu d’une expression.
# config.py
from pathlib import Path
BRUT = Path("data/raw/muac-screening-artibonite-2024.v1.csv")
INTERMEDIAIRE = Path("data/interim/muac-type.parquet")
TRAITE = Path("data/processed/muac-propre.parquet")
TABLEAUX = Path("output/tables")
MAS_MM = 115
MAG_MM = 125
PB_PLAUSIBLE = (80, 220)
AGE_MOIS = (6, 59)
CODE_MANQUANT = "-99"
# config.R
BRUT <- "data/raw/muac-screening-artibonite-2024.v1.csv"
INTERMEDIAIRE <- "data/interim/muac-type.parquet"
TRAITE <- "data/processed/muac-propre.parquet"
TABLEAUX <- "output/tables"
MAS_MM <- 115
MAG_MM <- 125
PB_PLAUSIBLE <- c(80, 220)
AGE_MOIS <- c(6, 59)
CODE_MANQUANT <- "-99"
Un relecteur qui veut connaître les seuils employés lit un seul fichier. Un
successeur qui adapte ce travail à un autre pays modifie un seul fichier. Et
quand l’OMS révise une valeur de coupure, vous changez un nombre au lieu de
chercher 125 dans quatre scripts en manquant celui qui s’écrit 12.5 * 10.
Faites échouer la chaîne bruyamment
Une analyse qui produit silencieusement un chiffre faux est pire qu’une analyse qui plante. Affirmez ce qui doit être vrai, à chaque frontière d’étape.
def controler_entree(df):
attendues = {
"child_id", "commune", "screening_date", "age_months",
"sex", "muac_mm", "oedema", "outcome",
}
absentes = attendues - set(df.columns)
if absentes:
raise ValueError(f"colonnes absentes de l'export : {sorted(absentes)}")
if df["child_id"].isna().any():
raise ValueError("lignes sans identifiant")
mesures = df["muac_mm"].dropna()
if len(mesures) == 0:
raise ValueError("aucune mesure de PB — mauvais fichier ?")
part_manquante = df["muac_mm"].isna().mean()
if part_manquante > 0.20:
raise ValueError(
f"{part_manquante:.1%} de PB manquants — au-dessus de la tolérance de 20 %. "
"À investiguer avant tout rapportage."
)
controler_entree <- function(df) {
attendues <- c("child_id", "commune", "screening_date", "age_months",
"sex", "muac_mm", "oedema", "outcome")
absentes <- setdiff(attendues, names(df))
if (length(absentes) > 0) {
stop("colonnes absentes de l'export : ", paste(absentes, collapse = ", "))
}
if (any(is.na(df$child_id))) stop("lignes sans identifiant")
part_manquante <- mean(is.na(df$muac_mm))
if (part_manquante > 0.20) {
stop(sprintf(
"%.1f%% de PB manquants - au-dessus de la tolerance de 20%%. A investiguer avant tout rapportage.",
part_manquante * 100
))
}
}
Le contrôle de seuil est le plus intéressant. Il encode un jugement éditorial — au-delà de 20 % de manquants, le taux ne mérite pas d’être publié — et l’applique automatiquement à chaque export futur, y compris à ceux traités par quelqu’un qui n’a jamais lu cette leçon.
Écrivez l’assertion correspondant à la défaillance qui vous embarrasserait, pas à celle que vous jugez probable. Les probables, vous les remarquerez.
Paramétrez au lieu de copier
Douze communes, douze rapports communaux. Ne copiez pas le script douze fois.
import sys
from pathlib import Path
def produire_rapport(commune: str | None = None):
df = pd.read_parquet(TRAITE)
if commune:
df = df[df["commune"] == commune]
if df.empty:
raise ValueError(f"aucune ligne pour la commune {commune!r}")
tableau = tableau_indicateurs(df, "tranche_age")
nom = commune.lower().replace(" ", "-") if commune else "toutes"
tableau.to_csv(TABLEAUX / f"mag-{nom}.csv", index=False)
return tableau
if __name__ == "__main__":
cible = sys.argv[1] if len(sys.argv) > 1 else None
produire_rapport(cible)
produire_rapport <- function(commune = NULL) {
df <- arrow::read_parquet(TRAITE)
if (!is.null(commune)) {
df <- filter(df, commune == !!commune)
if (nrow(df) == 0) stop("aucune ligne pour la commune ", commune)
}
tableau <- tableau_indicateurs(df, "tranche_age")
nom <- if (is.null(commune)) "toutes" else tolower(gsub(" ", "-", commune))
readr::write_csv(tableau, file.path(TABLEAUX, paste0("mag-", nom, ".csv")))
tableau
}
args <- commandArgs(trailingOnly = TRUE)
produire_rapport(if (length(args) > 0) args[1] else NULL)
Le même schéma s’étend au rapport narratif complet. Quarto compose un modèle unique avec un paramètre, douze fois, produisant douze documents qui ne peuvent pas diverger puisqu’il n’existe qu’un seul document.
Ce qui ne va jamais dans le dépôt
Ce point compte davantage dans ce secteur qu’ailleurs. Un dépôt contenant des données de programme peut contenir des noms de bénéficiaires, des points GPS qui identifient un ménage, ou une liste de cas de protection.
- Ne versionnez jamais de données brutes de bénéficiaires. Ni identifiées, ni pseudonymisées. Git conserve chaque version indéfiniment, et supprimer un fichier ne le retire pas de l’historique.
- Ne versionnez jamais d’identifiants de connexion. Jetons DHIS2, clés d’API KoboToolbox, mots de passe de base de données. Lisez-les depuis l’environnement.
- Ajoutez
data/raw/au.gitignoreet documentez dans le README où se trouve le vrai fichier — un disque partagé, un volume chiffré.
data/raw/
data/interim/
data/processed/
.env
*.Rhistory
__pycache__/
.Rproj.user/
Le code d’analyse est ce qui a sa place sous gestion de versions. Les données ont la leur là où la politique de protection des données de votre organisation le prescrit, et le README doit indiquer où.
Le README de passation
Rédigé pour quelqu’un qui aura votre intitulé de poste et rien de votre contexte. Cinq rubriques.
# Analyse du dépistage PB, Artibonite
## Ce que cela produit
Taux trimestriels de MAG et de MAS par commune, sexe et tranche d'âge, avec
intervalles à 95 %. Alimente la décision d'implantation des sites PCIMA et le
rapport bailleur trimestriel.
## D'où viennent les données
Export CommCare, projet `artibonite-nutrition`, formulaire `Dépistage
communautaire`. Exporté chaque mois par l'assistant suivi-évaluation vers le
disque partagé à <chemin>. Absent de ce dépôt.
## Comment l'exécuter
uv sync
uv run python scripts/01-lecture.py
uv run python scripts/02-nettoyage.py
uv run python scripts/03-indicateurs.py
## Décisions à connaître
Voir output/tables/journal-nettoyage.csv. Celle qui compte : les lignes sans
âge sont conservées, car 60 % de la semaine du 10 au 14 juin à Gros-Morne est
sans âge et leur suppression modifie le classement des communes.
## Qui contacter
Définitions d'indicateurs : <nom>, responsable suivi-évaluation.
Problèmes de collecte : <nom>, coordinateur terrain.
Ce README fait la différence entre un successeur qui poursuit votre travail et un successeur qui le reconstruit sur tableur faute d’avoir pu deviner ce que vos scripts supposaient.
Pour aller plus loin
Vous savez désormais mener un export de programme de son arrivée à un tableau d’indicateurs défendable, dans l’un ou l’autre langage, avec le raisonnement consigné. C’est le socle sur lequel s’appuie le reste du programme :
- Nettoyage et validation des données approfondit le profilage et les corrections des leçons 5 et 6, avec l’appariement approximatif et les jeux de règles de plausibilité.
- Conception d’indicateurs et cadre logique part de la fiche de référence de la leçon 7 et remonte jusqu’à la théorie du changement que l’indicateur est censé attester.
- Analyse d’enquêtes, échantillonnage et pondération couvre ce que cette formation n’a pas abordé : ce qui change lorsque les données constituent un échantillon probabiliste et non un recensement des personnes dépistées.
Entraînez-vous sur un autre jeu de données avant de poursuivre. L’enquête ménage EAH et le registre de couverture vaccinale de routine de cette plateforme portent un tout autre assortiment de défauts, et en traiter un de bout en bout sans la leçon sous les yeux est le véritable test de ce qui est resté.