Cours de Python / Nettoyer et agréger des données tabulaires avec pandas

La bibliothèque pandas représente des données en lignes et colonnes dans un DataFrame. Pour obtenir un total interprétable, séparez la lecture, la conversion des montants, le choix des lignes acceptées et l’agrégation, puis indiquez combien de lignes ont été exclues.

Choisir les types de colonnes et préparer pandas

Un DataFrame est une structure tabulaire ; une Series correspond notamment à une colonne sélectionnée. Le type déduit automatiquement à la lecture ne traduit pas toujours la règle de vos données. Un identifiant tel que "001" doit rester du texte pour conserver ses zéros initiaux ; convertissez ensuite seulement la colonne de montants.

pandas n’appartient pas à la bibliothèque standard. Dans l’environnement virtuel de votre projet, installez une version compatible avec votre Python et votre système :

python -m pip install pandas

Utilisez le même interpréteur pour installer et exécuter le programme. Si import pandas échoue après une installation réussie, vérifiez d’abord l’environnement sélectionné et l’interpréteur du terminal ou de l’éditeur. Les tutoriels officiels pandas présentent les structures tabulaires et les principales opérations.

Dans l’exemple, dtype="string" conserve les colonnes sous forme de chaînes à la lecture. keep_default_na=False évite d’interpréter automatiquement certains textes de l’entrée comme valeurs manquantes à cette étape. La référence de read_csv détaille ces paramètres ; la conversion des montants aura ses propres règles.

Exécuter le programme complet de validation et de synthèse

Créez un dossier d’exercice et enregistrez le fichier UTF-8 pandas_demo.py suivant. L’entrée est un texte CSV en mémoire, lu comme un fichier grâce à StringIO : vous n’avez pas de fichier CSV supplémentaire à préparer pour ce premier essai.

from io import StringIO
import pandas as pd

raw = "id,category,amount\ne1,food,3500\ne2,food,8000\ne3,transport,2500\ne4,food,bad\n"
df = pd.read_csv(StringIO(raw), dtype="string", keep_default_na=False)
amounts = pd.to_numeric(df["amount"], errors="coerce")
valid = amounts.notna() & (amounts >= 0) & (amounts % 1 == 0)
clean = df.loc[valid].copy()
clean["amount"] = amounts.loc[valid].astype("int64")
report = clean.groupby("category", as_index=False, sort=True)["amount"].sum()
print("accepted:", len(clean), "rejected:", int((~valid).sum()))
for row in report.itertuples(index=False):
    print(row.category, row.amount)

Dans le terminal du dossier avec l’environnement choisi, lancez python pandas_demo.py. Si votre commande d’interpréteur est python3 hors environnement virtuel sous macOS ou Ubuntu, adaptez-la. Le résultat attendu est :

accepted: 3 rejected: 1
food 11500
transport 2500

La ligne contenant bad n’est pas additionnée. Les trois autres donnent food=11500 et transport=2500. Le total accepté vaut donc 14 000. Les comptes sont tout aussi importants : trois lignes acceptées plus une ligne rejetée doivent retrouver les quatre lignes de données.

groupby réunit les montants par catégorie ; as_index=False garde la catégorie comme colonne ordinaire, et sort=True ordonne les groupes. La boucle itertuples(index=False) affiche les lignes du rapport sans ajouter leur index pandas. Ce programme affiche les résultats, mais ne signale pas les rejets par un code de sortie non nul : ne transposez pas automatiquement la politique du projet CSV précédent.

Interpréter les valeurs manquantes, le masque et la copie

pd.to_numeric(..., errors="coerce") convertit les textes reconnus en valeurs numériques et représente les échecs comme valeurs manquantes. Le masque valid combine trois conditions avec & : valeur présente, supérieure ou égale à zéro, et partie fractionnaire nulle. Les parenthèses préservent le sens des comparaisons. La référence de to_numeric précise les conversions et leurs limites.

Ce masque contrôle le résultat numérique, pas une grammaire limitée aux chiffres écrits. Ainsi, une représentation telle que 3.0 peut être acceptée parce que sa valeur est entière, tandis que 3.5 est refusée. Une règle exigeant uniquement des chiffres décimaux dans le fichier demanderait un autre contrôle. Les petits montants de cet exercice tiennent dans int64 ; ne généralisez pas cette méthode à de très grands entiers ou à des sommes monétaires décimales exactes sans étudier la précision et les dépassements, y compris lors de l’agrégation.

df.loc[valid].copy() crée la table sélectionnée clean avant de remplacer sa colonne de montants par les entiers convertis. La table source df conserve les textes lus. Vous pouvez examiner les lignes exclues avec df.loc[~valid] pour les conserver ou expliquer leur rejet. Remplacer systématiquement un montant absent par zéro confondrait « information manquante » et « montant réellement nul ».

Le masque ne vérifie ni les identifiants manquants ou en double, ni les catégories vides. Pour des données réelles, définissez ces contrôles séparément avant de déclarer un enregistrement entièrement valide. Le projet de rapport CSV avec lignes rejetées montre une politique explicite par enregistrement lorsque pandas n’est pas nécessaire.

Pour utiliser un fichier réel avec les mêmes conventions de lecture, remplacez la création de df par pd.read_csv("expenses-input.csv", dtype={"id":"string", "category":"string", "amount":"string"}, keep_default_na=False). Conservez ensuite la conversion et le masque. Si vous utilisez les réglages par défaut de détection des valeurs manquantes, réexaminez aussi les catégories : une clé manquante peut être omise d’un regroupement. Ne supposez pas que le contrôle du montant garantit la présence de tous les groupes.

Enregistrer le rapport et rapprocher les résultats

Après avoir construit le rapport, ajoutez cette ligne tout à la fin de pandas_demo.py :

report.to_csv("category-report.csv", index=False, encoding="utf-8")

Choisissez un dossier d’exercice où category-report.csv n’existe pas : cette ligne remplace par défaut un fichier de même nom. Si vous voulez que la présence d’un fichier provoque un refus, vous pouvez ajouter l’argument mode="x" à cet appel. La référence de to_csv décrit ces modes et l’export de l’index.

Le fichier doit contenir l’en-tête category,amount, puis les lignes food,11500 et transport,2500. index=False évite une colonne supplémentaire contenant l’index du DataFrame. Comparez la somme des montants acceptés et celle du rapport : les deux doivent valoir 14 000.

  1. Dans le texte CSV du programme, remplacez uniquement bad par 0. Vous devez obtenir quatre lignes acceptées et aucune rejetée ; le total reste 14 000.
  2. Remplacez ensuite cette même valeur par -1. Vous devez retrouver trois lignes acceptées et une rejetée, car le montant est négatif.
  3. Rétablissez bad et relancez le programme pour retrouver exactement le premier résultat. Conservez les sorties précédentes sous d’autres noms si vous voulez les comparer avant un nouvel export.

Contrôlez les comptes, les lignes exclues et les sommes ensemble. Un résultat agrégé apparemment raisonnable peut cacher une conversion échouée ou un groupe absent ; ces vérifications donnent un sens précis à ce que contient votre rapport.