NSI · Première

Données en tables : importer, trier et fusionner

Une table organise des enregistrements en lignes et des champs en colonnes. Apprends à importer un CSV, vérifier ses valeurs, sélectionner des lignes, trier et relier deux tables par une clé, sans masquer les anomalies.

Explications et exemples en accès libre. Ateliers, quiz et cartes avec l’abonnement.

Étude en accès libre
Environ 45 min à 1 h 25
Progression
6 étapes guidées
Vérification
10 questions
Rappel actif
12 cartes
Prévoir mon tempsExplications et exemples en accès libre

Une première étude des explications, schémas, exemples résolus et erreurs expliquées. Les activités, productions, quiz et cartes réservés à l’abonnement ne sont pas comptés ici.

Comprendre1426 mots d’explication et 4 schémas
10 à 17 min
Étudier les exemples et les erreurs18 cas, exemples et activités guidés
36 à 66 min

Étude du cours en accès libre, environ45 min à 1 h 25

Voir le calcul et le temps par étape

Le calcul suit automatiquement les explications et les tâches présentes dans le cours. Ses coefficients sont des repères de planification, pas des temps mesurés auprès d’élèves.

  • Lecture active : 1426 mots, à raison de 160 à 220 mots par minute.
  • Schémas : 4, avec 1 à 2 min pour lire chacun.
  • Exemple guidé : 6 × 2 à 4 min.
  • Suivre le code résolu et ses tests : 6 × 3 à 5 min.
  • Comprendre une erreur expliquée : 6 × 1 à 2 min.
  1. Importer un CSV : lire des champs, pas découper au hasard8 à 15 min
  2. Schéma et domaines : convertir sans inventer de valeur6 à 13 min
  3. Rechercher : sélectionner des lignes, projeter des champs7 à 15 min
  4. Doublons et cohérence : détecter n'est pas corriger6 à 13 min
  5. Trier : choisir une clé et suivre les égalités7 à 15 min
  6. Fusionner : relier des clés, pas des positions7 à 15 min

Les sous-totaux sont arrondis à la minute, puis additionnés. La fourchette totale est élargie aux cinq minutes voisines. Les étapes ci-dessus comprennent leurs explications et exemples ; elles ne s’ajoutent pas une seconde fois au total.

Adapte ce repère à tes acquis et au soin apporté aux exercices. Les pauses, les reprises, la consultation des sources externes et les révisions suivantes s’ajoutent selon tes besoins.

Avec les ateliers, le projet, le quiz et les cartes : environ 2 h 40 à 4 h 40, à répartir sur plusieurs séances.

Objectifs du cours

Ce que tu vas savoir faire

  • Importer un texte CSV dans une liste d'enregistrements en contrôlant en-tête, séparateur et types.
  • Définir un schéma simple et produire des erreurs localisées de domaine, de présence et d'unicité.
  • Rechercher des lignes avec des critères booléens et distinguer sélection, projection et agrégation.
  • Repérer doublons, références inconnues et incohérences sans corriger silencieusement les données.
  • Trier par une ou plusieurs clés typées en préservant l'entrée et en exploitant la stabilité.
  • Fusionner deux tables par identifiants, documenter les lignes orphelines et vérifier la conservation des effectifs.
01

Étape du cours · 8 à 15 min

Importer un CSV : lire des champs, pas découper au hasard

CSV décrit des lignes et des champs séparés, mais les fichiers réels varient : virgule ou point-virgule, présence d'un en-tête, guillemets, sauts de ligne et encodage. Le module standard csv gère mieux ces règles qu'un simple split, qui échoue dès qu'un champ contient le séparateur entre guillemets. DictReader utilise l'en-tête comme noms de champs et produit d'abord des chaînes.

Notre table Python est une liste de dictionnaires. table[0] est le premier enregistrement et table[0]['mesure'] sa valeur du champ mesure. DictReader utilise ici la première ligne comme en-tête, sans l'ajouter aux données. Dans le mode choisi, même '10' reste une chaîne. Le schéma attendu énumère des noms de champs distincts et dans l'ordre du CSV.

