Chapitre 4 : Données organisées en tables


Introduction

Organiser des données sous forme de tableaux est très répandu.

Par exemple, le bulletin d'un élève est organisé en table et indique pour chaque matière, la moyenne de l'élève, la moyenne de la classe, l'appréciation du professeur. La liste de présence des élèves est également représentée sous forme d'une table indiquant pour chaque élève s'il est présent ou absent ce jour. L'utilisation de tables est très ancienne car on retrouve des tables de données dans les livres de comptes dans l'Egypte ancienne.

En informatique, les tables de données sont rapidement devenues le principal moyen de stocker des données. L'analyse de ces données est utilisée dans de nombreux domaines : calcul scientifique, intelligence artificielle, programmation Web, bio-informatique, informatique financière, etc. De nombreuses données en tables sont disponibles sur les sites de données ouvertes.

Motivation :

Voici un exemple de table avec des informations sur des élèves :

prénom jour mois année sexe projet
Louan 13 4 2003 G être heureux
Mael 29 3 2003 G manger une glace
Alexis 20 10 2003 G gagner au loto
Théo 25 4 2003 G avoir une bonne note au prochain devoir
Arthur 14 1 2003 G devenir quelqu’un de célèbre
Tristan 18 9 2003 G gagner le Tour de France
Leo 10 5 2003 G dormir plus longtemps le matin
Valentin 30 12 2003 G créer un jeu vidéo
Elie 19 2 2003 G devenir astronaute
Jenifer 30 3 2003 F marcher sur la lune

Cette table peut être représenté par le fichier texte suivant :

prénom,jour,mois,année,sexe,projet
Louan,13,4,2003,G,être heureux
Mael,29,3,2003,G,manger une glace
Alexis,20,10,2003,G,gagner au loto
Théo,25,4,2003,G,avoir une bonne note au prochain devoir
Arthur,14,1,2003,G,devenir quelqu’un de célèbre
Tristan,18,9,2003,G,gagner le Tour de France
Leo,10,5,2003,G,dormir plus longtemps le matin
Valentin,30,12,2003,G,créer un jeu vidéo
Elie,19,2,2003,G,devenir astronaute
Jenifer,30,3,2003,F,marcher sur la lune

Pour travailler sur les données d'un tel fichier, il faudra commencer par charger les données. On pourra soit les charger dans un tableau de tableaux

[['prénom', 'jour', 'mois', 'année', 'sexe', 'projet'], ['Louan', '13', '4', '2003', 'G', 'être heureux'], ['Mael', '29', '3', '2003', 'G', 'manger une glace'], ['Alexis', '20', '10', '2003', 'G', 'gagner au loto'], ['Théo', '25', '4', '2003', 'G', 'avoir une bonne note au prochain devoir'], ['Arthur', '14', '1', '2003', 'G', 'devenir quelqu’un de célèbre'], ['Tristan', '18', '9', '2003', 'G', 'gagner le Tour de France'], ['Leo', '10', '5', '2003', 'G', 'dormir plus longtemps le matin'], ['Valentin', '30', '12', '2003', 'G', 'créer un jeu vidéo'], ['Elie', '19', '2', '2003', 'G', 'devenir astronaute'], ['Jenifer', '30', '3', '2003', 'F', 'marcher sur la lune']]

ou dans un tableau de dictionnaires :

[{'prénom': 'Louan', 'jour': '13', 'mois': '4', 'année': '2003', 'sexe': 'G', 'projet': 'être heureux'}, {'prénom': 'Mael', 'jour': '29', 'mois': '3', 'année': '2003', 'sexe': 'G', 'projet': 'manger une glace'}, {'prénom': 'Alexis', 'jour': '20', 'mois': '10', 'année': '2003', 'sexe': 'G', 'projet': 'gagner au loto'}, {'prénom': 'Théo', 'jour': '25', 'mois': '4', 'année': '2003', 'sexe': 'G', 'projet': 'avoir une bonne note au prochain devoir'}, {'prénom': 'Arthur', 'jour': '14', 'mois': '1', 'année': '2003', 'sexe': 'G', 'projet': 'devenir quelqu’un de célèbre'}, {'prénom': 'Tristan', 'jour': '18', 'mois': '9', 'année': '2003', 'sexe': 'G', 'projet': 'gagner le Tour de France'}, {'prénom': 'Leo', 'jour': '10', 'mois': '5', 'année': '2003', 'sexe': 'G', 'projet': 'dormir plus longtemps le matin'}, {'prénom': 'Valentin', 'jour': '30', 'mois': '12', 'année': '2003', 'sexe': 'G', 'projet': 'créer un jeu vidéo'}, {'prénom': 'Elie', 'jour': '19', 'mois': '2', 'année': '2003', 'sexe': 'G', 'projet': 'devenir astronaute'}, {'prénom': 'Jenifer', 'jour': '30', 'mois': '3', 'année': '2003', 'sexe': 'F', 'projet': 'marcher sur la lune'}]

Une fois les données chargées dans un tableau, on pourra travailler sur ces données : rechercher le nombre d'élèves nés en 2003, les élèves nés en avril, calculer le pourcentage de filles dans la classe, trier la table du plus jeune au plus vieux des élèves, etc.

Ne tardons plus, voici comment on peut procéder.

