Traitement de données en tables

Les élèves doivent savoir
  • Importer une table depuis un fichier texte tabulé ou un fichier CSV.
  • Rechercher les lignes d’une table vérifiant des critères exprimés en logique propositionnelle.
  • Trier une table suivant une colonne.
  • Construire une nouvelle table en combinant les données de deux tables.

18.    Indexation de tables

Rappels sur les données

Les « données » sont à la base de l’informatique, car toute l’informatique est justement le traitement de ces données afin d’en extraire des informations utiles ou de les transformer, rassembler, d’en déduire un raisonnement ou une prédiction.

En informatique, tout est donné, depuis les 0 et les 1 qui décrivent l’état des transistors dans un circuit électronique, jusqu’à une vidéo, en passant par les photos, les adresses, un relevé de température ou l’âge d’une personne. Les données sont souvent rassemblées pour caractériser un objet comme l’adresse d’une personne (composée du numéro de rue, du nom de la rue, du code postal, de la ville et du pays par exemple). Lorsque les données sont ainsi rassemblées pour décrire quelque chose avec plusieurs informations, on parle de données structurées (voir plus loin).

La conservation des données est un enjeu qui existe depuis l’aube des civilisations, bien avant l’informatique, car on peut considérer que les textes de loi, les comptes et la mémoire des évènements historiques sont autant de données qu’il a fallu faire passer de génération en génération (tablettes d’argile, parchemins, livres manuscrits, imprimerie…).

Lors de l’avènement du traitement informatique des données, celles-ci ont d’abord été conservées sur des cartes perforées avec un système de lecture optique, avant de passer sur des supports magnétiques (bandes, disques durs), puis à nouveau optique (CD, DVD, Bluray) avant de passer à des stockages dans des cellules mémoires (type transistors non volatil) pour les systèmes actuellement utilisés dans les systèmes informatiques (cartes mémoires sd, ssd…). Ces stockages de données sont de plus en plus rapides pour la lecture et l’écriture et leurs capacités augmentent très vite à mesure que toutes les informations analogiques de notre vie sont numérisées pour un traitement informatique de plus en plus massif.

Formats de stockage des données

Même si, au final, toutes les données numérisées vont être conservées en binaire (0 et 1) qui correspond au format traité par les ordinateurs, pour qu’elles soient faciles à traiter, elles vont être organisées en fonction de leurs types.

Pour des données qui doivent pouvoir être triées, recoupées et traitées ultérieurement pour en faire des rapports, des analyses, des graphiques… on utilise principalement des tableaux ou des listes. Ceux-ci peuvent être inscrits dans des fichiers textes lisibles avec un indicateur de séparation entre champs de données. Le plus courant est le séparateur par virgule (comma en anglais) : comma separated values (csv). Ce format convient bien pour des petites collections de données. Pour de plus grosses quantités, on utilisera des ensembles de tables, reliées entre elles par des règles et constituants des « bases de données » (database).

Quand les données sont plus spécifiques, on utilise de nombreux autres formats de stockage identifiés par leurs extensions : PNG, JPEG, HEIF… pour des images ; MP3, WAV, M4A… pour des sons ; MP4, AVI, M4V… pour des vidéos…

À cette extension est associé une structure logique des données et un en-tête de fichier qui permettra à un programme d’avoir des détails sur les informations conservées dans le fichier. Par exemple, un fichier vidéo enregistré sur un téléphone portable contiendra les informations suivantes :

Les colonnes de gauche contiennent le codage du fichier en hexadécimal (comptage en base 16 très utilisée en informatique) et à droite sa traduction en ASCII (american standard code for information interchange) qui permet de lire ce contenu « en clair ». On constate que le début du fichier contient des informations sur le type de codage utilisé pour la vidéo (H.264/MPEG-A AVC Codec) suivie de nombreuses informations nécessaires au décodage de cette vidéo.

Toutes ces informations sont nécessaires pour que le fichier puisse être ouvert et exploité par d’autres ordinateurs. On parle alors d’interopérabilité.

Données structurées et traitement