Les exemples utilisent StringIO, un flux de texte en mémoire. Sur ton ordinateur, un fichier UTF-8 se lit avec with open('table.csv', encoding='utf-8', newline='') as fichier: ; dans ce bloc indenté, texte = fichier.read() permet ensuite d'appeler charger_csv. L'encodage doit correspondre au fichier, et newline laisse le module csv interpréter les fins de ligne. Les données d'exercice ci-dessous peuvent être copiées dans un fichier local si tu veux essayer cette lecture.

Le lecteur contrôle l'en-tête, les champs manquants ou supplémentaires et les erreurs de citation reconnues par le mode strict. Un champ cité peut contenir un séparateur ou un retour à la ligne : un enregistrement ne correspond donc pas toujours à une ligne physique. Les lignes totalement vides sont ignorées par DictReader. La validation du domaine vient après cette lecture.

Voir pour comprendre

De la ligne CSV à la valeur utilisable

La syntaxe du fichier et le domaine de la mesure sont deux contrôles distincts.

  1. Lire le formatUn champ cité comme « Gamma; annexe » reste un seul champ.
  2. Associer l'en-têteLa valeur de mesure est d'abord la chaîne « 10 ».
  3. Valider le domaineConvertir en entier, vérifier 0 à 100, puis conserver 10 ou signaler l'erreur.

Lis le schéma. Indique à quelle étape détecter une colonne absente, puis une mesure égale à « abc ».

Importer correctement ne rend pas une donnée valide. Chaque étape préserve la cause de l'erreur.

Exemples résolus et erreurs expliquées

Lire un séparateur à l'intérieur d'un nom

  1. L'en-tête code;nom;mesure annonce trois champs. L'enregistrement C3;"Gamma; annexe";4 contient lui aussi trois champs, pas quatre.
  2. Les guillemets délimitent le texte Gamma; annexe. Le point-virgule interne appartient au nom ; les deux autres séparent les champs.
  3. DictReader construit {'code': 'C3', 'nom': 'Gamma; annexe', 'mesure': '4'}. Les guillemets de citation du CSV ne font pas partie du nom obtenu.
  4. table[0]['mesure'] vaut la chaîne '4'. La convertir en entier puis vérifier son domaine est une autre étape : un fichier bien formé peut encore contenir une mesure impossible.

Conclusion. Sépare trois questions : comment le fichier est encodé, où finissent ses champs, et quelles valeurs ces champs peuvent prendre.

Laboratoire de code

Languepython

Butimporter un CSV fictif avec un analyseur correct et contrôler son en-tête

Code solution
import csv
from io import StringIO

def charger_csv(texte, champs_attendus, separateur=';'):
    if not champs_attendus or len(set(champs_attendus)) != len(champs_attendus):
        raise ValueError("descripteurs attendus absents ou dupliqués")
    lecteur = csv.DictReader(StringIO(texte, newline=''), delimiter=separateur, strict=True)
    if lecteur.fieldnames != champs_attendus:
        raise ValueError("en-tête inattendu")
    lignes = []
    for ligne in lecteur:
        if None in ligne or any(valeur is None for valeur in ligne.values()):
            raise ValueError("nombre de champs incorrect")
        lignes.append(dict(ligne))
    return lignes

Tests

texte = 'code;nom;mesure\nA1;Alpha;10\nB2;Beta;7\n'
table = charger_csv(texte, ['code', 'nom', 'mesure'])
assert table[0] == {'code': 'A1', 'nom': 'Alpha', 'mesure': '10'}
assert len(table) == 2
assert charger_csv('code;nom;mesure\n', ['code', 'nom', 'mesure']) == []
avec_separateur = 'code;nom;mesure\nC3;"Gamma; annexe";4\n'
assert charger_csv(avec_separateur, ['code', 'nom', 'mesure'])[0]['nom'] == 'Gamma; annexe'
try:
    charger_csv('code;nom;mesure\nA;Alpha\n', ['code','nom','mesure'])
except ValueError:
    pass
else:
    raise AssertionError('entrée invalide acceptée')
try:
    charger_csv('code;nom;mesure\nA;Alpha;1;surplus\n', ['code','nom','mesure'])
except ValueError:
    pass
else:
    raise AssertionError('entrée invalide acceptée')

Trace

  • DictReader lit la première ligne et obtient les champs code, nom, mesure
  • la ligne A1;Alpha;10 devient un dictionnaire dont mesure vaut encore la chaîne '10'
  • le champ entre guillemets Gamma; annexe reste unique malgré le point-virgule interne

