TP et projets#
Les données en tables
TP La météo de la Riviera#
importer, nettoyer, interroger, croiser et exporter une vraie table
Durée : 2 à 3 h (deux séances) · Seul ou en binôme, sur machine
Fichiers à télécharger
But du TP
Un réseau de six stations météo, de Monaco à Gréolières, a transmis ses relevés quotidiens du mois d’août. Comme toutes les données réelles, le fichier reçu est imparfait : valeurs manquantes, lignes en double, capteur défectueux, station inconnue… Vous allez écrire en Python, sans bibliothèque spécialisée, la chaîne complète d’un traitement de données : importer le CSV, le contrôler et le nettoyer, l’interroger (filtres, moyennes, records), le trier, le croiser avec la table des stations, et produire un fichier CSV de bilan lisible dans un tableur.
Consignes
-
Un compte rendu court est demandé à la fin (partie 7).
-
Fichiers à télécharger (lien ci-dessus), à placer dans le même dossier :
releves_aout.csv,stations.csvettp_donnees_tables_depart.py(le programme à compléter : tout votre code s’y écrit). -
Les données sont fictives mais réalistes (août 2025). Niveau : ★ échauffement, ★★ classique (niveau bac), ★★★ défi ; les coups de pouce et les corrigés se déplient sous chaque exercice.
-
▶ À programmer et tester. En bas du programme, des tests vérifient votre travail : à chaque lancement, chaque partie affiche
[OK]ou[A FAIRE](au début, tout est[A FAIRE]: c’est normal). -
Usage de l’IA, indiqué sur chaque exercice : aucune ; en appui (déboguer, reformuler, vérifier ; la réponse finale est écrite et comprise par vous) ; l’exercice s’appuie sur une réponse d’IA. Voir la charte d’usage de l’IA.
Prise en main : importer la table#
★ Exercice 1 — Regarder le fichier avant de programmer #
Ouvrir releves_aout.csv dans un éditeur de texte (pas dans le tableur). En voici le début :
station;date;tmin;tmax;pluie
MON;2025-08-01;23,1;29,0;0,0
MON;2025-08-02;22,2;27,2;0,0
-
Quels sont les descripteurs ? Quel est le séparateur ? Pourquoi n’est-ce pas la virgule ?
-
Que représentent
tmin,tmaxetpluie(unités probables) ? Sous quel format les dates sont-elles écrites ? -
Ouvrir aussi
stations.csv. Quel descripteur est commun aux deux fichiers ?
Corrigé
Les résultats ci-dessous portent sur les fichiers fournis (193 lignes de relevés, 6 stations). Les fonctions complètes sont regroupées en fin de document.
def en_nombre(texte):
if texte == "":
return None
resultat = "" # on recopie le texte...
for car in texte:
if car == ",":
resultat += "." # ... en changeant la virgule en point
else:
resultat += car
return float(resultat)
def est_complete(ligne):
return ligne["tmin"] != "" and ligne["tmax"] != "" and ligne["pluie"] != ""
def compter_par_cle(table):
compteur = {}
for ligne in table:
cle = (ligne["station"], ligne["date"])
if cle in compteur:
compteur[cle] = compteur[cle] + 1
else:
compteur[cle] = 1
return compteur
def doublons(table):
compteur = compter_par_cle(table)
return [cle for cle in compteur if compteur[cle] > 1]
def est_coherente(ligne):
if not est_complete(ligne):
return False
tmin = en_nombre(ligne["tmin"])
tmax = en_nombre(ligne["tmax"])
return tmin <= tmax and -30 <= tmin <= 50 and -30 <= tmax <= 50
def nettoyer(table):
propre = []
deja_vus = {}
for ligne in table:
cle = (ligne["station"], ligne["date"])
if est_coherente(ligne) and cle not in deja_vus:
deja_vus[cle] = True
propre.append({"station": ligne["station"], "date": ligne["date"],
"tmin": en_nombre(ligne["tmin"]),
"tmax": en_nombre(ligne["tmax"]),
"pluie": en_nombre(ligne["pluie"])})
return propre
def releves_de(table, station):
return [ligne for ligne in table if ligne["station"] == station]
def moyenne(table, descripteur):
total = 0
for ligne in table:
total = total + ligne[descripteur]
return total / len(table)
def jour_le_plus_chaud(table):
champion = table[0]
for ligne in table:
if ligne["tmax"] > champion["tmax"]:
champion = ligne
return champion["station"], champion["date"], champion["tmax"]
def compter_si(table, descripteur, seuil):
n = 0
for ligne in table:
if ligne[descripteur] >= seuil:
n = n + 1
return n
1. Descripteurs : station, date, tmin, tmax, pluie. Séparateur : le point-virgule, car la virgule sert déjà de séparateur décimal (23,1). 2. Températures minimale et maximale de la journée en degrés Celsius, hauteur de pluie en millimètres ; dates au format AAAA-MM-JJ. 3. Le descripteur commun est station (un code de trois lettres).
★ Exercice 2 — Charger les deux tables #
▶
La fonction charger(fichier, separateur) est donnée : c’est celle du cours, avec en plus le paramètre delimiter de csv.DictReader.
-
Charger les deux fichiers dans des variables
relevesetstations. Afficher le nombre de lignes de chaque table, puisreleves[0]. -
Essayer
charger("releves_aout.csv", ","). Que contient alorsreleves[0]? Expliquer. -
De quel type est
releves[0]["tmax"]? Pourquoifloat(releves[0]["tmax"])provoque-t-il une erreur ?
Corrigé
1. len(releves) vaut 193, len(stations) vaut 6 ; releves[0] est {’station’: ’MON’, ’date’: ’2025-08-01’, ’tmin’: ’23,1’, ’tmax’: ’29,0’, ’pluie’: ’0,0’}.
2. Avec ",", l’en-tête n’est qu’une seule colonne nommée ’station;date;tmin;tmax;pluie’, et chaque ligne est découpée au milieu des nombres : releves[0] vaut {’station;date;tmin;tmax;pluie’: ’MON;2025-08-01;23’, None: [’1;29’, ’0;0’, ’0’]}. Le mauvais séparateur ne provoque aucune erreur : il produit silencieusement une table absurde.
3. C’est une chaîne (str). float("29,0") échoue (ValueError) car Python attend un point décimal.
★ Exercice 3 — Convertir un nombre « à la française » #
▶
Compléter en_nombre(texte) qui convertit une valeur lue dans le fichier en float ("23,4" donne 23.4) et renvoie None si la chaîne est vide (valeur manquante).
Coup de pouce
Construire une nouvelle chaîne en parcourant le texte caractère par caractère : on recopie chaque caractère, sauf la virgule, qu’on remplace par un point. Il ne reste plus qu’à appliquer float.
>>> en_nombre("23,4")
23.4
>>> en_nombre("") is None
True
Corrigé
def en_nombre(texte):
if texte == "":
return None
resultat = "" # on recopie le texte...
for car in texte:
if car == ",":
resultat += "." # ... en changeant la virgule en point
else:
resultat += car
return float(resultat)
Contrôle qualité : repérer les lignes douteuses#
Avant de calculer quoi que ce soit, un analyste vérifie ses données. Une seule valeur absurde suffit à fausser une moyenne ou un record.
★ Exercice 4 — Les valeurs manquantes #
▶
Compléter est_complete(ligne), qui renvoie True si tmin, tmax et pluie sont toutes renseignées. Afficher ensuite, par une compréhension, la station et la date des lignes incomplètes. Combien y en a-t-il ?
Corrigé
La compréhension
[(l["station"], l["date"]) for l in releves if not est_complete(l)]
donne 2 lignes : (’NIC’, ’2025-08-07’) (tmax vide) et (’SOS’, ’2025-08-23’) (tmin vide).
★★ Exercice 5 — Les doublons #
▶
Une même station ne doit envoyer qu’un relevé par jour : la clé d’une ligne est le couple (station, date).
-
Compléter
compter_par_cle(table), qui renvoie un dictionnaire associant à chaque clé(station, date)le nombre de lignes qui la portent.Coup de pouce
C’est le patron du comptage avec un dictionnaire : si la clé est déjà présente, on ajoute 1 ; sinon on la crée avec la valeur 1.
-
En déduire
doublons(table), la liste des clés présentes plus d’une fois. Quelles sont-elles ? -
Afficher les lignes de chacun de ces doublons. Sont-ils tous identiques ? Lequel pose un vrai problème ?
Corrigé
1. Patron du comptage : si la clé est déjà dans compteur, on ajoute 1, sinon on la crée avec la valeur 1 (code complet de compter_par_cle et de doublons en fin de document).
2. Trois clés en double : (’MON’, ’2025-08-14’), (’NIC’, ’2025-08-15’), (’SOS’, ’2025-08-02’). 3. Les doublons de Monaco et de Sospel sont identiques (relevé transmis deux fois : sans gravité). Celui de Nice est conflictuel : tmax vaut 30,4 dans la première ligne et 30,8 dans la seconde. On ne peut pas savoir laquelle est juste : on garde la première par convention, mais il faudrait le signaler.
★★ Exercice 6 — Les valeurs incohérentes #
▶
Compléter est_coherente(ligne) : elle renvoie True si la ligne est complète, si tmin \(\leqslant\) tmax, et si les deux températures sont comprises entre \(-30\) et \(50\) degrés. Lister les lignes complètes mais incohérentes et proposer, pour chacune, une explication plausible de l’erreur.
Corrigé
Trois lignes complètes mais incohérentes :
-
MEN, 12 août :tmin = 32,9ettmax = 25,8;ANT, 28 août :tmin = 29,5ettmax = 20,3\(\to\) les deux colonnes ont manifestement été inversées à la saisie ; -
GRE, 5 août :tmax = 99,9\(\to\) valeur impossible (à 1 400 m d’altitude !) : capteur défectueux ou code d’erreur.
★★ Exercice 7 — Nettoyer la table #
▶
Compléter nettoyer(table), qui construit une nouvelle table :
-
ne garde que les lignes complètes et cohérentes ;
-
ne garde, pour chaque clé
(station, date), que la première ligne rencontrée (un dictionnairedeja_vusretient les clés déjà gardées) ; -
range dans chaque nouvelle fiche des nombres (
float) et non plus des chaînes.
Combien de lignes reste-t-il ? Combien ont été écartées ? Tout le reste du TP utilise la table nettoyée, notée propre.
Coup de pouce
Pour chaque ligne : si elle est cohérente et que sa clé n’est pas dans deja_vus, on enregistre la clé, puis on ajoute à propre un nouveau dictionnaire dont les trois valeurs numériques passent par en_nombre.
Corrigé
Il reste 185 lignes ; 8 ont été écartées : 2 incomplètes, 3 incohérentes, 3 doublons. (On pourrait « réparer » les deux inversions plutôt que les supprimer : bonne idée à discuter, mais c’est une décision d’analyste à justifier.)
Interroger la table : filtres et calculs#
★ Exercice 8 — Une station à la loupe #
▶
Compléter releves_de(table, station) (filtre par compréhension) et moyenne(table, descripteur). Pour Monaco ("MON"), afficher le nombre de relevés et la moyenne des températures maximales, affichée au dixième avec une f-string : f"{x:.1f}".
Corrigé
Monaco : 31 relevés, moyenne des tmax : 29,1 degrés.
monaco = releves_de(propre, "MON")
m = moyenne(monaco, "tmax")
print(len(monaco), f"{m:.1f}") # 31 29.1 (affichage au dixieme)
★★ Exercice 9 — Records et comptages #
▶
-
Compléter
jour_le_plus_chaud(table), qui renvoie le triplet(station, date, tmax)de la ligne oùtmaxest maximale (invariant du champion). Quel est le record du mois, toutes stations confondues ? Et celui de Monaco ? -
Compléter
compter_si(table, descripteur, seuil), qui compte les lignes dont la valeur est \(\geqslant\)seuil. Une nuit tropicale est une nuit où la température ne descend pas sous 20 degrés : combien Monaco en a-t-elle connu ? Et Gréolières ? -
Un jour de pluie est un jour avec au moins 1 mm. Pour chaque station, afficher le cumul de pluie du mois et le nombre de jours de pluie (une boucle sur la table
stations).
Corrigé
1. Record du mois : (’SOS’, ’2025-08-11’, 34.4), à Sospel ; record de Monaco : (’MON’, ’2025-08-13’, 32.1).
2. Monaco : 31 nuits tropicales sur 31 ; Gréolières : 0.
3. Cumul de pluie (mm) et jours de pluie :
| Monaco | Menton | Nice | Antibes | Sospel | Gréolières | |
|---|---|---|---|---|---|---|
| cumul (mm) | 34,0 | 38,2 | 23,1 | 47,4 | 96,4 | 115,6 |
| jours \(\geqslant 1\) mm | 4 | 2 | 4 | 3 | 7 | 9 |
for s in stations:
r = releves_de(propre, s["station"])
cumul = 0
for ligne in r:
cumul = cumul + ligne["pluie"]
print(s["nom"], f"{cumul:.1f}", compter_si(r, "pluie", 1))
★★ Exercice 10 — L’épisode orageux #
▶
On veut maintenant regrouper les relevés par date, toutes stations confondues.
-
Écrire
pluie_par_jour(table)qui renvoie un dictionnaire associant à chaque date le cumul de pluie de toutes les stations ce jour-là. Combien de clés contient-il ? -
Quel jour le réseau a-t-il été le plus arrosé, et combien de millimètres sont tombés au total ? Quel autre jour le suit de près ?
-
Combien de jours du mois n’a-t-il plu nulle part ?
Coup de pouce
Même patron que compter_par_cle : la clé est la date, et au lieu d’ajouter 1, on ajoute la pluie de la ligne. Pour le jour le plus arrosé, appliquer l’invariant du champion aux clés du dictionnaire.
Corrigé
def pluie_par_jour(table):
cumul = {}
for ligne in table:
d = ligne["date"]
if d in cumul:
cumul[d] = cumul[d] + ligne["pluie"]
else:
cumul[d] = ligne["pluie"]
return cumul
cumul = pluie_par_jour(propre)
jour_max = None
for d in cumul:
if jour_max is None or cumul[d] > cumul[jour_max]:
jour_max = d
print(len(cumul), jour_max, f"{cumul[jour_max]:.1f}")
print(len([d for d in cumul if cumul[d] == 0]))
1. 31 clés (une par jour d’août). 2. Le 20 août (132,5 mm au total), suivi de près par le 19 août (127,8 mm) : c’est l’épisode orageux, très loin devant le 28 août (13,5 mm). 3. 14 jours sans aucune pluie sur le réseau.
Trier#
★★ Exercice 11 — Palmarès #
▶
-
Avec
sortedet une clé, afficher les cinq journées les plus chaudes du mois (station, date,tmax). Que remarquez-vous ? -
Construire la liste des couples
(nom de la station, cumul de pluie)et la trier du plus arrosé au plus sec. -
Les dates sont écrites
AAAA-MM-JJ. Comparersorted(["2025-08-15", "2025-09-02", "2025-08-20"])etsorted(["15/08/2025", "02/09/2025", "20/08/2025"]). Pourquoi les informaticiens préfèrent-ils le premier format ?
Corrigé
1. Les cinq journées les plus chaudes sont toutes à Sospel : 11 août (34,4), 12 août (34,1), 14 août (34,0), 13 août (33,7), 9 août (33,4). L’arrière-pays, loin de la mer, chauffe davantage ; la vague de chaleur se situe vers le 10–16 août.
top = sorted(propre, key=lambda l: l["tmax"], reverse=True)
for l in top[:5]:
print(l["station"], l["date"], l["tmax"])
2. Gréolières (115,6), Sospel (96,4), Antibes (47,4), Menton (38,2), Monaco (34,0), Nice (23,1).
pluies = []
for s in stations:
r = releves_de(propre, s["station"])
pluies.append((s["nom"], moyenne(r, "pluie") * len(r)))
for c in sorted(pluies, key=lambda c: c[1], reverse=True):
print(c[0], f"{c[1]:.1f}")
3. [’2025-08-15’, ’2025-08-20’, ’2025-09-02’] est dans l’ordre chronologique ; [’02/09/2025’, ’15/08/2025’, ’20/08/2025’] ne l’est pas (le 2 septembre passe en premier). Les chaînes se comparent caractère par caractère : avec l’année, puis le mois, puis le jour, l’ordre « des mots » coïncide avec l’ordre du temps. C’est le format international ISO 8601.
Croiser : la jointure avec la table des stations#
★★ Exercice 12 — Littoral ou arrière-pays ? #
▶
La fonction jointure du cours est fournie dans le programme.
-
Calculer la jointure de
propreet destationssur le descripteur"station". Combien de lignes obtient-on ? Comparer aveclen(propre): quelles lignes ont disparu, et pourquoi ? Comment un analyste devrait-il réagir ? -
Grâce au descripteur
littoralapporté par la jointure, comparer la moyenne destmindes stations du littoral et celle des stations de l’arrière-pays. Même question pour lestmax. -
Calculer, pour les deux groupes, l’écart moyen entre
tmaxettmin. Proposer une explication (la mer, l’altitude). -
La station
VILest celle de Villefranche-sur-Mer (altitude 10 m, sur le littoral). Ajouter sa fiche à la tablestationsavecappend(un dictionnaire ayant les mêmes descripteurs que les autres fiches), puis refaire la jointure : combien de lignes obtient-on maintenant ?
Corrigé
1. La jointure compte 181 lignes contre 185 : les 4 relevés de la station VIL (absente de stations.csv) ont disparu, faute de correspondance. Un analyste doit le remarquer (comparer les effectifs avant et après) et compléter la table des stations plutôt que de perdre ces données sans le dire.
2. et 3.
| lignes | moyenne tmin |
moyenne tmax |
écart moyen | |
|---|---|---|---|---|
| littoral | 121 | 22,6 | 29,4 | 6,7 |
| arrière-pays | 60 | 14,6 | 27,5 | 13,0 |
La mer, lente à se réchauffer et à se refroidir, adoucit les écarts : nuits chaudes, journées modérées. Dans l’arrière-pays, l’écart jour/nuit est deux fois plus grand ; l’altitude (Gréolières, 1 400 m) abaisse en plus toutes les températures, ce qui explique une moyenne des tmax plus basse malgré les records de Sospel.
4. Après
stations.append({"station": "VIL", "nom": "Villefranche-sur-Mer",
"commune": "Villefranche-sur-Mer", "altitude": "10",
"littoral": "oui"})
la jointure compte 185 lignes : plus aucun relevé n’est perdu. (Pour les questions 2 et 3, les valeurs du tableau ont été calculées avant cet ajout.)
j = jointure(propre, stations, "station")
littoral = [l for l in j if l["littoral"] == "oui"]
arriere = [l for l in j if l["littoral"] == "non"]
for groupe in (littoral, arriere):
m_min = moyenne(groupe, "tmin")
m_max = moyenne(groupe, "tmax")
print(f"{m_min:.1f} {m_max:.1f} {m_max - m_min:.1f}")
Produire : exporter un CSV de bilan#
★★ Exercice 13 — Le fichier bilan_stations.csv #
▶
-
Compléter
en_texte(nombre), réciproque deen_nombre, qui écrit le nombre avec un chiffre après la virgule :en_texte(23.4)vaut"23,4"eten_texte(31)vaut"31,0".Coup de pouce
La f-string
f"{nombre:.1f}"écrit le nombre au dixième, avec un point ; il reste à remplacer ce point par une virgule, caractère par caractère, comme dansen_nombre. -
Construire une table
bilancontenant une fiche par station, avec les descripteursstation,nom,tmin_moy,tmax_moy,pluie_totale,nuits_tropicales(moyennes et cumul arrondis au dixième, écrits avecen_texte). -
Compléter la fonction
exporter(une ligne manque : écrire chaque fiche avececrivain.writerow) et produirebilan_stations.csv, séparateur;. -
Vérifier le résultat de deux façons : en le rechargeant avec
charger, puis en l’ouvrant dans le tableur. Les nombres sont-ils bien reconnus ?
Remarque — Pourquoi écrire 23,4 et pas 23.4 ?
Un tableur réglé en français attend une virgule décimale ; avec 23.4, il risque de lire du texte, voire une date. Produire un fichier, c’est penser à celui qui va le relire.
Corrigé
def en_texte(nombre):
texte = f"{nombre:.1f}" # un chiffre apres le point
resultat = ""
for car in texte:
if car == ".":
resultat += "," # virgule decimale a la francaise
else:
resultat += car
return resultat
bilan = []
for s in stations:
r = releves_de(propre, s["station"])
bilan.append({"station": s["station"], "nom": s["nom"],
"tmin_moy": en_texte(moyenne(r, "tmin")),
"tmax_moy": en_texte(moyenne(r, "tmax")),
"pluie_totale": en_texte(moyenne(r, "pluie") * len(r)),
"nuits_tropicales": compter_si(r, "tmin", 20)})
exporter(bilan, "bilan_stations.csv", ";")
La boucle parcourt la table stations, qui contient sept fiches depuis l’ajout de VIL (exercice 12, question 4). La ligne manquante de exporter est la boucle for ligne in table: ecrivain.writerow(ligne). Fichier obtenu :
station;nom;tmin_moy;tmax_moy;pluie_totale;nuits_tropicales
MON;Monaco;23,4;29,1;34,0;31
MEN;Menton;22,9;29,6;38,2;30
NIC;Nice;22,4;29,2;23,1;29
ANT;Antibes;21,7;29,5;47,4;29
SOS;Sospel;17,0;31,2;96,4;0
GRE;Gréolières;12,1;23,9;115,6;0
VIL;Villefranche-sur-Mer;23,2;28,8;1,2;4
La fiche de VIL ne repose que sur 4 relevés : ses moyennes sont bien moins fiables que celles des autres stations (une trentaine de relevés chacune), il faut le signaler. Rechargé avec charger(..., ";"), on retrouve 7 fiches (dont toutes les valeurs sont redevenues des chaînes !). Dans un tableur réglé en français, les nombres doivent être reconnus comme tels (alignés à droite, utilisables dans une formule) ; avec des points décimaux, ce n’est pas garanti.
Pour aller plus loin, et compte rendu#
★★★ Exercice 14 — La plus longue vague de chaleur #
▶
Écrire plus_longue_serie(table, seuil) qui renvoie le plus grand nombre de jours consécutifs où tmax \(\geqslant\) seuil, pour une table ne contenant qu’une station. Quelle station a connu la plus longue série de jours à 30 degrés ou plus ? Attention : la table doit être parcourue dans l’ordre des dates, et il peut manquer des jours.
Coup de pouce
Trier d’abord les relevés par date. Garder deux compteurs : la longueur de la série en cours (remise à 0 dès qu’un jour est sous le seuil) et la meilleure longueur vue jusque-là.
Corrigé
def plus_longue_serie(table, seuil):
meilleure = 0
en_cours = 0
jour_precedent = 0
for ligne in sorted(table, key=lambda l: l["date"]):
jour = int(ligne["date"][8:10]) # "2025-08-13" -> 13
if ligne["tmax"] >= seuil and jour == jour_precedent + 1:
en_cours = en_cours + 1
elif ligne["tmax"] >= seuil:
en_cours = 1 # un jour manque : on repart
else:
en_cours = 0
if en_cours > meilleure:
meilleure = en_cours
jour_precedent = jour
return meilleure
Séries à 30 degrés ou plus : Monaco 9, Menton 5, Nice 7, Antibes 8, Sospel 11, Gréolières 0. La plus longue vague de chaleur est donc à Sospel (11 jours). Si l’on ne tient pas compte des jours manquants (version qui remet seulement le compteur à 0 sous le seuil), Menton obtient 8 au lieu de 5 : le relevé écarté du 12 août « recolle » artificiellement deux séries. La réponse finale (Sospel) ne change pas, mais la version simple n’est correcte que si l’on signale cette limite.
★★★ Exercice 15 — au-delà du programme La même chose avec pandas #
▶
Si la bibliothèque pandas est installée. Les scientifiques des données écrivent ce TP en quelques lignes :
import pandas
t = pandas.read_csv("releves_aout.csv", sep=";", decimal=",")
t = t.dropna() # lignes incompletes
t = t[(t["tmin"] <= t["tmax"]) & (t["tmax"] <= 50)] # lignes incoherentes
t = t.drop_duplicates(subset=["station", "date"]) # doublons
print(t.groupby("station")["tmax"].mean().round(1))
Exécuter ce code et comparer avec vos moyennes. Une station apparaît ici alors qu’elle avait disparu de votre première jointure (exercice 12, question 1) : laquelle, et pourquoi ? Qu’est-ce que votre propre code vous a appris que ces cinq lignes cachent ?
Corrigé
Les moyennes coïncident avec les nôtres (Sospel 31,2 ; Menton 29,6 ; Antibes 29,5 ; Nice 29,2 ; Monaco 29,1 ; Gréolières 23,9). La station VIL apparaît aussi (28,8) alors qu’elle avait disparu de notre première jointure : le code pandas ne fait pas de jointure avec la table des stations, il ne « perd » donc pas ces lignes. Ce que notre code a appris : chaque étape (séparateur, virgule décimale, doublons, lignes perdues par la jointure) est une décision ; pandas les rend invisibles, mais elles existent toujours.
Exercice 16 — Compte rendu #
Rédiger, en quelques lignes, un compte rendu (dans un fichier texte placé à côté du programme, ou sur le cahier) :
-
combien de lignes le fichier contenait, combien ont été écartées, et pour quelles raisons ;
-
trois résultats chiffrés tirés de votre bilan (un record, une comparaison littoral/arrière-pays, un classement) ;
-
une phrase sur ce qui se serait passé si l’on avait calculé les records sans nettoyer la table.
Corrigé
Attendus : 193 lignes lues, 8 écartées (2 incomplètes, 3 incohérentes dont 2 inversions et une valeur à 99,9, 3 doublons dont un conflictuel), 4 relevés perdus par la jointure ; trois résultats chiffrés exacts ; sans nettoyage, le « record » du mois aurait été 99,9 degrés à Gréolières, et les moyennes auraient planté (valeurs vides) ou été faussées.
Projet Les prénoms de l’INSEE#
filtrer, trier, regrouper et croiser un vrai fichier de données publiques
Durée : 2 à 3 séances · En binôme, sur machine
Fichiers à télécharger
But du projet
Depuis 1900, l’INSEE compte, année après année, les prénoms donnés aux enfants nés en France, et publie ces données en accès libre. Vous allez explorer ce fichier réel avec Python, sans bibliothèque spécialisée : l’importer et le nettoyer, l’interroger (popularité d’un prénom, palmarès d’une année), regrouper ses lignes avec un dictionnaire, trier, croiser deux tables et tracer des courbes. Pour finir, chaque binôme pose sa propre question aux données, la traite et présente sa réponse à la classe.
Consignes
-
Plus la restitution (partie 8). Les parties 2 à 7 sont guidées, la partie 8 est libre.
-
Fichiers à télécharger (lien ci-dessus), à placer dans le même dossier :
prenoms_insee_extrait.csv(les données),projet_prenoms_evenements.csv(une petite table pour la partie 6) etprojet_prenoms_insee_depart.py(le programme à compléter : tout votre code s’y écrit). -
▶ À programmer. À chaque exécution, le programme teste vos fonctions et affiche, pour chaque partie,
OKouECHEC. Au début, tout est en échec : c’est normal. Niveau : ★ échauffement, ★★ classique (niveau bac), ★★★ défi ; les coups de pouce et les corrigés se déplient sous chaque exercice. -
Usage de l’IA, indiqué sur chaque exercice : aucune ; en appui (déboguer, reformuler, vérifier ; la réponse finale est écrite et comprise par vous) ; l’exercice s’appuie sur une réponse d’IA. Voir la charte d’usage de l’IA.
Les données
Source : Insee, Fichier des prénoms, édition 2025 (parue en juillet 2026), fichier national : naissances en France de 1900 à 2025. Données publiées sous licence ouverte (Etalab 2.0) : on peut les copier, les transformer et les rediffuser librement, à condition de citer la source. Page de téléchargement : www.insee.fr/fr/statistiques/8595130.
Le fichier complet compte près de 725 000 lignes (16 Mo). L’extrait fourni a été fabriqué par un petit programme (projet_prenoms_extraire.py) qui garde le même format mais seulement les naissances de 1950 à 2025 et les lignes dont l’effectif atteint au moins 100.
Découvrir le fichier#
★ Exercice 1 — Regarder avant de programmer #
Ouvrir prenoms_insee_extrait.csv dans un éditeur de texte. En voici le début :
sexe;prenom;periode;valeur;rang
1;GABRIEL;2025;4625;1
1;NOAH;2025;3465;2
1;LÉO;2025;3420;3
-
Quels sont les descripteurs ? Quel est le séparateur ? Comment le sexe est-il codé (chercher
LOUISE) ? -
Comment les prénoms sont-ils écrits ? Que vaut le dernier chiffre de toutes les valeurs de
valeur? D’après la documentation de l’INSEE, les effectifs sont arrondis au multiple de 5 le plus proche : pourquoi, à votre avis ? -
Le cours rappelle l’ancien en-tête
sexe;preusuel;annais;nombre, celui des éditions précédentes du fichier. Quelle leçon en tirer pour un programme qui lit des données publiques ? -
Le prénom Léo n’apparaît pas en 1980 dans l’extrait. Peut-on en conclure qu’aucun Léo n’est né en 1980 ?
Importer et nettoyer#
★ Exercice 2 — Charger la table #
▶
Écrire charger(fichier, separateur) : c’est la fonction du cours, avec en plus le paramètre delimiter de csv.DictReader. Charger l’extrait dans une variable brut. Combien de lignes contient-il ? Afficher brut[0] : de quel type sont les valeurs ?
★★ Exercice 3 — Une fiche propre #
▶
Pour travailler confortablement, on transforme chaque ligne brute en une nouvelle fiche, plus lisible. Compléter convertir(ligne) :
>>> convertir({"sexe": "1", "prenom": "LÉO", "periode": "2025", "valeur": "3420", "rang": "3"})
{'sexe': 'G', 'prenom': 'Léo', 'annee': 2025, 'nombre': 3420, 'rang': 3}
Pour le prénom, compléter d’abord prenom_lisible(prenom), qui garde la majuscule en début de mot (premier caractère, ou caractère qui suit un tiret) et met les autres lettres en minuscules : prenom_lisible("JEAN-PIERRE") vaut "Jean-Pierre". Elle s’appuie sur la fonction en_minuscule, fournie dans le fichier de départ : dans la table Unicode, la minuscule d’une lettre majuscule, accentuée ou non (de A à Z et de À à Ü), est rangée 32 places plus loin.
def en_minuscule(car):
if "A" <= car <= "Z" or "À" <= car <= "Ü":
return chr(ord(car) + 32) # majuscule -> minuscule
return car # pas une majuscule : inchange
Coup de pouce
Construire le résultat caractère par caractère, avec une variable booléenne debut_de_mot, vraie au départ ; après chaque caractère, elle devient vraie si ce caractère est un tiret, fausse sinon.
★★ Exercice 4 — Un programme robuste #
▶
Les éditions précédentes du fichier contenaient des lignes qui ne décrivent pas un vrai prénom une année connue : le prénom _PRENOMS_RARES (le cumul des prénoms trop rares, regroupés pour qu’on ne puisse pas reconnaître les personnes) et l’année XXXX (année de naissance inconnue). L’extrait n’en contient pas, mais votre programme doit pouvoir lire aussi ces anciennes éditions.
-
Compléter
est_valide(ligne), qui renvoieFalsesi le prénom commence par_ou si l’année n’est pas écrite avec des chiffres.Coup de pouce
La tranche
"_PRENOMS"[:1]vaut"_"(le premier caractère). Pour l’année, parcourir ses caractères : un caractèrecarest un chiffre lorsque"0" <= car <= "9"; dès qu’on en trouve un qui n’en est pas un, la ligne n’est pas valide. -
Un camarade propose plutôt le test
"RARES" in ligne["prenom"]. Or le fichier complet contient la ligne1;RARES;2025;15;2053. Que décrit-elle ? Pourquoi ce test serait-il une erreur ? -
Compléter
nettoyer(table), qui renvoie la table des fiches converties, pour les seules lignes valides. Toute la suite utilise cette table nettoyée, notéetable.
Interroger la table#
★ Exercice 5 — Combien de Louise ? #
▶
Compléter nombre_de(table, prenom, sexe, annee), qui renvoie le nombre de naissances, ou 0 si la table ne contient pas de ligne correspondante. Combien de Louise sont nées en 1950, en 1975, en 2000, en 2025 ? Que signifie exactement le 0 renvoyé pour 1975 ?
★★ Exercice 6 — La carrière d’un prénom #
▶
-
Compléter
evolution(table, prenom, sexe), qui renvoie un dictionnaire{annee: nombre}. En combien d’années le prénom Kevin figure-t-il dans l’extrait ? Quelle est la première ? -
Compléter
annee_record(table, prenom, sexe), qui renvoie le couple(annee, nombre)de l’année la plus forte (invariant du champion sur les clés du dictionnaire). Donner l’année record de Marie, de Kevin, de Léo et de Jade. Pourquoi le record de Marie est-il à prendre avec précaution ?
★★ Exercice 7 — Le top 10 d’une année #
▶
Compléter top(table, annee, sexe, n), qui renvoie la liste des n couples (prenom, nombre) les plus donnés. Afficher le top 10 de 2025 pour les filles et pour les garçons, puis celui de 1950. Vérifier votre classement grâce au descripteur rang.
Regrouper avec un dictionnaire#
★★ Exercice 8 — Totaliser sur plusieurs années #
▶
Compléter total_par_prenom(table, sexe, debut, fin), qui renvoie un dictionnaire associant à chaque prénom le total de ses naissances de debut à fin (inclus), puis champion(dico), qui renvoie le couple (cle, valeur) de la plus grande valeur. Quel prénom de fille a été le plus donné de 2020 à 2025, et combien de fois ?
Coup de pouce
C’est le patron du comptage avec un dictionnaire, mais au lieu d’ajouter 1, on ajoute le nombre de la fiche.
★★ Exercice 9 — Le prénom de chaque décennie #
▶
Compléter plus_donne_par_decennie(table, sexe), qui renvoie un dictionnaire {1950: (prenom, total), 1960: …, 2020: …}. Présenter les résultats des filles et des garçons dans un tableau. Pourquoi la décennie 2020 n’est-elle pas comparable aux autres ?
Coup de pouce
Une boucle for debut in range(1950, 2030, 10), et dans la boucle, un appel à champion sur le résultat de total_par_prenom.
★★★ Exercice 10 — Des prénoms de plus en plus variés ? #
▶
Compléter diversite(table, annee, sexe), qui renvoie le nombre de prénoms différents de l’année, le total des naissances correspondantes et la part du top 10 dans ce total (en pourcentage, à afficher au dixième avec f"{part:.1f}"). Comparer 1950, 1975, 2000 et 2025, pour chaque sexe, puis rédiger deux phrases de conclusion. Le total obtenu pour 2025 est-il le nombre de bébés nés en France cette année-là ?
Trier#
★★ Exercice 11 — Le palmarès de 1950 à 2025 #
▶
Compléter palmares(totaux, n), qui transforme le dictionnaire totaux en une liste de couples (prenom, total), la trie avec sorted et une clé, et renvoie les n premiers. Afficher le palmarès des 10 prénoms les plus donnés de 1950 à 2025, pour chaque sexe. Combien de ces prénoms figurent dans un top 10 de 2025 ?
★★★ Exercice 12 — Les ex aequo #
▶
-
En 2025, deux prénoms de filles du top 20 ont exactement la même valeur. Lesquels ? Quel rang l’INSEE donne-t-il à chacun ?
-
Compléter
classement(table, annee, sexe), qui renvoie la liste des prénoms de l’année du plus donné au moins donné et, à égalité, dans l’ordre alphabétique.Coup de pouce
Trier deux fois : d’abord par prénom, puis par nombre (décroissant).
sortedne change jamais l’ordre de deux fiches qui ont la même clé : à nombre égal, l’ordre alphabétique obtenu au premier tri est donc conservé. -
Votre classement et celui de l’INSEE diffèrent-ils sur ces deux prénoms ? Pourquoi l’INSEE peut-il les départager, et pas vous ?
Croiser deux tables#
★★ Exercice 13 — Qui monte, qui descend en dix ans ? #
▶
On veut comparer 2015 et 2025 par une jointure sur le descripteur prenom (la fonction jointure du cours est fournie).
-
Pourquoi ne peut-on pas joindre directement les fiches de 2015 et celles de 2025 ? Que deviendrait le descripteur
nombredans la fiche fusionnée ? -
Compléter
colonne_annee(table, annee, sexe), qui renvoie des fiches réduites à deux descripteurs, comme{"prenom": "Louise", "n2015": 4545}. -
Compléter
montees(table, sexe, a1, a2)(voir sa spécification dans le programme). Combien de prénoms de filles figurent à la fois en 2015 et en 2025 ? Quels sont les 5 plus fortes hausses et les 5 plus fortes baisses, chez les filles puis chez les garçons ? -
Les prénoms donnés en 2025 mais absents en 2015 ont disparu de la jointure. Compléter
nouveaux(table, sexe, a1, a2)pour les retrouver. Combien y en a-t-il chez les garçons ? Expliquer la présence simultanée d’Elioparmi les hausses et d’Élioparmi les nouveaux.
★★★ Exercice 14 — L’effet d’un film, d’une série, d’un champion #
▶
On entend souvent qu’un film, une série ou un sportif lance la mode d’un prénom. Pour le vérifier, on dispose d’une petite table qui associe cinq prénoms à un événement et à son année (descripteurs prenom, annee_evt, evenement) : c’est le fichier projet_prenoms_evenements.csv.
-
Charger cette table, convertir
annee_evten entier, puis calculer sa jointure avectablesur le descripteurprenom. Combien de lignes obtient-on ? Que contient chacune ? -
Compléter
avant_apres(fusion, prenom)(voir sa spécification) et dresser, pour les cinq prénoms, le tableau : événement, nombre l’année d’avant, maximum dans les années qui suivent. -
Pour quels prénoms les données appuient-elles l’idée d’un « effet » ? Pour lequel la courbe montre-t-elle que la hausse avait commencé bien avant ? Un tableau de chiffres suffit-il à prouver qu’un événement est la cause d’une mode ?
Tracer des courbes#
★★ Exercice 15 — La courbe d’un prénom #
▶
La fonction tracer est presque complète : elle utilise la bibliothèque matplotlib, où plt.plot(abscisses, ordonnees) trace une courbe à partir de deux listes de même longueur.
-
Compléter la ligne
nombres = ...: une liste qui contient, pour chaque année deannees, le nombre de naissances, ou0si l’année est absente du dictionnaireevo. -
Tracer les courbes de Marie, Léa et Kevin, puis celles des cinq prénoms de la table des événements. Les courbes confirment-elles vos conclusions de l’exercice précédent ?
-
Le fichier
courbes.pngproduit servira pour la restitution.
Votre question, votre réponse#
★★★ Exercice 16 — Une question posée aux données #
▶
Chaque binôme formule une question précise à laquelle le fichier permet de répondre, la soumet au professeur, puis la traite en réutilisant les fonctions déjà écrites. Quelques pistes (on peut en choisir une autre) :
-
les prénoms composés (
Marie-…,Jean-…) : quelle part des naissances, décennie par décennie ? -
les prénoms mixtes, donnés aux filles et aux garçons la même année : lesquels, et depuis quand ?
-
la longueur moyenne des prénoms (pondérée par le nombre de naissances) a-t-elle changé ?
-
les variantes d’orthographe (Kevin, Kévin ; Eric, Éric ; Elio, Élio) : laquelle l’emporte, et quand ?
-
la « durée de vie » d’une mode : pendant combien d’années un prénom reste-t-il au-dessus de la moitié de son record ?
-
les prénoms de la génération de vos parents : que sont devenus les dix prénoms préférés de 1985 ?
Le binôme rend son programme (commenté) et prépare une restitution au choix : un court oral de 3 minutes devant la classe, ou une affiche A3. Dans les deux cas, on y trouve : la question ; la méthode (quelles fonctions, quels filtres, quels regroupements) ; le résultat, chiffré, avec au moins un tableau ou une courbe ; une limite des données qui pourrait fausser la réponse.
| Critères d’évaluation | Observations |
|---|---|
Parties 2 à 7 : fonctions écrites, tests OK, réponses aux questions |
|
| Question précise, à laquelle le fichier permet vraiment de répondre | |
| Traitement : fonctions réutilisées, code lisible, commenté et testé | |
| Résultat chiffré, présenté par un tableau ou une courbe lisible | |
| Regard critique : une limite des données identifiée (seuil de 100, arrondi à 5, orthographes…) | |
| Restitution claire, dans le temps imparti ; chaque membre du binôme sait expliquer le code |
Pour aller plus loin#
★★★ Exercice 17 — Le fichier complet #
▶
Télécharger le fichier national complet sur la page de l’INSEE (www.insee.fr/fr/statistiques/8595130, lien « Fichier national », archive prenoms-2025-nat_csv.zip), le décompresser, et relancer votre programme sur prenoms-2025-nat.csv. Combien de temps prend le chargement ? et la partie 6 (pensez au coût de la jointure) ? Les naissances de Léo en 1980, de Lilou en 1997 ou d’Arya en 2011 apparaissent-elles maintenant ? Quels résultats de votre projet changent ?
★★★ Exercice 18 — au-delà du programme Et avec pandas ? #
▶
Si la bibliothèque pandas est installée. Exécuter le code suivant et retrouver deux résultats de votre projet. Qu’a dû deviner pandas que votre fonction convertir faisait explicitement ?
import pandas
t = pandas.read_csv("prenoms_insee_extrait.csv", sep=";")
filles25 = t[(t["periode"] == 2025) & (t["sexe"] == 2)]
print(filles25.sort_values("valeur", ascending=False).head(3))
print(t[t["sexe"] == 1].groupby("prenom")["valeur"].sum().nlargest(3))