Traitement de données en tables : CSV, recherche, tri et fusion
Le fichier des inscrits à la médiathèque de Rennes contient 40 000 lignes. Comment retrouver en quelques instructions tous les lecteurs d'une même commune, détecter une adresse mail saisie deux fois, ou associer chaque emprunt au titre du livre correspondant ? Il faut d'abord savoir représenter ces données en Python, puis les interroger.
Qu'est-ce qu'une table ? Le format CSV
Une table est un ensemble de données organisé en lignes (les enregistrements) et en colonnes (les champs). Chaque colonne porte un nom, le descripteur, et ne contient que des valeurs d'un même domaine (entiers, textes, etc.).
Le format CSV (Comma-Separated Values) stocke une table dans un fichier texte : un enregistrement par ligne, les champs séparés par un séparateur (virgule, point-virgule ou tabulation). La première ligne contient souvent les descripteurs.
nom,ville,age
Durand,Lille,17
Petit,Nice,16
Roux,Lille,16
En Python, on représente une table par une liste de dictionnaires : chaque dictionnaire est un enregistrement, ses clés sont les descripteurs.
table = [
{"nom": "Durand", "ville": "Lille", "age": 17},
{"nom": "Petit", "ville": "Nice", "age": 16},
{"nom": "Roux", "ville": "Lille", "age": 16},
]
print(table[1]["ville"]) # Nice
On peut aussi utiliser une liste de p-uplets, plus légère mais moins lisible : on accède alors aux champs par leur indice.
Importer un fichier CSV
Le module csv de la bibliothèque standard lit le fichier. DictReader utilise la première ligne comme descripteurs et produit un dictionnaire par ligne.
import csv
with open("eleves.csv", encoding="utf-8") as f:
lecteur = csv.DictReader(f) # séparateur « , » par défaut
table = [ligne for ligne in lecteur]
L'instruction with ferme automatiquement le fichier à la sortie du bloc, même en cas d'erreur. Sans with, il faudrait appeler f.close(). Si le séparateur est un point-virgule (courant dans les fichiers français, car la virgule sert de virgule décimale), on écrit csv.DictReader(f, delimiter=";").
Voici le contenu d'un fichier CSV : a) Quel est le séparateur de champs ? Quels sont les descripteurs ? b) Combien d'enregistrements contient la table ? c) Représenter…
Voici le contenu d'un fichier CSV :
nom,ville,age
Durand,Lille,17
Petit,Nice,16
Roux,Lille,16
- a) Quel est le séparateur de champs ? Quels sont les descripteurs ?
- b) Combien d'enregistrements contient la table ?
- c) Représenter cette table sous forme d'une liste de dictionnaires en Python.
Écrire en Python les instructions qui ouvrent le fichier eleves.csv (encodage UTF-8), le lisent avec csv.DictReader et stockent les enregistrements dans une liste table.…
Écrire en Python les instructions qui ouvrent le fichier eleves.csv (encodage UTF-8), le lisent avec csv.DictReader et stockent les enregistrements dans une liste table. Préciser comment fermer correctement le fichier.
Rechercher dans une table
Rechercher, c'est sélectionner les enregistrements qui vérifient un critère exprimé avec and, or, not et des comparaisons. La compréhension est l'outil naturel.
Pour ajouter un enregistrement, on utilise append : table.append({"nom": "Blanc", "ville": "Brest", "age": 17}).
Doublons et cohérence
Deux contrôles classiques :
-
chercher les doublons : une même valeur (un mail, un identifiant) qui apparaît plusieurs fois ;
-
tester la cohérence : champs non vides, valeurs dans le bon domaine, présence d'un
@dans un mail.
Avec deux boucles imbriquées, la recherche de doublons compare toutes les paires : environ $n^2$ opérations. Avec un dictionnaire (ou un ensemble) de valeurs déjà rencontrées, un seul parcours suffit, car tester x in d est quasi immédiat :
def doublons(table, champ):
vus = {}
res = []
for e in table:
v = e[champ]
if v in vus and v not in res:
res.append(v)
vus[v] = True
return res
Le coût est proportionnel à $n$ : sur 40 000 lignes, on évite plus d'un milliard de comparaisons.
def coherente(table):
for e in table:
for valeur in e.values():
if valeur == "":
return False
if "@" not in e["mail"]:
return False
return True
On dispose de la table t, liste de dictionnaires, avec les champs nom, ville, age de l'exercice précédent. Chaque enregistrement est de la forme {"nom": "Martin",…
On dispose de la table t, liste de dictionnaires, avec les champs nom, ville, age de l'exercice précédent. Chaque enregistrement est de la forme {"nom": "Martin", "ville": "Lille", "age": 16} : le champ age est un entier.
- a) Écrire une compréhension qui renvoie la liste des enregistrements dont la ville est
"Lille".
- b) Écrire une compréhension qui renvoie la liste des noms des élèves d'âge 16.
- c) Écrire une instruction qui ajoute l'enregistrement de Blanc, Brest, 17 ans.
Une table inscrits est une liste de dictionnaires avec les champs id, nom, mail. a) Écrire une fonction doublons(table) qui renvoie la liste des adresses mail…
Une table inscrits est une liste de dictionnaires avec les champs id, nom, mail.
- a) Écrire une fonction
doublons(table)qui renvoie la liste des adresses mail apparaissant plus d'une fois.
- b) Écrire une fonction
coherente(table)qui renvoieTruesi tous les champs de chaque enregistrement sont non vides et si le mail contient le caractère@.
- c) Expliquer pourquoi l'utilisation d'un dictionnaire améliore l'efficacité de la recherche de doublons par rapport à deux boucles imbriquées.
Trier une table
La fonction sorted(table, key=f, reverse=False) renvoie une nouvelle liste triée ; table.sort(key=f) trie la liste en place. La fonction key reçoit un enregistrement et renvoie la valeur qui sert à comparer.
def population(v):
return v["population"]
par_pop = sorted(villes, key=population, reverse=True) # décroissant
# ou : sorted(villes, key=lambda v: v["population"], reverse=True)
Trier selon deux critères
On peut renvoyer un couple : Python compare d'abord le premier élément, puis le second en cas d'égalité.
sorted(t, key=lambda e: (e["ville"], e["age"])) # ville ↑, puis âge ↑
Pour une ville croissante et un âge décroissant, on utilise -e["age"] (valable pour un nombre) : key=lambda e: (e["ville"], -e["age"]).
Autre méthode : le tri de Python est stable : deux éléments égaux pour la clé gardent leur ordre relatif d'origine. On trie donc d'abord selon le critère secondaire, puis selon le critère principal :
t1 = sorted(t, key=lambda e: e["age"], reverse=True)
t2 = sorted(t1, key=lambda e: e["ville"])
Une table villes contient les champs nom, region, population (entier) et superficie (en km²). a) Écrire une compréhension qui renvoie les villes de la région "Bretagne"…
Une table villes contient les champs nom, region, population (entier) et superficie (en km²).
- a) Écrire une compréhension qui renvoie les villes de la région
"Bretagne"de plus de 50 000 habitants.
- b) Écrire une fonction qui renvoie la moyenne des densités (population divisée par superficie) de chaque ville de la table.
- c) Trier la table par population décroissante avec
sortedet une fonctionkey.
On veut trier une table t selon deux critères : d'abord par ville croissante, puis, à ville égale, par age décroissant. a) Écrire l'appel à sorted correspondant en…
On veut trier une table t selon deux critères : d'abord par ville croissante, puis, à ville égale, par age décroissant.
- a) Écrire l'appel à
sortedcorrespondant en utilisant une fonctionkeyrenvoyant un couple.
- b) Justifier que le tri de Python est stable et montrer comment l'utiliser pour obtenir le même résultat avec deux tris successifs. Préciser l'ordre des deux tris.
- c) Appliquer la méthode aux enregistrements (Lille, 16), (Nice, 17), (Lille, 18), (Nice, 15) et donner le résultat.
Fusionner des tables
Souvent, l'information est répartie sur plusieurs tables reliées par un champ commun. Ici, eleves contient id_club et clubs décrit chaque club :
| eleves | id | nom | id_club |
|---|---|---|---|
| 1 | Léa | 2 | |
| 2 | Hugo | 1 |
| clubs | id_club | club | salle |
|---|---|---|---|
| 1 | Échecs | B12 | |
| 2 | Théâtre | A03 |
Fusionner, c'est construire une nouvelle table qui combine les enregistrements des deux tables dont le champ commun est égal.
def fusion(eleves, clubs):
res = []
for e in eleves:
for c in clubs:
if e["id_club"] == c["id_club"]:
res.append({"nom": e["nom"],
"club": c["club"],
"salle": c["salle"]})
return res
Avec $n$ élèves et $m$ clubs, les deux boucles imbriquées font $n \times m$ comparaisons. Pour faire mieux, on indexe d'abord la petite table par un dictionnaire dont la clé est le champ commun :
index = {c["id_club"]: c for c in clubs}
res = []
for e in eleves:
c = index[e["id_club"]]
res.append({"nom": e["nom"], "club": c["club"], "salle": c["salle"]})
Le coût devient proportionnel à $n + m$.
Les contrôles de cohérence entre tables vérifient par exemple que chaque id_club d'une table existe dans l'autre, ou qu'un identifiant censé être unique l'est bien.
Deux tables : eleves avec les champs id, nom, id_club, et clubs avec les champs id_club, club, salle. a) Écrire une fonction fusion(eleves, clubs) qui renvoie une table…
Deux tables : eleves avec les champs id, nom, id_club, et clubs avec les champs id_club, club, salle.
- a) Écrire une fonction
fusion(eleves, clubs)qui renvoie une table dont chaque enregistrement contientnom,clubetsalle.
- b) Que devient un élève dont
id_clubn'existe pas dansclubs? Proposer un traitement.
- c) Donner la complexité de la fusion avec deux boucles imbriquées, puis expliquer comment l'améliorer.
Une table emprunts (champs id_livre, id_lecteur, date) et une table livres (champs id_livre, titre, auteur) décrivent une médiathèque à Rennes. a) Écrire une fonction…
Une table emprunts (champs id_livre, id_lecteur, date) et une table livres (champs id_livre, titre, auteur) décrivent une médiathèque à Rennes.
- a) Écrire une fonction qui renvoie le titre du livre le plus emprunté.
- b) Écrire une fonction qui renvoie, pour chaque lecteur, la liste des titres empruntés.
- c) Identifier deux contrôles de cohérence entre les tables et écrire une fonction qui les vérifie.
Calculer des agrégats
Un traitement fréquent : regrouper les enregistrements selon un champ et calculer une valeur par groupe (somme, moyenne, maximum). Le dictionnaire convient très bien : la clé est le groupe.
Une table releves contient les champs station, date et temp (température en °C, lue comme chaîne dans le CSV) pour des stations météo françaises. a) Écrire une fonction…
Une table releves contient les champs station, date et temp (température en °C, lue comme chaîne dans le CSV) pour des stations météo françaises.
- a) Écrire une fonction qui convertit les températures en flottants et signale les valeurs manquantes (chaîne vide) en les écartant.
- b) Écrire une fonction qui renvoie un dictionnaire donnant la température moyenne par station.
- c) En déduire la station la plus chaude en moyenne. Préciser ce qui se passe en cas d'égalité avec votre code.
Retour au début : pour les 40 000 inscrits de Rennes, on lit le CSV avec DictReader, on filtre par compréhension, on détecte les doublons avec un dictionnaire et on associe chaque emprunt à son livre avec un index sur id_livre.
L'essentiel
-
Une table est une liste de dictionnaires (un par enregistrement) ; le fichier CSV la stocke en texte avec un séparateur et une ligne de descripteurs.
-
csv.DictReaderlit le fichier dans un blocwith; toutes les valeurs sont des chaînes : convertir avecintoufloat. -
Rechercher : compréhension avec un critère en logique (
and,or,not) ; doublons et cohérence se contrôlent, un dictionnaire évite le coût en $n^2$. -
Trier :
sorted(t, key=…, reverse=…); pour deux critères, une clé en couple ou deux tris successifs (secondaire d'abord) grâce à la stabilité. -
Fusionner : combiner deux tables via un champ commun ; indexer avec un dictionnaire ramène le coût de $n \times m$ à $n + m$ ; traiter les clés absentes.