Clinique de bogue

Indice observéle nom entre guillemets Gamma; annexe est séparé en deux champs et l'en-tête devient une ligne ordinaire

Causesplit ignore les règles de citation du format et ne relie pas les valeurs aux descripteurs. Un parseur CSV doit interpréter séparateur, guillemets, en-tête et lignes vides selon une convention annoncée.

02

Étape du cours · 6 à 13 min

Schéma et domaines : convertir sans inventer de valeur

Après import, 10 est une chaîne et se trie avant 2 selon l'ordre lexical. Le schéma doit indiquer que mesure appartient ici aux entiers de 0 à 100, que code est une chaîne non vide et que categorie appartient à un ensemble fermé. Convertir sans contrôler peut accepter une valeur hors domaine ; contrôler sans convertir peut comparer des types incompatibles.

valider attend des dictionnaires de chaînes issus de l'import. Elle ne modifie pas la table d'origine : elle renvoie les enregistrements valides et la première erreur rencontrée pour chaque enregistrement rejeté. Chaque erreur contient son rang à partir de 1 dans les données importées, le champ et la raison. Ce rang n'est pas une ligne physique du CSV lorsqu'il contient des retours à la ligne cités ou des lignes vides.

Dans cet exercice, mesure est un indice fictif sans unité, entier entre 0 et 100 inclus. Une chaîne vide signifie une valeur manquante, pas zéro ; '2.5' n'est pas un entier acceptable. Le code et la catégorie sont débarrassés des espaces extérieurs selon la règle annoncée. L'unicité des codes est un contrôle séparé : convertir les domaines ne résout pas les doublons.

Exemples résolus et erreurs expliquées

Distinguer une absence, un zéro et une valeur hors domaine

  1. Pour {'code': 'X', 'categorie': 'A', 'mesure': '0'}, la conversion donne l'entier 0. Il appartient à l'intervalle 0 à 100 : la ligne est valide.
  2. La même ligne avec mesure égale à '' échoue lors de la conversion. Elle rejoint les erreurs avec le champ mesure, sans être changée en zéro.
  3. Avec '101', la conversion réussit mais le domaine échoue. Avec '2.5', c'est déjà la conversion en entier qui échoue. Les causes sont différentes.
  4. Pour les quatre enregistrements dans cet ordre, le bilan est un valide et trois erreurs aux rangs 2, 3 et 4. La table brute reste disponible pour retrouver chaque valeur initiale.

Conclusion. Le type et le domaine sont deux contrôles successifs. Compte les enregistrements acceptés et rejetés sans faire disparaître les entrées initiales.

Laboratoire de code

Languepython

Butconvertir les domaines valides et produire un journal d'erreurs localisé

Code solution
CATEGORIES = {'A', 'B', 'C'}

def valider(table):
    valides = []
    erreurs = []
    for numero, ligne in enumerate(table, start=1):
        code = ligne.get('code', '').strip()
        categorie = ligne.get('categorie', '').strip()
        try:
            mesure = int(ligne.get('mesure', ''))
        except ValueError:
            erreurs.append((numero, 'mesure', 'entier attendu'))
            continue
        if not code:
            erreurs.append((numero, 'code', 'valeur absente'))
        elif categorie not in CATEGORIES:
            erreurs.append((numero, 'categorie', 'valeur hors domaine'))
        elif not 0 <= mesure <= 100:
            erreurs.append((numero, 'mesure', 'hors intervalle 0..100'))
        else:
            valides.append({'code': code, 'categorie': categorie, 'mesure': mesure})
    return valides, erreurs

Tests

brutes = [{'code':'X','categorie':'A','mesure':'10'}, {'code':'','categorie':'B','mesure':'4'}, {'code':'Y','categorie':'Z','mesure':'8'}, {'code':'Q','categorie':'C','mesure':'abc'}]
valides, erreurs = valider(brutes)
assert valides == [{'code':'X','categorie':'A','mesure':10}]
assert erreurs[0] == (2, 'code', 'valeur absente')
assert (3, 'categorie', 'valeur hors domaine') in erreurs
assert (4, 'mesure', 'entier attendu') in erreurs
assert valider([]) == ([], [])

