Traitement de fichiers CSV

Onyxia

Dans ce TD, nous allons apprendre à manipuler un type de fichier texte très utilisé pour stocker et diffuser des données : le CSV.

CSV est l’abréviation de comma-separated values, soit “valeurs séparées par des virgules” – même si, bien souvent, le délimiteur standard (la virgule) est remplacée par un autre délimiteur.

Un fichier CSV reproduit la structure d’un tableau :

Un module de la bibliothèque standard de Python est spécialement conçu pour faciliter l’interaction avec ce type de fichiers : le module csv.

En pratique, on utilise souvent des modules de plus haut niveau (comme pandas) pour travailler avec des tableaux (DataFrames).

Nous allons néanmoins nous entraîner à l’utiliser en explorant deux fichiers de l’INSEE :

Préambule

Ce notebook est conçu pour être interactif. Dans les cellules prévues à cet effet, vous pouvez écrire du code en Python et l’exécuter en pressant SHIFT+ENTER.

Les variables restent en mémoire d’une cellule à l’autre. Essayez ci-dessous !

# C'est à vous !

Première exploration des données

Avant tout, téléchargez les fichiers source sur votre ordinateur et ouvrez-les avec un éditeur de texte brut (comme Notepad++) pour en examiner la structure.

Exploration de insee_data.csv

On note que :

  • Le fichier est encodé en UTF-8.
  • les valeurs sont séparées par des points-virgules (;).
  • La 1ère ligne du fichier contient le nom des colonnes : c’est l’entête, ou header.
  • Les 34 785 autres lignes contiennent les données, une ligne par commune.

On relève qu’il y a quatre colonnes :

  • Code : le numéro INSEE de la commune.
  • Libellé : le nom de la commune.
  • Population municipale 2022 : la population de référence de la commune, millésime 2022, entrée en vigueur le 1er janvier 2025, donnée issue du recensement de la population.
  • Boulangerie-pâtisserie (en nombre) 2024 : le nombre de boulangeries-pâtisseries que comptait la commune en 2024, donnée issue de la Base Permanente des Équipements (BPE).

Exploration de v_departement_2025.csv

On note que :

  • Le fichier est encodé en UTF-8.
  • les valeurs sont séparées par des virgules (,).
  • Le fichier comporte un header.
  • Les 101 autres lignes contiennent les données, une ligne par département.

On relève qu’il y a sept colonnes :

  • REGION : code région.
  • DEP : code département.
  • CHEFLIEU : code de la commune chef-lieu.
  • TNCC : Type de nom en clair (donne une indication grammaticale).
  • NCC : Nom en clair, en majuscules.
  • NCCENR : Nom en clair, en minuscules accentuées.
  • LIBELLE : Nom en clair, en minuscules accentuées avec article.

Chargement des données

Principe

On peut modéliser les données avec Python de plusieurs manières.

Liste de listes

On peut construire une liste de listes : la grande liste représente le tableau, elle contient plusieurs listes (autant que de lignes) dont chacun des élements représentent les valeurs des colonnes.

# On importe le module CSV
import csv

# On crée la première liste
rows = []

# On ouvre le fichier en mode lecture ('r') en spécifiant l'encodage ('utf-8'),
with open('data/insee_data.csv', 'r', encoding='utf-8') as mf:

    # On crée un objet reader() qui lit une ligne du fichier qui lui est passé,
    # détecte les délimiteurs et renvoie la liste des champs qu'ils séparent 
    csv_reader = csv.reader(mf, delimiter=';')

    # Cet objet est conçu comme un itérateur : à chaque fois qu'on l'appelle,
    # il passe à la ligne suivante, jusqu'à la fin du fichier. 
    # On stocke ainsi toutes les listes renvoyées dans la première liste.
    for row in csv_reader:
        rows.append(row)

# On affiche la valeur stockée 13e ligne, 2eme colonne
# (pour mémoire, Python compte à partir de 0) :
print('La valeur à la 13e ligne, 2eme colonne est la suivante : ', end=None)
print(rows[12][1])

