Tableaux de données avec Pandas

La bibliothèque Pandas, est un outil très performant pour manipuler des tableaux de données, faire des analyses statistiques et des visualisations; comme on pourrait le faire avec un tableur, mais programmatiquement, donc permettant l’automatisation. Elle intègre des outils de manipulation de données, d’analyse statistique et de visualisation. En s’appuyant sur des bibliothèques de parallélisme comme dask, pandas permet de traiter des données massives réparties sur de très nombreux ordinateurs.

Au centre de pandas sont les DataFrame: des tableaux à deux dimensions, avec en sus des labels sur les lignes et les colonnes. Qui plus est, les données ne sont pas forcément homogènes: certaines données peuvent être manquantes et, d’une colonne à l’autre, le type des données peut changer.

De manière similaire, il y a les Series qui en sont l’équivalent mais à une dimension, typiquement obtenues en extrayant une colonne d’un DataFrame.

On retrouve ces concepts de DataFrame et de Series dans les autres bibliothèques ou systèmes d’analyse de données comme R.

Dans la suite de cette feuille, par tableau, on entendra un tableau à deux dimensions de type DataFrame, tandis que par série on entendra un tableau à une dimension de type Series.

Séries de données

Nous devons commencer par importer la bibliothèque pandas; il est traditionnel de définir un raccourci pd:

import pandas as pd

Construisons une série de températures:

temperatures = pd.Series([8.3, 10.5, 4.4, 2.9, 5.7, 11.1], name="Température")
temperatures

Vous noterez que la série est considérée comme une colonne et que les indices de ses lignes sont affichés. Par défaut, ce sont les entiers \(0,1,\ldots\), mais d’autres indices sont possibles. Comme pour les tableaux C++ ou les listes Python, on utiliser la notation ‘t[i]’ pour extraire la ligne d’indice i:

temperatures[3]

La taille de la série s’obtient de manière traditionnelle avec Python avec la fonction len:

len(temperatures)

Calculons la moyenne des températures à l’aide de la méthode mean:

temperatures.mean()

Calculez la température maximale à l’aide de la méthode max:

# YOUR CODE HERE
raise NotImplementedError()

Tableaux de données (DataFrame)

Nous allons maintenant construire un tableau contenant les données d’acidité de l’eau de plusieurs puits. Il aura deux colonnes: l’une pour le nom des puits et l’autre pour la valeur du pH (l’acidité).

Nous pouvons maintenant construire le tableau à partir de la liste des noms des puits et la liste des pH des puits:

df = pd.DataFrame({
    "Noms" : ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10'],
    "pH"   : [ 7.0,  6.5,  6.8,  7.1,  8.0,  7.0,  7.1,  6.8,  7.1,  7.1 ]
})
df

Vous remarquerez que:

  • Il est traditionnel de nommer df (pour DataFrame) la variable contenant le tableau. Mais il est souhaitable d’utiliser un meilleur nom chaque fois que naturel!

  • La première colonne du tableau donne l’index des lignes. Par défaut, il s’agit de leur numéro, commençant à 0, en plus des colonnes “Noms” et “pH”.

Ce tableau à deux dimensions est vu comme une collection de colonnes. De ce fait, df[label] extrait la colonne d’étiquette label, sous la forme d’une série:

df['Noms']

Vous pouvez ensuite accéder à chacune des valeurs du tableau en en précisant le label de sa colonne puis l’indice de sa ligne. Voici le nom dans la deuxième ligne (indice 1):

df['Noms'][1]

Et la valeur du pH dans la quatrième ligne (indice 3):

df['pH'][3]

Là encore, vous remarquerez que l’accès est de la forme df[colonne][ligne] alors qu’avec un tableau C++ l’accès serait de la forme t[ligne][colonne].

Métadonnées et statistiques

Nous utilisons maintenant Pandas pour extraire quelques métadonnées et statistiques de nos données.

D’abord la taille du tableau:

df.shape

Le titre des colonnes:

df.columns

Le nombre de lignes:

len(df)

Des informations générales:

df.info()

La moyenne de chaque colonne pour laquelle cela fait du sens (ici que le pH):

df.mean()

Les écarts-types:

df.std()

La mediane:

df.median()

Le quantile 25%:

df.quantile(.25)

Les valeurs min et max:

df.min()
df.max()

Un résumé des statistiques principales:

df.describe()

L’indice du pH max:

df['pH'].idxmax()

Un histogramme des pH:

df['pH'].hist(bins=20);

Avec pyplot, il est possible de peaufiner le résultat en ajoutant des labels aux axes, etc:

import matplotlib.pyplot as plt

df['pH'].plot(kind='hist')
plt.grid()
plt.ylabel('Counts')
plt.xlabel('pH');

Opérations de bases de données

Pandas permet de faire des opérations de bases de données (pour ceux qui ont fait le projet Données Libres, souvenez-vous de «select», «group by», «join», etc.). Nous ne montrons ici que la sélection de lignes; vous jouerez avec «group by» plus loin.

Transformons le tableau pour que l’une des colonnes serve d’indices; ici nous nous servirons des noms:

df1 = df.set_index('Noms')
df1

Il est maintenant possible d’accéder à une valeur de pH en utilisant directement le nom comme index de ligne:

df1['pH']['P1']

Sélectionnons maintenant toutes les lignes de pH \(7.1\):

df1[df1['pH'] == 7.1]

Comment cela fonctionne-t’il?

Notons pH la colonne de même label:

pH = df1['pH']
pH

Comme avec NumPy, toutes les opérations sont vectorisées sur tous les éléments du tableau ou de la séries. Ainsi, si l’on écrit pH + 1 (ce qui n’a pas de sens mathématique, les objets étant de type très différents: pH est une série tandis que 1 est un nombre), cela ajoute 1 à toutes les valeurs de la série:

pH + 1

De manière similaire, si l’on écrit pH == 7.1, cela renvoie une série de booléens, chacun indiquant si la valeur correspondante est égale ou non à \(7.1\):

pH == 7.1

Enfin, si l’on indexe un tableau par une série de booléen, cela extrait les lignes pour lesquelles la série contient True:

df1[pH == 7.1]

Exercice: les notes

Vos notes d’Info 111 (anonymes!) sont dans le fichier CSV GradesInfo111.csv. Consultez le contenu de ce fichier: vous noterez que les valeurs sont séparées par des virgules ‘,’ (Comma Separated Value).

Voici comment charger ce fichier comme tableau Pandas:

df = pd.read_csv("GradesInfo111.csv", sep=",")

Affichez en les statistiques simples:

# YOUR CODE HERE
raise NotImplementedError()

Avec qgrid, vous pouvez explorer interactivement le tableau, et même changer des valeurs; faites quelques essais de filtrage, de tri, d’édition (non, non cela ne changera pas vos notes d’Info 111 :-) ):

import qgrid
qgrid.show_grid(df)

Exercice: Les prénoms

Dans cet exercice il s’agit d’analyser une base de données qui porte sur les prénoms donnés à Paris entre 2004 et 2018 (souvenirs du S1?). Ces données sont librement accessibles également sur le site opendata de la ville de Paris. La commande shell suivante va les télécharger dans le fichier liste_des_prenoms.csv s’il n’est pas déjà présent.

!if [ ! -f liste_des_prenoms.csv ]; then  \
    curl https://opendata.paris.fr/explore/dataset/liste_des_prenoms/download/\?format\=csv\&timezone\=Europe/Berlin\&lang\=fr\&use_labels_for_header\=true\&csv_separator\=%3B -o liste_des_prenoms.csv; \
fi
  1. Ouvrez le fichier pour consulter son contenu.

  2. En vous inspirant de l’exemple ci-dessus, importez le fichier liste_des_prenoms.csv dans un tableau prenoms. Attention: le fichier utilise des ‘;’ comme séparateurs et non des ‘,’.

# YOUR CODE HERE
raise NotImplementedError()

Si le test ci-dessous ne passe pas, vérifiez que vous avez bien appelé votre tableau prenoms:

# YOUR CODE HERE
raise NotImplementedError()
  1. Affichez les dix premières lignes du fichier.
    Indication: utiliser la méthode head.

# YOUR CODE HERE
raise NotImplementedError()
  1. Affichez les lignes correspondant à votre prénom:

# YOUR CODE HERE
raise NotImplementedError()

Faites de même graphiquement avec qgrid:

qgrid.show_grid(prenoms)
  1. Extraire dans une variable prenoms_femmes les prénoms de femmes (avec répétitions), sous la forme d’une série.
    Indication: retrouvez dans les exemples ci-dessus comment sélectionner des lignes et comment extraire une colonne.

# YOUR CODE HERE
raise NotImplementedError()

Combien y en a-t’il?

# YOUR CODE HERE
raise NotImplementedError()
# Vérifie que prenoms_femmes est bien une série
assert isinstance(prenoms_femmes, pd.Series)
# Vérifie le premier prénom alphabetiquement
assert prenoms_femmes.min() == 'Aaliyah'
# Vérifie le nombre de prénoms après suppression des répétitions
assert len(set(prenoms_femmes)) == 1391
  1. Procéder de même pour les prénoms d’hommes:

# YOUR CODE HERE
raise NotImplementedError()
# YOUR CODE HERE
raise NotImplementedError()

Une petite vérification:

assert len(prenoms_hommes) + len(prenoms_femmes) == len(prenoms)

Vous avez maintenant tous les outils requis pour passer à l’étude statistique qui est le cœur de ce TP. Vous finirez cette fiche ultérieurement.

Exercice \(\clubsuit\)

Quel est le prénom le plus déclaré, en cumulé sur toutes les années? Affectez le à la variable prenom.

Indication: consulter le premier exemple à la fin de la documentation de la méthode groupby pour calculer les nombres cumulés par prénom (avec sum). Puis utilisez qgrid pour visualiser le résultat et le trier, ou bien utilisez sort_values, ou idx_max.

# YOUR CODE HERE
raise NotImplementedError()
# YOUR CODE HERE
raise NotImplementedError()

Ce test vérifie que vous avez trouvé la bonne réponse; sans vous la donner; la magie des fonctions de hachage :-)

import hashlib
assert hashlib.md5(prenom.encode("utf-8")).hexdigest() == 'b70e2a0d855b4dc7b1ea34a8a9d10305'