Trace

  • le premier enregistrement importé porte le rang 1 ; l'en-tête n'est pas un enregistrement
  • mesure '10' devient l'entier 10 avant le contrôle 0..100 ; la ligne rejoint valides
  • une catégorie Z produit une erreur localisée et la ligne n'est ni corrigée ni effacée silencieusement

Clinique de bogue

Indice observéles mesures chaînes '10', '100' et '2' sont classées 10, 100, 2

CauseL'import CSV produit des chaînes. La clé de tri utilise donc l'ordre lexical des caractères au lieu de l'ordre numérique ; le schéma n'a pas encore converti le domaine.

03

Étape du cours · 7 à 15 min

Rechercher : sélectionner des lignes, projeter des champs

Une sélection conserve les lignes qui satisfont un prédicat ; une projection conserve certains champs. La question « codes des lignes de catégorie A dont la mesure est au moins 50 » combine d'abord un and, puis projette code. Remplacer and par or change fortement la population et peut sembler plausible sur un petit jeu de données où les deux conditions coïncident.

Un prédicat est une fonction qui renvoie un booléen pour chaque ligne. lambda ligne: ligne['mesure'] >= 50 est une écriture courte d'une telle fonction. La sélection conserve des références vers les enregistrements retenus, sans copier leurs dictionnaires. La projection construit ici de nouveaux dictionnaires avec les champs demandés ; elle ne supprime pas automatiquement les valeurs répétées.

Les tests contiennent les quatre combinaisons des deux conditions. Une table vide est un résultat possible. Pour agréger, on calcule ensuite une information sur les lignes sélectionnées : len(selection) les compte et sum(ligne['mesure'] for ligne in selection) additionne leurs mesures. Une moyenne demande en plus de traiter le cas vide avant de diviser.

Voir pour comprendre

Sélection : les quatre cas de A et mesure ≥ 50

Les quatre lignes fictives rendent and et or distinguables.

Sélection : les quatre cas de A et mesure ≥ 50
LigneEn A ?Mesure ≥ 50 ?A et seuil ?
AB : A, 60ouiouioui
A0 : A, 10ouinonnon
B9 : B, 90nonouinon
B0 : B, 5nonnonnon

Lis le schéma. Cache la dernière colonne et complète-la. Quelles deux lignes changeraient avec or ?

A0 et B9 font échouer un prédicat écrit avec or par erreur ; répéter seulement AB ne révélerait pas le bogue.

Exemples résolus et erreurs expliquées

D'une question à la sélection, puis à la projection

  1. Question : quels codes correspondent à la catégorie A et à une mesure d'au moins 50 ? Les lignes AB : A, 60 ; A0 : A, 10 ; B9 : B, 90 ; B0 : B, 5 couvrent les quatre cas.
  2. Le prédicat utilise and. Seule AB satisfait les deux conditions ; or conserverait aussi A0 et B9. Le schéma ci-dessus rend ces différences visibles.
  3. La sélection garde l'enregistrement complet d'AB. La projection sur le champ code donne [{'code': 'AB'}] ; extraire directement la valeur du code donne ['AB'].
  4. Projeter categorie sur les deux lignes A conserverait deux dictionnaires {'categorie': 'A'}. Choisir des champs et éliminer les doublons sont deux opérations différentes.

Conclusion. Demande d'abord « quelles lignes ? », puis « quels champs ? ». Compter, additionner ou dédoublonner constituent encore d'autres opérations.

Laboratoire de code

Languepython

Butsélectionner par prédicat, projeter des champs et tester toutes les combinaisons booléennes

Code solution
def selectionner(table, predicat):
    return [ligne for ligne in table if predicat(ligne)]

def projeter(table, champs):
    return [{champ: ligne[champ] for champ in champs} for ligne in table]

def codes_categorie_mesure(table, categorie, seuil):
    selection = selectionner(table, lambda ligne: ligne['categorie'] == categorie and ligne['mesure'] >= seuil)
    return [ligne['code'] for ligne in selection]

def dans_intervalle(table, minimum, maximum):
    assert minimum <= maximum
    return selectionner(table, lambda ligne: minimum <= ligne['mesure'] <= maximum)

Tests