I. Charger et mémoriser les données

Pour charger les données d'une table il faudra lire le fichier contenant ces données pour les importer dans Python et les charger dans une variable. Une fois que la table sera importée dans une variable, il faudra mémoriser le contenu de la table dans un tableau (de tableaux ou de dictionnaires).

A. Fichiers textes tabulés et fichiers CSV

Un fichier TXT tabulé est un simple fichier texte dans lequel :

  • chaque ligne correspond à une ligne de la table ;
  • chaque ligne est séparée en champs par une tabulation ;
  • toutes les lignes du fichier ont le même nombre de champs.

Un fichier CSV (pour l'anglais comma-separated values, soit "données séparées par des virgules") est un fichier texte dans lequel :

  • chaque ligne correspond à une ligne de la table;
  • chaque ligne est séparée en champs par le caractère ",";
  • toutes les lignes du fichier ont le même nombre de champs.

Exemple :

prénom,jour,mois,année,sexe,projet
Louan,13,4,2003,G,être heureux
Mael,29,3,2003,G,manger une glace
Alexis,20,10,2003,G,gagner au loto
Théo,25,4,2003,G,avoir une bonne note au prochain devoir
Arthur,14,1,2003,G,devenir quelqu’un de célèbre
Tristan,18,9,2003,G,gagner le Tour de France
Leo,10,5,2003,G,dormir plus longtemps le matin
Valentin,30,12,2003,G,créer un jeu vidéo
Elie,19,2,2003,G,devenir astronaute
Jenifer,30,3,2003,F,marcher sur la lune

Dans ce fichier CSV chaque ligne représente un élément de la table (ici un élève). La première ligne contient le nom des attributs ( = le nom de chaque champ) des éléments. Il y a donc en tout 6 attributs dans ce fichier CSV.

Pour les données tabulées l'usage est d'utiliser le format CSV, et c'est ce que nous allons faire par la suite.

Remarque : Un autre format de données en tables est le format JSON qui est plus élaboré. Il n'est pas explicitement au programme et vous en aurez juste un petit aperçu en fin de Notebook pour les plus curieux.

Exercice 1 :

  1. Téléchargez le fichier "eleves.csv" sur e-lyco : https://mounier.paysdelaloire.e-lyco.fr/.
  2. Ouvrez ensuite ce fichier avec un éditeur de texte (gedit sous Ubuntu ou Bloc-notes sous Windows) pour en visualiser le contenu.
  3. Ouvrez également ce fichier avec un tableur (Calc par exemple) pour en visualiser le contenu sous forme d'un tableau. Il faudra vérifier que la caractère de séparation est bien la virgule.

Remarque : le format CSV est le plus répandu des formats tabulés mais on rencontre souvent des variantes dans lesquelles le caractère de séparation est le point-virgule (";"), le deux-points (":") ou la barre verticale ("|"). En France, on utilise souvent le point-virgule comme caractère de séparation car la virgule est utilisée pour l'écriture des nombres décimaux ce qui peut poser des problèmes.

B. Lire un fichier CSV pour mémoriser sa table

Avant de lire le fichier "eleves.csv" il faut d'abord l'ouvrir dans Python en utilisant la fonction read.

In [ ]:
fichier = open('eleves.csv', 'r', encoding = 'UTF-8') # on charge le contenu dans la variable 'fichier'

On a utilisé le paramètre 'r' pour indiquer une ouverture en mode lecture ( read en anglais).

Remarque : On pourrait ensuite lire le contenu du fichier sous forme d'une unique chaîne de caractères en utilisant la fonction read

fichier = open('eleves.csv', 'r', encoding = 'UTF-8') 
texte = fichier.read() # on lit le contenu sous la forme d'une unique chaine de caractères
print(texte) # on affiche la chaine de caractères 'texte'

mais ce n'est pas ainsi que l'on procèdera car il faudrait encore travailler sur cette chaîne pour mémoriser les données dans un tableau et cela peut se révéler fastidieux.

Pour lire un fichier CSV (ou TXT tabulé), la biliothèque standard de Python contient un module csv qui contient des fonctions très pratiques pour lire et écrire des fichiers CSV. La mémorisation dans un tableau devenant alors très rapide. Voici comment on procède.

1) pour mémoriser la table dans un tableau de tableaux

In [ ]:
import csv
fichier = open('eleves.csv', 'r', encoding = 'UTF-8')
t = csv.reader(fichier, delimiter=',')
eleves = [] # on va mémoriser les données dans le tableau 'eleves'
for ligne in t:
    eleves.append(ligne)
print(eleves) # pour afficher le contenu du tableau 'table'

Explications : Après avoir importé le module csv, on ouvre le fichier "eleves.csv". La fonction reader du module csv prend en argument un fichier ouvert et renvoie une valeur spéciale représentant le fichier CSV. On parcourt ensuite chaque ligne de cette valeur spéciale (qui correspond à chaque ligne de la table) et on ajoute au fur et à mesure cette ligne au tableau eleves qui contient alors un tableau de tableaux de chaînes de caractères représentant les données.

Remarque : il est aussi possible de construire le tableau eleves par compréhension en remplaçant les lignes 4, 5 et 6 par l'instruction eleves =[ligne for ligne in t]. Voyez pas vous-même :

