Maths & NSI

Première

Traitement de données en tables

De nombreuses données du monde réel se présentent sous forme de tables : carnets de notes, catalogues, relevés de mesures... Ce chapitre présente comment importer une table depuis un fichier texte ou CSV, puis comment la manipuler : rechercher des lignes vérifiant un critère, la trier suivant une colonne, et fusionner les données de deux tables. Ces techniques préparent directement l'étude des bases de données en Terminale.

Indexation et import d'une table

Qu'est-ce qu'une table ?

Une table est une manière d'organiser des données structurées, comme un tableau à lignes et colonnes. On la retrouve par exemple dans une feuille de calcul d'un tableur.

NomVilleNote
SalmaRabat15
YassineFès12
NoraTanger18
  • Chaque ligne de la table correspond à un enregistrement : un ensemble de données, de types éventuellement différents, associées à des noms (les colonnes).
  • Chaque colonne correspond à un attribut (ou champ, ou clé).
  • Une cellule est la valeur d'un attribut pour une ligne donnée.

Représenter une table en Python

En Python, on représente une table par une liste de dictionnaires : chaque dictionnaire correspond à une ligne, ses clés sont les noms des colonnes.

Table = [
    {'Nom': 'Salma', 'Ville': 'Rabat', 'Note': '15'},
    {'Nom': 'Yassine', 'Ville': 'Fès', 'Note': '12'},
    {'Nom': 'Nora', 'Ville': 'Tanger', 'Note': '18'},
]

Le format CSV

Le format CSV (Comma Separated Value) est très utilisé pour échanger des données entre tableurs et programmes. C'est un simple fichier texte dans lequel :

  • chaque ligne du fichier correspond à une ligne de la table ;
  • les colonnes sont séparées par un caractère (souvent une virgule) ;
  • la première ligne contient les noms des attributs (les en-têtes de colonnes).

Le fichier eleves.csv correspondant à la table ci-dessus :

Nom,Ville,Note
Salma,Rabat,15
Yassine,Fès,12
Nora,Tanger,18

Importer un fichier CSV

Le module csv de Python fournit un outil, csv.DictReader, qui lit un fichier CSV et produit directement des dictionnaires à partir de la ligne d'en-tête.

import csv
 
def depuis_csv(fichier):
    lecteur = csv.DictReader(open(fichier + '.csv', 'r', encoding='utf-8'))
    return [dict(ligne) for ligne in lecteur]
 
Table = depuis_csv('eleves')

Remarque. Toutes les valeurs importées depuis un CSV sont des chaînes de caractères, même si elles représentent des nombres (ici '15' et non 15). Il faut les convertir avec int(...) ou float(...) avant de les utiliser dans un calcul.

Indexer une table

Une fois la table importée sous forme de liste, on y accède comme à n'importe quelle liste de dictionnaires :

Table[0]                 # premiere ligne (un dictionnaire)
Table[0]['Nom']          # 'Salma'
Table[2]['Ville']        # 'Tanger'
len(Table)                # nombre de lignes

L'entier utilisé pour désigner une ligne (0, 1, 2, ...) est l'index de cette ligne dans la table.

Exercice — Importer et indexer une table

Voici le contenu du fichier notes.csv :

Nom,Matiere,Note
Adam,Maths,14
Lina,NSI,17
Adam,NSI,16
Lina,Maths,13
  1. Écrire la fonction Python depuis_csv permettant d'importer ce fichier sous forme de table (liste de dictionnaires).
  2. En supposant que la table obtenue est stockée dans la variable Table, donner la valeur de Table[1], puis de Table[3]['Note'].
  3. Le type de la valeur Table[0]['Note'] est-il int ou str ? Justifier, puis écrire l'expression qui permet d'obtenir la note du premier élève sous forme d'un entier.
Exercice — Importer une table depuis un fichier texte tabulé

Le programme officiel distingue deux façons d'importer une table : depuis un fichier texte tabulé (les colonnes sont séparées par une tabulation) ou depuis un fichier CSV (colonnes séparées par une virgule). Le cours n'a montré que le cas du CSV ; on s'intéresse ici au fichier tabulé.

Voici le contenu du fichier clubs.txt, dans lequel chaque colonne est séparée de la suivante par une tabulation (notée ici \t) :

Club\tVille\tPoints
Wydad\tCasablanca\t42
Raja\tCasablanca\t39
FUS\tRabat\t35

La fonction csv.DictReader du cours accepte un paramètre optionnel delimiter, qui précise le caractère séparant les colonnes (par défaut, une virgule).

  1. En s'inspirant de la fonction depuis_csv du cours, écrire une fonction depuis_txt_tabule(fichier) qui importe clubs.txt sous forme de table (liste de dictionnaires), en utilisant delimiter='\t'.
  2. En supposant que la table obtenue est stockée dans la variable Table, donner la valeur de Table[0], puis de Table[2]['Points'].
  3. Quel est le type de la valeur Table[0]['Points'] ? Donner l'expression Python permettant de calculer, sous forme d'un entier, la somme des points du Wydad et du Raja, puis calculer cette somme.
Exercice — Importer une table et calculer une moyenne par balayage des index

Voici le contenu du fichier notes_nsi.csv :

Nom,Note
Yasmine,14
Omar,9
Sara,17
Karim,11
Nadia,15
  1. Écrire la fonction depuis_csv du cours, puis l'utiliser pour importer ce fichier dans une variable Table. Donner la valeur de len(Table).
  2. Écrire un programme qui parcourt la table à l'aide d'une boucle for i in range(len(Table)), et qui calcule, dans une variable somme, la somme des notes converties en entiers.
  3. Dérouler ("tracer") l'exécution de cette boucle dans un tableau donnant, à chaque tour, la valeur de i, celle de Table[i]['Note'] (avant conversion), sa valeur convertie en entier, et la valeur de somme après la mise à jour du tour.
  4. En déduire la moyenne de la classe (somme / len(Table)).
Exercice — Importer un CSV « à la française » : point-virgule et virgule décimale

Un tableur réglé en français exporte les relevés d'une station météo dans le fichier releves.csv suivant :

Station;Date;Temperature
Ifrane;2026-01-12;-3,5
Rabat;2026-01-12;14,2
Ifrane;2026-01-13;-1,0
Rabat;2026-01-13;15,8
Fès;2026-01-13;8,4
  1. On l'importe avec la fonction depuis_csv du cours : Table = depuis_csv('releves'). Le premier élément obtenu est :
{'Station;Date;Temperature': 'Ifrane;2026-01-12;-3', None: ['5']}

Expliquer ce résultat.

  1. Modifier depuis_csv pour qu'elle accepte un second paramètre, le caractère séparateur, valant ',' par défaut.
  2. Écrire une fonction convertir(table) qui remplace, dans chaque ligne, la température (une chaîne comme '-3,5') par le nombre flottant correspondant.
  3. Calculer la température moyenne relevée à Ifrane, puis trouver la ligne de la température la plus basse.
Exercice — Épreuve pratique NSI 2026 — Sujet 12 : refuge pour renards, classes et import CSV