table = [{'code':'AB','categorie':'A','mesure':60}, {'code':'A0','categorie':'A','mesure':10}, {'code':'B9','categorie':'B','mesure':90}, {'code':'B0','categorie':'B','mesure':5}]
assert codes_categorie_mesure(table, 'A', 50) == ['AB']
assert [x['code'] for x in dans_intervalle(table, 10, 60)] == ['AB', 'A0']
assert projeter([table[0]], ['code', 'mesure']) == [{'code':'AB','mesure':60}]
assert selectionner([], lambda ligne: True) == []
assert table[0]['categorie'] == 'A'

Trace

  • AB satisfait catégorie A et mesure≥50 : True and True vaut True
  • A0 satisfait seulement la catégorie ; B9 satisfait seulement le seuil ; les deux sont exclus par and
  • la projection finale lit seulement le code de la ligne AB, sans changer la ligne source

Clinique de bogue

Indice observéA0 et B9 sont retenus alors que le besoin exige simultanément catégorie A et mesure au moins 50

CauseL'opérateur or accepte une ligne dès qu'une seule condition est vraie. Le jeu de tests doit inclure les deux cas où exactement une condition est vraie pour révéler cette confusion.

04

Étape du cours · 6 à 13 min

Doublons et cohérence : détecter n'est pas corriger

Deux lignes identiques peuvent être un doublon d'import, tandis que deux lignes partageant un identifiant mais différant sur un champ signalent un conflit plus grave. La clé de détection doit donc venir du schéma : ici code doit être unique. Supprimer arbitrairement la seconde occurrence ferait perdre l'information sur le désaccord et pourrait conserver la mauvaise version.

Le contrôleur attend des champs déjà typés et construit l'index de la première occurrence. Chaque collision indique les indices des deux enregistrements, à partir de zéro, et s'ils sont identiques. Ce sont des positions dans la table Python, pas des numéros de lignes du fichier. Il vérifie aussi qu'un minimum ne dépasse pas un maximum. Une même ligne peut produire plusieurs anomalies : le nombre d'anomalies n'est pas toujours le nombre de lignes concernées.

Une anomalie localisée n'est pas une note globale de qualité. Pour choisir une correction, il faut connaître producteur, date, méthode de collecte et sens des champs. Dans le projet, on met de côté toutes les versions d'un code en conflit et on les conserve dans le rapport ; on ne devine pas laquelle est vraie.

Exemples résolus et erreurs expliquées

Même code, même contenu ou véritable conflit ?

  1. À l'indice 0, A possède les bornes 1 et 3. À l'indice 2, un autre A possède aussi 1 et 3 : c'est une répétition identique, signalée avec les indices (0, 2).
  2. À l'indice 3, A possède les bornes 0 et 4. Le code est le même mais le contenu diffère : la collision (0, 3) est un conflit, pas une simple copie identique.
  3. B, à l'indice 1, possède minimum = 9 et maximum = 2. Cette incohérence est détectable même si le code B est unique.
  4. Aucune de ces observations ne permet de choisir arbitrairement la bonne valeur. La table de quatre lignes est conservée, avec trois anomalies à examiner.

Conclusion. Distingue unicité d'une clé, égalité des lignes et cohérence entre champs. Un contrôle peut révéler un problème sans connaître sa correction.

Laboratoire de code

Languepython

Butrepérer collisions de clé et incohérences internes sans supprimer de ligne

Code solution
def anomalies(table):
    vues = {}
    resultats = []
    for indice, ligne in enumerate(table):
        code = ligne['code']
        if code in vues:
            premier_indice, premiere_ligne = vues[code]
            resultats.append({'type':'cle_dupliquee', 'code':code, 'lignes':(premier_indice, indice), 'identiques':premiere_ligne == ligne})
        else:
            vues[code] = (indice, ligne)
        if ligne['minimum'] > ligne['maximum']:
            resultats.append({'type':'intervalle_inverse', 'code':code, 'ligne':indice})
    return resultats

def cles_uniques(table):
    return len({ligne['code'] for ligne in table}) == len(table)

Tests

table = [{'code':'A','minimum':1,'maximum':3}, {'code':'B','minimum':9,'maximum':2}, {'code':'A','minimum':1,'maximum':3}, {'code':'A','minimum':0,'maximum':4}]
resultats = anomalies(table)
assert resultats[0] == {'type':'intervalle_inverse','code':'B','ligne':1}
assert resultats[1]['type'] == 'cle_dupliquee' and resultats[1]['identiques']
assert resultats[2]['type'] == 'cle_dupliquee' and not resultats[2]['identiques']
assert not cles_uniques(table)
assert cles_uniques([table[0], table[1]])
assert anomalies([]) == []