On parle de données structurées quand un ensemble de données donne des clefs d’accès simples aux données qu’il contient. C’est typiquement le cas d’une base de données qui contient des tableaux et des clefs d’indexation permettant d’identifier rapidement chaque ensemble de données (comme un numéro d’article ou un numéro de client), mais aussi d’un fichier csv qui contient des identificateurs de colonne permettant un tri rapide.

Exemple de la structure d’une base de données d’association (source Wikipédia).
Exemple de la structure d’une base de données d’association (source Wikipédia).

Dans l’image ci-dessus, on voit que chaque information sur une personne de la base est identifiée par un descripteur qui décrit ce qu’elle doit contenir (firstname – prénom ; lastname – nom ; date-of-birth : date de naissance….) et chacun de ces champs est défini par un type précis (varchar(50) : 50 caractères libres ; Date…). Pour chaque utilisateur il y aura une ligne dans le tableau « person » et cette ligne contiendra les valeurs saisies lors de l’enregistrement d’un nouveau membre de l’association.

Récupérer des données structurées

La protection des données personnelles fait que de nombreuses informations précises ne sont heureusement pas accessibles librement sur Internet. Il existe toutefois des sites d’information ouverts regroupant des bases de données à usage publiques : les Open Data.

Le site https://data.education.gouv.fr permet par exemple d’accéder à de très nombreuses informations générales sur le fonctionnement de l’éducation nationale en France (budget, élèves, réussites scolaires…)

Recherches, tri et calculs dans des tables de données

En choisissant un ensemble de données il est possible d’y effectuer de recherches spécifiques, de mettre en place un filtre (par année par exemple), puis de cliquer sur les colonnes du tableau pour effectuer un tri (croissant ou décroissant).

Il est également possible de récupérer les données au format csv afin de les utiliser pour effectuer des calculs ou des analyses graphiques en créant des représentations à partir des données.

Lire et écrire dans un fichier

Prenons un exemple de fichier open data disponible sur les sites gouvernementaux français : la population et la superficie des départements français par région en 2019. Le contenu du fichier csv brut ressemble à ceci :

fichier csv sur les départements de France

On constate, comme c’est assez souvent le cas (et c’est bien pratique) que ce fichier contient un identifiant des colonnes du tableau, que l’on appelle aussi les descripteurs (ou en-tête), sur sa première ligne. Chaque donnée est ensuite séparée par un point-virgule.

Ce type de fichier peut facilement être traité par un tableur pour trier, extraire ou regrouper des données, mais ici nous allons voir comment traiter ces données avec Python.

La première étape sera d’ouvrir le fichier avec un programme Python afin de pouvoir accéder à son contenu. Pour cela on utilise la commande « open » qui doit être suivie du nom du fichier à ouvrir et de la méthode d’ouverture : « r » pour lecture (read), « w » pour écriture (write), par exemple.

La lecture du contenu du fichier consiste à copier ce contenu dans une variable (tableau ou texte brut par exemple) à l’aide de la méthode « read() » :

fichier = open("population_superficie_departements.csv","r")
contenu = fichier.read()

De la même façon, il sera possible d’écrire dans un fichier avec la commande « write(contenu) » où « contenu » correspond à ce qu’il faut mettre dans le fichier.

Pour revenir à la lecture du contenu d’un fichier, il est important de comprendre qu’elle se fait de façon séquentielle : lorsque vous lisez le contenu, la position du curseur de lecture se place à la fin de ce que vous avez lu. Cela ne change rien si cous lisez tout le contenu d’un coup avec la méthode « read() », mais vous pouvez également lire le contenu du fichier ligne par ligne avec la commande « readline() ». Dans ce cas, à chaque fois que vous appelez « readline() », vous lisez la ligne en cours et placez le curseur de lecture à la ligne suivante.

Importer un fichier CSV dans une table

Il y a plusieurs manières d’accéder au contenu d’un fichier csv dans Python. Commençons avec l’utilisation des commandes de bases de Python :

