Livrer un jeu de données réutilisable
- Leçon 18
- 55 min
- Module 6 : Contrats de données et passation
Leçon 18 sur 18 · SQL et préparation des données pour l'IALivrer un jeu de données réutilisable
1. Sources et structure des données
2. L'essentiel de SQL
3. Des requêtes d'analyse utiles
4. Nettoyer et transformer les données
5. Préparer la modélisation sans risque
6. Contrats de données et passation
Ce que vous saurez faire
Livrez le jeu de données comme un projet que tout le monde peut relancer à partir de zéro avec une commande. Les mêmes données d’entrée doivent produire les mêmes fichiers. Si les données changent, l’exécution doit s’arrêter avec un message clair.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter écrit check_input.py et run_pipeline.py. Il exécute le pipeline deux fois à partir d’un instantané propre et compare les sommes de contrôle SHA-256. Ensuite, il utilise l’exportation d’août et fait l’exercice du module dans une nouvelle copie. Ses premières cellules installent pandas, téléchargent les données d’exercice et reconstruisent les fichiers des leçons précédentes. Il fonctionne donc sans votre projet local. Lisez les explications ici et exécutez le code dans le carnet.
Le carnet est en anglais. Colab et Kaggle ouvrent votre propre copie. Il faut un compte Google ou Kaggle gratuit. Vous êtes bloqué ? Consultez le carnet de solution.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Le contrat de données et les codes de sortie, dans Les règles de validation.
- Les sommes de contrôle, les registres de provenance et la fiche du jeu de données, dans Suivre la provenance des données.
- Pour revoir une passation avec une nouvelle copie, consultez Qualité et passation : prouvez-le : une nouvelle copie dans Python pour l’ingénierie de l’IA en pratique.
Ce qu’il vous faut sur votre ordinateur (seulement pour travailler localement; le carnet n’en a pas besoin)
- Le projet
~/projects/ticket-dataavecget_data.pyet les fichiers bruts dansdata/raw/, de Préparer une base de données d’exercice : créer la base de données. Cette leçon reconstruitlarkfield.dbavecpython get_data.py --reset, comme dans La base de données est protégée et vous pouvez la réinitialiser. - L’environnement virtuel
.venv, de Profiler avant de modifier : le même profil dans pandas.requirements.txtcontientpandas==3.0.6etscikit-learn==1.9.1, de Ajuster les transformations correctement : installer scikit-learn. - Les scripts des leçons précédentes :
scripts/extract.pyet les deux fichiers de requêtes dansqueries/, de Une extraction reproductible : créer extract.py.scripts/clean.py, de Traiter les valeurs désordonnées : terminer clean.py.scripts/build_features.py, de Caractéristiques et moment de la cible : build_features.py.scripts/split.py, de Choisir une séparation : créer et vérifier la séparation.scripts/validate.py, de Les règles de validation : le contrat dans validate.py.
DATASET.mddans le dossier du projet, de Suivre la provenance des données : la fiche du jeu de données.- Si un script manque ou ne fonctionne pas, copiez la version terminée du cours depuis C04/pipeline au même endroit dans votre projet. Vous n’avez pas à réparer les fichiers de sortie à la main :
run_pipeline.pyles supprime et les reconstruit. - Une connexion Internet, pour télécharger un petit fichier. Aucun compte n’est nécessaire et il n’y a aucun coût.
Testé avec Python 3.14.6 et pandas 3.0.6 sur macOS. Les commandes Windows n’ont pas été testées. Le carnet a été testé dans Jupyter.
Ce que signifie « réutilisable »
Section intitulée « Ce que signifie « réutilisable » »Priya ne veut pas seulement les fichiers. Elle veut pouvoir les recréer : le mois prochain, sur son ordinateur, après une correction. Mei fait trois promesses :
- Une commande. Toute la préparation s’exécute avec une commande, à partir d’un instantané propre. Personne n’a besoin de mémoriser l’ordre des étapes.
- Mêmes entrées, mêmes sorties. Deux exécutions sur le même instantané produisent les mêmes fichiers, octet pour octet. Un processus qui possède cette propriété est reproductible.
- Entrées modifiées, échec clair. Quand l’exportation change d’une façon que les scripts ne prévoient pas, l’exécution s’arrête et indique ce qui a changé.
Comment prouver la deuxième promesse? Vous ne pouvez pas comparer 1 369 lignes à l’œil. Vous comparez des sommes de contrôle. Voici un petit exemple dans le mode interactif de Python :
>>> import hashlib>>> hashlib.sha256(b"T-1,email,2\n").hexdigest()'aba761b1f15fa31acb1313ea20118288337bfaba145be01e9ead1e03c7e47ee2'>>> hashlib.sha256(b"T-1,email,2\n").hexdigest()'aba761b1f15fa31acb1313ea20118288337bfaba145be01e9ead1e03c7e47ee2'>>> hashlib.sha256(b"T-1,Email,2\n").hexdigest()'ebd66142a4ce9df959615da1256552e7679bbe07d069ed9017a31eabfac4b550'Les mêmes octets donnent la même somme de contrôle. Une seule majuscule en donne une complètement différente. Donc, si deux exécutions donnent la même somme de contrôle pour train.csv, les deux fichiers sont identiques.
Le projet, organisé pour la passation
Section intitulée « Le projet, organisé pour la passation »Le projet de Mei contient maintenant tout ce dont Priya a besoin, à un endroit fixe :
ticket-data/ run_pipeline.py the one command DATASET.md the dataset card (Track lineage) requirements.txt pinned versions get_data.py, q.py from the labs repository queries/ extract_tickets.sql, extract_outcomes.sql, 60-contract-checks.sql scripts/ check_input.py the input schema (new in this lesson) extract.py, clean.py, build_features.py, split.py validate.py the output contract (Validation rules) data/raw/ the export files data/extract/ data/clean/ data/model/ written by the pipeline reports/ cleaning.json and validation.json: the quality report| Question de Priya | Fichier qui y répond |
|---|---|
| Comment les données ont-elles été sélectionnées? | queries/*.sql, scripts/extract.py, data/extract/manifest.json |
| Quel schéma l’exportation doit-elle respecter? | scripts/check_input.py |
| Sur quoi puis-je compter dans les fichiers livrés? | scripts/validate.py, reports/validation.json |
| Comment la séparation a-t-elle été faite? | scripts/split.py, data/model/split_manifest.json |
| Qu’est-ce qui a été supprimé ou corrigé? | reports/cleaning.json, data/clean/quarantine.csv |
| Mes fichiers sont-ils identiques aux vôtres? | data/model/MANIFEST.json (cette leçon) |
Il manque deux nouveaux fichiers : la vérification des entrées et la commande unique.
La vérification des entrées
Section intitulée « La vérification des entrées »validate.py vérifie les sorties. La nouvelle exportation peut aussi rendre les entrées incompatibles : une colonne renommée ou des nombres écrits en texte. Le pipeline doit détecter cela avant de charger les données. Sinon, un fichier chargé peut provoquer des erreurs silencieuses. La vérification se fait dans un script distinct, scripts/check_input.py. Il utilise seulement la bibliothèque standard de Python.
"""Check the raw export files before anything loads them: each file must havethe expected columns, and each value must be readable as its column's type.
python scripts/check_input.py python scripts/check_input.py --tickets data/raw/tickets-2026-08-01.csv
This check looks at the shape of the data only. Messy values (spellings,duplicates, sentinels) are the cleaning step's job. Exit code 1 on failure."""
import argparseimport csvimport sysfrom datetime import datetimefrom pathlib import Path
# The schema of the export of 2026-07-01: column name -> type.SCHEMAS = { "customers": {"customer_id": "text", "segment": "text", "region": "text", "joined_on": "date"}, "tickets": { "ticket_id": "text", "customer_id": "text", "created_at": "datetime", "channel": "text", "team": "text", "priority": "integer", "order_value": "number", "word_count": "integer", "first_reply_minutes": "integer", "priority_now": "integer", "closed_at": "datetime", }, "outcomes": { "outcome_id": "text", "ticket_id": "text", "recorded_at": "datetime", "status": "text", "resolution_code": "text", "csat": "integer", },}
# Each type has a function that raises ValueError for a value it cannot read.READERS = { "text": str, "integer": int, "number": float, "date": lambda v: datetime.strptime(v, "%Y-%m-%d"), "datetime": lambda v: datetime.strptime(v, "%Y-%m-%d %H:%M:%S"),}
def check_file(path, schema): """Return a list of problems. An empty list means the file is OK.""" if not path.exists(): return [f"file not found: {path}"] with open(path, newline="", encoding="utf-8") as f: reader = csv.reader(f) header = next(reader, []) rows = list(reader)
problems = [] missing = [c for c in schema if c not in header] extra = [c for c in header if c not in schema] for column in missing: problems.append(f"missing column: {column}") for column in extra: hint = " (was a column renamed?)" if missing else "" problems.append(f"unexpected column: {column}{hint}")
# An empty value is a missing value. It is allowed here; only values that # are present must have the right type. for column, kind in schema.items(): if column not in header or kind == "text": continue i = header.index(column) bad = [] for line, row in enumerate(rows, start=2): # line 1 is the header value = row[i] if value == "": continue try: READERS[kind](value) except ValueError: bad.append((line, value)) if bad: line, value = bad[0] present = sum(1 for row in rows if row[i] != "") problems.append(f"{column}: {len(bad)} of {present} values are not a valid {kind}" f" (first: line {line}, {value!r})") return problems
def main(): parser = argparse.ArgumentParser() for name in SCHEMAS: parser.add_argument(f"--{name}", default=f"data/raw/{name}.csv", type=Path) args = parser.parse_args()
failed = False for name, schema in SCHEMAS.items(): path = getattr(args, name) problems = check_file(path, schema) print(f"{'PASS' if not problems else 'FAIL'} {path}") for problem in problems: print(f" {problem}") failed = failed or bool(problems)
if failed: print("The input does not match the expected schema. Do not load it.") sys.exit(1) print("The input matches the expected schema.")
if __name__ == "__main__": main()| Partie | Pourquoi |
|---|---|
SCHEMAS |
Le schéma attendu, écrit à la main, comme dans validate.py. C’est le contrat avec l’exportation du service de soutien. |
READERS |
Une fonction par type. float("$10.85") déclenche ValueError, donc la vérification détecte les nombres écrits en texte. |
--tickets et les autres options |
Par défaut, le script vérifie data/raw/. Avec une option, il vérifie un nouveau fichier avant son utilisation. |
| Seulement l’en-tête et les types | Les graphies comme Email (courriel) ne sont pas des problèmes de schéma. clean.py les traite et validate.py vérifie le résultat. Chaque vérification a un seul rôle. |
La commande unique
Section intitulée « La commande unique »run_pipeline.py exécute les six étapes dans l’ordre. Il s’arrête à la première étape qui échoue. À la fin, il écrit data/model/MANIFEST.json, avec une somme de contrôle et un nombre de lignes pour chaque fichier de sortie.
"""Run the whole preparation, in order, from larkfield.db to a validateddataset. Stop at the first step that fails. At the end, writedata/model/MANIFEST.json with a SHA-256 checksum of every output file.
python get_data.py --reset start from a clean snapshot python run_pipeline.py
python run_pipeline.py --tickets data/raw/tickets-2026-08-01.csv check a new export before you load it"""
import argparseimport csvimport hashlibimport jsonimport shutilimport sqlite3import subprocessimport sysfrom datetime import datetime, timezonefrom importlib.metadata import versionfrom pathlib import Path
STEPS = [ "scripts/check_input.py", "scripts/extract.py", "scripts/clean.py", "scripts/build_features.py", "scripts/split.py", "scripts/validate.py",]# Folders that only the pipeline writes. They are deleted before a run, so an# old file can never look like the output of this run.OUTPUT_FOLDERS = [Path("data/extract"), Path("data/clean"), Path("data/model")]OUTPUTS = [ "data/extract/tickets.csv", "data/extract/outcomes.csv", "data/clean/tickets.csv", "data/clean/quarantine.csv", "data/clean/outcomes.csv", "data/model/tickets_model.csv", "data/model/train.csv", "data/model/valid.csv", "data/model/test.csv", "data/model/build_summary.json", "data/model/split_manifest.json", "reports/cleaning.json", "reports/validation.json",]MANIFEST = Path("data/model/MANIFEST.json")
def sha256(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def describe(path): """Checksum, and the number of data rows for a CSV file.""" entry = {"sha256": sha256(path)} if path.endswith(".csv"): with open(path, newline="", encoding="utf-8") as f: entry["rows"] = sum(1 for _ in csv.reader(f)) - 1 # minus the header return entry
def main(): parser = argparse.ArgumentParser() parser.add_argument("--tickets", default="data/raw/tickets.csv", help="the raw tickets file for the input check") args = parser.parse_args()
if not Path("larkfield.db").exists(): sys.exit("larkfield.db not found. Run: python get_data.py") for folder in OUTPUT_FOLDERS: shutil.rmtree(folder, ignore_errors=True)
started = datetime.now(timezone.utc) for number, step in enumerate(STEPS, start=1): command = [sys.executable, step] # the same Python that runs this script if step == "scripts/check_input.py": command += ["--tickets", args.tickets] name = Path(step).stem result = subprocess.run(command, capture_output=True, text=True) if result.returncode != 0: print(f"{number}/{len(STEPS)} {name}: FAILED\n") print((result.stdout + result.stderr).rstrip()) print(f"\nStopped at step {number} ({name}). No MANIFEST.json was written: this run delivered no dataset.") sys.exit(1) print(f"{number}/{len(STEPS)} {name}: ok")
# The extraction manifest says which snapshot and which selection the # dataset comes from. Its time stamp goes in "run", with this run's. extract = json.loads(Path("data/extract/manifest.json").read_text(encoding="utf-8")) outputs = {path: describe(path) for path in OUTPUTS} manifest = { "dataset": "Larkfield ticket escalation dataset", "source": extract["source"], "selection": extract["selection"], "queries": {f["query"]: f["query_sha256"] for f in extract["files"]}, "steps": STEPS, "outputs": outputs, # One checksum for all outputs together: equal outputs give an # equal fingerprint, whatever the time of the run. "outputs_fingerprint": hashlib.sha256(json.dumps(outputs, sort_keys=True).encode()).hexdigest(), "run": { "started_at": started.isoformat(timespec="seconds"), "extracted_at": extract["extracted_at"], "python": sys.version.split()[0], "sqlite": sqlite3.sqlite_version, "pandas": version("pandas"), }, } MANIFEST.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") model = outputs["data/model/tickets_model.csv"]["rows"] parts = ", ".join(f"{p} {outputs[f'data/model/{p}.csv']['rows']}" for p in ["train", "valid", "test"]) print(f"Done: {model} rows ({parts}).") print(f"Wrote {MANIFEST}. Outputs fingerprint: {manifest['outputs_fingerprint']}")
if __name__ == "__main__": main()Quatre décisions permettent de relancer le pipeline sans risque :
- Supprimer d’abord les anciennes sorties. Si une étape échoue, son ancien fichier d’hier ne doit pas sembler être le résultat d’aujourd’hui. L’exécution commence donc avec des dossiers de sortie vides.
- Chaque étape est un programme distinct.
subprocess.runlance chaque script avec le même Python (sys.executable) et lit son code de sortie. C’est pourquoivalidate.pyetcheck_input.pyse terminent avecsys.exit(1)en cas d’échec. Une étape qui échoue arrête l’exécution, et son propre message s’affiche. - Séparer le contenu des heures. Les sommes de contrôle sont dans
outputs. L’heure de l’exécution et celle de l’extraction sont dansrun.extracted_atchange à chaque exécution. Donc,manifest.jsonlui-même n’est pas dansoutputs; son contenu est copié dans le manifeste. - Une empreinte unique.
outputs_fingerprintest une somme de contrôle de toutes les sommes de contrôle des sorties réunies. Pour comparer deux exécutions, vous comparez une seule ligne.
Exemple détaillé : deux exécutions à partir d’un instantané propre
Section intitulée « Exemple détaillé : deux exécutions à partir d’un instantané propre »Mei part d’un instantané propre. get_data.py --reset supprime la base de données et la reconstruit à partir des fichiers CSV vérifiés :
Deleted larkfield.db.customers 240 rowstickets 1417 rowsoutcomes 1874 rowsBuilt larkfield.db. Check: customers 240, tickets 1417, outcomes 1874.1/6 check_input: ok2/6 extract: ok3/6 clean: ok4/6 build_features: ok5/6 split: ok6/6 validate: okDone: 1369 rows (train 927, valid 227, test 215).Wrote data/model/MANIFEST.json. Outputs fingerprint: 2b2460321543ffb73fe9d5b04fd8f5a9d77ee990fff37823b0855b911ba4b855Une partie du manifeste :
"outputs": { "data/extract/tickets.csv": { "sha256": "96786af9e2ebb2492219867ac302f57fabdefb4c53fb60a1071d9412e70e6f39", "rows": 1416 }, ... "data/model/train.csv": { "sha256": "e83c4a056c6e26595777760918e49d9f9b9b39be7bb561a20aefd25dd4c177b8", "rows": 927 }, ... }, "outputs_fingerprint": "2b2460321543ffb73fe9d5b04fd8f5a9d77ee990fff37823b0855b911ba4b855", "run": { "started_at": "2026-10-07T03:13:22+00:00", "extracted_at": "2026-10-07T03:13:22+00:00", "python": "3.14.6", "sqlite": "3.50.4", "pandas": "3.0.6" }Elle conserve une copie du manifeste, relance le pipeline et compare. L’empreinte est identique. Ensuite, elle compare les deux fichiers ligne par ligne avec diff, un outil standard sur macOS et Linux :
77,78c77,78< "started_at": "2026-10-07T03:13:22+00:00",< "extracted_at": "2026-10-07T03:13:22+00:00",---> "started_at": "2026-10-07T03:13:25+00:00",> "extracted_at": "2026-10-07T03:13:25+00:00",Seules les deux heures diffèrent. Les 13 fichiers de sortie sont tous identiques. Le pipeline est reproductible.
Mei ajoute les sommes de contrôle des sorties à DATASET.md, pour que Priya puisse vérifier les fichiers reçus. Elle ajoute aussi check_input.py comme étape 1 dans le tableau Transformation order (ordre des transformations). Elle place la commande au-dessus : « Exécutez python get_data.py --reset, puis python run_pipeline.py. »
## Output checksums (SHA-256)
| File | Rows | SHA-256 ||---|---|---|| `data/model/tickets_model.csv` | 1,369 | cb6e783f28cb17f45a7ec1412a3714a49d4ec10a292f8077dcc7bafb7421235b || `data/model/train.csv` | 927 | e83c4a056c6e26595777760918e49d9f9b9b39be7bb561a20aefd25dd4c177b8 || `data/model/valid.csv` | 227 | 5a01365d2294d32db58a55a48a8f5b4c5958e0dc381ae06c9fdc33c5fb0e88cd || `data/model/test.csv` | 215 | 1c0aa441b17db00d66f5f5df9f604c38d99787fd69c9894f987a7ca548d04d1b |
Outputs fingerprint (all 13 output files): `2b2460321543ffb73fe9d5b04fd8f5a9d77ee990fff37823b0855b911ba4b855`. Two runs gave the same fingerprint. On Windows, pandas writes CSV files with different line endings, so the checksums differ from these; compare two runs on the same computer.Exercice guidé : la commande unique, deux fois
Section intitulée « Exercice guidé : la commande unique, deux fois »-
Créez
scripts/check_input.pyetrun_pipeline.pyavec le code ci-dessus. Placezrun_pipeline.pydans le dossier du projet, pas dansscripts/. -
Dans le terminal, allez dans le dossier de votre projet et vérifiez que l’environnement est actif.
-
Partez d’un instantané propre :
python get_data.py --reset. La dernière ligne estBuilt larkfield.db. Check: customers 240, tickets 1417, outcomes 1874.(base larkfield.db créée; vérification des nombres de clients, de billets et de résultats). -
Exécutez
python run_pipeline.py. Vous devriez voir six lignesok(réussite) etDone: 1369 rows (train 927, valid 227, test 215).(terminé : 1 369 lignes, dont 927 d’entraînement, 227 de validation et 215 de test). Notez l’empreinte. -
Conservez une copie du manifeste :
Terminal cp data/model/MANIFEST.json first-run.jsonPowerShell Copy-Item data\model\MANIFEST.json first-run.json -
Exécutez de nouveau
python run_pipeline.py. L’empreinte est la même qu’à l’étape 4. -
Comparez les sorties des deux manifestes avec Python :
Fenêtre de terminal python -c "import json; a = json.load(open('first-run.json')); b = json.load(open('data/model/MANIFEST.json')); print('same outputs' if a['outputs'] == b['outputs'] else 'different outputs')"Sortie same outputs -
Copiez les quatre sommes de contrôle de
data/model/depuisMANIFEST.jsondans une nouvelle section deDATASET.md.
À vous de jouer : utiliser l’exportation d’août dans le pipeline
Section intitulée « À vous de jouer : utiliser l’exportation d’août dans le pipeline »Le service de soutien a été mis à jour en juillet. L’exportation suivante, du 1 août 2026, contient 40 nouveaux billets de soutien. Avant de la charger, Mei la vérifie.
-
Téléchargez le fichier dans
data/raw/:Terminal curl -o data/raw/tickets-2026-08-01.csv https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C04/data/tickets-2026-08-01.csvPowerShell curl.exe -o data/raw/tickets-2026-08-01.csv https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C04/data/tickets-2026-08-01.csv -
Faites une prédiction : quelle étape s’arrête et quels problèmes le message indique-t-il?
-
Exécutez
python run_pipeline.py --tickets data/raw/tickets-2026-08-01.csv. -
Regardez dans
data/model/. Y a-t-il un fichierMANIFEST.json?
Afficher le résultat attendu
1/6 check_input: FAILED
PASS data/raw/customers.csvFAIL data/raw/tickets-2026-08-01.csv missing column: channel unexpected column: contact_channel (was a column renamed?) order_value: 34 of 34 values are not a valid number (first: line 2, '$10.85')PASS data/raw/outcomes.csvThe input does not match the expected schema. Do not load it.
Stopped at step 1 (check_input). No MANIFEST.json was written: this run delivered no dataset.La première étape arrête l’exécution. Le message indique les deux changements : channel est devenu contact_channel, et order_value contient un signe $. Parmi les 40 billets, 34 ont une valeur de commande. Les 6 autres concernent des comptes sans commande, donc leur valeur vide est permise. Il n’y a ni MANIFEST.json ni dossier data/model/ : l’exécution a supprimé les anciennes sorties et n’a rien livré.
La correction ne se fait pas dans la vérification. Mei interroge Grace sur la mise à jour. Ensuite, elle modifie clean.py (renommer la colonne, retirer le $) et le schéma dans check_input.py. Le résultat est une nouvelle version du jeu de données, consignée dans le tableau Changes (modifications) de DATASET.md.
Pour reconstruire le jeu de données du 1 juillet, exécutez python run_pipeline.py sans option.
Erreur courante
Une exportation modifiée sans vérification des entrées
Symptôme : un script qui fonctionnait le mois dernier donne des résultats étranges ou une erreur loin de la cause. Par exemple, le nombre de valeurs de commande « inconnues » (−1) devient soudainement 0. Ou le script s’arrête avec KeyError: 'channel' (erreur de clé : channel).
Cause : le schéma de l’exportation a changé, et rien ne l’a vérifié avant l’utilisation des données. pandas lit le fichier d’août sans avertissement :
>>> import pandas as pd>>> df = pd.read_csv("data/raw/tickets-2026-08-01.csv")>>> df["order_value"].head(3).tolist()['$10.85', '$25.58', '$91.70']>>> df["order_value"].dtype<StringDtype(na_value=nan)>>>> (df["order_value"] == -1).sum()np.int64(0)>>> df["channel"]Traceback (most recent call last): ...KeyError: 'channel'>>> pd.read_csv("data/raw/tickets-2026-08-01.csv", dtype={"order_value": "Float64"})Traceback (most recent call last): ...ValueError: Unable to parse string "$10.85" at position 0order_value est maintenant du texte. La comparaison avec −1 n’échoue pas; elle est fausse pour chaque ligne. La règle des valeurs « inconnues » ne trouve donc rien. KeyError indique une colonne, mais pas la raison. ValueError indique une valeur, mais ni la colonne ni le fichier.
Correction :
- Exécutez
scripts/check_input.pysur chaque nouvelle exportation avant de la charger. Il indique le fichier, la colonne manquante, la colonne inattendue et la colonne contenant les mauvaises valeurs. - Gardez la vérification comme étape 1 de
run_pipeline.py, pour qu’on ne puisse pas l’oublier. - Quand la vérification échoue, modifiez volontairement le schéma et le nettoyage, puis créez une nouvelle version du jeu de données.
Dans le travail en IA : un modèle en production reçoit ses entrées de systèmes qui changent. Une colonne renommée peut rendre toutes les valeurs d’une variable manquantes. Le modèle continue à faire des prédictions, mais elles sont moins bonnes. Une vérification des entrées transforme ce problème en arrêt clair.
Exercice du module
Section intitulée « Exercice du module »L’exercice du module : exécuter le pipeline de préparation à partir d’un instantané propre des entrées. D’abord, un petit exemple détaillé.
Exemple détaillé : les étapes à la main, avec leurs nombres
Section intitulée « Exemple détaillé : les étapes à la main, avec leurs nombres »Avant de faire confiance à la commande unique, Mei exécute chaque étape à la main, une fois, depuis un instantané propre. Elle note ce que chaque étape affiche. Chaque nombre doit correspondre aux registres de provenance de la dernière leçon.
| Commande | Ce qu’elle affiche (lignes principales) | Code de sortie |
|---|---|---|
python get_data.py --reset |
Built larkfield.db. Check: customers 240, tickets 1417, outcomes 1874. |
0 |
python scripts/check_input.py |
3 × PASS (réussite), The input matches the expected schema. (les entrées respectent le schéma attendu) |
0 |
python scripts/extract.py |
data/extract/tickets.csv: 1416 rows (1 416 lignes), data/extract/outcomes.csv: 1869 rows (1 869 lignes), Not selected: 1 ticket row(s) (non sélectionnée : 1 ligne de billet) |
0 |
python scripts/clean.py |
"exact_duplicates_removed": 14 (doublons exacts supprimés), "quarantined": 4 (en quarantaine), "clean_rows": 1398 (lignes nettoyées); résultats "clean_rows": 1834 |
0 |
python scripts/build_features.py |
"too_recent_for_label": 29 (trop récentes pour l’étiquette), "rows": 1369 (lignes), "escalated": 188 (transmis au niveau supérieur) |
0 |
python scripts/split.py |
entraînement "rows": 927, validation "rows": 227, test "rows": 215 |
0 |
python scripts/validate.py |
All 13 rules passed. (les 13 règles sont respectées) |
0 |
Chaque nombre découle du précédent : 1 417 − 1 = 1 416; 1 416 − 14 − 4 = 1 398; 1 398 − 29 = 1 369; 927 + 227 + 215 = 1 369. run_pipeline.py exécute les mêmes étapes, donc il doit donner les mêmes nombres.
Votre tâche : une nouvelle copie
Section intitulée « Votre tâche : une nouvelle copie »Prouvez que le projet fonctionne partout, pas seulement dans votre dossier avec ses anciens fichiers et son ancien environnement.
- Créez un nouveau dossier, par exemple
~/projects/ticket-data-fresh. - Copiez-y seulement le code et les données brutes :
get_data.py,q.py,run_pipeline.py,requirements.txt,DATASET.md, les dossiersqueries/etscripts/, etdata/raw/. Ne copiez paslarkfield.db,data/extract/,data/clean/,data/model/,reports/ou.venv/. - Dans le nouveau dossier, créez un nouvel environnement virtuel, activez-le et installez
requirements.txt, comme dans le cours de Python. - Exécutez
python get_data.py. - Exécutez
python run_pipeline.py. - Comparez l’empreinte à celle du dossier de votre projet.
- Exécutez le pipeline avec l’exportation d’août et vérifiez qu’il s’arrête à l’étape 1.
- Écrivez deux phrases dans
DATASET.md, sous Changes ou dans une nouvelle section Reproducibility (reproductibilité) : la date, l’empreinte et « deux environnements ont donné la même empreinte ».
Afficher la réponse modèle
La nouvelle copie de Mei, dans un autre dossier avec un autre environnement virtuel (pandas 3.0.6) :
1/6 check_input: ok2/6 extract: ok3/6 clean: ok4/6 build_features: ok5/6 split: ok6/6 validate: okDone: 1369 rows (train 927, valid 227, test 215).Wrote data/model/MANIFEST.json. Outputs fingerprint: 2b2460321543ffb73fe9d5b04fd8f5a9d77ee990fff37823b0855b911ba4b855L’empreinte est la même que dans le dossier du projet. Avec --tickets data/raw/tickets-2026-08-01.csv, l’exécution s’arrête à l’étape 1 et indique channel, contact_channel et order_value, comme dans « À vous de jouer ».
La note dans DATASET.md : « 6 octobre 2026 : une nouvelle copie avec un nouvel environnement virtuel a donné l’empreinte des sorties 2b2460321543ffb73fe9d5b04fd8f5a9d77ee990fff37823b0855b911ba4b855, identique à celle du dossier du projet. L’exportation d’août s’arrête à la vérification des entrées. »
Si vos empreintes diffèrent, comparez les sections outputs des deux manifestes pour trouver le premier fichier différent. Ensuite, regardez l’étape qui l’écrit. Causes fréquentes : un fichier que vous avez oublié de copier, un script modifié dans un seul dossier ou une version différente de pandas.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Rappel : nommez les quatre types de règles de validation d'un contrat de données, avec un exemple de Larkfield pour chacun.
Afficher la réponse
Valeurs obligatoires : ticket_id, created_at, channel et la cible ne doivent pas manquer. Valeurs et plages permises : channel vaut email (courriel), chat (clavardage), phone (téléphone), web_form (formulaire Web) ou unknown (inconnu); order_value va de 0 à 2 000. Clés uniques : chaque ticket_id apparaît une seule fois, et aucun billet n’est dans deux sous-ensembles. Distributions attendues : le taux d’escalade se situe entre 5 % et 30 %, dans l’ensemble et dans chaque sous-ensemble.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Prédiction : Mei augmente MAX_WORDS dans clean.py de 10 000 à 50 000 et exécute run_pipeline.py. Quelle étape s'arrête, et pourquoi? Quelles sommes de contrôle changeraient si l'étape ne s'arrêtait pas?
Afficher la réponse
validate.py (étape 6) arrête l’exécution. T-10361, qui compte 48 210 mots, n’est plus mis en quarantaine et atteint donc la table de modélisation. La règle « les nombres et les dates respectent les plages permises » échoue : word_count: outside 0 to 10000 in 1 row (first: T-10361, 48210) (word_count : hors de la plage de 0 à 10000 dans une ligne; première occurrence : T-10361, 48210). Aucun fichier MANIFEST.json n’est écrit. Sans cette règle, les deux fichiers d’extraction garderaient leurs sommes de contrôle, car l’extraction précède le nettoyage. Tous les fichiers produits à partir de clean.py changeraient, donc l’empreinte changerait aussi. La règle de nettoyage et le contrat doivent concorder, sinon le contrat arrête l’exécution.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Vérification de fin : montrez que votre préparation est reproductible et qu'elle échoue avec un message utile lorsque le schéma change. Quelles preuves donnez-vous à Priya?
Afficher la réponse
Deux preuves, toutes deux tirées de vos propres exécutions :
- Des résultats reproductibles. Deux exécutions de
python run_pipeline.pyà partir d’un instantané propre (python get_data.py --reset), avec la même valeur deoutputs_fingerprint. Les sectionsoutputs(résultats) des deux manifestes sont identiques; seulsstarted_at(début de l’exécution) etextracted_at(moment de l’extraction) diffèrent. Une exécution dans une nouvelle copie, avec un nouvel environnement, renforce la preuve. - Un échec avec un message utile. Le résultat de
python run_pipeline.py --tickets data/raw/tickets-2026-08-01.csv: l’exécution s’arrête à l’étape 1. Le message indique quechannelmanque, quecontact_channelest inattendu et queorder_valuen’est pas un nombre, avec l’exemple$10.85. Aucun fichierMANIFEST.jsonn’est écrit.
La vérification est démontrée lorsque les deux preuves sont présentées. Elle est partiellement démontrée dans deux cas : l’exportation modifiée n’est pas testée, ou le message d’échec ne nomme pas les colonnes. Elle est non encore démontrée si les étapes doivent être exécutées à la main, ou si les empreintes diffèrent sans explication.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
Dans ce module, vous avez transformé un ensemble de scripts en un jeu de données fiable qu’une autre personne peut recréer.
- Règles de validation : un contrat de données énumère les valeurs obligatoires, les valeurs et plages permises, les clés uniques et les distributions attendues.
validate.pyvérifie 13 règles sur les fichiers livrés, écritreports/validation.jsonet se termine avec le code 1 en cas d’échec. Chaque règle doit être testée sur des données qui ne la respectent pas. - Provenance des données : la table des instantanés,
manifest.json,cleaning.json,quarantine.csv,build_summary.jsonetsplit_manifest.jsonconsignent chaque étape.DATASET.mddécrit la provenance (données synthétiques,generate.py, graine 2026, CC0), l’ordre des transformations, les versions et les limites. - Livraison :
python get_data.py --resetetpython run_pipeline.pyconstruisent le jeu de données à partir d’un instantané propre avec une seule commande.MANIFEST.jsoncontient une somme de contrôle SHA-256 de chaque résultat, sauf le moment d’exécution, et deux exécutions donnent la même empreinte.check_input.pyarrête une exportation modifiée avant son chargement, avec un message qui nomme le problème.
Trois études de cas sur des données réelles suivent. Terminez-en au moins une pour obtenir le certificat. Puis vient le travail final : vous préparez le jeu de données depuis le début et le livrez avec les preuves de chaque étape.
Leçon suivante : Expliquer les ventes d'une boutique en ligne