In [ ]:
import csv
fichier = open('eleves.csv', 'r', encoding = 'UTF-8')
t = csv.reader(fichier, delimiter=',')
eleves = [ligne for ligne in t]  # création et construction du tableau par compréhension
print(eleves) # pour afficher le contenu du tableau 'table'

Comme on le voit, cette solution n'est pas forcément satisfaisante.

  • Tout d'abord, la première ligne contenant les attributs a été chargée comme une ligne de données. Il faudra prendre soin de l'ignorer durant les traitements ou de lire la première ligne du fichier avant en ajoutant le code suivant entre les lignes 2 et 3 (essayez de le faire)

    fichier.readline()

    Ce code permet de lire la première ligne et de positionner le curseur de lecture sur la seconde ligne qui sera la prochaine ligne à lire.

  • Deuxièmement, on constate que toutes les données ont été considérées comme des chaînes de caractères, même les nombres entiers.

  • Enfin, chaque ligne est représentée par un tableau et il est nécessaire de savoir à quel index se trouve chaque attribut. Par exemple, si on veut accéder au sexe du deuxième élève il faut savoir que le sexe est l'attribut d'indice 4 car pour y accéder il faut faire eleves[1][4]. Il faut donc se souvenir de la correspondance entre le nom de l'attribut et sa position dans le tableau.
In [ ]:
eleves[1][4] # pensez à exécuter le code précédent si un message d'erreur apparaît

Exercice 2 :

On considère la table précédente mémorisée dans un tableau de tableaux. Exécutez à nouveau le code du début du paragraphe 1) avant de commencer l'exercice.

  1. Quelle instruction permet d'accéder au projet de Théo ?
In [ ]:
 
  1. Quelle instruction permet d'accéder à l'année de naissance de Mathis ?
In [ ]:
 
  1. Complétez le programme ci-dessous afin qu'il affiche le prénom des élèves nés au mois d'avril.
In [ ]:
 

2) pour mémoriser la table dans un tableau de dictionnaires

Une alternative, qui va remédier aux deux premiers problèmes, consiste à utiliser la fonction DictReader du module csv. Elle s'utilise de manière identique et va permettre de mémoriser les données dans un tableau de dictionnaires.

In [ ]:
import csv
fichier = open('eleves.csv', 'r', encoding = 'UTF-8')
t = csv.DictReader(fichier, delimiter=',')
eleves = [] # on va mémoriser les données dans le tableau 'eleves'
for ligne in t:
    eleves.append(dict(ligne))
print(eleves) # pour afficher le contenu du tableau 'eleves'

Remarques :

  • On constate que la variable eleves contient désormais un tableau de dictionnaires.
  • La ligne d'en-tête contenant le nom des attributs n'est pas stockée dans le tableau en tant que donnée mais a servi pour créer les clés des différents dictionnaires.
  • On peut ainsi accéder plus facilement au sexe du deuxième élève juste en utilisant sa clé :
In [ ]:
eleves[1]['sexe']
  • On a ajouté la fonction dict à la ligne 6 du programme précédent car sans elle, on aurait obtenu un tableau de dictionnaires ordonnés (pour les dernières versions de Python) mais dont l'affichage est un peu moins joli. Vous pouvez essayer d'enlever la fonction dict sur cette ligne pour voir par vous-même. En revanche, on utiliserait le dictionnaire ordonné obtenu de la même façon : on accèderait au sexe du deuxième élève avec l'intruction eleves[1]['sexe'] comme précédemment.
  • Il est aussi possible de créer le tableau eleves par compréhension :
In [ ]:
import csv
fichier = open('eleves.csv', 'r', encoding = 'UTF-8')
t = csv.DictReader(fichier, delimiter=',')
eleves = [dict(ligne) for ligne in t] # création et construction du tableau par compréhension
print(eleves) # pour afficher le contenu du tableau 'eleves'

Exercice 3 :

On considère la table précédente mémorisée dans un tableau de dictionnaires. Exécutez à nouveau le code du début du paragraphe 2) avant de commencer l'exercice.

  1. Quelle instruction permet d'accéder au projet de Théo ?
In [ ]:
 
  1. Quelle instruction permet d'accéder à l'année de naissance de Mathis ?
In [ ]:
 
  1. Complétez le programme ci-dessous afin qu'il affiche le prénom des élèves nés au mois d'avril.
In [ ]:
 

Après avoir fait les exercices 2 et 3, vous devez constater que la mémorisation dans un tableau de dictionnaires est plus pratique pour y rechercher des informations, au sens où l'on n'a pas besoin de connaître l'index de l'information cherchée (5 par exemple pour le projet) mais uniquement sa clé ('projet').

Cependant, ce n'est pas encore parfait car pour trouver l'année de naissance de Mathis, il faut savoir que la ligne le concernant a pour index 13 dans le tableau. Imaginez ce que cela peut donner pour trouver un index dans des fichiers de plusieurs dizaines de milliers de lignes...

Heureusement, il est possible de chercher plus efficacement des données dans une table, c'est ce que nous allons voir maintenant.

II. Rechercher dans une table

A. Recherche

Une fois les données chargées dans une table, il est possible de travailler sur ces données en utilisant les opérations de manipulation sur les tableaux et les dictionnaires.