Liste de dictionnaires

Mais puisque notre fichier comporte un entête, il est intéressant de modéliser les données sous la forme d’une liste de dictionnaires : chaque ligne sera représentée par un dictionnaire qui fait correspondre aux noms des colonnes (ex: Code) leurs valeurs (ex: '01001').

Pour montrer l’intérêt, on ne conservera ici que les communes dont le champ Code commence par '77' (c’est-à-dire les communes de Seine-et-Marne).

# On crée une autre liste
seinemarne = []

with open('data/insee_data.csv', 'r', encoding='utf-8') as mf:
   
    # On utilise ici un objet DictReader()
    csv_reader = csv.DictReader(mf, delimiter=';')
    
    # Les objets renvoyés à chaque appel sont donc des dictionnaires
    for dico in csv_reader:
        
        # On utilise la méthode `startswith` dont disposent les objets de type 
        # natif `str` pour filtrer les éléments qui nous intéressent
        if dico['Code'].startswith('77'):
            seinemarne.append(dico)

# On affiche le libellé du dernier élément de la liste ainsi constituée
libelle = seinemarne[-1]['Libellé']
print("La dernière ville de la liste est : {}".format(libelle))

À vous de jouer !

Chargement de insee_data.csv

Chargez les données du fichier insee_data.csvdans une liste de dictionnaires que vous nommerez communes.

Affichez le nom et la population de la 10 000e ville de la liste.

# Entrez ici votre code
Solution
communes = []

with open('data/insee_data.csv', 'r', encoding='utf-8') as mf:
    csv_reader = csv.DictReader(mf, delimiter=';')
    for row in csv_reader:
        communes.append(row)


print("La 10000e ville de la liste est : {}, {} habitants.".format(
    communes[9999]['Libellé'],
    communes[9999]['Population municipale 2022']
    )
)

Chargement de v_departement_2025.csv

Chargez les données du fichier v_departement_2025.csv dans une liste de dictionnaires que vous nommerez depts.

Affichez le nom en majuscule du 17e département de la liste.

# Entrez ici votre code
Solution
depts = []

with open('data/v_departement_2025.csv', 'r', encoding='utf-8') as mf:
    csv_reader = csv.DictReader(mf, delimiter=',')
    for row in csv_reader:
        depts.append(row)

print("Le 17eme département de la liste est : {}.".format(depts[16]['NCC']))

Vérification et nettoyage des données

Il est utile de vérifier que les données ne présentent pas de “bizarreries” (éléments aberrants, valeurs manquantes, etc.).

Nous allons conduire quelques tests de cohérence.

Nombre de champs pour chaque enregistrement

On va s’assurer que chaque enregistrement des listes communes et depts comporte le bon nombre de champs : 4 pour les communes, 7 pour les départements.

Écrivez un programme qui vérifie pour les deux listes que chaque dictionnaire contient le nombre approprié de clés. On stockera dans des listes à part les problèmes éventuels (si tout va bien, ces listes seront vides).

# Entrez ici votre code
Solution
# Pour les communes tout d'abord
# On le fait de façon naïve, avec une boucle traditionnelle
communes_avec_pb = []
for com in communes:
    if len(com) != 4:
        communes_avec_pb.append(com)

print("{} enregistrements problématiques dans la liste 'communes'".format(
    len(communes_avec_pb)
    )
)

# Pour les départements ensuite
# On le fait e façon pythonique, avec une compréhension de liste
dept_avec_pb = [dep for dep in depts if len(dep) != 7]

print("{} enregistrements problématiques dans la liste 'dept'".format(
    len(dept_avec_pb)
    )
)

Cohérence géographique

Dans la liste depts, on va vérifier que la commune désignée comme chef-lieu d’un département appartient bien à ce département.

On se souviendra que le code INSEE d’une commune commence par le numéro du département auquel elle appartient, codé sur 2 caractères (3 pour l’outre-mer).

