14 - Traitement de données en tables

Cours associé : 14 - CSV .

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 publiée après la séance.

2. En Python, quel module permet de lire un fichier CSV avec des dictionnaires ?

  • A. json
  • B. csv
  • C. os
  • D. math

Correction publiée après la séance.

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 publiée après la séance.

4. On a défini la fonction note(d), qui renvoie d["note"]. Comment trier une liste de dictionnaires suivant la clé "note" ?

  • A. liste.sort()
  • B. sorted(liste, key=note)
  • C. liste.filter("note")
  • D. sorted(liste)

Correction publiée après la séance.


Le TP porte sur un jeu de données ouvertes concernant les prénoms donnés en France de 1900 à 2024 : le fichier à utiliser, prenoms.csv, se télécharge dans la partie « Présentation des données » ci-dessous.

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 : 1 pour masculin, 2 pour féminin ;
  • prenom : le prénom, en majuscules et avec ses accents (par exemple STÉPHANIE ou LÉA) ;
  • 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.

  1. Compléter la fonction charger_csv ci-dessous. Cette fonction prend en paramètre le nom d’un fichier CSV et renvoie une liste de dictionnaires.

    Remarque. La fonction csv.DictReader lit toutes les valeurs comme des chaînes de caractères. Par exemple, ligne['valeur'] vaut '295' (une chaîne) et non 295 (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')
    
    Indice

    int(...) convertit une chaîne en entier : appliquer cette conversion à ligne['valeur'] et à ligne['periode'].

  2. Écrire l’instruction qui affiche le nombre total de lignes dans la table.

    Indice

    La table est une liste : son nombre de lignes est sa longueur.

  3. Écrire une boucle qui affiche les 5 premières lignes de la table. Que représente chaque ligne ?

    Indice

    prenoms[i] est la ligne d’indice i, et une boucle for i in range(5): suffit. Lire les clés et les valeurs d’une ligne pour dire ce qu’elle décrit.

  4. Écrire l’instruction qui affiche les descripteurs (les clés) de la première ligne.

    Indice

    La première ligne, prenoms[0], est un dictionnaire : la méthode keys() donne ses clés.

Correction publiée après la séance.

Exercice 2 : tests de cohérence.

Avant d’exploiter des données, il est important de vérifier leur cohérence.

  1. Écrire une fonction verifier_sexe(table) qui prend en paramètre une table et renvoie True si toutes les valeurs du descripteur sexe sont '1' ou '2', et False sinon.

    Indice

    Parcourir les lignes et renvoyer False dès qu’une valeur de ligne['sexe'] n’est ni '1' ni '2' ; après la boucle, toutes les lignes sont correctes : renvoyer True.

    Code à copier puis compléter
    def verifier_sexe(table):
        for ligne in table:
            if ligne['sexe'] != '1' and ...:
                return ...
        return True
    
  2. Écrire une fonction annees_presentes(table) qui prend en paramètre une table et renvoie la liste des années présentes (sans doublons).

    Indice

    Partir d’une liste vide et n’y ajouter l’année d’une ligne que si elle n’y figure pas déjà (not in).

    Code à copier puis compléter
    def annees_presentes(table):
        annees = []
        for ligne in table:
            if ... not in annees:
                annees.append(...)
        return annees
    
  3. En utilisant cette fonction, vérifier s’il y a des années manquantes entre 1900 et 2024.

    Indice

    Parcourir les années de 1900 à 2024 avec range(1900, 2025) et garder celles qui ne sont pas dans la liste renvoyée par annees_presentes.

Correction publiée après la séance.

Recherche dans une table

Exercice 3 : recherche simple.

  1. É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 descripteur prenom est égal au prénom cherché.

    Indice

    Parcourir la table et ajouter à une liste résultat chaque ligne dont ligne['prenom'] est égal au prénom cherché.

    Code à copier puis compléter
    def rechercher_prenom(table, prenom):
        resultats = []
        for ligne in table:
            if ...:
                resultats.append(...)
        return resultats
    
  2. 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.

    Indice

    Appeler rechercher_prenom(prenoms, 'CLAUDE'), puis parcourir la liste obtenue et afficher ligne['periode'], ligne['valeur'] et ligne['sexe'] avec une f-string.

  3. É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 descripteur valeur).

    Indice

    Un accumulateur part de 0 ; pour chaque ligne du prénom cherché, on lui ajoute ligne['valeur'].

  4. Calculer le total des naissances pour le prénom 'MARIE' puis pour le prénom 'JEAN'.

  5. 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 publiée après la séance.

Exercice 4 : recherche avec plusieurs critères.

