Exercices - Traitement de données en tables¶
Fichiers CSV et importation¶
Exercice 1 - Lecture d'un fichier CSV¶
On dispose du fichier villes.csv suivant :
nom,population,departement,altitude
Lyon,522250,69,175
Marseille,870018,13,12
Lille,236234,59,27
Toulouse,493465,31,141
Bordeaux,257804,33,6
a. Combien d'enregistrements contient cette table ? Combien d'attributs ?
b. Écrire un programme Python qui lit ce fichier et stocke les données dans un tableau de dictionnaires, en convertissant population et altitude en entiers.
c. Afficher le nom et la population de chaque ville.
Solution
a. 5 enregistrements (lignes de données) et 4 attributs (colonnes : nom, population, departement, altitude).
b.
import csv
table = []
with open("villes.csv", "r", encoding="utf-8") as fichier:
lecteur = csv.DictReader(fichier)
for ligne in lecteur:
enregistrement = dict(ligne)
enregistrement["population"] = int(enregistrement["population"])
enregistrement["altitude"] = int(enregistrement["altitude"])
table.append(enregistrement)
c.
Exercice 2 - Écriture d'un fichier CSV¶
On dispose du tableau suivant :
films = [
{"titre": "Le Fabuleux Destin d'Amélie Poulain", "annee": 2001, "realisateur": "Jean-Pierre Jeunet"},
{"titre": "Intouchables", "annee": 2011, "realisateur": "Olivier Nakache"},
{"titre": "La Haine", "annee": 1995, "realisateur": "Mathieu Kassovitz"}
]
a. Écrire un programme qui enregistre ces données dans un fichier films.csv.
b. Ouvrir le fichier produit avec un éditeur de texte et vérifier son contenu.
Solution
a.
import csv
films = [
{"titre": "Le Fabuleux Destin d'Amélie Poulain", "annee": 2001, "realisateur": "Jean-Pierre Jeunet"},
{"titre": "Intouchables", "annee": 2011, "realisateur": "Olivier Nakache"},
{"titre": "La Haine", "annee": 1995, "realisateur": "Mathieu Kassovitz"}
]
with open("films.csv", "w", encoding="utf-8", newline="") as fichier:
colonnes = ["titre", "annee", "realisateur"]
ecrivain = csv.DictWriter(fichier, fieldnames=colonnes)
ecrivain.writeheader()
for film in films:
ecrivain.writerow(film)
b. Le fichier contient :
Recherche et filtrage¶
Exercice 3 - Sélection de lignes¶
On travaille avec la table des villes de l'exercice 1.
a. Écrire une instruction qui renvoie la liste des villes de plus de 300 000 habitants.
b. Écrire une instruction qui renvoie la liste des villes dont l'altitude est inférieure à 50 mètres.
c. Écrire une instruction qui renvoie la liste des villes du département 13 ou 69.
Solution
a.
grandes = [v for v in table if v["population"] > 300000]
for v in grandes:
print(v["nom"]) # Lyon, Marseille, Toulouse
b.
basses = [v for v in table if v["altitude"] < 50]
for v in basses:
print(f"{v['nom']} ({v['altitude']} m)")
# Marseille (12 m), Lille (27 m), Bordeaux (6 m)
c.
Exercice 4 - Projection et agrégation¶
a. À partir de la table des villes, créer un nouveau tableau ne contenant que les attributs nom et population.
b. Calculer la population totale des 5 villes.
c. Calculer l'altitude moyenne.
d. Trouver la ville la plus peuplée et la moins peuplée.
Solution
a.
b.
c.
altitudes = [v["altitude"] for v in table]
moyenne = sum(altitudes) / len(altitudes)
print(f"Altitude moyenne : {moyenne} m") # 72.2 m
d.
Tri¶
Exercice 5 - Tri d'une table¶
a. Trier la table des villes par population croissante. Afficher le résultat.
b. Trier par nom alphabétique.
c. Trier par altitude décroissante.
d. On ajoute des villes ayant la même altitude. Le tri est-il stable ? Vérifier.
Solution
a.
par_pop = sorted(table, key=lambda v: v["population"])
for v in par_pop:
print(f"{v['nom']} : {v['population']}")
# Lille, Toulouse, Bordeaux, Lyon, Marseille
b.
par_nom = sorted(table, key=lambda v: v["nom"])
for v in par_nom:
print(v["nom"])
# Bordeaux, Lille, Lyon, Marseille, Toulouse
c.
par_alt_desc = sorted(table, key=lambda v: v["altitude"], reverse=True)
for v in par_alt_desc:
print(f"{v['nom']} ({v['altitude']} m)")
# Lyon (175), Toulouse (141), Lille (27), Marseille (12), Bordeaux (6)
d. Oui, le tri de Python (Timsort) est stable : si deux villes ont la même altitude, elles restent dans leur ordre d'apparition d'origine.
Exercice 6 - Doublons et cohérence¶
On dispose de la table suivante :
notes = [
{"nom": "Dupont", "matiere": "NSI", "note": 15},
{"nom": "Martin", "matiere": "NSI", "note": 12},
{"nom": "Dupont", "matiere": "Maths", "note": 14},
{"nom": "Durand", "matiere": "NSI", "note": "18"},
{"nom": "Martin", "matiere": "NSI", "note": 16},
{"nom": "Petit", "matiere": "NSI", "note": -2}
]
a. Identifier les doublons sur l'attribut nom dans la matière NSI.
b. Repérer les problèmes de cohérence dans cette table (types, valeurs aberrantes).
c. Écrire une fonction qui nettoie la table : convertir les notes en entiers et supprimer les notes hors de [0, 20].
Solution
a. En filtrant sur la matière NSI : - Dupont n'apparait qu'une fois en NSI (l'autre est en Maths). - Martin apparait deux fois en NSI (notes 12 et 16) : c'est un doublon.
b. Problèmes :
- Ligne 3 (Durand) : la note "18" est une chaîne au lieu d'un entier.
- Ligne 5 (Petit) : la note -2 est aberrante (hors de [0, 20]).
c.
Fusion de tables¶
Exercice 7 - Jointure simple¶
On dispose de deux tables :
commandes = [
{"id_commande": 1, "id_client": 10, "produit": "Livre", "prix": 15},
{"id_commande": 2, "id_client": 20, "produit": "Stylo", "prix": 3},
{"id_commande": 3, "id_client": 10, "produit": "Cahier", "prix": 5},
{"id_commande": 4, "id_client": 30, "produit": "Gomme", "prix": 2}
]
clients = [
{"id_client": 10, "nom": "Dupont", "ville": "Lyon"},
{"id_client": 20, "nom": "Martin", "ville": "Paris"},
{"id_client": 30, "nom": "Durand", "ville": "Lille"}
]
a. Quelle est la clé de jointure ? Identifier la clé primaire et la clé étrangère.
b. Effectuer la jointure et afficher, pour chaque commande, le nom du client, le produit et le prix.
c. Après la jointure, filtrer les commandes des clients de Lyon.
Solution
a. La clé de jointure est "id_client". C'est la clé primaire de la table clients et la clé étrangère de la table commandes.
b.
def jointure(table1, table2, cle):
resultat = []
for l1 in table1:
for l2 in table2:
if l1[cle] == l2[cle]:
fusion = {}
fusion.update(l1)
fusion.update(l2)
resultat.append(fusion)
return resultat
complet = jointure(commandes, clients, "id_client")
for c in complet:
print(f"{c['nom']} a commandé {c['produit']} ({c['prix']} euros)")
# Dupont a commandé Livre (15 euros)
# Martin a commandé Stylo (3 euros)
# Dupont a commandé Cahier (5 euros)
# Durand a commandé Gomme (2 euros)
c.
Exercice 8 - Jointure avec données manquantes¶
On ajoute un client sans commande et une commande avec un client inconnu :
clients.append({"id_client": 40, "nom": "Morel", "ville": "Bordeaux"})
commandes.append({"id_commande": 5, "id_client": 99, "produit": "Règle", "prix": 4})
a. Effectuer la jointure. Morel et la commande 5 apparaissent-ils dans le résultat ?
b. Expliquer pourquoi.
Solution
a. Non, ni Morel ni la commande 5 n'apparaissent dans le résultat.
b. La jointure ne conserve que les lignes ayant une correspondance dans les deux tables :
- Morel (id_client = 40) n'a aucune commande → pas de correspondance dans
commandes. - La commande 5 (id_client = 99) n'a aucun client correspondant → pas de correspondance dans
clients.
Exercice 9 - Calculs après jointure¶
On reprend les tables commandes et clients de l'exercice 7.
a. Après jointure, calculer le montant total des commandes par client.
b. Trouver le client qui a dépensé le plus.
c. Calculer le montant total de toutes les commandes.
Solution
a.
complet = jointure(commandes, clients, "id_client")
totaux = {}
for c in complet:
nom = c["nom"]
if nom in totaux:
totaux[nom] += c["prix"]
else:
totaux[nom] = c["prix"]
for nom, total in totaux.items():
print(f"{nom} : {total} euros")
# Dupont : 20 euros
# Martin : 3 euros
# Durand : 2 euros
b.
meilleur_client = max(totaux, key=lambda n: totaux[n])
print(f"Meilleur client : {meilleur_client} ({totaux[meilleur_client]} euros)")
# Meilleur client : Dupont (20 euros)
c.
Exercice 10 - Questions transversales¶
On dispose de la table suivante (déjà en mémoire) :
pays = [
{"nom": "France", "capitale": "Paris", "population": 67, "continent": "Europe"},
{"nom": "Japon", "capitale": "Tokyo", "population": 126, "continent": "Asie"},
{"nom": "Brésil", "capitale": "Brasilia", "population": 213, "continent": "Amérique"},
{"nom": "Allemagne", "capitale": "Berlin", "population": 83, "continent": "Europe"},
{"nom": "Inde", "capitale": "New Delhi", "population": 1408, "continent": "Asie"},
{"nom": "Canada", "capitale": "Ottawa", "population": 38, "continent": "Amérique"},
{"nom": "Italie", "capitale": "Rome", "population": 60, "continent": "Europe"}
]
Les populations sont en millions.
a. Filtrer les pays d'Europe. Combien sont-ils ?
b. Trier tous les pays par population décroissante.
c. Calculer la population totale par continent.
d. Trouver le pays le plus peuplé de chaque continent.
Solution
a.
europe = [p for p in pays if p["continent"] == "Europe"]
print(f"Pays d'Europe : {len(europe)}") # 3
for p in europe:
print(p["nom"]) # France, Allemagne, Italie
b.
par_pop = sorted(pays, key=lambda p: p["population"], reverse=True)
for p in par_pop:
print(f"{p['nom']} : {p['population']} M")
# Inde, Brésil, Japon, Allemagne, France, Italie, Canada
c.
pop_continent = {}
for p in pays:
c = p["continent"]
if c in pop_continent:
pop_continent[c] += p["population"]
else:
pop_continent[c] = p["population"]
for c, pop in pop_continent.items():
print(f"{c} : {pop} M")
# Europe : 210 M
# Asie : 1534 M
# Amérique : 251 M
d.
continents = set(p["continent"] for p in pays)
for c in continents:
pays_du_continent = [p for p in pays if p["continent"] == c]
plus_peuple = max(pays_du_continent, key=lambda p: p["population"])
print(f"{c} : {plus_peuple['nom']} ({plus_peuple['population']} M)")
# Europe : Allemagne (83 M)
# Asie : Inde (1408 M)
# Amérique : Brésil (213 M)
Activités¶
🧪 Activité 1 - Explorer un jeu de données réel (open data)¶
Objectif
Importer un fichier CSV de données ouvertes, l'explorer et en extraire des informations pertinentes.
Données¶
Créer le fichier communes.csv suivant (extrait simplifié) :
nom,code_postal,population,superficie,region
Paris,75000,2161000,105,Ile-de-France
Marseille,13000,870731,241,Provence-Alpes-Cote d'Azur
Lyon,69000,522969,48,Auvergne-Rhone-Alpes
Toulouse,31000,493465,118,Occitanie
Nice,06000,342669,72,Provence-Alpes-Cote d'Azur
Nantes,44000,318808,65,Pays de la Loire
Montpellier,34000,290053,57,Occitanie
Strasbourg,67000,287228,78,Grand Est
Bordeaux,33000,257804,50,Nouvelle-Aquitaine
Lille,59000,236234,35,Hauts-de-France
Questions¶
a. Importer les données et convertir les types numériques.
b. Calculer la densité de population (habitants par km²) pour chaque commune et l'ajouter comme nouvel attribut.
c. Afficher les 5 communes les plus denses, triées par densité décroissante.
d. Calculer la population totale et la population moyenne des communes du fichier.
e. Regrouper les communes par région et calculer la population totale de chaque région.
f. Exporter les résultats (avec la densité) dans un nouveau fichier communes_enrichi.csv.
Correction
a.
import csv
table = []
with open("communes.csv", "r", encoding="utf-8") as f:
lecteur = csv.DictReader(f)
for ligne in lecteur:
e = dict(ligne)
e["population"] = int(e["population"])
e["superficie"] = int(e["superficie"])
table.append(e)
b.
c.
par_densite = sorted(table, key=lambda c: c["densite"], reverse=True)
print("Top 5 des communes les plus denses :")
for c in par_densite[:5]:
print(f" {c['nom']} : {c['densite']} hab/km2")
# Paris : 20581, Lyon : 10895, Lille : 6749, Montpellier : 5088, Nantes : 4905
d.
pop_totale = sum(c["population"] for c in table)
pop_moyenne = pop_totale / len(table)
print(f"Population totale : {pop_totale}") # 5 780 961
print(f"Population moyenne : {pop_moyenne:.0f}") # 578 096
e.
par_region = {}
for c in table:
r = c["region"]
if r in par_region:
par_region[r] += c["population"]
else:
par_region[r] = c["population"]
for region, pop in sorted(par_region.items(), key=lambda x: x[1], reverse=True):
print(f" {region} : {pop}")
f.
🧪 Activité 2 - Le bulletin de notes (fusion de tables)¶
Objectif
Fusionner plusieurs fichiers CSV pour produire un bulletin complet.
Données¶
Créer les trois fichiers suivants :
eleves.csv :
id_eleve,nom,prenom,classe
1,Dupont,Marie,1NSI-A
2,Martin,Léo,1NSI-A
3,Durand,Emma,1NSI-B
4,Petit,Hugo,1NSI-B
5,Leroy,Alice,1NSI-A
notes.csv :
id_eleve,matiere,note
1,NSI,15
1,Maths,14
1,Anglais,12
2,NSI,18
2,Maths,10
2,Anglais,16
3,NSI,12
3,Maths,17
3,Anglais,14
4,NSI,8
4,Maths,9
4,Anglais,11
5,NSI,16
5,Maths,15
5,Anglais,17
matieres.csv :
Questions¶
a. Importer les trois fichiers.
b. Fusionner notes et eleves sur id_eleve, puis fusionner le résultat avec matieres sur matiere.
c. Calculer la moyenne pondérée de chaque élève.
d. Afficher le bulletin complet sous forme de tableau lisible.
e. Déterminer le premier et le dernier de la classe (par moyenne pondérée).
Correction
import csv
def charger_csv(nom_fichier):
table = []
with open(nom_fichier, "r", encoding="utf-8") as f:
for ligne in csv.DictReader(f):
table.append(dict(ligne))
return table
def jointure(t1, t2, cle):
resultat = []
for l1 in t1:
for l2 in t2:
if l1[cle] == l2[cle]:
fusion = {}
fusion.update(l1)
fusion.update(l2)
resultat.append(fusion)
return resultat
# a. Importer
eleves = charger_csv("eleves.csv")
notes = charger_csv("notes.csv")
matieres = charger_csv("matieres.csv")
# Conversions
for n in notes:
n["id_eleve"] = int(n["id_eleve"])
n["note"] = float(n["note"])
for e in eleves:
e["id_eleve"] = int(e["id_eleve"])
for m in matieres:
m["coefficient"] = int(m["coefficient"])
# b. Fusions
etape1 = jointure(notes, eleves, "id_eleve")
complet = jointure(etape1, matieres, "matiere")
# c. Moyenne pondérée par élève
moyennes = {}
for ligne in complet:
nom = f"{ligne['prenom']} {ligne['nom']}"
if nom not in moyennes:
moyennes[nom] = {"somme_ponderee": 0, "somme_coefs": 0}
moyennes[nom]["somme_ponderee"] += ligne["note"] * ligne["coefficient"]
moyennes[nom]["somme_coefs"] += ligne["coefficient"]
# d. Affichage
print(f"{'Élève':<20} {'Moyenne':>8}")
print("-" * 30)
for nom, data in moyennes.items():
moy = data["somme_ponderee"] / data["somme_coefs"]
print(f"{nom:<20} {moy:>8.2f}")
# e. Premier et dernier
classement = sorted(moyennes.items(),
key=lambda x: x[1]["somme_ponderee"] / x[1]["somme_coefs"],
reverse=True)
premier = classement[0]
dernier = classement[-1]
moy_p = premier[1]["somme_ponderee"] / premier[1]["somme_coefs"]
moy_d = dernier[1]["somme_ponderee"] / dernier[1]["somme_coefs"]
print(f"\nPremier : {premier[0]} ({moy_p:.2f})")
print(f"Dernier : {dernier[0]} ({moy_d:.2f})")
🧪 Activité 3 - Détective de données (cohérence et nettoyage)¶
Objectif
Repérer et corriger les erreurs dans un jeu de données « sale ».
Données¶
Créer le fichier inscriptions.csv suivant (volontairement plein d'erreurs) :
nom,prenom,age,email,classe
Dupont,Marie,16,[email protected],1NSI-A
Martin,Léo,17,[email protected],1NSI-A
Durand,Emma,seize,[email protected],1NSI-B
,Hugo,15,[email protected],1NSI-B
Petit,Hugo,15,[email protected],1NSI-B
Martin,Léo,17,[email protected],1NSI-A
Leroy,Alice,-3,[email protected],1NSI-A
Bernard,Tom,16,,1NSI-B
Questions¶
a. Importer le fichier. Combien de lignes contient-il ?
b. Identifier tous les problèmes :
- Valeurs manquantes (nom vide, email vide)
- Types incorrects (age = "seize")
- Valeurs aberrantes (age = -3)
- Doublons (lignes identiques)
c. Écrire une fonction nettoyer(table) qui :
- Supprime les doublons exacts
- Signale les lignes avec des valeurs manquantes
- Signale les lignes avec un age non numérique ou hors [10, 20]
d. Exporter la table nettoyée dans inscriptions_propres.csv.
Correction
import csv
# a. Import
table = []
with open("inscriptions.csv", "r", encoding="utf-8") as f:
for ligne in csv.DictReader(f):
table.append(dict(ligne))
print(f"Nombre de lignes : {len(table)}") # 8
# b. Identification des problèmes
print("\n--- Analyse ---")
for i, ligne in enumerate(table):
problemes = []
# Valeurs manquantes
for cle, val in ligne.items():
if val.strip() == "":
problemes.append(f"{cle} manquant")
# Type de age
try:
age = int(ligne["age"])
if age < 10 or age > 20:
problemes.append(f"age aberrant ({age})")
except ValueError:
problemes.append(f"age non numerique ('{ligne['age']}')")
if problemes:
print(f"Ligne {i} ({ligne.get('nom', '?')}) : {', '.join(problemes)}")
# Doublons
for i in range(len(table)):
for j in range(i + 1, len(table)):
if table[i] == table[j]:
print(f"Doublon : lignes {i} et {j}")
# c. Nettoyage
def nettoyer(table):
propre = []
vus = []
for ligne in table:
# Suppression des doublons
cle_unique = (ligne["nom"], ligne["prenom"], ligne["email"])
if cle_unique in vus:
continue
vus.append(cle_unique)
# Valeurs manquantes
if any(v.strip() == "" for v in ligne.values()):
print(f" Ignoree (valeur manquante) : {ligne}")
continue
# Conversion et validation de l'age
try:
ligne["age"] = int(ligne["age"])
except ValueError:
print(f" Ignoree (age invalide) : {ligne}")
continue
if ligne["age"] < 10 or ligne["age"] > 20:
print(f" Ignoree (age aberrant) : {ligne}")
continue
propre.append(ligne)
return propre
print("\n--- Nettoyage ---")
propre = nettoyer(table)
print(f"\nLignes conservees : {len(propre)}")
# d. Export
with open("inscriptions_propres.csv", "w", encoding="utf-8", newline="") as f:
colonnes = ["nom", "prenom", "age", "email", "classe"]
ecrivain = csv.DictWriter(f, fieldnames=colonnes)
ecrivain.writeheader()
for ligne in propre:
ecrivain.writerow(ligne)
🎯 Projet final - Analyse d'un championnat sportif¶
Objectif
Créer et exploiter plusieurs tables de données reliées entre elles pour analyser les résultats d'un championnat.
Partie A - Créer les données¶
a. Créer un fichier equipes.csv contenant 6 équipes avec les colonnes : id_equipe, nom, ville, stade.
b. Créer un fichier matchs.csv contenant 10 matchs avec les colonnes : id_match, id_equipe_dom, id_equipe_ext, buts_dom, buts_ext.
Partie B - Importation et fusion¶
c. Importer les deux fichiers.
d. Fusionner pour afficher, pour chaque match, les noms des équipes et le score (au lieu des identifiants).
Partie C - Classement¶
e. Calculer pour chaque équipe : nombre de matchs joués, victoires, nuls, défaites, buts marqués, buts encaissés, différence de buts, points (3 pour une victoire, 1 pour un nul, 0 pour une défaite).
f. Afficher le classement trié par points décroissants (puis par différence de buts en cas d'égalité).
Partie D - Statistiques¶
g. Calculer le nombre total de buts marqués dans le championnat.
h. Trouver le match avec le plus de buts et la plus grosse victoire.
i. Exporter le classement final dans un fichier classement.csv.
Correction
import csv
# Exemple de données
equipes = [
{"id_equipe": 1, "nom": "Lyon", "ville": "Lyon", "stade": "Groupama"},
{"id_equipe": 2, "nom": "Marseille", "ville": "Marseille", "stade": "Velodrome"},
{"id_equipe": 3, "nom": "Paris", "ville": "Paris", "stade": "Parc des Princes"},
{"id_equipe": 4, "nom": "Lille", "ville": "Lille", "stade": "Pierre Mauroy"},
{"id_equipe": 5, "nom": "Nantes", "ville": "Nantes", "stade": "Beaujoire"},
{"id_equipe": 6, "nom": "Toulouse", "ville": "Toulouse", "stade": "Stadium"}
]
matchs = [
{"id_match": 1, "id_dom": 1, "id_ext": 2, "buts_dom": 2, "buts_ext": 1},
{"id_match": 2, "id_dom": 3, "id_ext": 4, "buts_dom": 3, "buts_ext": 0},
{"id_match": 3, "id_dom": 5, "id_ext": 6, "buts_dom": 1, "buts_ext": 1},
{"id_match": 4, "id_dom": 2, "id_ext": 3, "buts_dom": 0, "buts_ext": 2},
{"id_match": 5, "id_dom": 4, "id_ext": 1, "buts_dom": 1, "buts_ext": 3},
{"id_match": 6, "id_dom": 6, "id_ext": 5, "buts_dom": 2, "buts_ext": 0},
{"id_match": 7, "id_dom": 1, "id_ext": 3, "buts_dom": 1, "buts_ext": 1},
{"id_match": 8, "id_dom": 2, "id_ext": 4, "buts_dom": 2, "buts_ext": 2},
{"id_match": 9, "id_dom": 5, "id_ext": 1, "buts_dom": 0, "buts_ext": 1},
{"id_match": 10, "id_dom": 6, "id_ext": 3, "buts_dom": 1, "buts_ext": 4}
]
# Dictionnaire id -> nom pour accès rapide
noms = {e["id_equipe"]: e["nom"] for e in equipes}
# Classement
stats = {}
for e in equipes:
stats[e["id_equipe"]] = {
"nom": e["nom"], "mj": 0, "v": 0, "n": 0, "d": 0,
"bp": 0, "bc": 0, "diff": 0, "pts": 0
}
for m in matchs:
dom = m["id_dom"]
ext = m["id_ext"]
bd = m["buts_dom"]
be = m["buts_ext"]
stats[dom]["mj"] += 1
stats[ext]["mj"] += 1
stats[dom]["bp"] += bd
stats[dom]["bc"] += be
stats[ext]["bp"] += be
stats[ext]["bc"] += bd
if bd > be:
stats[dom]["v"] += 1
stats[dom]["pts"] += 3
stats[ext]["d"] += 1
elif bd < be:
stats[ext]["v"] += 1
stats[ext]["pts"] += 3
stats[dom]["d"] += 1
else:
stats[dom]["n"] += 1
stats[ext]["n"] += 1
stats[dom]["pts"] += 1
stats[ext]["pts"] += 1
for s in stats.values():
s["diff"] = s["bp"] - s["bc"]
# Affichage du classement
classement = sorted(stats.values(),
key=lambda s: (s["pts"], s["diff"]),
reverse=True)
print(f"{'Pos':<4} {'Equipe':<12} {'MJ':<4} {'V':<4} {'N':<4} {'D':<4} "
f"{'BP':<4} {'BC':<4} {'Diff':<5} {'Pts':<4}")
print("-" * 55)
for i, s in enumerate(classement):
diff = f"+{s['diff']}" if s['diff'] > 0 else str(s['diff'])
print(f"{i+1:<4} {s['nom']:<12} {s['mj']:<4} {s['v']:<4} {s['n']:<4} "
f"{s['d']:<4} {s['bp']:<4} {s['bc']:<4} {diff:<5} {s['pts']:<4}")
# Statistiques
total_buts = sum(m["buts_dom"] + m["buts_ext"] for m in matchs)
print(f"\nTotal buts : {total_buts}")
plus_de_buts = max(matchs, key=lambda m: m["buts_dom"] + m["buts_ext"])
print(f"Match avec le plus de buts : {noms[plus_de_buts['id_dom']]} "
f"{plus_de_buts['buts_dom']}-{plus_de_buts['buts_ext']} "
f"{noms[plus_de_buts['id_ext']]}")