Traiter les valeurs désordonnées
- Leçon 12
- 55 min
- Module 4 : Nettoyer et transformer les données
Leçon 12 sur 18 · SQL et préparation des données pour l'IATraiter les valeurs désordonnées
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
Transformez le profil en décisions, puis les décisions en un seul script. Chaque problème reçoit une action que vous pouvez expliquer. Les valeurs originales restent, et chaque ligne d’entrée aboutit à un seul endroit.
Carnet JupyterFaites cette leçon dans un carnet Jupyter
Le carnet (notebook) Jupyter écrit tout le fichier clean.py, selon les mêmes parties que cette page, puis l’exécute. Il contient les tâches et les vérifications de la leçon et de l’exercice du module. Ses cellules de préparation installent pandas, créent la base de données et produisent l’extraction. Aucune configuration locale n’est donc nécessaire. 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
- Les problèmes trouvés par le profil et le sens de chaque valeur manquante, dans Profiler avant de modifier.
- Le chargement avec
dtype=,drop_duplicates(), le type"string"avecfillna(False)et la modification des lignes avec.loc, dans Transformer avec Python.
Ce qu’il vous faut sur votre ordinateur (seulement pour le parcours local; le carnet n’en a pas besoin)
- Le dossier
~/projects/ticket-datacontenant l’extractiondata/extract/tickets.csv(1 416 lignes) etdata/extract/outcomes.csv(1 869 lignes), dans Une extraction reproductible : exercice guidé. Si ces fichiers manquent, exécutez de nouveaupython scripts/extract.py. - L’environnement
.venvavec pandas 3.0.6, dans Profiler avant de modifier : le même profil dans pandas. Activez-le dans un terminal, dans le dossier du projet. L’invite commence par(.venv). scripts/clean.pyavecload_tickets(), dans Transformer avec Python : exemple détaillé. Cette leçon y ajoute du code. Si votre script manque ou ne fonctionne plus, copiezscripts/clean.pydepuis les scripts terminés du cours. Il contient déjà les étapes de cette leçon : lisez chaque partie ci-dessous et comparez.reports/profile.md, dans Profiler avant de modifier : exercice guidé. Les décisions de cette leçon partent de ce fichier.
Testé sur macOS avec Python 3.14.6 et pandas 3.0.6. Le carnet a été testé dans Jupyter.
Trois actions : exclure, corriger, imputer
Section intitulée « Trois actions : exclure, corriger, imputer »Pour chaque problème du profil, vous avez trois actions possibles :
- Exclusion : vous retirez la ligne de la table nettoyée. Vous ne la supprimez pas : elle va dans une table de quarantaine, avec un motif de rejet. Une table de quarantaine est un fichier distinct pour les lignes que vous ne pouvez pas utiliser avec confiance. Une personne peut vérifier ces lignes plus tard.
- Correction : vous remplacez une valeur par celle que vous savez correcte, parce qu’une règle explique l’erreur. L’original reste dans une deuxième colonne.
- Imputation : vous remplacez une valeur manquante par une estimation, par exemple la médiane de la colonne. L’estimation est une supposition, pas un fait.
Il existe un quatrième choix, souvent le meilleur : garder la valeur manquante et expliquer pourquoi dans une colonne indicatrice.
Examinez quatre billets de soutien inventés :
| ticket_id | channel | order_value | created_at | joined_on | Action | Pourquoi |
|---|---|---|---|---|---|---|
| T-1 | Email |
42.50 | 2026-03-02 | 2025-06-01 | corriger channel en email |
La règle est claire : même canal, autre écriture. |
| T-2 | chat |
4250 | 2026-01-20 | 2025-11-05 | corriger à 42,50 | Seulement si la règle s’applique : un billet de formulaire Web avant le 1er février utilise des cents. T-2 est un billet de clavardage, donc cette règle ne s’applique pas. Gardez 4 250 et posez la question. |
| T-3 | phone |
NULL | 2026-03-03 | 2024-02-11 | garder la valeur manquante | T-3 est un billet lié au compte : il n’y a aucune commande. |
| T-4 | chat |
64.50 | 2026-03-04 | 2026-03-10 | mettre en quarantaine | Le billet précède le compte. Une des deux dates est fausse, et vous ne pouvez pas savoir laquelle. |
Comment choisir :
- Corrigez seulement si vous connaissez la vraie valeur.
- Gardez la valeur manquante quand son absence a un sens.
- Mettez en quarantaine quand la ligne est incorrecte et que vous ne pouvez pas la réparer.
- Imputez seulement quand le modèle a besoin d’un nombre et que la valeur existe, mais reste inconnue.
Les décisions pour Larkfield
Section intitulée « Les décisions pour Larkfield »Mei apporte le profil à Grace, la responsable du soutien. Ensemble, elles décident quoi faire pour chaque problème. Les nombres concernent l’extraction après le retrait des 14 doublons exacts (1 402 lignes) :
| Problème | Lignes | Action | Nouvelle valeur | Original conservé dans |
|---|---|---|---|---|
| Lignes exactement identiques | 14 | retirer et compter | (ligne retirée) | le compte dans reports/cleaning.json |
Autres écritures de channel |
39 | corriger : strip, lower, webform → web_form |
email, chat, web_form |
channel_raw |
channel est NULL |
38 | garder la valeur manquante comme catégorie distincte | unknown (inconnu) |
channel_raw (NULL) |
segment vaut Trade |
(clients) | corriger : minuscules | trade |
— |
Aucun customer_id (invité) |
12 | corriger selon le sens | segment guest (invité) |
customer_id reste NULL |
Client absent de customers |
9 | garder la valeur manquante comme catégorie distincte | segment unknown |
customer_id |
region est NULL |
139 | garder la valeur manquante comme catégorie distincte | unknown |
— |
order_value NULL, équipe des comptes |
188 | garder la valeur manquante | NULL, indicateur not_applicable (sans objet) |
order_value_raw |
order_value vaut -1 |
11 | corriger la valeur sentinelle en véritable valeur manquante | NULL, indicateur unknown |
order_value_raw |
| Formulaire Web avant le 1er février 2026 | 24 | corriger : diviser par 100 | dollars, indicateur converted_from_cents (converti à partir de cents) |
order_value_raw |
| Création avant l’inscription du client | 3 | mettre en quarantaine | — | data/clean/quarantine.csv |
word_count dépasse 10 000 |
1 | mettre en quarantaine | — | data/clean/quarantine.csv |
word_count vaut 0 |
6 | garder : valide (pièce jointe seulement) | — | — |
Pourquoi mettre T-10361, le billet de 48 210 mots, en quarantaine plutôt que le corriger? Le vrai nombre de mots du texte du client est inconnu. Un modèle qui voit 48 210 parmi des valeurs d’environ 100 subit l’influence d’une seule ligne. Grace peut la vérifier à la main et la réintégrer plus tard.
Où est l’imputation? Les 11 valeurs de commande unknown existent, mais personne ne les connaît. Un modèle a besoin d’un nombre à cet endroit. Mei décide maintenant qu’elles recevront la valeur médiane des commandes, avec une colonne supplémentaire indiquant « cette valeur a été imputée ». Mais elle ne les remplit pas dans la table nettoyée. La médiane doit provenir uniquement des lignes d’entraînement. L’imputation a donc lieu au module 5, dans Ajuster les transformations correctement. Si vous calculez la médiane sur toutes les lignes, les lignes de test influencent les données d’entraînement.
Le parcours des données brutes aux données nettoyées
Section intitulée « Le parcours des données brutes aux données nettoyées »Description textuelle du diagramme
L’extraction, data/extract/tickets.csv, contient 1 416 lignes. La première étape retire et compte 14 doublons exacts, ce qui laisse 1 402 lignes. La deuxième étape corrige channel, segment, region et order_value. Elle conserve les originaux dans des colonnes *_raw et un indicateur. Elle ne change pas le nombre de lignes : 1 402. La troisième étape applique les règles de quarantaine. Les 1 398 lignes acceptées vont dans data/clean/tickets.csv. Quatre lignes sont rejetées : 3 billets créés avant l’inscription du client et 1 billet de plus de 10 000 mots. Elles vont dans data/clean/quarantine.csv avec un motif de rejet. Le fichier reports/cleaning.json consigne les trois comptes.
Exemple détaillé : Mei écrit l’étape de nettoyage
Section intitulée « Exemple détaillé : Mei écrit l’étape de nettoyage »Mei ajoute trois constantes dans scripts/clean.py : CHANNELS au-dessus de TIME_FORMAT, et les deux autres en dessous. (TIME_FORMAT est déjà dans le fichier; il apparaît ici pour montrer où placer les nouvelles lignes.) Chaque constante contient une décision, avec un nom :
CHANNELS = {"email", "chat", "phone", "web_form"}TIME_FORMAT = "%Y-%m-%d %H:%M:%S"WEB_FORM_FIXED = pd.Timestamp("2026-02-01") # the web form sent cents before this dayMAX_WORDS = 10_000Elle écrit ensuite clean_tickets, sous load_tickets. La fonction renvoie les lignes nettoyées, les lignes en quarantaine et un dictionnaire de comptes. Lisez-la partie par partie.
def clean_tickets(raw): counts = {"input_rows": len(raw)} df = raw.drop_duplicates() counts["exact_duplicates_removed"] = len(raw) - len(df) assert df["ticket_id"].is_unique, "a ticket ID appears twice with different values"
# Channel: one spelling per value. A missing channel stays missing, and # becomes its own category, "unknown", so the model can still use the row. df["channel_raw"] = df["channel"] channel = df["channel"].str.strip().str.lower().replace({"webform": "web_form"}) counts["channel_respelled"] = int((channel != df["channel"]).fillna(False).sum()) df["channel"] = channel.fillna("unknown") assert set(df["channel"]) <= CHANNELS | {"unknown"}
# Segment and region: the customer table spells "Trade" in two ways. # Guests have no customer; deleted customers have an ID but no row. df["segment"] = df["segment"].str.strip().str.lower() no_customer = df["customer_id"].isna() df.loc[no_customer, "segment"] = "guest" df["segment"] = df["segment"].fillna("unknown") df["region"] = df["region"].fillna("unknown")assert df["ticket_id"].is_uniquevérifie le constat du profil : après le retrait des copies exactes, aucun identifiant n’apparaît deux fois. Si une nouvelle exportation contient deux lignes différentes pour un billet, le script s’arrête ici avec un message, plutôt que de garder les deux.assert set(df["channel"]) <= CHANNELS | {"unknown"}vérifie que chaque canal figure maintenant dans la liste autorisée.<=signifie « est un sous-ensemble de ». Une nouvelle écriture dans une exportation ultérieure arrête le script.- Les invités reçoivent d’abord
guest. Seules les lignes encore manquantes reçoivent ensuiteunknown: les 9 billets de clients supprimés.
# Order value: keep the original, then correct or blank it, with a flag. df["order_value_raw"] = df["order_value"] df["order_value_flag"] = "ok" df.loc[df["team"] == "account", "order_value_flag"] = "not_applicable" unknown = df["order_value"] == -1 df.loc[unknown, "order_value"] = pd.NA df.loc[unknown, "order_value_flag"] = "unknown" cents = (df["channel"] == "web_form") & (df["created_at"] < WEB_FORM_FIXED) & df["order_value"].notna() df.loc[cents, "order_value"] = (df.loc[cents, "order_value"] / 100).round(2) df.loc[cents, "order_value_flag"] = "converted_from_cents" counts.update({f"order_value_{k}": int(v) for k, v in df["order_value_flag"].value_counts().items()})L’ordre des étapes compte. La règle cents utilise le channel corrigé, ce qui permet aussi de trouver le billet avec webform (T-10013). La règle ignore aussi les valeurs manquantes. La valeur d’un billet de compte provenant du formulaire Web reste donc manquante. La dernière ligne compte chaque indicateur.
# Rows we cannot correct with confidence go to quarantine, with a reason. df["reject_reason"] = pd.NA df.loc[df["created_at"] < df["joined_on"], "reject_reason"] = "created_before_customer_joined" df.loc[df["word_count"] > MAX_WORDS, "reject_reason"] = "word_count_above_10000" quarantine = df[df["reject_reason"].notna()] clean = df[df["reject_reason"].isna()].drop(columns="reject_reason") counts["quarantined"] = len(quarantine) counts["clean_rows"] = len(clean) assert counts["clean_rows"] + counts["quarantined"] + counts["exact_duplicates_removed"] == counts["input_rows"] return clean, quarantine, countsLe dernier assert vérifie les lignes : lignes nettoyées + lignes en quarantaine + doublons retirés doivent égaler le nombre d’entrée. Si une modification ultérieure retire des lignes sans raison, le script s’arrête.
Les événements de résultat nécessitent deux étapes. Les 30 événements répétés (même billet, heure et statut, mais nouveau outcome_id) sont retirés. Mei garde celui avec le plus petit outcome_id, le premier consigné. Les événements des 4 billets en quarantaine sont aussi retirés, car leur billet ne figure pas dans la table nettoyée.
def clean_outcomes(tickets): df = pd.read_csv(IN / "outcomes.csv", dtype={"csat": "Int64"}, keep_default_na=False, na_values=[""]) df["recorded_at"] = pd.to_datetime(df["recorded_at"], format=TIME_FORMAT) counts = {"input_rows": len(df)} # A retry logged some events twice: same ticket, time and status, new ID. # Keep the first one logged. df = df.sort_values("outcome_id").drop_duplicates(["ticket_id", "recorded_at", "status"], keep="first") counts["repeated_events_removed"] = counts["input_rows"] - len(df) df = df[df["ticket_id"].isin(tickets["ticket_id"])] counts["events_of_quarantined_tickets"] = counts["input_rows"] - counts["repeated_events_removed"] - len(df) counts["clean_rows"] = len(df) return df, countsEnfin, Mei remplace le main() temporaire de la leçon 2. Le nouveau écrit trois fichiers CSV et les comptes :
def main(): OUT.mkdir(parents=True, exist_ok=True) REPORTS.mkdir(exist_ok=True) tickets, quarantine, ticket_counts = clean_tickets(load_tickets()) outcomes, outcome_counts = clean_outcomes(tickets) for df in (tickets, quarantine): df["joined_on"] = df["joined_on"].dt.strftime("%Y-%m-%d") tickets.to_csv(OUT / "tickets.csv", index=False, date_format=TIME_FORMAT) quarantine.to_csv(OUT / "quarantine.csv", index=False, date_format=TIME_FORMAT) outcomes.to_csv(OUT / "outcomes.csv", index=False, date_format=TIME_FORMAT) summary = {"tickets": ticket_counts, "outcomes": outcome_counts} (REPORTS / "cleaning.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8") print(json.dumps(summary, indent=2))joined_on est un jour sans heure. Les deux lignes strftime l’écrivent sous la forme 2026-04-05, et non 2026-04-05 00:00:00. Prédisez avant d’exécuter : combien de lignes de billets nettoyées?
python scripts/clean.py{ "tickets": { "input_rows": 1416, "exact_duplicates_removed": 14, "channel_respelled": 39, "order_value_ok": 1179, "order_value_not_applicable": 188, "order_value_converted_from_cents": 24, "order_value_unknown": 11, "quarantined": 4, "clean_rows": 1398 }, "outcomes": { "input_rows": 1869, "repeated_events_removed": 30, "events_of_quarantined_tickets": 5, "clean_rows": 1834 }}Mei vérifie la concordance de chaque groupe de nombres :
| Vérification | Calcul | Résultat |
|---|---|---|
| Lignes de billets | 1 398 nettoyées + 4 en quarantaine + 14 doublons | 1 416 = l’extraction |
| Indicateurs de valeur de commande | 1 179 + 188 + 24 + 11 | 1 402 : les indicateurs sont comptés après le retrait des doublons et avant la quarantaine |
| Événements de résultat | 1 834 nettoyés + 30 répétés + 5 associés aux billets en quarantaine | 1 869 = l’extraction |
Les originaux sont toujours là. Mei examine le fichier de quarantaine et la première ligne nettoyée pour chaque indicateur :
import pandas as pd
quarantine = pd.read_csv("data/clean/quarantine.csv")columns = ["ticket_id", "created_at", "joined_on", "word_count", "reject_reason"]print(quarantine[columns].to_string(index=False))
clean = pd.read_csv("data/clean/tickets.csv", keep_default_na=False, na_values=[""])columns = ["ticket_id", "team", "order_value_raw", "order_value", "order_value_flag"]first_of_each = clean.drop_duplicates("order_value_flag")print(first_of_each[columns].to_string(index=False))ticket_id created_at joined_on word_count reject_reason T-10361 2026-02-20 07:23:19 2025-11-22 48210 word_count_above_10000 T-11185 2026-03-25 17:00:00 2026-04-05 40 created_before_customer_joined T-11224 2026-05-17 23:00:00 2026-05-24 98 created_before_customer_joined T-11375 2026-03-23 23:00:00 2026-04-10 73 created_before_customer_joinedticket_id team order_value_raw order_value order_value_flag T-10001 returns 47.25 47.25 ok T-10013 warranty 3260.00 32.60 converted_from_cents T-10023 account NaN NaN not_applicable T-10046 returns -1.00 NaN unknowndrop_duplicates("order_value_flag") garde la première ligne de chaque valeur d’indicateur : une façon rapide de voir un exemple de chaque cas. N’importe qui peut maintenant voir les changements, vérifier une ligne à la main et annuler une décision.
L’erreur : remplir les valeurs manquantes sans comprendre leur sens
Section intitulée « L’erreur : remplir les valeurs manquantes sans comprendre leur sens »order_value contient 199 valeurs manquantes dans la table nettoyée. Une réaction courante est « remplissons-les avec la moyenne ». Mei montre à Omar, le gestionnaire de produit, ce que cela fait :
import pandas as pd
clean = pd.read_csv("data/clean/tickets.csv", keep_default_na=False, na_values=[""])value = clean["order_value"]print(f"rows: {len(clean)}, with a value: {value.notna().sum()}, missing: {value.isna().sum()}")print(clean.loc[value.isna(), "order_value_flag"].value_counts())
mean = round(value.mean(), 2)filled = value.fillna(mean)account = clean["team"] == "account"print(f"mean of the real values: {mean}")print(f"account tickets with an order after filling: {filled[account].notna().sum()}")print(f"orders invented for account tickets: {filled[account].sum():.2f} dollars")rows: 1398, with a value: 1199, missing: 199order_value_flagnot_applicable 188unknown 11Name: count, dtype: int64mean of the real values: 123.08account tickets with an order after filling: 188orders invented for account tickets: 23139.04 dollarsParmi les 199 valeurs manquantes, 188 concernent des billets de compte. Ces billets n’ont aucune commande. Les remplir attribue à 188 réinitialisations de mot de passe une commande de 123,08 dollars chacune. Cela représente 23 139,04 dollars de commandes inexistantes. La colonne dit maintenant « chaque billet a une commande », ce qui est faux. La distinction entre les billets de compte et les autres billets a disparu. Seules les 11 valeurs unknown sont de vraies commandes dont le montant est inconnu. Pour elles, une estimation est raisonnable, avec un indicateur, et calculée uniquement sur les données d’entraînement.
Erreur courante
Les valeurs manquantes sont remplies, et les données disent maintenant quelque chose de faux
Symptôme : après une étape qui « remplit les valeurs manquantes », la colonne n’en contient plus et sa moyenne semble normale. Mais chaque billet de compte a une commande de 123,08 dollars. Un rapport indique 23 139,04 dollars de commandes qui n’ont jamais existé. Rien n’échoue.
Cause : le code a traité toutes les valeurs manquantes de la même façon. Ici, l’absence a deux sens : « il n’y a aucune commande » (188 billets de compte) et « le montant est inconnu » (11 billets avec -1). Le remplissage ne convient ni au premier sens ni à un modèle qui doit connaître la différence.
Correction :
- Avant de remplir une colonne, regroupez ses valeurs manquantes par indicateur ou par colonne liée, comme
team. Demandez au responsable des données ce que chaque groupe signifie. - Gardez les valeurs « sans objet » manquantes et marquez-les avec un indicateur comme
order_value_flag = not_applicable. - Imputez seulement les valeurs qui existent, mais restent inconnues. Ajoutez une colonne indicatrice pour que le modèle sache quelles valeurs sont des estimations.
- Apprenez l’estimation (la moyenne ou la médiane) uniquement à partir des lignes d’entraînement. Le module 5 fait cela dans le prétraitement du modèle.
Dans le travail en IA : le modèle apprend des valeurs remplies comme si elles étaient réelles. Il ne peut pas distinguer les billets de compte des petites commandes. Tous les nombres que vous rapportez à partir de cette colonne sont faux.
Exercice guidé : terminer clean.py
Section intitulée « Exercice guidé : terminer clean.py »- Ajoutez les constantes
CHANNELS,WEB_FORM_FIXEDetMAX_WORDSàscripts/clean.py, autour deTIME_FORMAT. - Ajoutez les trois parties de la fonction
clean_tickets, sousload_tickets. - Ajoutez la fonction
clean_outcomes. - Remplacez le
main()temporaire par le nouveau. - Exécutez
python scripts/clean.py. Vérifiez queclean_rows(lignes nettoyées) vaut1398pour les billets et1834pour les résultats. - Ouvrez
reports/cleaning.json. Il contient les mêmes comptes que la sortie. - Créez
scripts/show_clean.pyet exécutez-le. Vérifiez que la quarantaine contient 4 lignes, chacune avec une raison.
Si un assert arrête le script, le message nomme la vérification qui a échoué. Comparez votre code aux parties ci-dessus. La cause la plus courante est une ligne manquante dans la partie 1, qui laisse une écriture de canal inchangée.
À vous de jouer : une étape dans un autre ordre
Section intitulée « À vous de jouer : une étape dans un autre ordre »Dans la partie 2, la règle cents utilise le channel corrigé. Modifiez-la pour utiliser channel_raw, la valeur originale :
cents = (df["channel_raw"] == "web_form") & (df["created_at"] < WEB_FORM_FIXED) & df["order_value"].notna()Prédisez d’abord : combien de lignes la règle convertit-elle maintenant? Quel billet est différent? Exécutez le script, comparez les comptes à la première exécution, puis rétablissez la ligne.
Afficher la réponse
order_value_converted_from_cents vaut 23, pas 24, et order_value_ok vaut 1180, pas 1 179. Pour T-10013, channel_raw = webform, donc la règle ne le voit pas. Son order_value reste à 3 260 dollars, avec l’indicateur ok (valide). Tous les autres comptes restent identiques, et la vérification des lignes réussit toujours : elle compte les lignes, pas les valeurs. Voilà pourquoi l’ordre des étapes est une décision à consigner : « corriger d’abord le canal, puis les unités ».
Exercice du module
Section intitulée « Exercice du module »Exemple détaillé : sept billets désordonnés
Section intitulée « Exemple détaillé : sept billets désordonnés »La tâche prévue pour ce module : nettoyer des billets volontairement désordonnés et créer un résumé de la qualité. Mei montre d’abord la méthode sur sept lignes inventées, pour rendre chaque résultat intermédiaire visible. Chaque ligne a un problème :
import io
import pandas as pd
MESSY = """ticket_id,team,channel,created_at,joined_on,order_value,word_countT-1,delivery,Email,2026-01-10 09:00:00,2025-06-01,42.5,80T-2,account,chat,2026-03-02 11:30:00,2024-02-11,,45T-3,returns,web_form,2026-03-03 14:00:00,2025-01-20,-1,120T-3,returns,web_form,2026-03-03 14:00:00,2025-01-20,-1,120T-4,payment,,2026-03-04 08:15:00,2026-03-10,64.5,60T-5,warranty,phone,2026-03-05 16:45:00,2023-09-30,210,31000T-6,delivery,web_form,2026-01-20 10:00:00,2025-11-05,4250,95"""
raw = pd.read_csv(io.StringIO(MESSY), dtype={"channel": "string", "order_value": "Float64"}, keep_default_na=False, na_values=[""])raw["created_at"] = pd.to_datetime(raw["created_at"], format="%Y-%m-%d %H:%M:%S")raw["joined_on"] = pd.to_datetime(raw["joined_on"], format="%Y-%m-%d")print(f"1. input: {len(raw)} rows")
df = raw.drop_duplicates()print(f"2. exact duplicates removed: {len(raw) - len(df)} -> {len(df)} rows")
df["channel_raw"] = df["channel"]df["channel"] = df["channel"].str.strip().str.lower().fillna("unknown")
df["order_value_raw"] = df["order_value"]df["order_value_flag"] = "ok"df.loc[df["team"] == "account", "order_value_flag"] = "not_applicable"unknown = df["order_value"] == -1df.loc[unknown, "order_value"] = pd.NAdf.loc[unknown, "order_value_flag"] = "unknown"cents = (df["channel"] == "web_form") & (df["created_at"] < "2026-02-01") & df["order_value"].notna()df.loc[cents, "order_value"] = (df.loc[cents, "order_value"] / 100).round(2)df.loc[cents, "order_value_flag"] = "converted_from_cents"
df["reject_reason"] = pd.NAdf.loc[df["created_at"] < df["joined_on"], "reject_reason"] = "created_before_customer_joined"df.loc[df["word_count"] > 10_000, "reject_reason"] = "word_count_above_10000"print(df[["ticket_id", "channel_raw", "channel", "order_value_raw", "order_value", "order_value_flag", "reject_reason"]].to_string(index=False))
quarantine = df[df["reject_reason"].notna()]clean = df[df["reject_reason"].isna()]print(f"3. quarantined: {len(quarantine)}, clean: {len(clean)}")print(f"4. check: {len(clean)} + {len(quarantine)} + {len(raw) - len(df)} = {len(raw)}")1. input: 7 rows2. exact duplicates removed: 1 -> 6 rowsticket_id channel_raw channel order_value_raw order_value order_value_flag reject_reason T-1 Email email 42.5 42.5 ok <NA> T-2 chat chat <NA> <NA> not_applicable <NA> T-3 web_form web_form -1.0 <NA> unknown <NA> T-4 <NA> unknown 64.5 64.5 ok created_before_customer_joined T-5 phone phone 210.0 210.0 ok word_count_above_10000 T-6 web_form web_form 4250.0 42.5 converted_from_cents <NA>3. quarantined: 2, clean: 44. check: 4 + 2 + 1 = 7Lisez la table ligne par ligne. T-1 : le canal est corrigé, l’original reste. T-2 : billet de compte, donc la valeur manquante reste manquante. T-3 : la copie a disparu, et -1 est devenu une véritable valeur manquante avec l’indicateur unknown. T-4 et T-5 vont en quarantaine, chacun avec sa raison. T-6 : une valeur de formulaire Web de janvier, divisée par 100. La dernière ligne comptabilise les 7 lignes.
Votre tâche : le résumé de la qualité
Section intitulée « Votre tâche : le résumé de la qualité »Exécutez scripts/clean.py sur la vraie extraction, comme dans l’exercice guidé. Écrivez ensuite reports/quality.md pour Priya, qui entraînera le modèle. Ce fichier doit répondre aux questions suivantes dans de courtes sections :
- Combien de lignes sont entrées, et où chacune est-elle allée?
- Quelles valeurs ont été corrigées, combien, et où sont les originaux?
- Quelles valeurs restent manquantes, et que signifie chaque type de valeur manquante?
- Quelles lignes sont en quarantaine, et pourquoi?
- Qu’avez-vous supposé? Écrivez chaque hypothèse dont dépend une correction, et qui l’a confirmée.
- Qu’est-ce qui est décidé, mais pas encore fait?
Afficher un exemple de réponse
# Quality summary: Larkfield tickets after cleaning
Input: data/extract/tickets.csv (1,416 rows) and data/extract/outcomes.csv(1,869 rows), snapshot 2026-07-01 06:00. Script: scripts/clean.py.All counts: reports/cleaning.json.
## Where every row went| Tickets | Rows ||--------------------------------------|-------|| data/clean/tickets.csv | 1,398 || data/clean/quarantine.csv | 4 || exact duplicates removed (counted) | 14 || total = input | 1,416 |
Outcome events: 1,834 clean + 30 repeated events removed (kept the lowestoutcome_id) + 5 events of quarantined tickets = 1,869.
## Corrections (originals kept)- channel: 39 respelled (strip, lower, webform -> web_form). Original: channel_raw.- order_value: 24 web form values before 2026-02-01 divided by 100 (flag converted_from_cents). Original: order_value_raw.- order_value: 11 values of -1 set to missing (flag unknown). Original: order_value_raw.- segment: lower-cased (Trade -> trade).
## Missing values and their meaning- order_value missing, flag not_applicable (188): account tickets, no order. Do not fill.- order_value missing, flag unknown (11): an order exists, amount unknown.- channel "unknown" (38): not recorded.- segment "guest" (12): no account. segment "unknown" (9): deleted customer.- region "unknown": the customer gave no region, or there is no customer row.
## Quarantine (4 rows, data/clean/quarantine.csv)- created_before_customer_joined: T-11185, T-11224, T-11375. One date is wrong; we cannot tell which.- word_count_above_10000: T-10361 (48,210 words, a pasted log file).
## Assumptions behind the corrections- The old web form sent cents until 2026-02-01, and only the web form (Grace).- -1 in order_value means "not found in the order system" (Grace).- Email, EMAIL and " chat" are the same channels as email and chat.- Exact duplicate rows are copies from an export page overlap, not two tickets.- 0 words means an attachment only, and is valid (Grace).
## Decided, not done yet- The 11 unknown order values get the median with an indicator column, learned from the training set only (Module 5).- Grace checks the 4 quarantined tickets by hand.Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Nommez les trois actions possibles pour une valeur désordonnée. Donnez un exemple de Larkfield pour chacune.
Afficher la réponse
Exclusion : retirer la ligne et la garder en quarantaine avec une raison, par exemple T-10361 avec 48 210 mots. Correction : remplacer une valeur par celle que vous savez correcte, et garder l’original. Exemple : une valeur de 4 503 cents provenant du formulaire Web en janvier → 45,03 dollars. Imputation : remplacer une valeur manquante par une estimation. Exemple : la médiane pour les 11 valeurs de commande inconnues, calculée au module 5 à partir des lignes d’entraînement seulement. (Retirer les 14 doublons exacts est aussi une exclusion. Mais aucune information n’est perdue, donc on les compte sans les mettre en quarantaine.)
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édisez : un collègue ajoute clean["order_value"] = clean["order_value"].fillna(clean["order_value"].mean()) juste avant le return dans clean_tickets. La vérification des lignes réussit-elle encore? Quel est le problème avec le résultat?
Afficher la réponse
La vérification des lignes réussit : elle compte les lignes, et aucune ligne n’est ajoutée ou retirée. Mais les 199 valeurs manquantes ont maintenant la moyenne, soit environ 123 dollars. Les 188 billets de soutien liés aux comptes reçoivent une commande qui n’existe pas : environ 23 000 dollars de commandes inventées. Les 11 valeurs inconnues reçoivent une estimation sans colonne indicatrice. De plus, la moyenne inclut les lignes qui formeront les données de test. La colonne indicatrice affiche encore not_applicable (sans objet), donc le fichier se contredit maintenant.
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 finale : rendez compte de chaque ligne de l'extrait de billets et nommez deux hypothèses derrière vos corrections.
Afficher la réponse
1 416 lignes d’entrée = 1 398 lignes nettoyées + 4 lignes en quarantaine + 14 doublons exacts retirés. Les lignes nettoyées se trouvent dans data/clean/tickets.csv. Les lignes en quarantaine se trouvent dans data/clean/quarantine.csv : 3 created_before_customer_joined (création avant l’inscription du client) et 1 word_count_above_10000 (plus de 10 000 mots). Le script vérifie ces nombres avec un assert (assertion), et reports/cleaning.json consigne chaque nombre. Exemples d’hypothèses : le formulaire Web envoyait des cents seulement avant le 1er février 2026. -1 signifie « inconnu » dans le système de commandes. Les lignes en double sont des copies d’exportation, pas des billets distincts. Chaque hypothèse figure dans reports/quality.md, avec la personne qui l’a confirmée.
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 êtes passé d’une exportation brute à une table nettoyée que vous pouvez expliquer :
- Profiler d’abord (leçon 1) : cinq questions, chaque décompte dans
reports/profile.md, et le sens de chaque valeur manquante confirmé par la personne qui connaît les activités. - Charger avec des types (leçon 2) :
dtype=,keep_default_na=False, des formats de date explicites et des jointures avecvalidate=etindicator=True. Ainsi, les erreurs silencieuses deviennent des erreurs signalées. - Décider pour chaque problème (leçon 3) : mettre en quarantaine avec une raison. Corriger seulement avec une règle connue, et garder l’original. Garder les valeurs manquantes qui ont un sens, avec un indicateur. Imputer plus tard, à partir des données d’entraînement seulement.
- Tout compter : 1 416 = 1 398 + 4 + 14 billets, et 1 869 = 1 834 + 30 + 5 événements, dans
reports/cleaning.jsonetreports/quality.md.
Ensuite, le module 5 vous demande quelles colonnes le modèle peut utiliser. Certaines colonnes de la table nettoyée, comme priority_now, ne sont connues qu’après la création du billet. Vous définissez ce qui est connu au moment de la prédiction, puis vous construisez la cible.
Leçon suivante : Caractéristiques et moment de la cible