On souhaite rechercher des lignes vérifiant plusieurs critères à la fois.

  1. É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.

    Indice

    Même parcours que rechercher_prenom, avec une condition qui réunit les deux critères par and : ligne['sexe'] == sexe et ligne['periode'] == annee.

  2. Utiliser cette fonction pour trouver tous les prénoms féminins attribués en 2020. Combien y en a-t-il ?

    Indice

    Le sexe féminin est codé '2', une chaîne, et l’année 2020 est un entier. Le nombre de prénoms est la longueur de la liste obtenue.

  3. 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.

    Indice

    Même méthode que annees_presentes à l’exercice 2 : ajouter ligne['prenom'] à une liste seulement s’il n’y figure pas encore, puis compter.

Correction publiée après la séance.

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 ».

  1. É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.

    Indice

    Combiner deux méthodes déjà vues : ne garder que les lignes du sexe demandé, et n’ajouter un prénom à la liste que s’il n’y figure pas encore.

  2. En utilisant cette fonction, construire la liste masculins des prénoms masculins et la liste feminins des prénoms féminins.

  3. É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.

    Indice

    Parcourir masculins et garder chaque prénom qui figure aussi dans feminins (in).

  4. Combien y a-t-il de prénoms mixtes ? Donner quelques exemples.

    Indice

    La longueur de la liste renvoyée par prenoms_mixtes donne le nombre, et mixtes[:10] ses dix premiers éléments.

Correction publiée après la séance.

Tri d’une table

Rappel : trier suivant une colonne. La fonction sorted(table, key=fonction) trie une table : le paramètre key reçoit une fonction qui, appliquée à une ligne, renvoie la valeur à comparer (cours CSV, section « Trier une table suivant une colonne »). Pour ce TP, on utilise les deux fonctions suivantes :

def periode(ligne):
    return ligne['periode']

def valeur(ligne):
    return ligne['valeur']

Exercice 6 : tri suivant une colonne.

  1. En utilisant rechercher_prenom, récupérer toutes les lignes correspondant au prénom 'EMMA'.

  2. Trier cette liste par année croissante en utilisant emma_trie = sorted(emma, key=periode).

  3. Afficher l’évolution du prénom EMMA entre 2000 et 2024 (année et nombre de naissances).

    Indice

    Parcourir emma_trie et n’afficher que les lignes dont l’année vérifie 2000 <= ligne['periode'] <= 2024.

  4. Écrire une fonction top_prenoms(table, annee, sexe, n) qui prend en paramètres une table, une année, un sexe et un entier n, et renvoie les n prénoms les plus attribués cette année-là pour ce sexe, triés par nombre de naissances décroissant. Indication : utiliser sorted(..., key=valeur, reverse=True).

    Indice

    Trois étapes : rechercher(table, sexe, annee) donne les lignes de cette année et de ce sexe ; sorted(..., key=valeur, reverse=True) les range de la plus attribuée à la moins attribuée ; la tranche [:n] garde les n premières.

    Code à copier puis compléter
    def top_prenoms(table, annee, sexe, n):
        selection = rechercher(table, ..., ...)
        selection_triee = sorted(selection, key=valeur, reverse=True)
        return selection_triee[...]
    
  5. Afficher le top 10 des prénoms féminins en 2024.

    Indice

    Parcourir le résultat de top_prenoms(prenoms, 2024, '2', 10) avec les indices, pour afficher le rang i + 1 devant chaque prénom.

Correction publiée après la séance.

Exercice 7 : analyse de tendances.

  1. É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 : max(donnees, key=valeur) renvoie la ligne dont la colonne 'valeur' est la plus grande.

    Indice

    Récupérer d’abord les lignes du prénom avec rechercher_prenom, puis la ligne du maximum avec max(donnees, key=valeur) : l’année cherchée est sa valeur 'periode'.

    Code à copier puis compléter
    def annee_pic(table, prenom):
        donnees = rechercher_prenom(table, prenom)
        ligne_max = max(donnees, key=...)
        return ligne_max[...]
    
  2. Calculer l’année du pic pour les prénoms suivants : 'BRIGITTE', 'SYLVIE', 'NATHALIE', 'STÉPHANIE', 'LÉA'. Que remarque-t-on sur la succession de ces prénoms dans le temps ?

    Indice

    Une boucle sur la liste des cinq prénoms appelle annee_pic pour chacun. Ranger ensuite les années obtenues dans l’ordre.

  3. 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 publiée après la séance.

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
LÉA;hebreu;fatiguee ou lionne
LUCAS;grec;lumiere
JADE;espagnol;pierre verte
LOUIS;germanique;illustre au combat