Banque nationale de sujets 2026 de l'épreuve pratique, sujet n°12 (situation d'évaluation d'une heure).

Refuge pour renards

Le renard, longtemps considéré comme nuisible, est aujourd'hui de plus en plus protégé pour son rôle dans la régulation de la biodiversité. Afin d'aider à la réhabilitation des individus blessés ou orphelins, un refuge de protection a été construit. La personne en charge de l'infrastructure souhaite réaliser une base de données en CSV et Python pour stocker les informations essentielles sur les renards pris en charge.

Deux entités distinctes sont représentées. La première entité est le Renard. Un renard est défini par un identifiant de type entier, un nom sous forme de chaîne de caractères, un poids en kilogrammes de type flottant, ainsi qu'une date d'arrivée représentée par une chaîne de caractères au format AAAA-MM-JJ. La seconde entité est le Refuge. Un refuge est défini par son nom, son adresse postale, et une liste regroupant les objets de type Renard qu'il héberge.

Toutes les données relatives aux animaux sont fournies dans le fichier donnees_renards.csv, structuré au format CSV avec le point-virgule comme séparateur.

Extrait d'informations fournies dans le fichier donnees_renards.csv (tel qu'il figure dans l'énoncé) :

idnompoidsdate_arrivee
101Édgar6.52023-01-15
102César5.82023-02-10
103Gérard7.22023-03-05
104Sybille4.92024-11-20

Le fichier gestion_refuge.py comporte des éléments à compléter pour définir la classe Renard.

Question 1. Écrire le code du constructeur __init__ de la classe Renard.

Question 2. Écrire le code de la méthode __str__ de la classe Renard qui renvoie une chaîne de caractères qui présente l'animal sous le format précis : "Renard ID [id] - [Nom] (Arrivé le [date_arrivee])". Tester ensuite cette classe en instanciant un renard dans une variable renard1 ayant pour identifiant 200, se nommant Oscar, ayant un poids de 5.1 kg et étant arrivé le 1er janvier 2026. Afficher les informations de cette instance dans la console.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Le fichier gestion_refuge.py comporte une classe Refuge. Une méthode importer_donnees y est pré-écrite pour lire le fichier CSV et peupler le refuge.

Question 3. L'exécution de la méthode importer_donnees provoque une erreur logique lors de l'utilisation ultérieure des données, notamment lors de la manipulation du poids et de l'identifiant des renards. Identifier la source de cette erreur dans la lecture des données brutes, proposer une correction du code de la méthode, puis tester cette correction en instanciant le refuge « SOS Goupil » (situé « 12 rue de la Forêt ») et en y important le fichier donnees_renards.csv.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Le refuge utilise ces données pour surveiller la santé des animaux. Les vétérinaires considèrent qu'un renard est peu corpulent si son poids est strictement inférieur à 6.0 kg. La classe Refuge dispose de deux méthodes nommées lister_peu_corpulents et pourcentage_peu_corpulents pour effectuer ce suivi.

Question 4. Exécuter les deux méthodes d'analyse de la corpulence sur l'instance de votre refuge. Justifier le pourcentage obtenu en isolant et en affichant le nombre de renards peu corpulents par rapport au nombre total de renards hébergés.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Fichiers fournis

Le dossier comporte une version PDF de l'énoncé, le code source de départ gestion_refuge.py et le fichier donnees_renards.csv contenant toutes les données des renards. La bibliothèque csv doit être disponible.

gestion_refuge.py

import csv
 
 
class Renard:
    """
    Classe représentant un renard dans le refuge.
    Attributs : identifiant, nom, poids, date_arrivee.
    """
 
    def __init__(self, identifiant, nom, poids, date_arrivee):
        pass  # Question 1 à compléter
 
    def __str__(self):
        pass  # Question 2 à compléter
 
 
class Refuge:
    """
    Classe représentant le refuge contenant la liste des renards.
    """
 
    def __init__(self, nom, adresse):
        self.nom = nom
        self.adresse = adresse
        self.liste_renards = []
 
    def recueillir(self, un_renard):
        """
        Méthode d'ajout d'un renard au refuge.
        """
        self.liste_renards.append(un_renard)
 
    def lister_peu_corpulents(self):
        """
        Méthode qui renvoie une liste des Renards dont le poids est < 6.0 kg.
        """
        return [renard for renard in self.liste_renards if renard.poids < 6.0]
 
    def pourcentage_peu_corpulents(self):
        """
        Méthode qui renvoie le pourcentage des renards peu corpulents.
        """
        if len(self.liste_renards) == 0:
            return 0.0
        return len(self.lister_peu_corpulents()) / len(self.liste_renards) * 100
 
    def importer_donnees(self, nom_fichier):
        """
        Fonction qui importe les données des renards à partir d'un fichier CSV.
        """
        print(f"Tentative d'importation depuis {nom_fichier}...")
        with open(nom_fichier, 'r', encoding='utf-8') as f:
            lignes = csv.DictReader(f, delimiter=';')
            for ligne in lignes:
                renard = Renard(ligne['id'], ligne['nom'],
                                ligne['poids'], ligne['date_arrivee'])
                self.recueillir(renard)

donnees_renards.csv (début du fichier, 30 renards en tout)

id;nom;poids;date_arrivee
101;Zorro;6.5;2023-01-15
102;Roxy;5.8;2023-02-10
103;Filou;7.2;2023-03-05
104;Kira;4.9;2024-11-20
105;Rouky;6.1;2023-05-12
106;Vixen;5.5;2023-06-01
107;Goupil;7.8;2023-07-18
...
Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

QCM — Import et indexation de tables

