Traitement de tables matricielles (tableur)

information-icon

Les premières épreuves du bac de français édition 2024 sont pour bientôt ! Consulte les dates du bac de français, notre programme de révision accompagné de toutes nos fiches de révisions pour te préparer au mieux à ton examen 💪

Introduction :

Les données organisées en tables structurent les informations individuelles qu’elles contiennent. Nous allons successivement aborder l’indexation de données, puis la recherche d’informations dans les tables. Nous aborderons ensuite le tri ainsi que la fusion de tables.

Indexation de tables

Avant de pouvoir effectuer des opérations de tri ou des recherches sur des données organisées en tables, nous devons accéder à ces données. Nous allons nous placer dans le cas de figure où les données ont été stockées sous forme tabulaire dans un fichier stocké dans le répertoire courant de notre ordinateur.

Données tabulaires

bannière definition

Définition

Données tabulaires :

Des données sont dites tabulaires, s’il est possible de les organiser en tables, c’est-à-dire en lignes et en colonnes où chaque type d’information est toujours placé au même endroit.

bannière exemple

Exemple

Les logiciels de type tableur sont des outils conçus pour manipuler des données tabulaires.

Le stockage de données tabulaires sous forme de fichiers peut s’effectuer avec différents formats ouverts ou propriétaires. Le format ouvert le plus connu est le format CSV. Un format propriétaire faisant souvent l’objet d’un brevet, nécessite un logiciel spécifique pour être lisible, contrairement à un format ouvert dont les spécifications techniques sont publiques avec un accès illimité aux données.

Format CSV

Le format CSV est au départ un format ouvert de fichier texte où les données sont séparées entre elles par une virgule. L’extension de fichier correspondante est .csv.

Le sigle CSV est l’abréviation de « Comma-Separated Values », soit en français valeurs séparées par des virgules.

bannière exemple

Exemple

Le fichier nommé eleves.csv contient prénom, âge et taille des élèves. Il est structuré ainsi :

Alice,18,165
Paul,17,175
Caroline,17,170
Elsa,19,162

Il est possible de générer un tel fichier avec un simple éditeur de texte. Les logiciels tableurs proposent également la possibilité d’exporter des données à ce format. La première ligne du fichier contient parfois les intitulés des données.

On observe que chaque ligne est agencée de la même manière, avec un retour à la ligne après chaque groupe de données. On remarque aussi que chaque type d’information est toujours au même endroit. Ainsi l’âge est toujours en deuxième position, après le prénom et avant la taille.

  • L’emploi d’un séparateur permet de stocker des données individuelles de longueur variable plus efficacement qu’en imposant une longueur fixe pour chacune de ces données.
bannière attention

Attention

Le choix du séparateur est important : ce caractère ne doit jamais se retrouver dans les données elles-mêmes. Dans le cas contraire, une donnée comportant le caractère choisi comme séparateur sera vue comme deux données élémentaires.

Le format CSV ne répond pas à une spécification formelle très précise. Il en existe de nombreuses variantes, notamment avec le recours au signe « ;  » (point-virgule), qui est moins susceptible d’entrer dans la composition des données élémentaires elles-mêmes, que la virgule ou le point. La virgule est en effet utilisée comme séparateur décimal et le point parfois comme séparateur de milliers. Les données peuvent aussi être séparées par des tabulations.

Acquisition de données tabulaires

Prenons comme base le fichier nommé eleves.csv, fourni en exemple ci-dessus. Nous allons écrire un programme Python qui va lire le contenu de ce fichier et nous permettre ensuite de manipuler les données qu'il contient.

L’acquisition de données tabulaires est techniquement possible, sans recourir à aucun module spécialisé. Toutefois, cela nécessiterait de programmer soi-même la lecture du fichier et le traitement de chaque ligne, dont, notamment, le découpage des contenus sur la base du séparateur et la gestion des caractères d’échappement.
Les caractères d’échappement sont des caractères faisant partie de la syntaxe de beaucoup de langages de programmation et permettent de déclencher une interprétation alternative du ou des caractères qui le suivent.