# Entrez ici votre code
Solution
# Voici la façon naïve, avec une boucle traditionnelle
pb_chef_lieu = []
for dep in depts:
    codeinsee = dep['CHEFLIEU']
    if codeinsee[:2] != dep['DEP'] and codeinsee[:3] != dep['DEP']:
        pb_chef_lieu.append(dep)

pb_chef_lieu

# Et la même chose de façon pythonique, avec une compréhension de liste
pb_chef_lieu = [ 
    dep for depts in depts 
    if  dep['CHEFLIEU'][:2] != dep['DEP']
    and dep['CHEFLIEU'][:3] != dep['DEP']
]

print("{} communes situées hors du département.".format(len(pb_chef_lieu)))

Valeurs manquantes

Bien souvent, les données manquantes dans une série de données sont signalées par une chaîne de caractère spécifique (comme N/A) et non par l’absence de valeurs. Cela peut-être gênant.

Dans la table communes, on va ainsi filtrer les enregistrements qui contiennent des valeurs non numériques dans les champs de données.

On utilisera pour cela la méthode is_decimal dont disposent les objets de type natif str (cf documentation). Cette méthode renvoie True si tous les caractères d’une chaîne sont des caractères décimaux et qu’elle contient au moins un caractère, False sinon.

# Entrez ici votre code
Solution
# Voici la façon naïve, avec une boucle traditionnelle
pb_nombre = []
for com in communes:
    if not com['Population municipale 2022'].isdecimal():
        pb_nombre.append(com)
        next
    elif not com['Boulangerie-pâtisserie (en nombre) 2024'].isdecimal():
        pb_nombre.append(com)


# Et la même chose de façon pythonique, avec une compréhension de liste
pb_nombre = [
    com for com in communes
    if not com['Population municipale 2022'].isdecimal()
    or not com['Boulangerie-pâtisserie (en nombre) 2024'].isdecimal()
]

print("{} communes avec données manquantes.".format(len(pb_nombre)))

Ainsi donc, des données sont manquantes pour une vingtaine de communes… On les éliminera de la liste communes.

# Entrez ici votre code
Solution
print("Nombre d'enregistrements avant nettoyage : {}".format(len(communes)))

for ville in pb_nombre:
    communes.remove(ville)

print("Nombre d'enregistrements après nettoyage : {}".format(len(communes)))

Gestion des types

Dans la liste communes, les valeurs stockées dans les champs de données sont pour l’instant représentées par des chaînes de caractères.

print(type(communes[0]['Population municipale 2022']))

Pour les utiliser dans des calculs, il faut d’abord les convertir en nombres entiers. Allez-y !

# Entrez ici votre code
Solution
for c in communes:
    for champ in ['Population municipale 2022', 'Boulangerie-pâtisserie (en nombre) 2024']:
        c[champ] = int(c[champ])

print(type(communes[0]['Population municipale 2022']))

Requêtes sur les données

Utilisez les deux tables communes et depts construites précédemment pour répondre aux questions suivantes.

Boulangeries de(s) Champs

Combien y a-t-il de boulangeries à Champs-sur-Marne (77083) ?

# Entrez ici votre code
Solution
for c in communes:
    if c['Code'] == '77083':
        print("{} : {} boulangeries".format(
            c['Libellé'],
            c['Boulangerie-pâtisserie (en nombre) 2024']
            )
        )

Villes-fantômes

Quelles sont les villes ne comportant aucun habitant ? Pourquoi ?

# Entrez ici votre code
Solution
villes_mortes = [ 
    v for v in communes 
    if v['Population municipale 2022'] == 0
]

print("Villes sans habitants :")

# Ce sont toutes des communes de la Meuse, non loin de Verdun : 
# souvenir de la Grande Guerre...

for v in villes_mortes: 
    print("- {} ({})".format(v['Libellé'], v['Code']))

Habitants de caractères

Quelles communes ont autant d’habitants que de caractères dans leur nom ?