Trace

  • A est indexé à la ligne 0 ; B est indexé mais son intervalle 9..2 produit une anomalie
  • le second A retrouve la ligne 0 et la comparaison complète indique un doublon identique
  • le troisième A possède la même clé mais d'autres bornes : la collision est conservée comme conflit, sans suppression

Clinique de bogue

Indice observéune collision de code conserve silencieusement la dernière ligne et efface les versions précédentes

CauseLa compréhension de dictionnaire impose l'unicité par écrasement, mais ne vérifie ni égalité des lignes ni règle de priorité. Elle transforme une anomalie de source en décision cachée.

05

Étape du cours · 7 à 15 min

Trier : choisir une clé et suivre les égalités

Trier une table entière n'a de sens qu'après avoir choisi une clé. Le nom ordonne des chaînes, la mesure ordonne des nombres et un p-uplet de clés exprime un ordre lexicographique multi-critères. Si une colonne numérique reste une chaîne, 100 précède 20. La clé doit donc recevoir une valeur déjà validée ou effectuer une conversion contrôlée, jamais masquer des erreurs par une conversion opportuniste.

sorted renvoie une nouvelle liste de références vers les mêmes enregistrements ; il ne copie pas leurs dictionnaires. table.sort() trie la liste sur place et renvoie None. Les deux tris sont stables : deux lignes de même clé gardent leur ordre relatif. On peut ainsi trier d'abord par critère secondaire, puis par critère principal. reverse=True choisit l'ordre décroissant sans supprimer cette stabilité.

Dans le laboratoire, (categorie, mesure, code) ordonne d'abord par catégorie, puis mesure, puis code croissants. L'autre fonction choisit catégorie croissante et mesure décroissante, sans critère supplémentaire sur le code : une égalité complète garde donc l'ordre antérieur. La correction du bogue montre un tri numérique de chaînes ; convertir la clé de tri ne convertit pas les valeurs stockées dans la table.

Voir pour comprendre

Deux tris, une priorité finale

x : B, 8 ; a : A, 2 ; c : A, 9 ; b : A, 9. On suit seulement les codes pour observer l'ordre.

Deux tris, une priorité finale
ÉtapeCritèreOrdre
Départaucun trix, a, c, b
Premier trimesure décroissantec, b, x, a
Second tricatégorie croissantec, b, a, x

Lis le schéma. Repère c puis b à chaque étape. Pourquoi leur ordre est-il conservé malgré deux tris ?

c et b ont la même mesure et la même catégorie. La stabilité conserve c avant b ; elle n'ajoute pas un tri alphabétique sur leur code.

Exemples résolus et erreurs expliquées

Composer deux tris stables sans inverser les priorités

  1. Dans l'ordre de départ x, a, c, b, les couples (catégorie, mesure) valent respectivement (B, 8), (A, 2), (A, 9), (A, 9). On veut catégorie croissante, puis mesure décroissante.
  2. Le premier tri porte sur le critère secondaire, la mesure décroissante : c, b, x, a. c reste avant b car leurs mesures sont égales et le tri est stable.
  3. Le second tri porte sur la catégorie croissante : c, b, a, x. Les A restent dans l'ordre obtenu à l'étape précédente.
  4. Les mêmes enregistrements sont présents, mais l'ordre a changé. La liste d'entrée conserve x, a, c, b. Un critère supplémentaire code croissant aurait, lui, placé b avant c.

Conclusion. Pour deux tris successifs, commence par le critère secondaire. Une règle de départage par code est différente de la stabilité.

Laboratoire de code

Languepython

Buttrier une copie par clés typées et démontrer la stabilité sur les égalités

Code solution
def trier_par_categorie_mesure(table):
    return sorted(table, key=lambda ligne: (ligne['categorie'], ligne['mesure'], ligne['code']))

def trier_categorie_croissante_mesure_decroissante(table):
    etape = sorted(table, key=lambda ligne: ligne['mesure'], reverse=True)
    return sorted(etape, key=lambda ligne: ligne['categorie'])

def ordre_codes(table):
    return [ligne['code'] for ligne in table]

Tests