La bibliothèque standard comporte un module dédié qui facilite grandement l’acquisition de données tabulaires. Ce module s’appelle csv.

  • Il se mobilise par un simple import.

import csv

L’emploi du module csv, pour l’acquisition de données tabulaires contenues dans un fichier de notre répertoire courant, s’effectue ensuite de la manière suivante :

with open('eleves.csv') as fichier_csv:

lecteur_csv = csv.reader(fichier_csv)

for ligne in lecteur_csv:

print(ligne)

fichier_csv, lecteur_csv et ligne sont des noms de variables fixés par le programmeur.

Ce code produit l’affichage suivant :

['Alice', '18', '165']
['Paul', '17', '175']
['Caroline', '17', '170']
['Elsa', '19', '162']

  • On observe que chaque ligne lue par le lecteur fourni par le module csv (csv.reader) est une liste. Cette liste est composée de chacun des éléments individuels présents sur chaque ligne du fichier.

Quand les données sont séparées par des tabulations, le format est parfois appelé TSV (pour Tabulation-Separated Values). L’extension correspondante est .tsv. Mais en pratique, il arrive souvent que des fichiers dont les données sont séparées par des tabulations portent l’extension .csv. ou l’extension .txt. Le lecteur du module csv est capable de les importer correctement, à condition de lui préciser le délimiteur employé sous la forme \t qui représente la tabulation.

bannière exemple

Exemple

with open('tsv.txt') as fichier_tsv:

lecteur_tsv = csv.reader(fichier_tsv, delimiter='\t')

for ligne in lecteur_tsv:

print(ligne)

Stockage en mémoire de données tabulaires

Les données lues par le module csv sont fournies sous forme de listes. Notre code précédent s’est contenté d’afficher les données en provenance du fichier source. Mais il serait plus commode de pouvoir y accéder à loisir pour effectuer des recherches ou des traitements.

Modifions donc notre code initial pour stocker l’ensemble des données dans une liste.

eleves = []

with open('eleves.csv') as fichier_csv:

lecteur_csv = csv.reader(fichier_csv)

for ligne in lecteur_csv:

eleves.append(ligne)

Vérifions le contenu de la variable eleves.

print(eleves)

Produit l’affichage suivant :

[['Alice', '18', '165'], ['Paul', '17', '175'], ['Caroline', '17', '170'], ['Elsa', '19', '162']]

Notre liste contient bien des sous-listes qui correspondent à chaque ligne du fichier .csv. Nous pouvons donc accéder aux données individuelles avec la notation indexée, en utilisant un double index : le premier désigne la ligne souhaitée, le second l’élément dans la ligne.

bannière exemple

Exemple

Nous pouvons ainsi afficher l’âge de Caroline de la manière suivante :

print(eleves[2][1])

# affiche 17

Au lieu de stocker les données lues depuis le fichier .csv en listes, nous aurions pu choisir de les stocker dans des tuples.

eleves = []

with open('eleves.txt') as fichier_csv:

lecteur_csv = csv.reader(fichier_csv)

for ligne in lecteur_csv:

eleves.append(tuple(ligne))

Vérifions le contenu de la variable eleves avec le recours aux tuples.

print(eleves)

Produit l’affichage suivant :

[('Alice', '18', '165'), ('Paul', '17', '175'), ('Caroline', '17', '170'), ('Elsa', '19', '162')]

Le choix de stocker les données en listes ou en tuples dépend des manipulations que l’on souhaite effectuer ensuite sur les données. Le caractère non mutable des tuples permet uniquement leur lecture. Nous pourrions toutefois ajouter ou supprimer des tuples au sein de la liste qui les contient. Les listes étant mutables, le stockage en liste de listes, autorise en revanche une éventuelle mise à jour des données individuelles.

Nous avons appris à intégrer des données provenant de fichiers externes. Nous allons maintenant aborder les possibilités de recherche au sein de ces données.

Recherche dans une table

