cassionAnalyse de données

Leçon 6 sur 8

Unité · Un modèle, plusieurs sorties

Une chaîne qui échoue vaut mieux qu'une chaîne qui devine

L'export amont gagne une colonne, en perd une, change un code de « yes » à « Y », ou arrive avec la moitié des lignes. Une chaîne qui continue produit un nombre faux et plausible. Sept vérifications l'arrêtent, et chacune a une défaillance réelle derrière elle.

PythonR135 minCritères d'évaluation du CAD de l'OCDEDéfinitions d'indicateurs de l'UNICEFNorme humanitaire fondamentale (CHS)

La défaillance qui n’a pas de message d’erreur

L’export mensuel arrive. Une colonne qui disait true dit maintenant Y. Votre conversion booléenne transforme chacune d’elles en valeur manquante, le dénominateur perd 30 %, et la chaîne s’exécute jusqu’au bout et produit un rapport.

Rien n’a échoué. Le graphique est tracé, le pourcentage est plausible, le fichier est daté d’aujourd’hui, et le nombre est faux.

C’est le mode de défaillance pour lequel cette leçon existe, et il est bien plus courant qu’un plantage. Un plantage se corrige le matin même.

Sept vérifications, chacune avec une défaillance réelle derrière

Chacune correspond à un défaut documenté dans les jeux de données de cette plateforme.

Une : les colonnes attendues sont présentes.

REQUIRED = {"household_id", "district", "water_source", "round_trip_minutes"}
missing = REQUIRED - set(survey.columns)
if missing:
    raise ValueError(f"Export is missing columns: {sorted(missing)}")
stopifnot(all(required %in% names(survey)))

Deux : le nombre de lignes est dans la plage attendue.

if not 2_000 <= len(survey) <= 3_000:
    raise ValueError(f"Expected 2,000-3,000 households, got {len(survey):,}")

La moitié d’un export est la défaillance silencieuse la plus courante. Un téléchargement tronqué, un filtre resté actif, une plage de dates décalée d’un mois — tous produisent un fichier valide avec trop peu de lignes.

Trois : les codes sont les codes que vous connaissez.

KNOWN = {"piped-into-dwelling", "piped-into-yard", "public-tap", "borehole",
         "protected-well", "protected-spring", "unprotected-well",
         "unprotected-spring", "surface-water", "tanker-truck", "rainwater"}
unknown = set(survey["water_source"].dropna()) - KNOWN
if unknown:
    raise ValueError(f"Unknown water_source values: {sorted(unknown)}")
setdiff(unique(survey$water_source), known)

Un nouveau code est une décision, non une donnée. Quelqu’un a ajouté une option au formulaire et l’analyse doit décider où elle va — la faire tomber discrètement dans « autre » revient à laisser un .fillna() prendre la décision.

Quatre : l’identifiant est unique là où il doit l’être.

duplicates = survey["household_id"].duplicated().sum()
if duplicates:
    raise ValueError(f"{duplicates} duplicate household_id values")

Le registre scolaire de cette plateforme a exactement ce défaut — deux élèves y figurent deux fois après un transfert jamais radié — et une jointure nue duplique leurs lignes. Une vérification l’aurait attrapé à la jointure plutôt que dans un coefficient.

Cinq : les valeurs manquantes sont là où vous les attendez.

completeness = survey.notna().mean()
if completeness["district"] < 0.99:
    raise ValueError(f"district is {completeness['district']:.1%} complete")

Six : les nombres sont dans une plage possible.

implausible = survey[(survey["litres_per_person_day"] < 0)
                     | (survey["litres_per_person_day"] > 200)]
if len(implausible) > 20:
    raise ValueError(f"{len(implausible)} implausible litres values")

Notez le seuil plutôt qu’une tolérance nulle. Onze ménages avec une erreur d’unité est un défaut documenté que cette analyse traite ; deux cents est un problème nouveau.

Sept : la sortie est celle que vous avez déclarée.

assert summary["n"].sum() == len(survey), "rows lost between input and summary"