def ImportCSV(nom:str):
   """
   Permet d'importer un fichier csv et de le découper en tables
   :param nom: str, chemin d'accès du fichier csv
   :return: entete_csv : list, table des en-têtes du fichier csv
   table_csv : list, contenu du fichier en table de tables
   """
   fichier = open(nom,"r") #accès au fichier csv
   entete_csv = fichier.readline() #lecture de la première ligne : en-têtes
   liste_entete = entete_csv.rstrip().split(";") #conversion en liste
   ligne_csv = fichier.readlines() #crée une liste de toutes les lignes
   table_csv = [] #crée une table vide pour le résultat formaté
   for ligne in ligne_csv: #crée une sous-table pour chaque ligne
      data = ligne.rstrip().split(";")
      table_csv.append(data)
   fichier.close() #fermeture de l'accès au fichier
   return liste_entete, table_csv

La fonction ci-dessus ouvre un fichier csv dont on lui donne le chemin d’accès (nom) et renvoi deux tables : l’une avec les en-têtes et l’autre avec le contenu brut du fichier csv.

La méthode « rstrip() » est ici utilisée pour supprimer le caractère de fin de ligne (\n). La méthode « split(« ; ») » convertit la suite de caractères de la ligne en une table en utilisant le point-virgule pour délimiter les éléments de cette table. On obtient donc une table (table_csv) contenant des tables pour chaque ligne du fichier, dont les éléments sont les colonnes du fichier csv d’origine.

Il est également possible d’utiliser la bibliothèque « csv » de python qui possède des outils spécialement adaptés au traitement des fichiers de ce type. Le code devient alors plus simple (mais est destiné uniquement aux fichiers « csv » !!) :

import csv
with open("population_superficie_departements.csv") as fichier_csv :
   table_csv = list(csv.reader(fichier_csv, delimiter=';'))

Les données sont alors placées dans une table de tables dont la première « ligne » contient les en-têtes :