Pour découvrir les possibilités et les modalités de recherche au sein de données tabulaires, nous allons à nouveau nous appuyer sur un exemple.
Cette fois nous disposons d’une liste d’adhérents à une association d’initiation aux sciences numériques. Le prénom et le nom de chaque adhérent sont inclus, dans cet ordre, dans un tuple. L’ensemble des tuples est stocké dans une liste.

adherents = [
('Alice', 'Martin'),
('Richard', 'Durand'),
('Sarah', 'Martin'),
('Jean Paul', 'Dupont'),
('Martin', 'Jean'),
('Lisa', 'Dupond'),
('Jean', 'Petit'),
('Anna Lisa', 'Thomas'),
('Paul', 'Dubois'),
('Alice', 'Richard')
]

Recherche simple

Nous souhaitons dans un premier temps vérifier si une personne prénommée « Lisa » figure parmi les adhérents.

for adherent in adherents:

prenom, nom = adherent

if prenom == 'Lisa':

print(prenom, nom)

Ce code produit l’affichage suivant :

Lisa Dupond

Ce même algorithme fonctionne aussi quand plusieurs personnes portent le prénom recherché.

for adherent in adherents:

prenom, nom = adherent

if prenom == 'Alice':

print(prenom, nom)

Ce code produit l’affichage suivant :

Alice Martin
Alice Richard

Dans le cas présent, le même prénom est porté par plusieurs personnes distinctes. Mais il arrive parfois que des données soient présentes à plusieurs reprises dans un ensemble de données, par exemple à la suite d’une erreur de double-saisie.

  • Cette redondance s’appelle un doublon.
bannière definition

Définition

Doublon :

Information redondante dans un ensemble de données.

Nous avons jusqu’à présent recherché des chaînes de caractères exactes, mais nous pouvons modifier légèrement notre recherche pour obtenir la liste de tous les prénoms comportant celui de « Lisa ».

for adherent in adherents:

prenom, nom = adherent

if ‘Lisa’ in prenom:

print(prenom, nom)

Ce code produit l’affichage suivant :

Lisa Dupond
Anna Lisa Thomas

Nous allons maintenant effectuer des recherches un peu plus avancées.

Recherche avec connecteurs propositionnels

Il est possible d’affiner nos recherches en combinant plusieurs critères grâce à des connecteurs propositionnels.

bannière definition

Définition

Connecteur logique propositionnel :

Un connecteur logique propositionnel relie entre elles des propositions simples.

Les connecteurs les plus courants sont « et » et « ou », exprimés par and et or dans le langage Python.

Effectuons d’abord une recherche simple sur les personnes dont le nom de famille est « Martin » pour connaitre le nombre de résultats correspondants.

for adherent in adherents:

prenom, nom = adherent

if nom == 'Martin':

print(prenom, nom)

Ce code produit l’affichage suivant :

Alice Martin
Sarah Martin

Nous pouvons obtenir seulement la fiche de « Sarah Martin » en combinant nos critères de recherches avec le connecteur logique « et ».

for adherent in adherents:

prenom, nom = adherent

if nom == 'Martin' and prenom == 'Sarah':

print(prenom, nom)

Ce code produit l’affichage suivant :

Sarah Martin

Nous pouvons aussi élargir nos critères de recherche en employant le connecteur logique « ou ».

for adherent in adherents:

prenom, nom = adherent

if nom == 'Martin' or prenom == 'Sarah':

print(prenom, nom)

Ce code produit l’affichage suivant :

Alice Martin
Sarah Martin

  • Le connecteur ne porte pas nécessairement sur la même variable.
bannière exemple

Exemple

Richard est un prénom courant. C’est aussi un nom de famille très fréquent. Nous pouvons rechercher un adhérent dont le prénom ou le nom est « Richard » avec le code suivant :

for adherent in adherents:

prenom, nom = adherent

if prenom == 'Richard' or nom == 'Richard':

print(prenom, nom)

Ce code produit l’affichage suivant :

Richard Durand
Alice Richard

Recherche avec opérateur d’appartenance

