cassionAnalyse de données

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.

PythonR60 minGestion axée sur les résultats (GAR)

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 .gitignore et 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é.

Animer cette leçon

La leçon en diaporama, la prose étant reléguée dans les notes du présentateur plutôt que projetée. Produit à partir de cette page, dont il ne peut donc pas s'écarter.

Le PDF ne requiert aucun logiciel et se projette depuis n'importe quel poste. Le fichier PowerPoint est fait pour être modifié : appliquez la charte de votre organisation, retirez une section pour une séance plus courte, ou fusionnez deux leçons en atelier.