source = [{'code':'x','categorie':'B','mesure':8}, {'code':'a','categorie':'A','mesure':2}, {'code':'b','categorie':'A','mesure':9}, {'code':'c','categorie':'A','mesure':9}]
assert ordre_codes(trier_par_categorie_mesure(source)) == ['a', 'b', 'c', 'x']
assert ordre_codes(trier_categorie_croissante_mesure_decroissante(source)) == ['b', 'c', 'a', 'x']
assert ordre_codes(source) == ['x', 'a', 'b', 'c']
egalites = [{'code':'premier','categorie':'A','mesure':1}, {'code':'second','categorie':'A','mesure':1}]
assert ordre_codes(sorted(egalites, key=lambda x: x['mesure'])) == ['premier','second']
assert trier_par_categorie_mesure([]) == []

Trace

  • le tri mesure décroissante place b puis c avant a, tout en conservant b avant c pour l'égalité 9
  • le tri stable par catégorie regroupe ensuite les A sans changer leur ordre interne b,c,a
  • la source conserve x,a,b,c parce que chaque étape utilise sorted et renvoie une nouvelle liste

Clinique de bogue

Indice observéavec des chaînes issues du CSV, l'ordre produit est 10, 100, 2 au lieu de 2, 10, 100

CauseLa comparaison s'applique au type str et suit l'ordre des caractères. Le programme doit valider et convertir le domaine numérique avant le tri plutôt que supposer que le descripteur suffit.

06

Étape du cours · 7 à 15 min

Fusionner : relier des clés, pas des positions

Deux tables peuvent partager une relation sans répéter tous leurs champs. Une table de mesures porte site_id et une table de sites associe chaque identifiant unique à un nom. Fusionner consiste à enrichir chaque mesure par la ligne de référence correspondante. Comparer les positions des lignes serait faux : les tables peuvent être triées différemment ou avoir des effectifs distincts.

L'algorithme attend des identifiants textuels non vides, des sites à clé unique et des mesures ne possédant pas encore le champ site_nom. Il refuse un code de site dupliqué et un champ qui serait écrasé. Une référence connue produit une ligne fusionnée, une référence inconnue rejoint les orphelines. Le bilan vérifie que fusionnées plus orphelines égale le nombre de mesures d'entrée.

Plusieurs mesures peuvent correspondre au même site : cette relation plusieurs-vers-un ne duplique pas une mesure. Si plusieurs lignes de droite pouvaient correspondre à la même clé, une jointure générale pourrait produire davantage de lignes ; notre fonction refuse ce cas. Concaténer deux tables de même schéma ajoute leurs lignes, tandis que fusionner par clé rapproche leurs champs : ces opérations ne sont pas interchangeables.

Voir pour comprendre

Fusionner par clé et conserver les absences

Les sites de référence sont S2 = Sud et S1 = Nord, dans cet ordre.

Fusionner par clé et conserver les absences
MesureClé cherchéeSortie
M1S1fusionnée avec Nord
M2SXorpheline conservée
M3S2fusionnée avec Sud

Lis le schéma. Suis chaque identifiant, sans utiliser la position des sites. Vérifie le bilan : 2 fusionnées + 1 orpheline = 3 entrées.

Une absence de correspondance est une information à signaler, pas une autorisation de fabriquer un nom ou de supprimer la ligne.

Exemples résolus et erreurs expliquées

Faire correspondre trois mesures à deux sites

  1. Les sites sont rangés S2 : Sud puis S1 : Nord. Les mesures sont M1 liée à S1, M2 liée à SX et M3 liée à S2. Les positions des deux tables ne correspondent pas.
  2. L'index retrouve S1 pour M1 : cette mesure reçoit site_nom = Nord. M3 retrouve S2 et reçoit Sud, même si S2 était placé avant S1.
  3. SX est inconnu : M2 est copiée dans les orphelines, sans nom inventé. Deux fusionnées plus une orpheline rendent compte des trois mesures.
  4. Ajouter une deuxième mesure liée à S1 donnerait une troisième fusionnée, toujours une orpheline, soit quatre mesures conservées. Ajouter un deuxième site portant S1 serait au contraire refusé.

Conclusion. La règle de correspondance et l'unicité à droite expliquent le bilan d'effectifs. La conservation ne s'affirme pas sans ces hypothèses.

Laboratoire de code