L’emploi des connecteurs peut s’avérer fastidieux quand les éléments recherchés sont nombreux.

bannière exemple

Exemple

Nous voulons rechercher tous les adhérents nommés « Alice », « Lisa », « Sarah », ou « Paul ».
Au lieu d’écrire ceci :

if prenom == 'Alice' or prenom == 'Lisa' or prenom == 'Sarah' or prenom == 'Paul':

Python nous permet d’écrire ceci pour une recherche équivalente :

if prenom in ['Alice', 'Lisa', 'Sarah', 'Paul']:

Intégrons cette formulation dans notre algorithme de recherche :

for adherent in adherents:

prenom, nom = adherent

if prenom in ['Alice', 'Lisa', 'Sarah', 'Paul']:

print(prenom, nom)

Nous obtenons les résultats suivants :

Alice Martin
Sarah Martin
Lisa Dupond
Paul Dubois
Alice Richard

Dans cette construction l’opérateur d’appartenance in remplace avantageusement l’enchaînement des connecteurs logiques or.

Nous pouvons également employer l’opérateur d’appartenance avec une négation not pour obtenir facilement tous les prénoms autres que ceux cités.

Modifions notre instruction conditionnelle :

if prenom not in ['Alice', 'Lisa', 'Sarah', 'Paul']:

Nous obtenons l’affichage suivant :

Richard Durand
Jean Paul Dupont
Martin Jean
Jean Petit
Anna Lisa Thomas

Sans l’opérateur d’appartenance, il nous faudrait formuler la condition ainsi :

if prenom != 'Alice' and prenom != 'Lisa' and prenom != 'Sarah' and prenom != 'Paul':

ou encore ainsi :

if not (prenom == 'Alice' or prenom == 'Lisa' or prenom == 'Sarah' or prenom == 'Paul'):

  • L’opérateur d’appartenance peut se révéler utile pour conserver un code lisible avec certaines recherches multi-critères.

La fiabilité de nos recherches repose sur la bonne construction de nos propositions et leur bonne articulation logique le cas échéant. Mais elle dépend aussi de la qualité et la cohérence des données.

Cohérence et normalisation des données

La cohérence des données est importante car elle conditionne la qualité des recherches et des traitements qui peuvent être effectués ensuite.

En général, on s’attache à valider la cohérence des données dès la saisie.

bannière exemple

Exemple

  • Un formulaire de saisie de données peut imposer l’entrée de valeurs strictement numériques, pour un âge ou pour un code postal français. Certaines rubriques peuvent être rendues obligatoires selon les besoins pour valider la saisie des données, comme la saisie d’un nom et d’un prénom, ou encore d’une adresse de courrier électronique.
  • Les tests de cohérence peuvent également limiter les valeurs permises, avec par exemple un âge positif et ne pouvant dépasser la longévité humaine, ou encore un nombre composé impérativement de cinq chiffres pour un code postal français.

En l’absence de garanties sur la cohérence à la source des données acquises, il sera nécessaire d’inclure des vérifications ou des transformations préalables pour pouvoir travailler sur les données.

bannière exemple

Exemple

Il arrive fréquemment qu’un fichier contienne des noms de personnes ou de lieux, saisis sans cohérence entre minuscules et majuscules. « Martin », « martin » et « MARTIN » sont peut-être la même personne pour un humain, mais pour un ordinateur ce sont trois chaînes de caractères distinctes.

Dans ce cas, on choisira d’effectuer une normalisation préalable des données, par exemple en convertissant au choix toutes les chaînes de caractères en majuscules, ou en minuscules, ou avec la première lettre en majuscule et le reste en minuscules.

Cette étape préalable permet d’effectuer des recherches simples où il suffit de formater l’élément recherché à l’identique de la normalisation appliquée aux données.

Sans cette normalisation, les expressions logiques de recherche seraient laborieuses à implémenter.
Dans l’exemple ci-dessus, il faudrait écrire :

if prenom == 'Martin' or prenom == 'martin' or prenom == 'MARTIN':

print(prenom, nom)