# Entrez ici votre code
Solution
villes_lettres = [ 
    v for v in communes 
    if len(v['Libellé']) == v['Population municipale 2022']
]

print("Villes qui ont autant d'habitants que de lettres dans leur nom :")

for v in villes_lettres: 
    print("- {} ({}) : {} habitants, {} caractères".format(
        v['Libellé'], 
        v['Code'],
        v['Population municipale 2022'],
        len(v['Libellé'])
        )
    )

Du pain, du pain

Combien y a-t-il de boulangeries en France ? Quelle est la commune qui a la plus forte densité de boulangeries parmi les villes de plus de 5000 habitants ?

# Entrez ici votre code
Solution
nb_boul = sum([c['Boulangerie-pâtisserie (en nombre) 2024'] for c in communes])

print("Boulangeries-pâtisseries en France en 2024 : {}".format(nb_boul))

densite_max = 0
laureat = ''

for com in communes:
    boulang    = com['Boulangerie-pâtisserie (en nombre) 2024']
    population = com['Population municipale 2022']
    
    if population > 5000 and boulang/population*1000 > densite_max:
        densite_max = boulang/population*1000
        laureat = com['Libellé']

print("Ville à la plus forte densité : {} ({:.1f} boulangeries/1000 hab)".format(
    laureat, 
    densite_max
    )
)

Essartage

Quelles communes contiennent le toponyme ‘essart’ dans leur nom ?

# Entrez ici votre code
Solution
essarts = []

for com in communes:
    if 'essart' in com['Libellé'].lower():
        essarts.append(com)

print("Villes avec le toponyme Essart :")

for v in essarts:
    print("- {name} ({code})".format(code=v['Code'], name=v['Libellé']))

Chef-lieu chétif

Dans quels départements le chef-lieu n’est-il pas la commune la plus peuplée ? Quelles sont les communes plus peuplées ?

Indice : on commencera par créer un dictionnaire associant à chaque chef-lieu sa population municipale.

## Entrez ici votre code
Solution
dico_cheflieu = {}
resultats = {}

# Création du dictionnaire qui associe le code d'un chef-lieu à sa population
for d in depts:
    code = d['CHEFLIEU']
    for c in communes:
        if code == c['Code']:
            dico_cheflieu[code] = c['Population municipale 2022']

# Examen de chacune des communes
for c in communes:
    code_insee  = c['Code']
    population  = c['Population municipale 2022']
    departement = 'XX'
    
    # On récupère le département : 
    # les trois premiers chiffres du code INSEE en outre-mer,
    # les deux premiers chiffres sinon 
    if code_insee.startswith('97'):
        departement = code_insee[:3]
    else:
        departement = code_insee[:2]
    
    # Quel est le chef-lieu de ce département ?
    chl = '0000'
    for d in depts:
        if d['DEP'] == departement:
            chl = d['CHEFLIEU']
    
    # On compare la population de la commune à celle du chef-lieu
    if population > dico_cheflieu[chl]:
        # Si le département n'est pas déjà dans le dictionnaire, on l'ajoute
        if departement not in resultats:
            resultats[departement] = []
        # Et on ajoute la ville à la liste
        resultats[departement].append(c['Libellé'])

print("Départements comptant au moins une ville plus peuplée que le chef-lieu :")
for k, v in sorted(resultats.items()):
    print("- {} : ".format(k), end='')
    print(', '.join(v))

Nom commun

Quels sont les dix noms de ville le plus répandu ? (ie. ceux que le plus grand nombre de communes portent) ?

# Entrez ici votre code
Solution
dico_noms = {}

for c in communes:
    nom = c['Libellé'].upper()
    if nom in dico_noms:
        dico_noms[nom]+=1
    else:
        dico_noms[nom]=1

top10 = sorted(dico_noms.items(), key=lambda x: x[1], reverse=True)[:10]

print('Top 10 des noms les plus répandus : ')
for ville, occurence in top10:
    print('- {} ({})'.format(ville, occurence))