Un nombre de lignes qui change au travers d’une jointure est l’assertion la plus utile de l’analyse de programme, parce qu’une jointure interne qui écarte un tiers des données ressemble exactement à une jointure interne qui n’en écarte aucune.

Échouez au point de défaillance, non à la fin

def load_survey(path: pathlib.Path) -> pd.DataFrame:
    survey = pd.read_csv(path)
    check_columns(survey)
    check_rows(survey)
    check_codes(survey)
    return survey                # nothing downstream runs on a bad file
load_survey <- function(path) {
  survey <- readr::read_csv(path)
  check_columns(survey); check_rows(survey); check_codes(survey)
  survey
}

Vérifiez à la frontière — là où les données entrent, et après chaque jointure. Une vérification en fin de chaîne vous dit que quelque chose ne va pas ; une vérification à la frontière vous dit quoi.

Levez une erreur, n’avertissez pas. Un avertissement dans un journal que personne ne lit équivaut à aucune vérification, et le journal n’est pas lu précisément les jours chargés où l’export casse.

Ce que fait cette plateforme

Sept vérifications font échouer astro build, délibérément, et elles ont la même forme.

Vérification Attrape
Schéma Zod Un champ manquant ou du mauvais type
Références inter-collections Un chemin pointant vers un cours qui n’existe pas
Fichiers hors d’un répertoire de langue Une entrée sans langue
Parité de traduction Une entrée publiée dans une seule langue
Cohérence sujet-secteur Un sujet étiqueté hors de son secteur
Synthétique uniquement Un jeu de données non déclaré synthétique
Ossature du programme Un cours publié absent de la carte du cursus

Quatre autres s’exécutent dans pnpm test plutôt que dans la compilation, parce qu’elles ont besoin de node:fs et que la compilation pré-rend dans un worker Cloudflare qui n’en a pas. C’est une contrainte réelle qui a décidé où vivent les vérifications, et elle vaut d’être nommée : placez la vérification là où elle peut s’exécuter, non là où c’est le plus élégant.

L’une d’elles vérifie une relation que le graphe de références ne peut structurellement pas atteindre. Les règles de références vérifient qu’un slug déclaré se résout ; elles ne peuvent pas vérifier qu’une entrée est pointée. Un cours pourrait donc être livré sans pratique attachée et chaque barrière resterait verte — d’où l’existence de course-practice.test.ts, qui compte à rebours depuis chaque cours publié vers le laboratoire et l’exercice qui doivent le nommer.

Toute règle du type « chaque X a au moins un Y » exige un test de cette forme. Un graphe de références est le mauvais outil pour cela.

La règle à retenir

Tout champ nommant un fichier livré exige un drapeau ready et un test de système de fichiers à côté. Cette plateforme l’a appris trois fois : vingt exemples travaillés déclarés et jamais écrits, trente-cinq livrables de projet déclarés et jamais écrits, et un ensemble de chemins de figures qui ne pointaient vers rien.

Un chemin dans un frontmatter est une chaîne de caractères, et rien dans une compilation ne peut dire s’il pointe vers quelque chose. Le schéma met donc ready à false par défaut et un test vérifie que le fichier existe.

Rapportez-le en entier

Pipeline checks

  The loader validates every raw export before anything downstream runs:
  required columns present, 2,000-3,000 rows, water_source values within the
  known set, household_id unique, district at least 99% complete.

  Implausible litres-per-person values are tolerated up to 20 rows, which is
  the documented unit-entry defect; above that the run stops.

  Row counts are asserted across every join. A join that changes the row
  count stops the pipeline rather than producing a summary.

  All checks raise rather than warn. The March run stopped on an unknown
  water_source value ("piped-shared"), which turned out to be a new form
  option added upstream; it is now mapped explicitly in src/clean.py.

La dernière phrase est ce qui rend la section crédible. Une section de vérifications qui n’a jamais rien attrapé est une section de vérifications que personne n’a testée.

La suite

Tout ce qui précède suppose que vous êtes encore là. La leçon suivante porte sur le document qui doit fonctionner quand vous n’y êtes plus — écrit pour quelqu’un qui a votre poste et rien de votre contexte.

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.

Lancer le diaporamaLire les diapositives

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.