Dans ce paragraphe et dans toute la suite, nous allons supposer que les données ont été chargées dans un tableau de dictionnaires comme vu dans le paragraphe I. B. 2. Sachez cependant qu'il est possible de réaliser le même genre de choses sur des tableaux de tableaux mais il faut connaître les index des attributs, ce qui est plus fastidieux.

1. Rechercher en utilisant la clé d'un attribut

On cherche ici à rechercher la présence d'un élément dans une table (un élève dans notre cas). Nous allons utiliser des techniques de recherche dans un tableau comme celles vues au chapitre 2 de la séquence 3. Pour rappel, voici la fonction qui renvoie True ou False selon la présence ou l'absence d'un élément v dans un tableau T.

def appartient(v, T):
    for e in T:
        if e == v:
            return True
    return False

On va légèrement adapter cette fonction pour chercher un élève désigné par son prénom dans notre table.

In [ ]:
def appartient(p, eleves):
    ''' p est un chaîne de caractères et eleves est un tableau de dictionnaires '''
    for e in eleves:
        if e['prénom'] == p:
            return True
    return False

Explications : on passe en revue tous les éléments du tableau (chaque dictionnaire) et on teste si l'un d'entre eux possède un attribut 'prénom' égal à p, autrement dit si un des élèves a le prénom p.

On peut alors appeler cette fonction pour tester la présence d'un élève. S'il y a un souci, vous exécuterez à nouveau le premier programme du paragraphe II. B. 2 qui va mémoriser les données dans eleves.

In [ ]:
appartient('Haude', eleves) # est-ce que 'Haude' est dans 'eleves' ?
In [ ]:
appartient('Lucas', eleves) # est-ce que 'Lucas' est dans 'eleves' ?

2. Récupérer une donnée simple

On peut adapter la fonction précédente pour qu'elle renvoie le projet de l'élève dont le prénom p est entré en paramètre.

In [ ]:
def projet_de(p, eleves):
    ''' p est un chaîne de caractères et eleves est un tableau de dictionnaires '''
    for e in eleves:
        if e['prénom'] == p:   # si un élève a le prénom p
            return e['projet'] # on renvoie le projet de cet élève
In [ ]:
projet_de('Leo', eleves)

Remarque : Si le prénom n'est pas dans eleves, cette fonction renvoie None.

Exercice 4 :

Ecrire une fonction qui renvoie le sexe d'un élève de la table eleves dont le prénom p est entré en paramètre.

In [ ]:
 

Il est possible de préciser la recherche en combinant les conditions sur plusieurs attributs.

In [ ]:
def nom_eleve(a, m, eleves):
    ''' a et m sont des chaînes de caractères et eleves est un tableau de dictionnaires '''
    for e in eleves:
        if e['année'] == a and e['mois'] == m:   # si un élève est né l'année a et le mois m
            return e['prénom']                   # on renvoie le prénom de cet élève
In [ ]:
nom_eleve('2003', '3', eleves)

Remarques :

  • Il y avait deux élèves nés en mars 2003 mais seul celui dont la ligne apparaît en premier a été renvoyé.
  • Vous constatez que les paramètres de l'année et du mois sont écrits comme des chaînes de caractères dans l'appel de la fonction. C'est normal car les données sont toutes mémorisées en chaîne de caractères dans la table. Il est toutefois possible de donner des paramètres entiers mais à condition de convertir les valeurs des attributs en type int dans la fonction de manière à comparer des variables de même type.
In [ ]:
def nom_eleve_bis(a, m, eleves):
    ''' a et m sont des entiers et eleves est un tableau de dictionnaires '''
    for e in eleves:
        if int(e['année']) == a and int(e['mois']) == m:   # conversion avec la fonction int
            return e['prénom']
nom_eleve_bis(2003, 3, eleves)  # on peut utiliser des paramètres entiers

Exercice 5 :

Sans l'exécuter, que permet de faire la fonction suivante ?

In [ ]:
def mystere(a, s, eleves):
    ''' a et s sont des chaînes de caractères et eleves est un tableau de dictionnaires '''
    for e in eleves:
        if e['année'] == a and e['sexe'] == s:
            return e['prénom'] 

Réponse :

Exercice 6 :

Ecrire une fonction age_de(p, eleves) qui renvoie l'âge d'un élève de la table eleves dont le prénom p est entré en paramètre.

In [ ]:
 

3. Comptage du nombre d'occurences

Exercice 7:

  1. Ecrire une fonction nb_filles(eleves) qui renvoie le nombre de filles de la table eleves.
In [ ]:
 
  1. Ecrire une fonction prop(a, eleves) qui renvoie la proportion d'élèves de la table eleves nés l'année a. On considérera que a est une chaîne de caractères.
In [ ]:
 

B. Sélection de lignes

Toutes les opérations précédentes permettent de produire des résultats simples : afficher le projet d'un élève, afficher le sexe d'un élève, afficher un nombre d'occurences.

Or, dans le domaine du traitement de données, il est très souvent nécessaire d'extraire différentes données d'une table pour en construire une nouvelle. Par exemple, extraire tous les élèves qui sont nés un mois donné afin de leur souhaiter un bon anniversaire.