Languepython

Buteffectuer une jointure contrôlée par identifiant et conserver les références orphelines

Code solution
def index_unique(table, cle):
    index = {}
    for ligne in table:
        valeur = ligne[cle]
        if not isinstance(valeur, str) or not valeur.strip():
            raise ValueError("clé de référence absente")
        if valeur in index:
            raise ValueError("clé de référence dupliquée")
        index[valeur] = ligne
    return index

def fusionner(mesures, sites):
    index_sites = index_unique(sites, 'site_id')
    fusionnees = []
    orphelines = []
    for mesure in mesures:
        if not isinstance(mesure['site_id'], str) or not mesure['site_id'].strip():
            raise ValueError("référence de mesure absente")
        if 'site_nom' in mesure:
            raise ValueError("champ site_nom déjà présent")
        site = index_sites.get(mesure['site_id'])
        if site is None:
            orphelines.append(dict(mesure))
        else:
            fusionnees.append({**mesure, 'site_nom': site['site_nom']})
    assert len(fusionnees) + len(orphelines) == len(mesures)
    return fusionnees, orphelines

Tests

sites = [{'site_id':'S2','site_nom':'Sud'}, {'site_id':'S1','site_nom':'Nord'}]
mesures = [{'mesure_id':'M1','site_id':'S1','valeur':7}, {'mesure_id':'M2','site_id':'SX','valeur':3}, {'mesure_id':'M3','site_id':'S2','valeur':5}]
fusionnees, orphelines = fusionner(mesures, sites)
assert [x['site_nom'] for x in fusionnees] == ['Nord','Sud']
assert orphelines == [{'mesure_id':'M2','site_id':'SX','valeur':3}]
assert len(fusionnees) + len(orphelines) == len(mesures)
assert sites[0] == {'site_id':'S2','site_nom':'Sud'}

Trace

  • l'index devient S2→Sud puis S1→Nord indépendamment de l'ordre des mesures
  • M1 cherche S1 et produit une ligne enrichie ; M2 cherche SX et rejoint les orphelines
  • M3 retrouve S2 ; deux fusionnées plus une orpheline conservent les trois mesures d'entrée

Clinique de bogue

Indice observéune mesure S1 reçoit le site situé à la même position, même si son identifiant est S2, et une différence d'effectif provoque IndexError

CauseLa position n'exprime aucune relation entre les tables. Une fusion doit comparer la clé étrangère de chaque mesure à une clé unique de la table de référence et traiter l'absence. La correction conserve aussi les orphelines et refuse les clés de référence répétées : réparer l'association ne doit pas supprimer les autres contrôles du contrat.

Poursuivre avec l’abonnement

Suis les données jusqu'au bilan final

Les explications, schémas et exemples résolus restent en accès libre.

Avec l’abonnement, essaie les ateliers, vérifie tes choix au quiz et réalise le mini-projet avec ses deux CSV fictifs et sa correction. Reprends ensuite les notions du chapitre avec les cartes de rappel.

10 questions · 12 cartes. Ton bilan et tes révisions restent dans ce navigateur.

Comparer les formules

Vérifier et prolonger

Sources du cours

Édition Maxdecours · Vérifié le .

Spécialité NSI, Première générale · programme du BO spécial du 22 janvier 2019

  1. Programme officiel NSI Premièreindexation, recherche, tri, fusion et domaines · consulté le 2026-09-05
  2. Éduscol, programmes et ressources NSIétat en vigueur et ressources d'accompagnement · consulté le 2026-09-05
  3. Éduscol, Manipulation de tablesCSV, DictReader, sélection, tri typé et multi-critères · consulté le 2026-09-05
  4. Python, module csvsémantique primaire de DictReader, délimiteurs et citations · consulté le 2026-09-05
  5. RFC 4180, Common Format and MIME Type for CSV Filesformat commun documenté et limites de standardisation de CSV · consulté le 2026-09-05
  6. data.gouv.fr, bien documenter un jeu de donnéesprovenance, description, mise à jour et qualité des jeux ouverts · consulté le 2026-09-05
  7. Python, techniques de triclés, directions, stabilité et composition des tris · consulté le 2026-09-05
  8. Python, valeurs flottantes finiesapprofondissement : distinguer mesure finie, NaN et infinis · consulté le 2026-09-05