13 - Traitement de données en tables
QCM – vérification des prérequis
Pour chaque question, une seule réponse est correcte.
1. Qu’est-ce qu’un fichier CSV ?
- A. Un fichier image compressé
- B. Un fichier texte dont les données sont séparées par un caractère délimiteur (virgule, point-virgule…)
- C. Un fichier binaire utilisé par les bases de données
- D. Un fichier exécutable Python
Correction
Réponse : B. CSV signifie Comma-Separated Values. C’est un fichier texte dans lequel chaque ligne représente un enregistrement et les champs sont séparés par un délimiteur (souvent ; en France, , en anglais).
- A est faux : les fichiers image sont en PNG, JPG, etc.
- C est faux : un fichier CSV est un fichier texte, lisible avec un simple éditeur.
- D est faux : un fichier Python a l’extension
.py.
2. En Python, quel module permet de lire un fichier CSV avec des dictionnaires ?
- A.
json - B.
csv - C.
os - D.
math
Correction
Réponse : B. Le module csv fournit notamment csv.DictReader qui lit chaque ligne comme un dictionnaire dont les clés sont les en-têtes (descripteurs) du fichier.
- A est faux :
jsontraite les fichiers au format JSON, pas CSV. - C est faux :
osgère les interactions avec le système d’exploitation (chemins, fichiers). - D est faux :
mathfournit des fonctions mathématiques.
3. Que renvoie csv.DictReader lorsqu’on itère dessus ?
- A. Une liste de listes
- B. Un dictionnaire unique contenant toutes les données
- C. Un itérateur de dictionnaires, un par ligne
- D. Une chaîne de caractères
Correction
Réponse : C. DictReader renvoie un itérateur : à chaque itération, on obtient un dictionnaire représentant une ligne du fichier, avec les en-têtes comme clés.
- A est faux : c’est
csv.reader(sans « Dict ») qui renvoie des listes. - B est faux : on obtient un dictionnaire par ligne, pas un seul pour tout le fichier.
- D est faux : le résultat n’est pas une simple chaîne.
4. Comment trier une liste de dictionnaires par la valeur d’une clé "note" ?
- A.
liste.sort() - B.
sorted(liste, key=lambda d: d["note"]) - C.
liste.filter("note") - D.
sorted(liste)
Correction
Réponse : B. sorted avec le paramètre key permet de spécifier le critère de tri. La fonction lambda d: d["note"] extrait la valeur associée à la clé "note" pour chaque dictionnaire.
- A est faux :
sort()sans argument tente de comparer les dictionnaires entre eux, ce qui lève une erreur. - C est faux :
filtern’est pas une méthode de liste (et ne trie pas). - D est faux : même problème que A, Python ne sait pas comment comparer des dictionnaires par défaut.
Le fichier nat2020.csv est un jeu de données ouvertes concernant les prénoms donnés en France de 1900 à 2020.
TP : exploration du fichier des prénoms
Dans ce TP, vous allez explorer un jeu de données réel issu de l’INSEE : le fichier des prénoms attribués aux enfants nés en France depuis 1900. Vous apprendrez à importer, rechercher, trier et fusionner des données organisées en tables. Durée : 5 à 6 heures.
Compétences à acquérir : importer une table depuis un fichier CSV ; rechercher les lignes d’une table vérifiant des critères exprimés en logique propositionnelle ; recherche de doublons, tests de cohérence ; trier une table suivant une colonne ; construire une nouvelle table en combinant les données de deux tables ; notion de domaine de valeurs.
Les données utilisées proviennent de l’INSEE et sont placées sous licence ouverte.
Présentation des données
Télécharger l’archive prenoms.zip
et la décompresser dans votre répertoire de travail : elle contient le fichier prenoms.csv (13 Mo, plus de 700 000 lignes) des prénoms attribués aux enfants nés en France entre 1900 et 2024. Il est composé de quatre colonnes (descripteurs) :
sexe:1pour masculin,2pour féminin ;prenom: le prénom (en majuscules, sans accents) ;periode: l’année de naissance ;valeur: le nombre de naissances (arrondi au multiple de 5).
Extrait du fichier :
sexe;prenom;periode;valeur
1;AARON;1980;5
1;AARON;1990;25
1;AARON;2000;120
1;AARON;2010;1605
2;EMMA;2000;6845
2;EMMA;2010;6400
Import des données
Exercice 1 : chargement du fichier CSV.
Compléter la fonction
charger_csvci-dessous. Cette fonction prend en paramètre le nom d’un fichier CSV et renvoie une liste de dictionnaires.Remarque. La fonction
csv.DictReaderlit toutes les valeurs comme des chaînes de caractères. Par exemple,ligne['valeur']vaut'295'(une chaîne) et non295(un entier). Il faut donc convertir les valeurs numériques en entiers pour pouvoir faire des calculs.import csv def charger_csv(nom_fichier): """ Charge un fichier CSV et renvoie une liste de dictionnaires. Chaque dictionnaire représente une ligne du fichier. """ table = [] with open(nom_fichier, 'r', encoding='utf-8') as fichier: lecteur = csv.DictReader(fichier, delimiter=';') for ligne in lecteur: # À COMPLÉTER : convertir les chaînes en entiers ligne['valeur'] = ... ligne['periode'] = ... table.append(ligne) return table # Chargement des données prenoms = charger_csv('prenoms.csv')Écrire l’instruction qui affiche le nombre total de lignes dans la table.
Écrire une boucle qui affiche les 5 premières lignes de la table. Que représente chaque ligne ?
Écrire l’instruction qui affiche les descripteurs (les clés) de la première ligne.
Correction
- Code complété :
import csv
def charger_csv(nom_fichier):
table = []
with open(nom_fichier, 'r', encoding='utf-8') as fichier:
lecteur = csv.DictReader(fichier, delimiter=';')
for ligne in lecteur:
ligne['valeur'] = int(ligne['valeur'])
ligne['periode'] = int(ligne['periode'])
table.append(ligne)
return table
prenoms = charger_csv('prenoms.csv')
Nombre de lignes :
print(len(prenoms)).Affichage des 5 premières lignes :
for i in range(5):
print(prenoms[i])
Chaque ligne est un p-uplet nommé (dictionnaire) qui associe un prénom, un sexe et une année au nombre de naissances correspondant.
- Descripteurs :
print(list(prenoms[0].keys())).
Exercice 2 : tests de cohérence.
Avant d’exploiter des données, il est important de vérifier leur cohérence.
- Écrire une fonction
verifier_sexe(table)qui prend en paramètre une table et renvoieTruesi toutes les valeurs du descripteursexesont'1'ou'2', etFalsesinon. - Écrire une fonction
annees_presentes(table)qui prend en paramètre une table et renvoie la liste des années présentes (sans doublons). - En utilisant cette fonction, vérifier s’il y a des années manquantes entre 1900 et 2024.
Correction
- Vérification du sexe :
def verifier_sexe(table):
for ligne in table:
if ligne['sexe'] != '1' and ligne['sexe'] != '2':
return False
return True
print(verifier_sexe(prenoms))
- Années présentes :
def annees_presentes(table):
annees = []
for ligne in table:
if ligne['periode'] not in annees:
annees.append(ligne['periode'])
return sorted(annees) # tri optionnel
annees = annees_presentes(prenoms)
print(min(annees), max(annees))
- Vérification des années manquantes :
manquantes = []
for a in range(1900, 2025):
if a not in annees:
manquantes.append(a)
print(manquantes)
Recherche dans une table
Exercice 3 : recherche simple.
- Écrire une fonction
rechercher_prenom(table, prenom)qui prend en paramètres une table et un prénom (chaîne de caractères), et renvoie la liste de toutes les lignes dont le descripteurprenomest égal au prénom cherché. - Utiliser cette fonction pour récupérer toutes les lignes correspondant au prénom
'CLAUDE'. Afficher, pour chaque ligne, l’année, le nombre de naissances et le sexe. - Écrire une fonction
total_naissances(table, prenom)qui prend en paramètres une table et un prénom, et renvoie le nombre total de naissances pour ce prénom (somme des valeurs du descripteurvaleur). - Calculer le total des naissances pour le prénom
'MARIE'puis pour le prénom'JEAN'. - Question personnelle : rechercher votre propre prénom (en majuscules) et calculer le nombre total de naissances. Que pouvez-vous dire sur la popularité de votre prénom ?
Correction
- Fonction de recherche :
def rechercher_prenom(table, prenom):
resultats = []
for ligne in table:
if ligne['prenom'] == prenom:
resultats.append(ligne)
return resultats
- Évolution de CLAUDE :
claude = rechercher_prenom(prenoms, 'CLAUDE')
for ligne in claude:
print(ligne['periode'], ligne['valeur'], ligne['sexe'])
On observe que CLAUDE était très populaire dans les années 1930-1950, puis a fortement décliné.
- Total des naissances :
def total_naissances(table, prenom):
total = 0
for ligne in table:
if ligne['prenom'] == prenom:
total = total + ligne['valeur']
return total
Totaux :
print(total_naissances(prenoms, 'MARIE'))etprint(total_naissances(prenoms, 'JEAN')).Réponse personnelle.
Exercice 4 : recherche avec plusieurs critères.
On souhaite rechercher des lignes vérifiant plusieurs critères à la fois.
- Écrire une fonction
rechercher(table, sexe, annee)qui prend en paramètres une table, un sexe ('1'ou'2') et une année (entier), et renvoie la liste des lignes vérifiant ces deux critères simultanément. - Utiliser cette fonction pour trouver tous les prénoms féminins attribués en 2020. Combien y en a-t-il ?
- Combien de prénoms masculins différents ont été attribués en 1950 ? Indication : construire d’abord la liste des lignes correspondantes, puis extraire les prénoms sans doublons.
Correction
- Fonction de recherche multicritères :
def rechercher(table, sexe, annee):
resultats = []
for ligne in table:
if ligne['sexe'] == sexe and ligne['periode'] == annee:
resultats.append(ligne)
return resultats
- Prénoms féminins en 2020 :
feminins_2020 = rechercher(prenoms, '2', 2020)
print(len(feminins_2020))
- Prénoms masculins différents en 1950 :
masculins_1950 = rechercher(prenoms, '1', 1950)
prenoms_uniques = []
for ligne in masculins_1950:
if ligne['prenom'] not in prenoms_uniques:
prenoms_uniques.append(ligne['prenom'])
print(len(prenoms_uniques))
Exercice 5 : recherche de doublons.
Certains prénoms sont attribués à la fois à des garçons et à des filles. On appelle ces prénoms des « prénoms mixtes ».
- Écrire une fonction
liste_prenoms_par_sexe(table, sexe)qui prend en paramètres une table et un sexe, et renvoie la liste des prénoms (sans doublons) correspondant à ce sexe. - En utilisant cette fonction, construire la liste
masculinsdes prénoms masculins et la listefemininsdes prénoms féminins. - Écrire une fonction
prenoms_mixtes(masculins, feminins)qui prend en paramètres ces deux listes et renvoie la liste des prénoms présents dans les deux listes. - Combien y a-t-il de prénoms mixtes ? Donner quelques exemples.
Correction
- Liste des prénoms par sexe :
def liste_prenoms_par_sexe(table, sexe):
prenoms_liste = []
for ligne in table:
if ligne['sexe'] == sexe:
if ligne['prenom'] not in prenoms_liste:
prenoms_liste.append(ligne['prenom'])
return prenoms_liste
- Construction des listes :
masculins = liste_prenoms_par_sexe(prenoms, '1')
feminins = liste_prenoms_par_sexe(prenoms, '2')
- Prénoms mixtes :
def prenoms_mixtes(masculins, feminins):
mixtes = []
for prenom in masculins:
if prenom in feminins:
mixtes.append(prenom)
return mixtes
- Résultat :
mixtes = prenoms_mixtes(masculins, feminins)
print(len(mixtes))
print(mixtes[:10])
Tri d’une table
Rappel sur les fonctions anonymes (lambda). La fonction sorted(liste, key=fonction) permet de trier une liste. Le paramètre key indique la fonction à utiliser pour extraire la valeur de tri de chaque élément. On utilise souvent une fonction anonyme (ou lambda) pour définir cette fonction de tri. Par exemple, lambda x: x['periode'] est équivalent à :
def extraire_annee(x):
return x['periode']
L’avantage de la syntaxe lambda est sa concision : on peut définir la fonction directement dans l’appel à sorted, sans avoir à lui donner un nom.
Exercice 6 : tri suivant une colonne.
- En utilisant
rechercher_prenom, récupérer toutes les lignes correspondant au prénom'EMMA'. - Trier cette liste par année croissante en utilisant
emma_trie = sorted(emma, key=lambda x: x['periode']). - Afficher l’évolution du prénom EMMA entre 2000 et 2024 (année et nombre de naissances).
- Écrire une fonction
top_prenoms(table, annee, sexe, n)qui prend en paramètres une table, une année, un sexe et un entiern, et renvoie lesnprénoms les plus attribués cette année-là pour ce sexe, triés par nombre de naissances décroissant. Indication : utilisersorted(..., key=lambda x: x['valeur'], reverse=True). - Afficher le top 10 des prénoms féminins en 2024.
Correction
Récupération :
emma = rechercher_prenom(prenoms, 'EMMA').Tri par année :
emma_trie = sorted(emma, key=lambda x: x['periode']).Évolution d’EMMA entre 2000 et 2024 :
for ligne in emma_trie:
if 2000 <= ligne['periode'] <= 2024:
print(f"Année {ligne['periode']} : {ligne['valeur']} naissances")
On observe une forte croissance jusqu'en 2005-2006, puis une diminution progressive.
- Fonction
top_prenoms:
def top_prenoms(table, annee, sexe, n):
selection = rechercher(table, sexe, annee)
selection_triee = sorted(selection, key=lambda x: x['valeur'], reverse=True)
return selection_triee[:n]
- Top 10 féminin 2024 :
top_f = top_prenoms(prenoms, 2024, '2', 10)
for i in range(len(top_f)):
ligne = top_f[i]
print(f"{i+1}. {ligne['prenom']} ({ligne['valeur']})")
Exercice 7 : analyse de tendances.
- Écrire une fonction
annee_pic(table, prenom)qui prend en paramètres une table et un prénom, et renvoie l’année où ce prénom a été le plus attribué (c’est-à-dire l’année avec le plus grand nombre de naissances). Indication : utiliser la fonctionmax(liste, key=...)pour trouver l’élément ayant la plus grande valeur pour un critère donné. - Calculer l’année du pic pour les prénoms suivants :
'BRIGITTE','SYLVIE','NATHALIE','STEPHANIE','LEA'. Que remarque-t-on sur la succession de ces prénoms dans le temps ? - Question de réflexion : à votre avis, quels facteurs peuvent influencer les cycles de popularité des prénoms ? Pensez aux médias, aux personnalités célèbres, aux événements historiques…
Correction
- Année du pic :
def annee_pic(table, prenom):
donnees = rechercher_prenom(table, prenom)
if len(donnees) == 0:
return None
ligne_max = max(donnees, key=lambda x: x['valeur'])
return ligne_max['periode']
- Pics des prénoms :
for p in ['BRIGITTE', 'SYLVIE', 'NATHALIE', 'STEPHANIE', 'LEA']:
print(p, annee_pic(prenoms, p))
On observe une succession de prénoms « à la mode », chacun dominant une décennie. Cela illustre les cycles de popularité des prénoms.
- Plusieurs facteurs influencent la popularité des prénoms : les célébrités (actrices, chanteuses, sportives), les personnages de fiction (films, séries, livres), les événements historiques, l’effet de mode et d’imitation sociale, ou encore la volonté de se distinguer quand un prénom devient trop courant.
Fusion de tables
On dispose d’un second fichier origines.csv (à déposer dans votre répertoire de travail) contenant l’origine étymologique de certains prénoms :
prenom;origine;signification
EMMA;germanique;universel ou puissant
GABRIEL;hebreu;force de Dieu
LEA;hebreu;fatiguee ou lionne
LUCAS;grec;lumiere
JADE;espagnol;pierre verte
LOUIS;germanique;illustre au combat
Exercice 8 : chargement et fusion de deux tables.
Charger le fichier
origines.csvdans une tableorigines(sans conversion de types, car toutes les valeurs sont des chaînes) :def charger_csv_simple(nom_fichier): table = [] with open(nom_fichier, 'r', encoding='utf-8') as fichier: lecteur = csv.DictReader(fichier, delimiter=';') for ligne in lecteur: table.append(ligne) return table origines = charger_csv_simple('origines.csv')Écrire une fonction
fusionner(table1, table2, cle)qui prend en paramètres deux tables et le nom d’un descripteur commun (la clé), et renvoie une nouvelle table contenant les lignes detable1enrichies des informations detable2lorsque les valeurs de la clé correspondent.Principe : pour chaque ligne de
table1, on cherche danstable2une ligne ayant la même valeur pour le descripteurcle. Si on en trouve une, on crée une nouvelle ligne combinant les informations des deux.def fusionner(table1, table2, cle): resultat = [] for ligne1 in table1: # À compléter : parcourir table2 # À compléter : vérifier si la clé correspond # À compléter : créer la nouvelle ligne enrichie return resultatFusionner les tables
prenomsetoriginessur la clé'prenom'.Pour chaque prénom présent dans
origines, afficher son origine, sa signification et le nombre de naissances en 2024.
Correction
Chargement : voir le code de l’énoncé.
Fonction de fusion (version simple avec deux boucles) :
def fusionner(table1, table2, cle):
resultat = []
for ligne1 in table1:
for ligne2 in table2:
if ligne1[cle] == ligne2[cle]:
nouvelle_ligne = {{}}
for k in ligne1:
nouvelle_ligne[k] = ligne1[k]
for k in ligne2:
nouvelle_ligne[k] = ligne2[k]
resultat.append(nouvelle_ligne)
return resultat
- Fusion :
prenoms_enrichis = fusionner(prenoms, origines, 'prenom')
print(len(prenoms_enrichis))
- Affichage enrichi :
for ligne_origine in origines:
nom = ligne_origine['prenom']
for ligne in prenoms_enrichis:
if ligne['prenom'] == nom and ligne['periode'] == 2024:
print(f"{{nom}} ({{ligne_origine['origine']}}) : {{ligne['valeur']}} naissances")
Pour aller plus loin : optimisation de la fusion. La fonction fusionner ci-dessus utilise deux boucles imbriquées. Pour chaque ligne de table1, elle parcourt toute la table2. Si les deux tables contiennent \(n\) et \(m\) lignes, cela représente \(n \times m\) comparaisons. On peut améliorer l’efficacité en construisant d’abord un dictionnaire d’accès rapide pour table2 :
def fusionner_optimise(table1, table2, cle):
dict_table2 = {{}}
for ligne in table2:
dict_table2[ligne[cle]] = ligne
resultat = []
for ligne1 in table1:
valeur_cle = ligne1[cle]
if valeur_cle in dict_table2:
nouvelle_ligne = {{}}
for k in ligne1:
nouvelle_ligne[k] = ligne1[k]
ligne2 = dict_table2[valeur_cle]
for k in ligne2:
nouvelle_ligne[k] = ligne2[k]
resultat.append(nouvelle_ligne)
return resultat
Cette version ne parcourt table2 qu’une seule fois pour construire le dictionnaire, puis l’accès à chaque élément se fait en temps constant.
Exercice 9 : notion de domaine de valeurs.
- Écrire une fonction
valeurs_distinctes(table, descripteur)qui prend en paramètres une table et le nom d’un descripteur, et renvoie la liste des valeurs distinctes (sans doublons) de ce descripteur dans la table. - Appliquer cette fonction au descripteur
'origine'de la tableorigines. La liste obtenue s’appelle le domaine de valeurs du descripteur. - Quel est le domaine de valeurs du descripteur
'sexe'dans la tableprenoms?
Correction
- Fonction :
def valeurs_distinctes(table, descripteur):
valeurs = []
for ligne in table:
if ligne[descripteur] not in valeurs:
valeurs.append(ligne[descripteur])
return valeurs
Domaine de
'origine':print(valeurs_distinctes(origines, 'origine')).Domaine de
'sexe':print(valeurs_distinctes(prenoms, 'sexe')), soit['1', '2'].
Le domaine de valeurs d’un descripteur est l’ensemble des valeurs que ce descripteur peut prendre. Cette notion est fondamentale en bases de données pour garantir la cohérence des données.
Pour aller plus loin
Exercice 10 : synthèse, portrait d’un prénom.
Écrire une fonction portrait_prenom(table, prenom) qui prend en paramètres une table et un prénom, et affiche un résumé complet de ce prénom :
- le nombre total de naissances ;
- l’année du pic de popularité et le nombre de naissances cette année-là ;
- la répartition entre masculin et féminin (en pourcentage) ;
- la première et la dernière année d’apparition dans les données.
Tester la fonction avec les prénoms 'CAMILLE', 'DOMINIQUE' et 'CLAUDE'.
Correction
def portrait_prenom(table, prenom):
donnees = rechercher_prenom(table, prenom)
if len(donnees) == 0:
print(f"Aucune donnée pour le prénom {prenom}")
return
# Total des naissances
total = 0
for ligne in donnees:
total = total + ligne['valeur']
# Année du pic
ligne_max = max(donnees, key=lambda x: x['valeur'])
annee_max = ligne_max['periode']
nombre_max = ligne_max['valeur']
# Répartition M/F
total_m = 0
total_f = 0
for ligne in donnees:
if ligne['sexe'] == '1':
total_m = total_m + ligne['valeur']
else:
total_f = total_f + ligne['valeur']
if total > 0:
pct_m = round(100 * total_m / total, 1)
pct_f = round(100 * total_f / total, 1)
else:
pct_m = 0
pct_f = 0
# Première et dernière année
annees = []
for ligne in donnees:
annees.append(ligne['periode'])
premiere = min(annees)
derniere = max(annees)
# Affichage
print(f"=== Portrait du prénom {prenom} ===")
print(f"Total des naissances : {total}")
print(f"Pic de popularité : {annee_max} ({nombre_max} naissances)")
print(f"Répartition : {pct_m}% masculin, {pct_f}% féminin")
print(f"Période : de {premiere} à {derniere}")
# Tests
portrait_prenom(prenoms, 'CAMILLE')
portrait_prenom(prenoms, 'DOMINIQUE')
portrait_prenom(prenoms, 'CLAUDE')
Autres jeux de données
Exercice 11 : requêtes sur les villes de France.
Le fichier villes.csv décrit les communes françaises (nom, département, population en 2012, densité, altitude minimale, etc.). Écrire les programmes Python qui répondent aux questions suivantes.
- Donner la liste des villes du Rhône qui ont une altitude minimale supérieure à 500 m.
- Donner la liste des villes du Rhône qui ont une densité d’habitants inférieure à 200 hab/km² (la table contiendra trois colonnes : le nom de la ville, la densité de la population et l’altitude minimale).
- Donner la liste de toutes les villes françaises ayant une densité de population supérieure à 50 hab/km² et une altitude minimale supérieure à 1500 m.
- Donner le nombre d’habitants du Rhône en 2012.
- Trier les données de façon à obtenir la ville française ayant l’altitude minimale la plus élevée.
- Quelle est la ville française ayant la densité de population la plus forte ?
Exercice 12 : concaténation et jointure.
Les communes du département des Alpes-Maritimes ont été oubliées dans le fichier
villes.csvet se trouvent dans le fichier villes-06.csv . Les descripteurs étant les mêmes dans les deux fichiers, écrire un programme Python permettant de fusionner les deux tables.On dispose de deux fichiers clients.csv et commandes.csv qui n’ont qu’un descripteur en commun :
n_client.- Écrire un programme Python qui permet de fusionner les deux tables.
- L’ordre a-t-il une importance lors de la fusion ?
- Si vous avez fusionné à partir de la table
clients, vous avez peut-être remarqué que Mme Élodie Gaulin (numéro de client 895) est bien présente mais qu’il n’y a pas de commande correspondante. Pourquoi d’après vous ? - De la même manière, aucune trace de la commande numéro 1324 du 01/02/2017 dans la table fusionnée. Pourquoi d’après vous ?
On remarquera aussi que Mme Neufville a fait deux commandes. D’où la nécessité de structurer correctement les données.