C’est non seulement laborieux, mais il serait difficile de prévoir tous les cas de figure. Ainsi, le prénom écrit « marTin » ou « martiN » ne serait pas trouvé.

De même, une entrée sous la forme « Martin  » (le prénom suivi d’un espace) ne serait pas trouvée. C’est pourquoi les éventuels caractères de type espace présents en début ou en fin de chaîne sont généralement supprimés systématiquement dans le cadre d’une normalisation. Des opérations complémentaires spécifiques peuvent être appliquées en fonction du profil des données d’origine.

Nous allons maintenant aborder les opérations de tri sur les données tabulaires ainsi que les fusions de données tabulaires d’ensembles distincts.

Tri et fusion de tables

Pour cette partie nous reprenons les données sur les élèves, composées de leur nom, leur âge et leur taille.

eleves = [['Alice', '18', '165'], ['Paul', '17', '175'], ['Caroline', '17', '170'], ['Elsa', '19', '162']]

Tri

Les fonctionnalités de tri permettent de réorganiser des ensembles de données tabulaires selon différents critères, à des fins de traitements ou de présentation de ces données.

Python propose deux solutions de tri :

  • Le tri sur place avec la méthode sort() qui effectue un tri en place et ne retourne rien, c’est-à-dire modifie directement la liste passée en argument ;
  • Le tri avec la fonction sorted() qui retourne une nouvelle liste triée à partir de la liste passée en argument.

Pour nos besoins nous allons utiliser sorted() qui ne modifie pas la liste de données passée en argument. Nous pourrons ainsi constater l’effet de tris distincts effectués sur une base inchangée, celle de la liste nommée « eleves ».

Nous afficherons chaque fois la liste retournée par la fonction, que nous pourrions aussi stocker dans une nouvelle variable si nécessaire.

Dans son cas d’usage le plus simple, la fonction sorted() prend en argument la liste qu’on souhaite trier.

print(sorted(eleves))

# affiche [['Alice', '18', '165'], ['Caroline', '17', '170'], ['Elsa', '19', '162'], ['Paul', '17', '175']]

Nous obtenons une liste de listes triées par ordre croissant selon le premier champ de donnée, puis le second et le troisième, de manière ascendante. Le tri se fait sur les éléments successifs de chaque sous-liste, de la même manière qu’un dictionnaire ordonne les mots sur la base des lettres successives composant le mot.

Un paramètre optionnel de la fonction sorted() permet de trier facilement la liste en ordre inverse.

print(sorted(eleves, reverse=True))

# affiche [['Paul', '17', '175'], ['Elsa', '19', '162'], ['Caroline', '17', '170'], ['Alice', '18', '165']]

La fonction sorted() dispose aussi d’un paramètre key qui permet de spécifier une clé de tri pour personnaliser l’ordre dans lequel les données sont triées.

  • La clé de tri est une fonction quelconque, native ou créée par l’utilisateur.

Nous allons créer une fonction qui retourne le second élément d’une liste donnée, et utiliser cette fonction comme critère de tri de notre liste.

def tri_age(liste_quelconque):

return liste_quelconque

print(sorted(eleves, key=tri_age))

# affiche [['Paul', '17', '175'], ['Caroline', '17', '170'], ['Alice', '18', '165'], ['Elsa', '19', '162']]

Le tri de la liste a bien été effectué par ordre croissant de la seconde donnée, c’est-à-dire à l’âge des élèves.

Vous noterez l’absence de parenthèse après tri_age car il ne s’agit pas d’un appel à la fonction. C’est la fonction tri_age elle-même qui est passée comme argument au paramètre key.

Le tri a porté sur l’âge, mais les tailles pour un même âge ne sont pas nécessairement dans l’ordre. Corrigeons cela avec une nouvelle fonction de tri personnalisée prenant en compte les deux critères.

def tri_age_puis_taille(liste_quelconque):

return liste_quelconque[1], liste_quelconque[2]

print(sorted(eleves, key=tri_age_puis_taille))