Pour cela, il suffit de sélectionner les lignes vérifiant une certaine condition pour produire une nouvelle table. Pour créer cette nouvelle table, deux possibilités s'offrent à vous:

  • utiliser la construction d'un tableau en compréhension ;
  • ou le construire en utilisant une boucle.
In [ ]:
# CONSTRUCTION PAR COMPREHENSION
t = [e for e in eleves if e['mois'] == '2']  
t  # affichage du résultat
In [ ]:
# CONSTRUCTION AVEC UNE BOUCLE
t = [] # on part d'un tableau vide que l'on complète au fur et à mesure
for e in eleves:           
    if e['mois'] == '2':
        t.append(e)
t  # affichage du résultat

Explication : le tableau t ainsi construit contient tous les dictionnaires des élèves de la table eleves qui sont nés en février.

Exercice 8 :

  1. A partir de la table eleves, contruire une table t0 contenant tous les garçons de la classe.
In [ ]:
 
  1. A partir de la table eleves, contruire une table t1 contenant les élèves nés en janvier.
In [ ]:
 
  1. A partir de la table eleves, contruire une table t2 contenant les élèves nés en janvier 2003.
In [ ]:
 
  1. A partir de la table eleves, contruire une table t3 contenant les élèves nés entre le 1er janvier 2003 et le 5 mai 2003.
In [ ]:
 
  1. Quelle instruction permet alors très rapidement de connaître le nombre d'élèves nés entre le 1er janvier 2003 et le 5 mai 2003 ?
In [ ]:
 

C. Sélectionner des lignes et des colonnes

Dans le paragraphe précédent, vous avez vu comment construire une nouvelle table en sélectionnant certaines des lignes d'une autre table. Cela signifie que toutes les colonnes des lignes sélectionnées ont été ajoutées à la table créée. On peut aussi très facilement construire un nouveau tableau ne contenant que certaines colonnes de la table de départ.

Par exemple, si on souhaite construire un tableau avec uniquement le prénom des élèves de la classe, on ne va sélectionner que la colonne contenant le prénom. La construction est la suivante :

In [ ]:
t = [e['prénom'] for e in eleves]
t

On dit que l'on a fait une projection sur la colonne contenant les prénoms des élèves.

Il est bien sûr possible de combiner une sélection sur les colonnes avec une sélection sur les lignes. Ainsi, l'instruction

In [ ]:
t = [e['prénom'] for e in eleves if e['sexe'] == 'G']
t

permet de créer un tableau contenant les prénoms des garçons de la classe. On a donc sélectionné la première colonne contenant les prénoms des élèves mais seulement des lignes correspondant à des garçons.

Exercice 9 :