Exercice 8 : chargement et fusion de deux tables.

  1. Charger le fichier origines.csv dans une table origines (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')
    
  2. É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 de table1 enrichies des informations de table2 lorsque les valeurs de la clé correspondent.

    Principe : pour chaque ligne de table1, on cherche dans table2 une ligne ayant la même valeur pour le descripteur cle. 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 resultat
    
    Indice

    Deux boucles imbriquées : pour chaque ligne1, parcourir toutes les lignes ligne2 de table2. Quand ligne1[cle] == ligne2[cle], créer un dictionnaire vide, y recopier toutes les paires de ligne1 puis celles de ligne2, et l’ajouter au résultat.

    Code à copier puis compléter
    def fusionner(table1, table2, cle):
        resultat = []
        for ligne1 in table1:
            for ligne2 in table2:
                if ...:
                    nouvelle_ligne = {}
                    for k in ligne1:
                        nouvelle_ligne[k] = ...
                    for k in ligne2:
                        nouvelle_ligne[k] = ...
                    resultat.append(nouvelle_ligne)
        return resultat
    
  3. Fusionner les tables prenoms et origines sur la clé 'prenom'.

  4. Pour chaque prénom présent dans origines, afficher son origine, sa signification et le nombre de naissances en 2024.

    Indice

    Parcourir la table fusionnée et n’afficher que les lignes de l’année 2024 : chacune contient à la fois 'origine', 'signification' et 'valeur'.

Correction publiée après la séance.

Exercice 9 : notion de domaine de valeurs.

  1. É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.

    Indice

    Même méthode que annees_presentes, avec ligne[descripteur] à la place de ligne['periode'].

  2. Appliquer cette fonction au descripteur 'origine' de la table origines. La liste obtenue s’appelle le domaine de valeurs du descripteur.

  3. Quel est le domaine de valeurs du descripteur 'sexe' dans la table prenoms ?

    Indice

    Appeler valeurs_distinctes sur la table prenoms avec le descripteur 'sexe', ou se souvenir du test de cohérence de l’exercice 2.

Correction publiée après la séance.

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'.

Indice

Réutiliser ce qui a déjà été écrit : rechercher_prenom pour les lignes du prénom, un accumulateur pour le total, max(..., key=valeur) pour le pic, deux accumulateurs (un par sexe) pour la répartition, et min et max sur la liste des années.

Correction publiée après la séance.

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.

Indice

Ce fichier utilise la virgule comme séparateur : csv.DictReader(fichier, delimiter=','). Ses descripteurs sont dep, nom, nb_hab_2012, dens, alt_min, etc., et toutes les valeurs sont lues comme des chaînes : convertir avec int() avant de comparer des nombres. Le Rhône est le département '69'.

  1. Donner la liste des villes du Rhône qui ont une altitude minimale supérieure à 500 m.

    Indice

    Même parcours que rechercher à l’exercice 4, avec la condition ligne['dep'] == '69' and int(ligne['alt_min']) > 500.

  2. 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).

    Indice

    Pour chaque ville retenue, ajouter au résultat un nouveau dictionnaire qui ne garde que trois clés : {'nom': ..., 'dens': ..., 'alt_min': ...}.

  3. 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.

    Indice

    Une seule condition réunit les deux critères avec and, sans critère de département.

  4. Donner le nombre d’habitants du Rhône en 2012.

    Indice

    Additionner int(ligne['nb_hab_2012']) pour toutes les villes du département '69'.

  5. Trier les données de façon à obtenir la ville française ayant l’altitude minimale la plus élevée.

    Indice

    Écrire une fonction alt_min(ligne) qui renvoie int(ligne['alt_min']), puis trier avec sorted(villes, key=alt_min, reverse=True) : la ville cherchée est la première.

  6. Quelle est la ville française ayant la densité de population la plus forte ?

    Indice

    Même méthode avec la densité, ou directement max(villes, key=...) avec une fonction qui renvoie int(ligne['dens']).

Exercice 12 : concaténation et jointure.

  1. Les communes du département des Alpes-Maritimes ont été oubliées dans le fichier villes.csv et 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.

    Indice

    Les deux tables ont les mêmes descripteurs : il suffit de mettre les lignes de la seconde à la suite de celles de la première, avec + ou avec une boucle et append.

  2. On dispose de deux fichiers clients.csv et commandes.csv qui n’ont qu’un descripteur en commun : n_client.

    1. Écrire un programme Python qui permet de fusionner les deux tables.

      Indice

      Charger les deux fichiers, en vérifiant leur séparateur, puis utiliser la fonction fusionner de l’exercice 8 avec la clé 'n_client'.

    2. L’ordre a-t-il une importance lors de la fusion ?

      Indice

      Comparer fusionner(clients, commandes, 'n_client') et fusionner(commandes, clients, 'n_client') : obtient-on les mêmes lignes, dans le même ordre ?

    3. 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 ?

      Indice

      Chercher le numéro 895 dans la colonne n_client de la table commandes : y figure-t-il ?

    4. 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 ?

      Indice

      Chercher dans commandes le numéro de client de la commande 1324, puis ce numéro dans la table clients.

    On remarquera aussi que Mme Neufville a fait deux commandes. D’où la nécessité de structurer correctement les données.