# affiche [['Caroline', '17', '170'], ['Paul', '17', '175'], ['Alice', ' 18', '165'], ['Elsa', '19', '162']]

Le tri a bien porté d’abord sur l’âge, puis sur la taille.

  • Voyons maintenant comment nous pouvons trier la liste par ordre décroissant de longueur des prénoms. On définit une fonction qui retourne la longueur du premier élément à partir d’une liste.

def tri_longueur_prenom(liste_quelconque):

return len(liste_quelconque[0])

Cette fonction devient notre nouveau critère de tri personnalisé :

print(sorted(eleves, key=tri_longueur_prenom))

# affiche [['Paul', '17', '175'], ['Elsa', '19', '162'], ['Alice', '18', '165'], ['Caroline', '17', '170']]

Le tri peut donc être grandement personnalisé en fonction des besoins, ceci grâce à la fonction de tri que l’on peut lui associer. Abordons maintenant la fusion de données tabulaires.

Fusion de tables

Des données réparties dans plusieurs ensembles distincts peuvent être réunies pour former un ensemble complet et unique.

Nous disposons d’une liste d’élèves dont nous connaissons les prénoms, les âges et les tailles et nous souhaitons rapprocher ces données d’une autre liste qui contient leurs noms complets.

noms_complets = [['Alice', 'Martin'], ['Paul', 'Dubois'], ['Caroline', 'Dupont'], ['Elsa', 'Durand']]

Pour pouvoir rapprocher ces deux listes et les fusionner, nous devons déterminer un critère sur lequel les réunir. Dans le cas présent nous allons utiliser le prénom. Nous recherchons dans la seconde liste les informations associées au prénom trouvé dans la première liste pour pouvoir y associer les données correspondantes. Nous stockons l’ensemble dans une nouvelle liste.

fusion = []

for eleve in eleves:

prenom, age, taille = eleve

for nomcomplet in nomscomplets:

if nom_complet[0] == prenom:

nomfamille = nomcomplet

fusion.append([prenom, nom_famille, age, taille])

print(fusion)

On obtient l’affichage suivant :

[['Alice', 'Martin', '18', '165'], ['Paul', 'Dubois', '17', '175'], ['Caroline', 'Dupont', '17', '170'], ['Elsa', 'Durand', '19', '162']]

Selon la manière dont les informations sont agencées et le type d’information à réunir, certaines difficultés peuvent se présenter.
La présence de doublons ou l’absence d’entrée en correspondance sur le critère de fusion seraient de nature à poser un problème. Cela se traduirait dans l’exemple précédent par un prénom qui n’est pas commun aux deux listes ou, à l’inverse, un prénom qui se trouve dupliqué dans l’unes des deux listes.
C’est pourquoi, il faut penser à traiter les problèmes de doublons et bien prendre en compte le domaine de valeurs de l’élément servant au rapprochement, c’est-à-dire l’ensemble des valeurs possibles pour cet élément.

bannière astuce

Astuce

Quand des valeurs sont manquantes dans le cadre d’une fusion, il est préférable de le mentionner explicitement. Différentes codifications peuvent être employées pour représenter des données manquantes :

  • L’objet Python None exprime l’absence de valeur ;
  • L’identifiant « NA » pour « Not Available » (« Non Disponible » en français) est fréquemment employé par différents logiciels et peut figurer dans un fichier de type .csv.
bannière rappel

Rappel

La fusion de données et le traitement de données nominatives sont strictement encadrés juridiquement. On se référera aux dispositions du règlement européen sur la protection des données (R.G.P.D.), consultable sur le site de la Commission Nationale de l’Informatique et des Libertés.

Conclusion :

La structuration des informations en données tabulaires permet de réaliser un certain nombre de traitements. Nous avons d’abord étudié leur indexation puis les différentes recherches que l’on peut y pratiquer, selon des critères simples ou articulés en logique propositionnelle.
Nous avons ensuite découvert différentes manières de trier ces données, ainsi que la possibilité de fusionner des données tabulaires provenant d’ensembles distincts.