A partir de la table eleves construire un tableau t1 contenant les prénoms des élèves nés en 2003. (Maxime et Raphaël ne doivent pas s'y trouver).

In [ ]:
 

Le principal objectif des projections est de pouvoir constuire une nouvelle table à partir de certaines colonnes de la table de départ. Pour cela, au lieu d'extraire la valeur d'un seul attribut, on peut construire un nouveau dictionnaire à partir de plusieurs attributs. Ainsi, l'instruction

In [ ]:
t = [{'prénom': e['prénom'], 'projet': e['projet']} for e in eleves]
t

construit pour chaque ligne de la table eleves un dictionnaire contenant un attribut 'prénom' et un attribut 'projet' dont les valeurs sont exactement les valeurs des attributs de même nom dans la ligne considérée. Autrement dit, on extrait de la table eleves une table ne conservant que les prénoms et les projets. On dit que l'on a effectué une projection sur ces 2 colonnes.

Exercice 10 :

  1. Construire une table t1 ne conservant que les prénoms et les années de naissance des élèves de la table eleves.
In [ ]:
 
  1. Construire une table t2 contenant les prénoms, les années de naissance et l'âge des élèves.
In [ ]:
 
  1. Construire une table t3 ne contenant que les prénoms et les projets des élèves nés entre janvier et mars 2003.
In [ ]:
 

III. Ecrire dans un fichier CSV

Grâce au module csv de Python, il est également très facile d'écrire dans un fichier CSV. Imaginons qu'à partir du fichier "eleves.csv" initial, on souhaite créer un nouveau fichier CSV contenant uniquement les prénoms des élèves nés en 2003 ainsi que leurs projets.

On peut commencer par sélectionner les lignes et colonnes souhaitées et les stocker dans un tableau :

In [ ]:
ma_table = [{'prénom': e['prénom'], 'projet': e['projet']} for e in eleves if int(e['année']) == 2003]
ma_table

Pour enregistrer cette table dans un fichier CSV, il suffit d'ouvrir un fichier en écriture et d'utiliser la fonction DictWriter comme suit

In [ ]:
sortie = open("projets_eleves_2003.csv", "w", newline = '', encoding = "utf-8") 
w = csv.DictWriter(sortie, ['prénom', 'projet'])
w.writeheader()
w.writerows(ma_table)
sortie.close()

Explications :

  • Ligne 1 : On a commencé par ouvrir un fichier en écriture. Si le fichier existe déjà il est écrasé et remplacé, sinon il est créé. Le paramètre "w" indique que le fichier est ouvert en écriture (w pour write en anglais), la paramètre newline est mis à '' pour éviter un saut de ligne entre chacune des lignes du fichier.
  • Ligne 2 : On utilise la fonction DictWriter en lui donnant en paramètres le fichier ouvert en écriture ainsi que la liste des attributs. Cette fonction renvoie un objet w permettant d'écrire des lignes dans le fichier.
  • Ligne 3 : Cette instruction permet d'écrire dans le fichier la ligne d'entête avec les attributs.
  • Ligne 4 : L'instruction w.writerows(ma_table) permet d'écrire les lignes de ma_table dans le fichier.
  • Ligne 5 : On ferme le flux d'écriture afin que le fichier soit utilisable par un autre logiciel, typiquement pour pouvoir l'ouvrir avec un tableur (ou éditeur de texte) pour admirer le résultat.

Vous devez constater qu'un fichier CSV nommé "projets_eleves_2003.csv" a été créé dans le repertoire du notebook. Ouvrez celui-ci avec un éditeur de texte puis avec un tableur en prenant soin de vérifier que le caractère de séparation du tableur est bien la virgule.

Remarque : Il est aussi possible de ne pas utiliser writerows et d'écrire le fichier ligne à ligne avec la fonction writerow (sans s) avec une boucle for qui parcoure chaque ligne de la table et l'écrit dans le fichier au fur et à mesure.

In [ ]:
sortie = open("projets_eleves_2003.csv", "w", newline = '', encoding = "utf-8") 
w = csv.DictWriter(sortie, ['prénom', 'projet'])
w.writeheader()
for e in ma_table:  # ce deux lignes ont le même rôle que 'writerows'
    w.writerow(e)
sortie.close()

Exercice 11 :

  1. Commencez par construire une table tab contenant les prénoms, les projets et l'âge des filles de la classe.
In [ ]:
 
  1. Ecrivez cette table dans un nouveau fichier CSV puis ouvrez ce fichier avec un tableur pour vérifier (admirer) le résultat.
In [ ]:
 

IV. Tests de cohérence d'une table

A. Conversion des données

Nous avons vu que les données étaient mémorisées sous forme de chaînes de caractères, même les informations qui sont des nombres (le jour, le mois et l'année de naissance dans notre exemple). On peut s'en sortir en veillant à convertir ces données en de vrais nombres Python lorsque c'est nécessaire, comme nous l'avons fait à certaines reprises avec la fonction int.

Pour éviter d'oublier de faire ces conversions lors des différents traitements, il est possible de convertir les données de la table avant de chercher ou sélectionner des lignes / colonnes dans celle-ci.

Une façon de procéder est de définir une fonction conversion qui prend en paramètre un dictionnaire et qui renvoie un dictionnaire avec les valeurs converties.

In [ ]:
def conversion(d):
    return {'prénom': d['prénom'], 'jour': int(d['jour']), 'mois': int(d['mois']), 'année': int(d['année']), 'sexe': d['sexe'], 'projet': d['projet']}

On peut alors parcourir la table eleves et appliquer à chaque dictionnaire la fonction conversion. On stocke la table convertie dans une variable appelée eleves_convertie. Par compréhension cela donne

In [ ]:
eleves_convertie = [conversion(e) for e in eleves]
eleves_convertie

On peut aussi construire table_convertie avec une boucle comme ce qui suit :

In [ ]:
eleves_convertie = []
for e in eleves:
    eleves_convertie.append(conversion(e))
eleves_convertie

Retenir : Cela peut être une bonne pratique de convertir les données de la table juste après l'avoir mémorisée. En effet, c'est fait dès le départ et cela évite de se demander à chaque traitement s'il faut ou non convertir les données et d'éventuellement de se tromper.

Par exemple, en travaillant avec table_convertie on peut construire une table contenant les prénoms et l'âge des élèves nés avant le 25 février de manière plus simple.

In [ ]:
tab = [{'prénom': e['prénom'], 'âge': 2019 - e['année']} for e in eleves_convertie if e['mois'] == 1 or e['mois'] == 2 and e['jour'] <= 25]
tab

B. Cohérence des données

Nous avons travaillé sur une table dont les données sont correctes mais il faut souvent se méfier. En effet, une simple faute de frappe dans la saisie des données du fichier CSV peut avoir des répercussions importantes, et ce n'est pas rare.

Méthode :

Si par mégarde on a saisi comme jour de naissance pour un élève la valeur '34' au lieu de '24', alors la table contient une valeur incohérente. De même, si dans la saisie des données on a oublié de rentrer une valeur pour le mois de naissance, les données seront incohérentes (car il y aurait un décalage dans les valeurs : l'année deviendrait le mois, le projet deviendrait l'année, et le projet n'aurait pas de valeur). Dans les deux cas, selon les traitements effectués, il est possible de provoquer des erreurs.

Une façon d'éviter ces soucis est de tester la cohérence de chaque dictionnaire afin de ne conserver que les lignes valides. A première vue, pour vérifier si une ligne de la table eleves est cohérente on peut vérifier que le jour est compris entre 1 et 31, le mois entre 1 et 12 et l'année inférieure ou égale à 2020. Pour se faire, la fonction est_coherente suivante va faire le travail :

In [ ]:
def est_coherent(e):
    jour = int(e['jour'])
    mois = int(e['mois'])
    annee = int(e['année'])
    if 1 <= jour <= 31 and 1 <= mois <= 12 and annee <= 2020:
        return True
    else:
        return False    

On peut alors vérifier cette fonction grâce à l'instruction suivante :

In [ ]:
table_coherente = [e for e in eleves if est_coherent(e)]
print(table_coherente) 
len(table_coherente)   # pour vérifier qu'il y a bien 16 élèves dans table_coherente 

Explication : on a créé une table appelée table_coherente qui contient les lignes de la table eleves qui sont cohérentes. On a aussi affiché la taille de table_coherente pour vérifier qu'il y a bien 16 élèves (et donc que toutes les lignes sont cohérentes).

Amélioration

Imaginons désormais que l'on ait entré la valeur '1é' au lieu de '12' pour le mois de naissance de Valentin. Dans ce cas, cela peut provoquer des erreurs dans les traitements souhaités : l'extraction des éléves nés en décembre risque de renvoyer une erreur. Pire encore, la fonction est_coherente précédente sera interrompue car '1é' ne peut pas être converti en un entier (ligne 3) et aucune table ne sera produite.

Avant de convertir les valeurs en des entiers il est donc nécessaire de vérifier qu'il s'agit d'une chaîne de caractère qui représente bien un nombre. La méthode isdecimal fait ce travaille : elle renvoie True si c'est le cas et False sinon.

In [ ]:
ch1 = '12'
print(ch1.isdecimal())
ch2 = '1é'
print(ch2.isdecimal())

On peut alors améliorer la fonction est_coherente de la façon suivante

In [ ]:
def est_coherente(e):
    jour = e['jour'] # on ne convertit pas de suite le jour, le mois et l'année
    mois = e['mois']
    annee = e['année']
    if jour.isdecimal() and mois.isdecimal() and annee.isdecimal() and 1 <= int(jour) <= 31 and 1 <= int(mois) <= 12 and int(annee) <= 2019:
        return True
    else:
        return False 

ou de manière un peu plus synthétique comme cela :

In [ ]:
def est_coherente(e):
    jour = e['jour'] # on ne convertit pas de suite le jour, le mois et l'année
    mois = e['mois']
    annee = e['année']
    return jour.isdecimal() and mois.isdecimal() and annee.isdecimal() and 1 <= int(jour) <= 31 and 1 <= int(mois) <= 12 and int(annee) <= 2019

Exercice 12 :

  1. Commencez par ouvrir le fichier "eleves.csv" avec un éditeur de texte ou un tableur et remplacez le mois d'anniversaire de Valentin par "1é" pour simuler une faute de frappe et enregistrez les modifications.
  2. Exécutez ensuite le script du paragraphe I. B. 2 pour mémoriser les données du fichier dans la table eleves.
  3. Exécutez ensuite le script suivant pour vérifier que la ligne concernant Valentin n'est pas dans table_cohérente.
In [ ]:
eleves_coherente = [e for e in eleves if est_coherent(e)]
print(eleves_coherente) 
len(eleves_coherente)
  1. Effectuez d'autres modifications dans le fichier "eleves.csv" de manière à entrer des données incohérentes puis refaites les questions 2 et 3 pour vérifier que table_coherente ne contient que les lignes cohérentes.

C. Validation des données

Vous constaterez que dans l'exercice 12, les données n'ont pas été converties en des entiers. En effet, la fonction est_coherente ne se contente que de tester si une ligne est cohérente c'est-à-dire si les valeurs entrées pour le jour, le mois et l'année représentent bien des entiers et si les valeurs de ces entiers sont cohérentes.

En combinant cette fonction avec la fonction conversion du paragraphe 1, on peut créer un tableau ne contenant que des lignes valides, c'est-à-dire des lignes qui sont à la fois cohérente et qui ont été converties.

Il suffit de construire la table valide par compréhension de la façon suivante :`

In [ ]:
eleves_validee = [conversion(e) for e in eleves if est_coherente(e)]
eleves_validee

Il s'agit de la même construction que dans le paragraphe A (du IV) mais avec la condition supplémentaire que la ligne doit être cohérente pour qu'elle soit convertie et ajouté à eleves_validee.

V. Activité pratique

Commencez par télécharger sur le site info-mounier.fr le fichier "population_pdl.csv" qui contient des informations sur la population des communes de la région Pays de la Loire.

Ce fichier est aussi disponible en open data sur le site data.maine-et-loire.fr. Comme indiqué sur cette page Web, le caractère de séparation est le point-virgule.

Partie 1 : mémorisation de la table

  1. Ouvrez le fichier avec un tableur pour en visualiser le contenu. Quels sont les intitulés des attributs correspondants au nom du département, au nom de la commune ainsi qu'au nombre d'habitants de la commune.

Réponse :

  1. Mémorisez les données de ce fichier dans un tableau de dictionnaires appelé communes.
In [ ]:
 
  1. On souhaite travailler uniquement sur les données de l'année 2015. Complétez le programme Python ci-dessous pour construire une nouvelle table contenant uniquement les données de 2015.
In [ ]:
communes_2015 = [e for e in communes if  ..................]

Partie 2 : Projection sur certaines colonnes et conversion des données

  1. On souhaite travailler uniquement avec les colonnes concernant le nom du département, le nom de la commune ainsi que le nombre d'habitants de la commune (pour les données de l'année 2015). Construisez une table table_communes qui ne conserve que ces trois colonnes de la table communes_2015
In [ ]:
 
  1. Le nom des attributs est long à écrire donc modifiez la table table_communes pour que le nom des attributs soient plus simplement : 'dep', 'commune' et 'population'.
In [ ]:
 
  1. Modifiez le code précédent de façon à convertir le nombre d'habitants de chaque commune en un entier. Comme le nombre d'habitant est représenté par un flottant, il faudra d'abord convertir la chaine avec float puis convertir le flottant obtenu avec int.
    Exemple : l'instruction int(float('256.4')) va renvoyer l'entier 256.
In [ ]:
 

Si vous êtes arrivés jusque là, bravo ! Vous allez maintenant pouvoir travailler sur cette table appelée table_communes.

Partie 3 : Rechercher et calculer dans la table

  1. Quel est le rôle de la fonction suivante ?
In [ ]:
def fonction_mystere(c, table_communes):
    ''' c est une chaîne de caractères et table_communes est un tableau de dictionnaires '''
    for e in table_communes:
        if e['commune'] == c:
            return e['population']

Réponse :

  1. En vous inspirant de la question précédente, écrivez une fonction departement_de(c, table_communes) qui renvoie le département d'une commune c de tables_communes.
In [ ]:
 
  1. Ecrivez une fonction nb_communes_ML qui prend en paramètre la table table_communes et qui renvoie le nombre de communes du département du Maine-et-Loire
In [ ]:
 

Partie 4 : Sélectionner des lignes et des colonnes

  1. Construisez une table communes_LA qui ne contient que les lignes des communes du département de Loire-Atlantique.
In [ ]:
 
  1. Comme il ne s'agit plus que des communes du même département, la première colonne devient inutile. Construisez une table communes_LAbis qui ne contient plus que les deux dernière colonnes (nom de la commune et population).
In [ ]:
 
  1. Construisez une table grosses_communes_ML qui ne contient que les communes de plus de 5000 habitants de Maine-et-Loire.
In [ ]:
 

Partie 5 : Ecrire dans un fichier CSV

  1. Ecrivez les deux tables grosses_communes_ML et communes_LAbis dans des fichiers CSV différents et ouvrez les fichiers créés avec un tableur pour en vérifier le contenu.
In [ ]:
 

VI. Pour aller plus loin : le format JSON

Le format JSON (Java Script Object Notation) permet de mémoriser dans un fichier texte et de relire, les principaux types de données utilisés en python : listes, tuples, dictionnaire et bien sûr les types de base entiers, flottants, chaînes et booléens.

C'est une alternative intéressante aux formats tabulés quand l'information à mémoriser a une structure plus complexe qu'une table à double entrées. L'intérêt est aussi de fournir une représentation externe des informations manipulées, dans un format normalisé pouvant être utilisé par d'autres programmes écrits dans d'autres langages.

En pratique, les fichiers JSON sont particulièrement utilisés pour structurer et transmettre des données sur le Web : on peut par exemple récupérer les données ouvertes en temps réels (au format JSON) du nombre de places de parking disponibles à Angers les analyser et les afficher sur un beau site Web au design soigné (c'est une idée de projet pour vous).

Voici quelques éléments pour présenter le format.

In [ ]:
repertoire = [{'Nom':'Dupont', 'Prenom':'Jean', 'Naissance':{'Jour':10, 'Mois':2, 'Annee':1984}},
      {'Nom':'Durand', 'Prenom':'Marie', 'Naissance':{'Jour':21, 'Mois':12, 'Annee':1964}},
      {'Nom':'Dupond', 'Prenom':'Pierre', 'Naissance':{'Jour':1, 'Mois':8, 'Annee':1960}}]

La fonction json.dump permet d'écrire dans un fichier au format JSON une structure de données calculée par programme.

In [ ]:
import json
f = open('repertoire.json', 'w')
json.dump(repertoire, f)
f.close()

Le fichier texte généré est lisible avec un éditeur de texte et peut aussi être relu par programme avec la fonction json.load.

In [ ]:
g = open('repertoire.json', 'r')
nouveaurep = json.load(g)
nouveaurep

On a ainsi avec le format JSON une technique performante pour mémoriser dans des fichiers des informations structurées et les retrouver sans perte d'information.

In [ ]:
import json
fichier = open('parking-angers.json')
parkings=json.loads(fichier.read())
fichier.close()
parkings

Conclusion

La maîtrise de la lecture d'un fichier de données est un préalable à son traitement. L'écriture est aussi importante pour garder une trace pérenne des informations calculées.

Pour les données ouvertes obtenues sur des sites spécialisés, il convient d'être vigilant en cas de données manquantes ou de données particulières, et de prévoir un prétraitement adapté.

Sources :

  • Documents ressources du DIU EIL Nantes
  • Olivier PINSON, Nathalie LEMAIRE, pour le dernier paragraphe sur le format JSON
  • Numérique et Sciences Informatiques, 1re, T. BALABONSKI, S. CONCHON, J.-C. FILLIATRE, K. NGUYEN, éditions ELLIPSES : Site du livre
  • Ressource Eduscol : Manipulation de tables

Germain BECKER & Sébastien POINT, Lycée Mounier, ANGERS Licence Creative Commons