[['Code région', 'Nom de la région', 'Code département', 'Nom du département', "Nombre d'arrondissements", 'Nombre de cantons', 'Nombre de communes', 'Population totale', 'Superficie (en km²)'], ['84', 'Auvergne-Rhône-Alpes', '1', 'Ain', '4', '23', '407', '655171', '5762'], ['84', 'Auvergne-Rhône-Alpes', '3', 'Allier', '3', '19', '317', '349336', '7340'], ['84', 'Auvergne-Rhône-Alpes', '7', 'Ardèche', '3', '17', '339', '334591', '5529'], ['84', 'Auvergne-Rhône-Alpes', '15', 'Cantal', '3', '15', '247', '151615', '5726'], …

Il est également possible de mettre le contenu du fichier « csv » dans une liste de dictionnaire, ce qu’on appelle alors un dictionnaire ordonné, avec la commande « DictReader » à la place de « reader » :

with open("population_superficie_departements.csv") as fichier_csv:
   dict_csv = list(csv.DictReader(fichier_csv, delimiter=';'))

Le résultat sera alors le suivant :

[OrderedDict([('Code région', '84'), ('Nom de la région', 'Auvergne-Rhône-Alpes'), ('Code département', '1'), ('Nom du département', 'Ain'), ("Nombre d'arrondissements", '4'), ('Nombre de cantons', '23'), ('Nombre de communes', '407'), ('Population totale', '655171'), ('Superficie (en km²)', '5762')]), OrderedDict([('Code région', '84'), ('Nom de la région', 'Auvergne-Rhône-Alpes'), ('Code département', '3'), ('Nom du département', 'Allier'), ("Nombre d'arrondissements", '3'), ('Nombre de cantons', '19'), ('Nombre de communes', '317'), ('Population totale', '349336'), ('Superficie (en km²)', '7340')]), OrderedDict([('Code région', '84'), ('Nom de la région', 'Auvergne-Rhône-Alpes'), ('Code département', '7'), ('Nom du département', 'Ardèche'), ("Nombre d'arrondissements", '3'), ('Nombre de cantons', '17'), ('Nombre de communes', '339'), ('Population totale', '334591'), ('Superficie (en km²)', '5529')]), ….

Ce dictionnaire ordonné pourrait être transformé en liste de dictionnaires (plus lisible) à l’aide de la commande « dict(ligne) ». On obtiendra alors un tableau contenant des dictionnaires pour chaque ligne du fichier csv et la possibilité d’y effectuer des recherches par clé :

departements = []
with open("population_superficie_departements.csv") as fichier_csv:
   dict_csv = list(csv.DictReader(fichier_csv, delimiter=';'))
   for ligne in dict_csv:
      departements.append(dict(ligne))
[{'Code région': '84', 'Nom de la région': 'Auvergne-Rhône-Alpes', 'Code département': '1', 'Nom du département': 'Ain', "Nombre d'arrondissements": '4', 'Nombre de cantons': '23', 'Nombre de communes': '407', 'Population totale': '655171', 'Superficie (en km²)': '5762'}, {'Code région': '84', 'Nom de la région': 'Auvergne-Rhône-Alpes', 'Code département': '3', 'Nom du département': 'Allier', "Nombre d'arrondissements": '3', 'Nombre de cantons': '19', 'Nombre de communes': '317', 'Population totale': '349336', 'Superficie (en km²)': '7340'}, {'Code région': '84', 'Nom de la région': 'Auvergne-Rhône-Alpes', 'Code département': '7', 'Nom du département': 'Ardèche', "Nombre d'arrondissements": '3', 'Nombre de cantons': '17', 'Nombre de communes': '339', 'Population totale': '334591', 'Superficie (en km²)': '5529'}, …

Attention, le paramètre « delimiter » de la bibliothèque « csv » est à préciser obligatoirement si votre séparateur n’est pas une virgule « , ».

19.    Recherche dans une table

Méthode de recherche dans une table

La méthode la plus commune pour effectuer une recherche dans une table et stocker cette recherche, sera de créer une table de résultat par compréhension et d’afficher (ou de réutiliser) cette table.

Prenons un exemple concret : on cherche à savoir quels sont les départements qui ont plus de deux millions d’habitants en France.

Après avoir importé le fichier csv des données par départements dans un tableau de dictionnaires, on effectue une extraction de données par compréhension en faisant un test en logique propositionnelle sur le champ « Population totale » :

departements = []
with open("population_superficie_departements.csv") as fichier_csv:
dict_csv = list(csv.DictReader(fichier_csv, delimiter=';'))
for ligne in dict_csv:
departements.append(dict(ligne))

extraction = [d["Nom du département"]for d in departements if int(d["Population totale"])>2000000]
print(extraction)
['Nord', 'Paris', 'Bouches-du-Rhône']

Si les données avaient été importées dans une simple table, on aurait pu y accéder par leur indice dans la table, mais l’utilisation d’une table de dictionnaires rend le code beaucoup plus lisible.

Recherche de doublons

Si un élément est présent plusieurs fois dans une table, on dit qu’il y a des doublons.

Pour éliminer les doublons, on peut créer une nouvelle table à partir de la table d’origine en copiant les éléments de la première table un à un dans la seconde, mais en vérifiant que l’élément n’est pas déjà présent dans la seconde table avant de l’ajouter.

Voici un exemple avec une table simple :

table1=[1,5,10,2,3,5,3,5,10,2,1,3,5,7,6,1,9]
 table2=[]
 for valeur in table1:
 if valeur not in table2:
 table2.append(valeur)
 table2.sort()
 print(table2)
[1, 2, 3, 5, 6, 7, 9, 10]

Dans le code précédent, on a utilisé la méthode « sort() » pour trier les éléments de la table avant l’affichage. La vérification de la présence d’un doublon se fait avec l’expression « if valeur not in table2 ».

Test de cohérence

Dans un monde idéal, toutes les données que l’on reçoit par un traitement informatique sont formatées de la bonne manière, dans un type correct et en respectant des consignes lors de la collecte afin que toutes les opérations ultérieures sur ces données ne génèrent pas d’erreurs.

Malheureusement, il est fréquent qu’il y ait des erreurs de saisies : nombres saisis à la place de lettre (et inversement), saisie trop longue (ou pas assez), adresse mail sans arobase… Parfois « l’erreur » provient d’un formatage particulier qui peut gêner les fonctions de recherches ou les opérations sur les tables par la suite (par exemple un code qui mélange lettres et chiffres alors que l’on souhaiterait faire des recherches numériques sur ce code).

Effectuer des tests de cohérence est donc nécessaire lors de la création de tables de données afin de vérifier que toutes les valeurs sont bien celles attendues. Il vaut mieux effectuer ces tests au moment de l’importation d’un fichier « csv » afin que la suite de votre code ne génère pas d’erreurs.

Les tests de cohérences les plus courants à effectuer sont la recherche de doublons (voir plus haut), la vérification de la bonne présence des descripteurs et la vérification de la conformité du type de valeurs.

Reprenons l’exemple de notre fichier csv contenant des informations sur les départements. Si l’on veut effectuer une recherche numérique sur les numéros de départements, nous serons « gênés » par les départements corses qui contiennent des lettres (2A et 2B).

Exemple : nous souhaitons afficher la liste de tous les départements dont le numéro est inférieur à 20 :

departements = []
with open("population_superficie_departements.csv") as fichier_csv:
   dict_csv = list(csv.DictReader(fichier_csv, delimiter=';'))
   for ligne in dict_csv:
      departements.append(dict(ligne))

depmoins20=[]
for dept in departements:
   try:
      if int(dept["Code département"])<21:
      depmoins20.append(dept)
   except:
      depmoins20.append(dept)

vingtpremierscodedep=[d["Nom du département"]for d in depmoins20]
print((vingtpremierscodedep))
['Ain', 'Allier', 'Ardèche', 'Cantal', 'Cher', 'Corse-du-Sud', 'Haute-Corse', 'Ardennes', 'Aube', 'Aisne', 'Calvados', 'Charente', 'Charente-Maritime', 'Corrèze', 'Ariège', 'Aude', 'Aveyron', 'Alpes-de-Haute-Provence', 'Alpes-Maritimes', 'Bouches-du-Rhône', 'Hautes-Alpes']

On voit ici que nous utilisons la gestion des exceptions avec « try …except ».

La conversion des deux départements corses va générer une erreur lors de la conversion en entier (car leur code de département contient des lettres). Comme nous savons que ce sont les seuls départements concernés, on contourne alors l’erreur en les ajoutant de toute façon à notre liste de résultats.

Avec « try et except » il est possible également d’exclure certaines données avant d’effectuer leur traitement.

20.    Tri d’une table

Méthodes de tri

Trier une table est l’action d’ordonner une table selon un ou plusieurs critères prédéfinis. On peut, par exemple, trier les élèves d’une classe en fonction de leur moyenne dans une discipline, ou trier une liste de films selon leur durée ou leur année de production.

Quand on veut trier une table, il faut définir le critère de tri, que l’on nomme « clé » et décider dans quel ordre on veut effectuer ce tri : croissant ou décroissant.

Attention, un tri ne peut se faire que sur une grandeur comprise par le programme : une valeur numérique, la longueur d’une chaîne de caractère…

Fonction de tri intégrée à Python

Python possède deux fonctions de tri intégrées : « sort » et « sorted ».

« sort » est un méthode applicable à une table. Nous l’avons déjà vu lors de l’étude des tables indexées au chapitre précédent. « sort » va trier une table en modifiant son contenu pour en ordonner les éléments. Dans le cas d’une table de dictionnaires, comme nous en avons créé à partir de notre fichier csv, on peut passer à cette méthode une clé dont la valeur peut être définie par une fonction :

import csv

def cle_tri(t):
   return int(t["Population totale"])

departements = []
with open("population_superficie_departements.csv") as fichier_csv:
   dict_csv = list(csv.DictReader(fichier_csv, delimiter=';'))
   for ligne in dict_csv:
      departements.append(dict(ligne))

departements.sort(key=cle_tri)
print(departements)
[{'Code région': '76', 'Nom de la région': 'Occitanie', 'Code département': '48', 'Nom du département': 'Lozère', "Nombre d'arrondissements": '2', 'Nombre de cantons': '13', 'Nombre de communes': '158', 'Population totale': '80141', 'Superficie (en km²)': '5167'}, {'Code région': '75', 'Nom de la région': 'Nouvelle-Aquitaine', 'Code département': '23', 'Nom du département': 'Creuse', "Nombre d'arrondissements": '2', 'Nombre de cantons': '15', 'Nombre de communes': '258', 'Population totale': '123500', 'Superficie (en km²)': '5565'},…

Le tri est effectué ici dans l’ordre croissant.

La fonction « cle_tri(t) » permet d’extraire la valeur de chaque ligne qui va servir de tri dans la table. Comme notre fichier tableau contenait cette valeur sous forme de texte, elle est ici convertie en entier.

Si l’on souhaite trier dans l’ordre décroissant, on peut modifier la fonction de tri de la façon suivante :

departements.sort(key=cle_tri, reverse=True)

Il est également possible de définir une fonction « lambda », qui est une méthode de création de petites fonctions sur une seule ligne, afin de créer la clé de tri de la façon suivante :

Lambda valeur_entrée : calcul_de_valeur_sortie 

Dans l’exemple précédent, on pourrait donc supprimer la définition de « cle_tri » et modifier la fonction de tri de la façon suivante :

departements.sort(key=lambda dept: int(dept["Population totale"]))

Pour créer une nouvelle table triée à partir d’une table non triée, on pour utiliser la fonction intégrée de Python « sorted ».

Par exemple pour trier nos départements avec sorted, on pourra écrire :

print(sorted(departements, key=cle_tri))

De cette façon on affiche une table triée sans avoir modifié la table d’origine.

Pour trier selon plusieurs critères, il faut modifier la définition de la clé en ajoutant des critères dans l’ordre. Par exemple, si on souhaite trier les départements par ordre de population croissante par numéro de région croissante, on écrira :

def cle_tri(t):
    return (int(t["Code région"]),int(t["Population totale"]))

21.    Fusion de tables

Combiner des tables

Combiner des tables consiste à les fusionner en une seule, on parle également de « jointure » entre les tables.

Il y a plusieurs manières de fusionner des tables. La plus simple consiste à ajouter une table à une autre en faisant une simple addition :

table_combinee = table1 + table2

Dans ce cas, les éléments de la seconde table sont ajoutés à la première sans que ces deux tables aient besoin d’avoir le même nombre de colonnes ou le même type de contenu.

Si deux tables ont des colonnes en commun et qu’on souhaite fusionner leur contenu, il faut alors définir leur « domaine de valeur », c’est-à-dire quels sont les « champs » (ou colonnes) communs aux deux tables.

Avant de fusionner, on fera en sorte que les deux tables soient formatées de la même manière : colonnes dans le même ordre. Cela peut se faire en créant une table intermédiaire au besoin.

Enfin, on peut utiliser les fonctions de créations de tables par compréhension, vues au chapitre précédent, pour créer une nouvelle table à partir de deux tables ayant un domaine de valeur commun en fusionnant les colonnes.

Voici un exemple à partir de deux listes d’élèves dont on souhaite fusionner les renseignements (on imagine que la recherche de doublons et le tri ont déjà été effectués) :

table1=[{"nom":"Duchmol","prenom":"Jean","moyenne":12},{"nom":"Martin","prenom":"Lise","moyenne":14}]
table2=[{"nom":"Duchmol","prenom":"Jean","classe":"3A"},{"nom":"Martin","prenom":"Lise","classe":"5B"}]
fusion=[{**table1[i],**table2[i]} for i in range(len(table1))]

print(fusion)

Résultat

[{'nom': 'Duchmol', 'prenom': 'Jean', 'moyenne': 12, 'classe': '3A'}, {'nom': 'Martin', 'prenom': 'Lise', 'moyenne': 14, 'classe': '5B'}]

La fusion est ici faite par compréhension avec l’écriture « ** », apparue dans Python 3.5 et nommée « dictionary unpacking ». Elle permet de fusionner sans faire de doublons des champs déjà présents dans la première liste, mais écrase leurs valeurs avec celle de la seconde liste si le contenu des champs est différent.

Chapitre précédentChapitre suivant