1. Dans le vocabulaire des tables, comment appelle-t-on une ligne représentée en Python ?
2. Dans un fichier CSV, à quoi correspond la première ligne ?
3. En reprenant la table du cours, où Table[1]['Note'] vaut '12' et Table[2]['Note'] vaut '18' (des chaînes de caractères, comme toutes les valeurs importées d'un CSV), que vaut Table[1]['Note'] + Table[2]['Note'] ?
4. Un fichier CSV sépare ses colonnes par des points-virgules. Quel argument faut-il transmettre à csv.DictReader pour le lire correctement ?
5. Une table importée contient 120 lignes. Quel est l'index de sa dernière ligne ?

Recherche, tri et fusion de tables

Rechercher les lignes vérifiant un critère

Pour extraire d'une table les lignes qui vérifient une condition, on utilise les opérateurs de la logique propositionnelle habituels : <, >, <=, >=, ==, !=, in, not, and, or.

Méthode. On écrit une fonction select qui prend en paramètres une table et un critère (une fonction qui, appliquée à une ligne, renvoie True ou False), et renvoie la liste des lignes qui vérifient ce critère.

def select(table, critere):
    return [ligne for ligne in table if critere(ligne)]

Exemple. Pour sélectionner les élèves de la table Table ayant plus de 16 en note :

>>> select(Table, lambda ligne: int(ligne['Note']) > 16)
[{'Nom': 'Nora', 'Ville': 'Tanger', 'Note': '18'}]

On peut combiner plusieurs conditions avec and, or et not :

>>> select(Table, lambda ligne: ligne['Ville'] == 'Rabat' and int(ligne['Note']) >= 15)

Trier une table suivant une colonne

La fonction native sorted accepte un paramètre key (une fonction qui indique selon quoi trier) et un paramètre reverse (un booléen, pour trier dans l'ordre décroissant).

def tri(table, attribut, decroissant=False):
    def critere(ligne):
        return ligne[attribut]
    return sorted(table, key=critere, reverse=decroissant)

Exemple. Trier Table par note décroissante :

>>> tri(Table, 'Note', True)

Attention. Comme les notes sont des chaînes de caractères après un import CSV, un tri direct sur 'Note' les compare comme du texte ('9' > '18' est vrai !). Il faut alors trier sur une valeur convertie, par exemple avec key=lambda ligne: int(ligne['Note']).

Fusionner deux tables (jointure)

Lorsque des données sont réparties dans plusieurs tables, on peut les regrouper en une nouvelle table en les fusionnant (ou joignant) suivant un attribut commun aux deux tables.

from copy import deepcopy
 
def jointure(table1, table2, cle1, cle2=None):
    if cle2 is None:
        cle2 = cle1
    nouvelle_table = []
    for ligne1 in table1:
        for ligne2 in table2:
            if ligne1[cle1] == ligne2[cle2]:
                nouvelle_ligne = deepcopy(ligne1)
                for cle in ligne2:
                    if cle != cle2:
                        nouvelle_ligne[cle] = ligne2[cle]
                nouvelle_table.append(nouvelle_ligne)
    return nouvelle_table

Exemple. Avec Table1 (Nom, Ville, Note) et Table2 donnant l'âge des élèves (Nom, Age) :

>>> jointure(Table1, Table2, 'Nom')

produit une table contenant, pour chaque élève présent dans les deux tables, toutes les colonnes réunies. Un élève absent de l'une des deux tables n'apparaît pas dans le résultat.

À noter. En Terminale, vous retrouverez cette opération de jointure sous le nom SQL JOIN.

Les figures ci-dessous illustrent les trois opérations sur une petite table d'exemple (Table1, colonnes Nom/Ville/Note — on retrouve bien Nora comme seule élève avec plus de 16, comme dans l'exemple ci-dessus) :

Sélection : Note > 16

NomVilleNote
NoraTanger18
YanisRabat15
SamiRabat9
LinaFès12

Table initiale : 4 ligne(s). Critère : Note > 16.

Étape 1/10

Tri par Note décroissante

NomVilleNote
NoraTanger18
YanisRabat15
SamiRabat9
LinaFès12

Table initiale.

Étape 1/7

Jointure Table1 / Table2 (Sami absent de Table2)

Table 1

NomVilleNote
NoraTanger18
YanisRabat15
SamiRabat9
LinaFès12

Table 2

NomAge
Nora17
Yanis16
Lina17

On compare chaque ligne de Table 1 à chaque ligne de Table 2 selon la colonne commune « Nom ».

Étape 1/14
Exercice — Sélectionner et trier une table

On considère la table suivante, stockée dans la variable Table :

Table = [
    {'Nom': 'Salma', 'Ville': 'Rabat', 'Note': '15'},
    {'Nom': 'Yassine', 'Ville': 'Fès', 'Note': '12'},
    {'Nom': 'Nora', 'Ville': 'Tanger', 'Note': '18'},
    {'Nom': 'Karim', 'Ville': 'Rabat', 'Note': '9'},
]
  1. En utilisant la fonction select du cours, écrire l'appel permettant d'obtenir les élèves dont la note est inférieure à 10.
  2. Écrire l'appel permettant d'obtenir les élèves de Rabat ayant une note supérieure ou égale à 15.
  3. En utilisant sorted, écrire l'appel permettant de trier Table par ordre croissant de note (en tenant compte du fait que les notes sont des chaînes de caractères).
Exercice — Fusionner deux tables

On dispose de deux tables :

Table1 = [
    {'Nom': 'Salma', 'Note': '15'},
    {'Nom': 'Yassine', 'Note': '12'},
    {'Nom': 'Nora', 'Note': '18'},
]
 
Table2 = [
    {'Nom': 'Salma', 'Age': '16'},
    {'Nom': 'Nora', 'Age': '17'},
]
  1. En utilisant la fonction jointure du cours, écrire l'appel permettant de fusionner Table1 et Table2 selon le nom des élèves.
  2. Donner le résultat de cet appel. Pourquoi Yassine n'apparaît-il pas dans la table obtenue ?
Exercice — Rechercher les doublons et tester la cohérence d'une table

Un professeur a importé la table des inscriptions à un concours. Comme toujours après un import CSV, toutes les valeurs sont des chaînes de caractères.

inscriptions = [
    {'Id': '101', 'Nom': 'Salma',   'Classe': '1G2', 'Note': '15'},
    {'Id': '102', 'Nom': 'Yassine', 'Classe': '1G1', 'Note': '12'},
    {'Id': '103', 'Nom': 'Nora',    'Classe': '1G2', 'Note': '25'},
    {'Id': '102', 'Nom': 'Yassine', 'Classe': '1G1', 'Note': '12'},
    {'Id': '104', 'Nom': 'Adam',    'Classe': '1G3', 'Note': ''},
    {'Id': '105', 'Nom': 'Lina',    'Classe': '1G1', 'Note': '9'},
]
  1. Écrire une fonction doublons(table, attribut) qui renvoie la liste des valeurs qui apparaissent plusieurs fois dans la colonne attribut. Que renvoient doublons(inscriptions, 'Id') et doublons(inscriptions, 'Classe') ? Lequel de ces deux résultats signale une anomalie ?
  2. Une note est cohérente si elle est renseignée, entière et comprise entre 0 et 20. Écrire une fonction note_valide(ligne) qui renvoie True si la note de la ligne est cohérente.
  3. À l'aide de la fonction select du cours, écrire une instruction qui renvoie les lignes dont la note n'est pas cohérente.
  4. Écrire une fonction nettoyer(table) qui renvoie une nouvelle table sans doublon d'identifiant (on garde la première occurrence) et sans note incohérente. Calculer la moyenne des notes de la table nettoyée.
  5. Pourquoi vaut-il mieux rechercher les doublons sur la colonne Id plutôt que sur la colonne Nom ?
Exercice — Domaine de valeurs : pourquoi une jointure perd des lignes

On dispose de deux tables :

eleves = [
    {'Nom': 'Salma',   'Ville': 'Fès'},
    {'Nom': 'Yassine', 'Ville': 'Rabat'},
    {'Nom': 'Nora',    'Ville': 'Tanger'},
    {'Nom': 'Adam',    'Ville': 'Fes'},
]
villes = [
    {'Ville': 'Fès',    'Region': 'Fès-Meknès'},
    {'Ville': 'Rabat',  'Region': 'Rabat-Salé-Kénitra'},
    {'Ville': 'Tanger', 'Region': 'Tanger-Tétouan-Al Hoceïma'},
]
  1. On calcule jointure(eleves, villes, 'Ville') avec la fonction du cours. Combien de lignes contient le résultat ? Quel élève manque, et pourquoi ?
  2. On appelle domaine de valeurs d'un attribut l'ensemble des valeurs qu'il peut prendre. Écrire une fonction hors_domaine(table, attribut, domaine) qui renvoie les lignes de table dont la valeur de attribut n'appartient pas à la liste domaine. Comment l'utiliser pour repérer, avant la jointure, les lignes qui vont disparaître ?
  3. Une seconde classe envoie sa table eleves2, de mêmes colonnes Nom et Ville. Écrire une fonction concatener(table1, table2) qui renvoie une nouvelle table contenant les lignes des deux tables, à condition qu'elles aient les mêmes attributs, et None sinon.
Exercice — Épreuve pratique NSI 2024 — Sujet 40, exercice 1 : sélectionner les animaux d'un enclos

Banque nationale de sujets 2024 de l'épreuve pratique, sujet n°40, exercice 1.

On considère des tables, c'est-à-dire des tableaux de dictionnaires ayant tous les mêmes clés, qui contiennent des enregistrements relatifs à des animaux hébergés dans un refuge. Les attributs des enregistrements sont 'nom', 'espece', 'age', 'enclos'.

Voici un exemple d'une telle table :

animaux = [ {'nom':'Medor', 'espece':'chien', 'age':5, 'enclos':2},
            {'nom':'Titine', 'espece':'chat', 'age':2, 'enclos':5},
            {'nom':'Tom', 'espece':'chat', 'age':7, 'enclos':4},
            {'nom':'Belle', 'espece':'chien', 'age':6, 'enclos':3},
            {'nom':'Mirza', 'espece':'chat', 'age':6, 'enclos':5}]

Programmer une fonction selection_enclos qui :

  • prend en paramètres :
    • une table table_animaux contenant des enregistrements relatifs à des animaux (comme dans l'exemple ci-dessus),
    • un numéro d'enclos num_enclos ;
  • renvoie une table contenant les enregistrements de table_animaux dont l'attribut 'enclos' est num_enclos.

Exemples avec la table animaux ci-dessus :

>>> selection_enclos(animaux, 5)
[{'nom':'Titine', 'espece':'chat', 'age':2, 'enclos':5},
 {'nom':'Mirza', 'espece':'chat', 'age':6, 'enclos':5}]
>>> selection_enclos(animaux, 2)
[{'nom':'Medor', 'espece':'chien', 'age':5, 'enclos':2}]
>>> selection_enclos(animaux, 7)
[]
Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

Exercice — Épreuve pratique NSI 2026 — Sujet 04 : croissance de plantes en serre

Banque nationale de sujets 2026 de l'épreuve pratique, sujet n°04 (situation d'évaluation d'une heure).

Croissance de plantes en serre

Une équipe d'agronomes souhaite étudier la croissance de différentes plantes cultivées en serre afin de déterminer l'impact des conditions environnementales sur leur développement. Chaque jour, les chercheurs mesurent la hauteur de chaque plante, la température ambiante et l'humidité de la serre.

L'objectif de ce sujet est de concevoir un programme permettant d'analyser ces données afin de dégager des tendances sur la croissance des plantes.

Deux fichiers Python sont fournis :

  • plantes.py qui contient la description des différentes plantes cultivées ;
  • mesures.py qui contient les relevés journaliers effectués dans la serre.

Le fichier plantes.py présente les plantes étudiées sous la forme de liste d'objets. Chaque plante est représentée par une instance de la classe Plante. Chaque instance de la classe Plante contient les attributs suivants :

  • son nom ;
  • son espèce ;
  • sa durée moyenne de croissance (en jours) ;
  • sa taille moyenne (en cm) ;
  • son type d'exposition à la lumière parmi "ombre", "mi-ombre" ou "plein soleil".

Le fichier mesures.py contient les données collectées pendant plusieurs jours sous la forme d'une liste de dictionnaires. Chaque dictionnaire correspond à une mesure quotidienne. Chaque mesure comporte les champs suivants :

  • plante : nom d'une plante figurant dans plantes.py ;
  • jour : entier représentant le numéro du jour de culture ;
  • hauteur : flottant correspondant à la hauteur de la plante en centimètres ;
  • temperature : flottant représentant la température moyenne du jour (en °C) ;
  • humidite : flottant représentant le taux d'humidité (en %) mesuré dans la serre.

Question 1. Écrire une fonction croissance_moyenne(plantes) qui prend en paramètre une liste d'instances de la classe Plante et renvoie la moyenne des durées de croissance de l'ensemble de ces plantes (en jours). Si la liste fournie est vide, la fonction doit renvoyer None. Écrire au moins deux tests pour valider le bon fonctionnement de cette fonction, dont un traitant le cas d'une liste vide.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Question 2. Écrire une fonction dictionnaire_mesure(plantes, mesures) qui prend en paramètre la liste des plantes et la liste des mesures. Elle doit renvoyer un dictionnaire où chaque clé est le nom d'une plante (présente dans la liste plantes), et chaque valeur associée est la liste des mesures concernant cette plante spécifique. Si une plante de la liste ne possède aucune mesure associée, la liste correspondante dans le dictionnaire devra être vide. Concevoir une série de tests pertinente pour vérifier le bon comportement de cette fonction.

Exemple de retour :

{
    "plante1": [
        {"plante": "plante1", "jour": 1, "hauteur": 100, "temperature": 20, "humidite": 100},
        {"plante": "plante1", "jour": 2, "hauteur": 101, "temperature": 20, "humidite": 100},
    ],
    "plante2": [
        {"plante": "plante2", "jour": 1, "hauteur": 10, "temperature": 20, "humidite": 20}
    ]
}

Afin de ne conserver que les données correspondant à des conditions climatiques idéales, un chercheur a rédigé la fonction purger_mesures_extremes(liste_mesures). Cette fonction, fournie dans le fichier culture.py, est censée retirer de la liste toutes les mesures ayant été prises à une température strictement inférieure à 20 °C ou strictement supérieure à 25 °C. Cependant, lorsqu'on exécute cette fonction sur les données, on constate qu'un certain nombre de relevés pris à des températures extrêmes sont toujours présents dans la liste finale.

Question 3. Exécuter le test de la fonction test_purger et analyser le code de la fonction purger_mesures_extremes pour identifier la source de cette erreur logique.

Question 4. Proposer une version corrigée de la fonction purger_mesures_extremes répondant parfaitement à l'objectif.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Fichiers fournis

Le dossier fourni au candidat comporte une version PDF de l'énoncé, le fichier culture.py à compléter, un jeu de données de plantes plantes.py et un jeu de données de mesures mesures.py.

culture.py

#############################################################################
# Jeux de données fournis                                                   #
#############################################################################
from plantes import Plante, plantes
from mesures import mesures
 
#############################################################################
# Écrire le code de la fonction croissance_moyenne de la question 1         #
#############################################################################
 
 
def croissance_moyenne(plantes):
    pass
 
 
#############################################################################
# Écrire le code de la fonction dictionnaire_mesure de la question 2      #
#############################################################################
 
def dictionnaire_mesure(plantes, mesures):
    pass
 
 
#############################################################################
# Fonction défaillante à analyser et corriger pour les questions 3 et 4     #
#############################################################################
 
def purger_mesures_extremes(liste_mesures):
    """
    Supprime de la liste toutes les mesures dont la température 
    n'est pas comprise entre 20 et 25°C inclus.
    """
    for mesure in liste_mesures:
        if mesure['temperature'] < 20 or mesure['temperature'] > 25:
            liste_mesures.remove(mesure)
 
 
def test_purger():
    mesures_test = [
        {'jour': 1, 'plante': 'Basilic', 'temperature': 18.0},
        {'jour': 2, 'plante': 'Basilic', 'temperature': 19.0},
        {'jour': 3, 'plante': 'Basilic', 'temperature': 22.0},
        {'jour': 4, 'plante': 'Basilic', 'temperature': 28.0},
        {'jour': 5, 'plante': 'Basilic', 'temperature': 29.0}
    ]
 
    purger_mesures_extremes(mesures_test)
 
    print("Résultat après la purge :")
    for m in mesures_test:
        print(f"Jour {m['jour']} : {m['temperature']}°C")

plantes.py

class Plante:
    def __init__(self, nom, espece, croissance, taille, exposition):
        self.nom = nom
        self.espece = espece
        self.croissance = croissance  # en jours
        self.taille = taille          # en cm
        self.exposition = exposition  # type d'exposition
 
 
plantes = [
    Plante("Basilic", "Ocimum basilicum", 60, 40, "plein soleil"),
    Plante("Tomate", "Solanum lycopersicum", 80, 100, "plein soleil"),
    Plante("Menthe", "Mentha spicata", 80, 50, "mi-ombre"),
    Plante("Tournesol", "Helianthus annuus", 85, 200, "plein soleil"),
    Plante("Fougère", "Dryopteris filix-mas", 90, 80, "ombre")
]

mesures.py (début du fichier ; il contient 305 mesures, du jour 1 au jour 85)

mesures = [
    {'jour': 1, 'plante': 'Basilic', 'hauteur': 0.85,
        'temperature': 29.3, 'humidite': 50.89},
    {'jour': 1, 'plante': 'Tomate', 'hauteur': 1.27,
        'temperature': 21.51, 'humidite': 47.19},
    {'jour': 1, 'plante': 'Menthe', 'hauteur': 0.67,
        'temperature': 27.75, 'humidite': 61.14},
    {'jour': 1, 'plante': 'Tournesol', 'hauteur': 2.29,
        'temperature': 18.42, 'humidite': 49.3},
    ...
]
Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

Exercice — Épreuve pratique NSI 2026 — Sujet 16 : bandes de réchauffement et prévision

Banque nationale de sujets 2026 de l'épreuve pratique, sujet n°16 (situation d'évaluation d'une heure).

Bandes de réchauffement (warming stripes)

Conçues par le climatologue Ed Hawkins en 2018, les warming stripes (« bandes de réchauffement ») permettent de représenter visuellement l'évolution des températures au cours du temps par rapport à une moyenne historique.

Le fichier datas.csv est issu du site de l'agence américaine d'observation océanique et atmosphérique (NOAA). Il contient les écarts de températures mondiales par rapport à la moyenne du XXᵉ siècle (1901-2000), pour chaque année de 1851 à 2025. Par exemple, la ligne 2020,1.01 signifie qu'en 2020, la température moyenne mondiale (terres et océans) était supérieure de 1,01 °C à la moyenne de référence.

L'image warming_stripes.png représente les warming stripes obtenues à partir des données présentes dans le fichier datas.csv : une suite de bandes verticales, une par année, dont les couleurs varient du bleu au rouge, les tons de bleu indiquant des températures inférieures à la moyenne de référence et les tons de rouge des températures supérieures à cette même moyenne (figure 1 du sujet).

La fonction charger(fichier), dont le code est fourni dans le script warming_stripes.py, lit ce fichier et renvoie une liste de dictionnaires. Chaque dictionnaire représente une année avec les clés 'année' (type entier) et 'écart' (type flottant).

Question 1. Écrire la fonction ecart_temperature(datas, annee) qui prend en paramètres la liste des données et une année cible. Elle doit renvoyer l'écart de température correspondant à cette année, ou None si l'année n'est pas présente dans les données. Rédiger ensuite un jeu de tests (via des assertions) permettant de vérifier le bon fonctionnement de votre fonction, y compris pour une année hors du jeu de données.

Question 2. En utilisant la fonction derniere_annee_ecart_negatif (déjà fournie et qui utilise votre fonction), déterminer quelle a été la dernière année où la température mondiale était inférieure à la moyenne de référence.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

La fonction prevision(datas, annee, n), dont le code est fourni, effectue une régression linéaire sur les n dernières années pour estimer l'écart de température d'une année future. C'est-à-dire que cette fonction calcule une droite proche des points des n dernières années et renvoie l'ordonnée du point sur cette droite dont l'abscisse est l'année cible.

Question 3. En exécutant prevision(datas_temperature, 2040, 20), le résultat obtenu semble absurde compte tenu du réchauffement actuel. Cette absurdité provient d'une erreur de logique qui s'est glissée dans la fonction annexe moyenne_ecarts. Analyser le code de cette fonction, identifier l'erreur, et la corriger. Que devient alors la prévision pour 2040 ?

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Question 4. On souhaite générer la représentation graphique des warming stripes. Dans la fonction graphique(datas), le code pour gérer les couleurs (du bleu pour le froid au rouge pour le chaud) est déjà préparé. Compléter le code de la fonction en générant les listes annees (pour l'axe des abscisses) et ordonnees.

Indication : pour obtenir des bandes colorées de hauteur uniforme couvrant tout le graphique, la liste ordonnees devra contenir la valeur 1 pour chaque année traitée.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Fichiers fournis

Le dossier comporte une version PDF de l'énoncé, le code source de départ warming_stripes.py, le fichier datas.csv (écarts de 1851 à 2025) et l'image warming_stripes.png. Les bibliothèques matplotlib et csv doivent être disponibles.

warming_stripes.py

import csv
import matplotlib.pyplot as plt
 
############ Question 1  ############
 
 
def charger(nom_fichier):
    """
    Lit un fichier CSV (contenant les colonnes 'Year' et 'Anomaly') 
    et renvoie une liste de dictionnaires correctement typés.
    """
    donnees = []
    with open(nom_fichier, mode='r', encoding='utf-8') as f:
        lecteur = csv.DictReader(f)
        for ligne in lecteur:
            annee = int(ligne["Year"])
            ecart = float(ligne["Anomaly"])
            donnees.append({"année": annee, "écart": ecart})
    return donnees
 
 
# Chargement global des données
datas_temperature = charger("datas.csv")
 
#############################################################################
# Question 1 : Recherche de l'écart                                         #
#############################################################################
 
# Écrire la fonction ecart_temperature et ses tests ici
 
#############################################################################
# Question 2 : Dernière année négative                                      #
#############################################################################
 
 
def derniere_annee_ecart_negatif(datas):
    annee = max([element["année"] for element in datas])
    ecart = ecart_temperature(datas, annee)
    while ecart >= 0:
        annee = annee - 1
        ecart = ecart_temperature(datas, annee)
    return annee
 
 
#############################################################################
# Question 3 : Analyse et correction de bug                                 #
#############################################################################
 
def moyenne_ecarts(annee_debut, annee_fin, datas):
    """
    Renvoie la moyenne des écarts de température pour la période comprise 
    entre annee_debut et annee_fin (incluses).
    """
    somme = 0
    compteur = 0
    for dico in datas:
        if annee_debut <= dico["année"] and dico["année"] <= annee_fin:
            somme = somme - dico["écart"]
            compteur += 1
    return somme / compteur
 
 
def prevision(datas, annee, n):
    """
    Renvoie l'écart de température attendu calculé par régression linéaire 
    sur les n dernières années.
    """
    longueur = len(datas)
    annee_debut = datas[longueur-n]["année"]
    annee_fin = datas[longueur-1]["année"]
 
    moy_annees = (annee_debut + annee_fin) / 2
    moy_temperatures = moyenne_ecarts(annee_debut, annee_fin, datas)
 
    numerateur = 0
    denominateur = 0
    for i in range(1, n+1):
        ecart_annee = datas[longueur-i]["année"] - moy_annees
        ecart_temp = datas[longueur-i]["écart"] - moy_temperatures
        numerateur += ecart_annee * ecart_temp
        denominateur += ecart_annee ** 2
 
    a = numerateur / denominateur
    b = moy_temperatures - a * moy_annees
 
    return a * annee + b
 
# print("Prévision pour 2040 :", prevision(datas_temperature, 2040, 20))
 
 
#############################################################################
# Question 4 : Dataviz (Warming Stripes)                                    #
#############################################################################
 
def graphique(datas):
    """
    Représente visuellement les warming stripes.
    """
    fig, ax = plt.subplots(figsize=(10, 2))
 
    # Création d'une palette de couleurs basée sur l'amplitude thermique
    cmap = plt.get_cmap("seismic")
    temperatures = [dico["écart"] for dico in datas]
    max_val = max(max(temperatures), -min(temperatures))
    norm = plt.Normalize(-max_val, max_val)
 
    # A COMPLETER
    # Création des listes annees et ordonnees 
    # pour les abscisses et ordonnées
 
    # Génération du graphique
    ax.bar(annees, ordonnees, width=1.0, color=cmap(norm(temperatures)))
    ax.set_title("Warming Stripes mondiales - Base 1901-2000")
    plt.yticks([], [])  # Masque l'axe Y car seule la couleur compte
    ax.set_xlabel("Année")
 
    plt.tight_layout()
    plt.show()
 
# graphique(datas_temperature)

datas.csv (début et fin du fichier)

Year,Anomaly
1851,-0.15
1852,-0.05
1853,-0.03
...
2023,0.9
2024,1.22
2025,1.29
Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

QCM — Recherche, tri et fusion de tables

1. Que renvoie l'expression select(Table, lambda ligne: ligne['Ville'] == 'Rabat') ?
2. Quel paramètre de la fonction sorted permet d'obtenir un tri par ordre décroissant ?
3. Pour fusionner deux tables ayant un attribut commun, quelle condition doit être vérifiée entre deux lignes pour qu'elles soient regroupées en une seule ?
4. Dans une table d'élèves, sur quel attribut faut-il rechercher les doublons pour repérer une inscription saisie deux fois ?
5. On réalise la jointure, sur la colonne Ville, d'une table où une ville est écrite « Fès » et d'une table où elle est écrite « Fes ». Que deviennent les lignes de cette ville ?

Exercices bilan

Lire et indexer une table de bibliothèque

ApplicationCorrigé gratuit

Le fonds d'une bibliothèque de classe est décrit par la table suivante, représentée comme dans le cours par une liste de dictionnaires :

livres = [
    {'Titre': 'Le Horla', 'Auteur': 'Maupassant', 'Annee': '1887', 'Pages': '112'},
    {'Titre': 'Vendredi', 'Auteur': 'Tournier', 'Annee': '1967', 'Pages': '254'},
    {'Titre': 'La Peste', 'Auteur': 'Camus', 'Annee': '1947', 'Pages': '279'},
    {'Titre': 'Cannibale', 'Auteur': 'Daeninckx', 'Annee': '1998', 'Pages': '103'},
]

1. Vocabulaire. Combien cette table comporte-t-elle d'enregistrements ? Combien d'attributs ? Citer ces attributs. Quelle est la valeur de la cellule située à l'intersection de la deuxième ligne et de l'attribut 'Auteur' ?

2. Indexer. Donner la valeur de chacune de ces quatre expressions :

livres[1]['Auteur']
livres[3]['Titre']
len(livres)
len(livres[0])

3. Un piège. Un élève écrit livres[0]['Pages'] + livres[1]['Pages'] en espérant obtenir 366366. Que renvoie réellement Python ? Expliquer, puis corriger l'expression.

4. Programmer. Écrire une fonction total_pages(table) qui renvoie le nombre total de pages de tous les livres de la table. Donner le résultat pour livres.

5. Une colonne. Écrire, en compréhension, la liste des titres de tous les livres, puis donner son contenu exact.

6. Le fichier CSV. Écrire le contenu du fichier livres.csv qui, importé par la fonction depuis_csv du cours, produirait exactement cette table.

Importer un fichier CSV et convertir les types

Application

Un club de VTT enregistre les sorties de ses membres dans un fichier sorties.csv, dont voici le contenu exact (les distances sont en kilomètres, les temps en minutes) :

Prenom,Ville,Distance,Temps
Salma,Rabat,12,30
Yassine,Fes,9,20
Nora,Tanger,20,50

On rappelle la fonction d'import du cours :

import csv
 
def depuis_csv(fichier):
    lecteur = csv.DictReader(open(fichier + '.csv', 'r', encoding='utf-8'))
    return [dict(ligne) for ligne in lecteur]

1. Lire le code.

  • Pourquoi écrit-on depuis_csv('sorties') et non depuis_csv('sorties.csv') ?
  • À quoi sert la première ligne du fichier pour csv.DictReader ?
  • À quoi sert le paramètre encoding='utf-8' ?

2. Le résultat de l'import. Écrire exactement ce que renvoie depuis_csv('sorties'), en respectant les guillemets.

3. Un piège. On pose sorties = depuis_csv('sorties'). Que renvoie sorties[0]['Distance'] * 2 ? Expliquer.

4. Convertir. Écrire une fonction convertir_en_entiers(table, attributs) qui convertit en entiers, dans chaque ligne de la table, les attributs dont les noms figurent dans la liste attributs. Préciser si la fonction modifie la table d'origine.

5. Exploiter. Après conversion des attributs 'Distance' et 'Temps' :

  • calculer la vitesse moyenne de chaque membre, en km/h ;
  • calculer la moyenne des trois vitesses ainsi obtenues ;
  • calculer la vitesse moyenne du club, c'est-à-dire la distance totale divisée par le temps total ;
  • comparer les deux derniers résultats et commenter.

6. Un autre séparateur. Un tableur français exporte souvent des fichiers dont les colonnes sont séparées par des points-virgules. Comment adapter la fonction depuis_csv ?

Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

Sélectionner des lignes dans un catalogue de films

EntraînementCorrigé gratuit

Un catalogue de films est importé depuis un fichier CSV : toutes les valeurs sont donc des chaînes de caractères.

films = [
    {'Titre': 'Arrival', 'Genre': 'SF', 'Annee': '2016', 'Duree': '116', 'Note': '8'},
    {'Titre': 'Persepolis', 'Genre': 'Animation', 'Annee': '2007', 'Duree': '96', 'Note': '8'},
    {'Titre': 'Le Havre', 'Genre': 'Comedie', 'Annee': '2011', 'Duree': '93', 'Note': '7'},
    {'Titre': 'Dune', 'Genre': 'SF', 'Annee': '2021', 'Duree': '155', 'Note': '8'},
    {'Titre': 'Wall-E', 'Genre': 'Animation', 'Annee': '2008', 'Duree': '98', 'Note': '9'},
    {'Titre': 'Blade Runner', 'Genre': 'SF', 'Annee': '1982', 'Duree': '117', 'Note': '9'},
]

On rappelle la fonction de sélection du cours :

def select(table, critere):
    return [ligne for ligne in table if critere(ligne)]

1. Donner la liste des titres renvoyés par select(films, lambda ligne: ligne['Genre'] == 'SF').

2. Écrire l'appel à select qui donne les films de science-fiction sortis après 2010, puis donner les titres obtenus.

3. Écrire l'appel à select qui donne les films durant moins de 100 minutes, puis donner les titres obtenus.

4. Le piège des chaînes. On exécute les deux appels suivants :

select(films, lambda ligne: ligne['Annee'] > '2010')
select(films, lambda ligne: ligne['Duree'] > '100')
  • Combien de films chacun renvoie-t-il ?
  • Le premier donne le bon résultat, le second non. Expliquer pourquoi, et corriger le second.

5. Projeter. Écrire une fonction projection(table, attributs) qui renvoie une nouvelle table ne conservant que les attributs indiqués. Donner le résultat de projection(select(films, lambda ligne: ligne['Genre'] == 'SF'), ['Titre', 'Note']).

6. Combiner. Écrire l'appel à select qui donne les films d'animation ou notés au moins 9, puis donner les titres obtenus dans l'ordre où ils apparaissent.

Trier une table : clé de tri, ordre et stabilité

Entraînement

Les résultats d'un tournoi de robotique sont stockés dans la table suivante, importée d'un CSV (toutes les valeurs sont donc des chaînes). Le Temps est exprimé en secondes ; à nombre de points égal, le concurrent le plus rapide est le mieux classé.

scores = [
    {'Nom': 'Amina', 'Equipe': 'Bleue', 'Points': '42', 'Temps': '95'},
    {'Nom': 'Karim', 'Equipe': 'Rouge', 'Points': '37', 'Temps': '88'},
    {'Nom': 'Lina', 'Equipe': 'Bleue', 'Points': '42', 'Temps': '101'},
    {'Nom': 'Yanis', 'Equipe': 'Rouge', 'Points': '9', 'Temps': '77'},
    {'Nom': 'Sofia', 'Equipe': 'Verte', 'Points': '37', 'Temps': '84'},
]

On rappelle la fonction de tri du cours :

def tri(table, attribut, decroissant=False):
    def critere(ligne):
        return ligne[attribut]
    return sorted(table, key=critere, reverse=decroissant)

1. Donner, dans l'ordre, les noms renvoyés par tri(scores, 'Points'). Le résultat est-il celui qu'on attendait ? Expliquer.

2. Écrire l'appel à sorted qui trie la table par points croissants, correctement. Donner l'ordre des noms obtenus.

3. Stabilité. On trie maintenant par points décroissants avec reverse=True. Donner l'ordre des noms obtenus. En observant le sort d'Amina et de Lina, qui ont le même nombre de points, que peut-on dire du traitement des ex aequo par sorted ? Le paramètre reverse inverse-t-il aussi les ex aequo ?

4. Un vrai classement. Écrire une fonction classement(table) qui trie la table par points décroissants puis, en cas d'égalité, par temps croissant. On utilisera une clé renvoyant un p-uplet. Donner l'ordre des noms obtenus.

5. Une autre méthode. On peut obtenir le même classement par deux tris successifs, sans p-uplet. Écrire ces deux tris, préciser dans quel ordre les effectuer, et vérifier qu'on retrouve bien le résultat de la question 4. Quelle propriété de sorted rend cette méthode correcte ?

6. La fonction tri du cours modifie-t-elle la table qu'on lui passe ? Que se passerait-il si elle utilisait la méthode .sort() à la place de sorted ?

Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

Fusionner la table des emprunts et celle des ouvrages

Entraînement

Une médiathèque conserve ses données dans deux tables distinctes, reliées par le code de l'ouvrage.

emprunts = [
    {'Code': 'L12', 'Lecteur': 'Amina', 'Jours': '14'},
    {'Code': 'L07', 'Lecteur': 'Karim', 'Jours': '3'},
    {'Code': 'L25', 'Lecteur': 'Lina', 'Jours': '21'},
    {'Code': 'L12', 'Lecteur': 'Sofia', 'Jours': '5'},
]
 
ouvrages = [
    {'Code': 'L07', 'Titre': 'Cannibale', 'Genre': 'Roman'},
    {'Code': 'L12', 'Titre': 'Le Horla', 'Genre': 'Fantastique'},
    {'Code': 'L31', 'Titre': 'Vendredi', 'Genre': 'Roman'},
]

On rappelle la fonction de fusion du cours :

from copy import deepcopy
 
def jointure(table1, table2, cle1, cle2=None):
    if cle2 is None:
        cle2 = cle1
    nouvelle_table = []
    for ligne1 in table1:
        for ligne2 in table2:
            if ligne1[cle1] == ligne2[cle2]:
                nouvelle_ligne = deepcopy(ligne1)
                for cle in ligne2:
                    if cle != cle2:
                        nouvelle_ligne[cle] = ligne2[cle]
                nouvelle_table.append(nouvelle_ligne)
    return nouvelle_table

1. Donner exactement la table renvoyée par jointure(emprunts, ouvrages, 'Code'), dans l'ordre, avec toutes ses colonnes.

2. Combien de lignes contient-elle ? Quelles données des deux tables de départ ont disparu, et pourquoi ?

3. L'appel jointure(ouvrages, emprunts, 'Code') donne-t-il le même résultat ? Justifier en donnant l'ordre des lignes obtenues.

4. Pourquoi la fonction recopie-t-elle ligne1 avec deepcopy au lieu d'écrire simplement nouvelle_ligne = ligne1 ? On s'appuiera sur le fait que le code L12 apparaît deux fois dans la table emprunts.

5. Écrire l'appel qui donne, à partir de la table fusionnée, les emprunts de plus de 10 jours. On utilisera la fonction select du cours. Donner le résultat.

6. Combien de comparaisons la fonction jointure effectue-t-elle pour une table de nn lignes et une table de mm lignes ? Que cela implique-t-il pour de très grandes tables ?

Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

Regrouper et agréger les ventes d'une épicerie

Entraînement

Une épicerie enregistre le montant total vendu chaque jour dans chaque rayon. Les montants sont en dirhams, importés d'un CSV.

ventes = [
    {'Jour': 'lundi', 'Rayon': 'fruits', 'Montant': '120'},
    {'Jour': 'lundi', 'Rayon': 'pain', 'Montant': '45'},
    {'Jour': 'mardi', 'Rayon': 'fruits', 'Montant': '90'},
    {'Jour': 'mardi', 'Rayon': 'pain', 'Montant': '60'},
    {'Jour': 'mardi', 'Rayon': 'fromage', 'Montant': '150'},
    {'Jour': 'mercredi', 'Rayon': 'fruits', 'Montant': '110'},
    {'Jour': 'mercredi', 'Rayon': 'fromage', 'Montant': '75'},
]

1. Regrouper. Écrire une fonction total_par(table, attribut, mesure) qui renvoie un dictionnaire associant à chaque valeur de attribut la somme des mesure correspondantes. Donner le résultat de total_par(ventes, 'Rayon', 'Montant') puis de total_par(ventes, 'Jour', 'Montant').

2. Vérifier. Contrôler la cohérence des deux dictionnaires obtenus. Que doivent avoir en commun leurs sommes ?

3. Trouver le maximum. Écrire une fonction meilleur(totaux) qui renvoie la clé associée à la plus grande valeur d'un dictionnaire. Quel rayon rapporte le plus ?

4. Revenir à une table. Écrire une fonction dictionnaire_vers_table(totaux, nom_cle, nom_valeur) qui transforme un tel dictionnaire en table (liste de dictionnaires) à deux colonnes. Trier ensuite cette table par total décroissant à l'aide de la fonction tri du cours, et donner le résultat.

5. Moyenne par groupe. Écrire une fonction moyenne_par(table, attribut, mesure) qui renvoie le dictionnaire des moyennes par groupe. Donner le résultat pour l'attribut 'Jour', en expliquant le type des valeurs obtenues.

6. Prendre du recul. Pourquoi utiliser un dictionnaire plutôt qu'une liste pour effectuer ce regroupement ? Et que teste exactement l'expression cle in totaux lorsque totaux est un dictionnaire ?

Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

Piloter un réseau de vélos en libre-service

Type bac

Cet exercice, composé de trois parties A, B et C, porte sur le traitement de données en tables : import d'un fichier CSV, recherche, tri et fusion de tables.

Une ville exploite un réseau de vélos en libre-service. Les caractéristiques fixes des stations sont conservées dans un fichier CSV, tandis qu'un relevé transmis toutes les minutes indique le nombre de vélos présents.

Partie A : importer la table des stations

Le fichier stations.csv contient exactement :

Code,Nom,Quartier,Capacite
S01,Gare,Centre,20
S02,Marche,Centre,15
S03,Universite,Nord,30
S04,Stade,Sud,12
S05,Plage,Sud,25
S06,Ecole,Nord,8

On l'importe avec la fonction du cours : stations = depuis_csv('stations').

A.1. Donner exactement le premier élément de la liste stations.

A.2. Combien la table comporte-t-elle d'enregistrements ? Combien d'attributs ?

A.3. Que valent stations[2]['Nom'] et len(stations) ?

A.4. Expliquer pourquoi la capacité de la première station est '20' et non 20, et indiquer les conséquences pratiques de ce fait.

Partie B : rechercher et trier

On rappelle les deux fonctions du cours :

def select(table, critere):
    return [ligne for ligne in table if critere(ligne)]
 
def tri(table, attribut, decroissant=False):
    def critere(ligne):
        return ligne[attribut]
    return sorted(table, key=critere, reverse=decroissant)

B.1. Écrire l'appel donnant les stations du quartier 'Sud', puis donner leurs noms.

B.2. Écrire l'appel donnant les stations de capacité au moins égale à 20, puis donner leurs noms.

B.3. On exécute tri(stations, 'Capacite'). Donner l'ordre des noms obtenus. Le résultat est-il correct ? Identifier précisément la station qui révèle le problème et expliquer son origine.

B.4. Écrire l'appel correct triant les stations par capacité décroissante, puis donner l'ordre des noms obtenus.

B.5. Écrire une fonction capacite_totale(table) renvoyant la somme des capacités. Donner sa valeur.

B.6. Écrire une fonction capacite_par_quartier(table) renvoyant un dictionnaire associant à chaque quartier la somme des capacités de ses stations. Donner le résultat, et vérifier sa cohérence avec la question B.5.

Partie C : fusionner avec le relevé

Le relevé de 8 h 00 est la table suivante :

releve = [
    {'Code': 'S01', 'Velos': '4'},
    {'Code': 'S03', 'Velos': '27'},
    {'Code': 'S04', 'Velos': '0'},
    {'Code': 'S05', 'Velos': '11'},
    {'Code': 'S09', 'Velos': '6'},
]

On rappelle la fonction de fusion du cours :

from copy import deepcopy
 
def jointure(table1, table2, cle1, cle2=None):
    if cle2 is None:
        cle2 = cle1
    nouvelle_table = []
    for ligne1 in table1:
        for ligne2 in table2:
            if ligne1[cle1] == ligne2[cle2]:
                nouvelle_ligne = deepcopy(ligne1)
                for cle in ligne2:
                    if cle != cle2:
                        nouvelle_ligne[cle] = ligne2[cle]
                nouvelle_table.append(nouvelle_ligne)
    return nouvelle_table

C.1. On pose fusion = jointure(stations, releve, 'Code'). Donner le nom des stations présentes dans fusion, dans l'ordre, ainsi que les attributs de chaque ligne.

C.2. Quelles lignes des deux tables de départ n'apparaissent pas dans fusion ? Donner pour chacune une explication plausible du point de vue de l'exploitant.

C.3. Écrire une fonction ajouter_taux(table) qui ajoute à chaque ligne un attribut 'Taux', pourcentage entier de vélos présents par rapport à la capacité. Donner le taux de chaque station de fusion.

C.4. Écrire l'appel donnant les stations dont le taux est strictement inférieur à 25 %, c'est-à-dire celles à réapprovisionner. Donner leurs noms.

C.5. Donner l'ordre des noms obtenus en triant fusion par taux décroissant. Faut-il une conversion dans la clé de tri ? Justifier.

C.6. L'exploitant propose de supprimer le relevé et d'ajouter une colonne Velos directement dans stations.csv. Donner deux arguments contre cette proposition.

Correction réservée aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

Chapitre suivant