Avec Numpy
et Matplotlib
, la librairie Pandas
fait partie des librairies de base pour la data science en Python. Pandas fournit des structures de données puissantes et simples à utiliser, ainsi que les moyens d'opérer rapidement des opérations sur ces structures. Dans ce chapitre, nous verrons l'intérêt de la librairie Pandas, ainsi que les opérations basiques sur l'objet phare de cette librairie, le dataframe.
Mais commençons par un peu de réflexion !
Réfléchissons un peu
Imaginons que nous souhaitions représenter des animaux. Disons... des pandas par exemple !
Nous pouvons caractériser un panda par sa taille ; ou plutôt, par ses tailles : disons par exemple, la taille de ses pattes, la longueur moyenne des poils de sa fourrure, la taille de sa queue, et le diamètre de son ventre.
On peut représenter ce pandas par un tableau numpy :
import numpy as np
un_panda_numpy = np.array([100,5,20,80])
un_panda_numpy
array([100, 5, 20, 80])
Ici, notre panda a des pattes de 100cm, des poils de 5cm en moyenne, une queue de 20cm et un ventre de 80cm de diamètre.
Et si je veux représenter plusieurs pandas, je crée une liste de np.array
?
C'est une bonne idée. Il est effectivement possible de faire cela :
famille_panda = [
np.array([100, 5 , 20, 80]), # maman panda
np.array([50 , 2.5, 10, 40]), # bébé panda
np.array([110, 6 , 22, 80]), # papa panda
]
Mais comme nous l'avons vu précédemment, nous pouvons faire mieux car notre liste de 3 pandas, c'est en fait un tableau multidimensionnel, que NumPy gère très bien !
famille_panda = [
[100, 5 , 20, 80], # maman panda
[50 , 2.5, 10, 40], # bébé panda
[110, 6 , 22, 80], # papa panda
]
famille_panda_numpy = np.array(famille_panda)
famille_panda_numpy
array([[ 100. , 5. , 20. , 80. ], [ 50. , 2.5, 10. , 40. ], [ 110. , 6. , 22. , 80. ]])
Quel avantage?
Par exemple, supposons que je veuille obtenir la taille des pattes (situé en position 0 dans chaque liste qui décrit un panda) du panda situé en position 2 dans ma liste (c'est-à-dire papa panda). Numpy offre la possibilité de le faire élégamment :
famille_panda_numpy[2, 0] # taille des pattes de papa panda, à la manière numpy
110.0
Et si je veux connaître les tailles des pattes de toute ma famille panda ?
Facile ! Il suffit de supprimer le 2 (qui correspondait au papa panda), et de le remplacer par le caractère:
, signifiant que je veux TOUS les pandas !
famille_panda_numpy[:, 0]
array([ 100., 50., 110.])
Voilà ! La taille des pattes de la maman est de 100cm, celle du bébé 50cm, et celle du papa 110cm.
C'est assez pratique certes, mais écrire famille_panda_numpy[:, 0]
quand on veut connaître la taille des pattes des pandas, ce n'est pas très explicite. j'aimerais pouvoir spécifier quelque-part que ce 0
correspond à la taille des pattes !
Cela nous amène à la librairie Pandas !
La librairie Pandas
Tout est une histoire de tableau !
Au fait, la manière dont j'ai écrit la variablefamille_panda
:
famille_panda = [
[100, 5 , 20, 80],
[50 , 2.5, 10, 40],
[110, 6 , 22, 80],
]
... ça ressemble un peu à un tableau avec des lignes et des colonnes non ? Vous ne trouvez pas que c'est très similaire à cela?
| pattes | poil | queue | ventre |
maman_panda | 100 | 5 | 20 | 80 |
bébé_panda | 50 | 2.5 | 10 | 40 |
papa_panda | 110 | 6 | 22 | 80 |
(La bonne réponse est : "Oui, c'est très similaire")
Dans ce cas, pourquoi ne pas utiliser une librairie Python qui manipulerait des tableaux comme celui-ci ? Cette librairie existe, et elle s'appelle Pandas
.
C'est parti, représentons nos pandas avec Pandas
!
import pandas as pd
famille_panda_df = pd.DataFrame(famille_panda)
famille_panda_df
0 1 2 3 0 100 5.0 20 80 1 50 2.5 10 40 2 110 6.0 22 80
L'objet qui permet de représenter des "tableaux" est l'objet DataFrame
. Pour instancier un tel objet (et pour lui donner de la donnée), on lui transmet une liste de rang 2, c'est à dire une liste de listes.
Nous pouvons même faire mieux, en indiquant les noms de colonnes et les noms des lignes. Et encore mieux : la librairie Pandas
se base en grande partie sur la librairie Numpy
dans son fonctionnement interne. Comme pandas
connaît intimement Numpy
, on peut très bien transmettre à l'objet DataFrame
de la donnée au format ndarray
:
famille_panda_df = pd.DataFrame(famille_panda_numpy,
index = ['maman', 'bebe', 'papa'],
columns = ['pattes', 'poil', 'queue', 'ventre'])
famille_panda_df
pattes poil queue ventre maman 100.0 5.0 20.0 80.0 bebe 50.0 2.5 10.0 40.0 papa 110.0 6.0 22.0 80.0
Vous allez encore me demander quel est l'intérêt de cette librairie n'est-ce pas ?
En fait, l'objet DataFrame est très similaire à certains concepts que l'on trouve en dehors du cadre du langage Python. Il est similaire :
aux tables des bases de données relationnelles (type MySQL, PostgreSQL, etc.) que l'on manipule grâce au langage SQL
à l'objet dataframe sur lequel se base tout le langage R, langage destiné aux statisticiens et aux Data Analysts
Du coup, si vous connaissez déjà le langage SQL ou le langage R, vous aurez beaucoup de facilité à utiliser le DataFrame
de Pandas
!
Quelques fonctionnalités des DataFrames
Voici quelques petites fonctionnalités des Dataframes.
Tout d'abord, accédons à la colonne ventre de notre table. Il y a deux syntaxes possibles, qui renvoient exactement le même résultat :
famille_panda_df.ventre
famille_panda_df["ventre"]
maman 80.0 bebe 40.0 papa 80.0 Name: ventre, dtype: float64
Parcourrons maintenant tous les pandas un à un, grâce à la méthode iterrows
qui renvoie (à chaque itération de la boucle for
) un tuple dont le premier élément est l'index de la ligne, et le second le contenu de la ligne en question :
for ind_ligne, contenu_ligne in famille_panda_df.iterrows():
print("Voici le panda %s :" % ind_ligne)
print(contenu_ligne)
print("--------------------")
Voici le panda maman : pattes 100.0 poil 5.0 queue 20.0 ventre 80.0 Name: maman, dtype: float64 -------------------- Voici le panda bebe : pattes 50.0 poil 2.5 queue 10.0 ventre 40.0 Name: bebe, dtype: float64 -------------------- Voici le panda papa : pattes 110.0 poil 6.0 queue 22.0 ventre 80.0 Name: papa, dtype: float64 --------------------
Accédons maintenant au papa panda : d'abord par sa position (position 2), puis par son nom "papa". Le résultat retourné est exactement le même dans les 2 cas.
famille_panda_df.iloc[2] # Avec iloc(), indexation positionnelle
famille_panda_df.loc["papa"] # Avec loc(), indexation par label
pattes 110.0 poil 6.0 queue 22.0 ventre 80.0 Name: papa, dtype: float64
Déterminons les pandas dont le diamètre du ventre est de 80cm :
famille_panda_df["ventre"] == 80
maman True bebe False papa True Name: ventre, dtype: bool
Le résultat de cette opération est très pratique pour filtrer des lignes ! Par exemple, pour sélectionner uniquement les pandas dont le ventre est de 80cm, il suffit d'intégrer ce précédent résultat en tant que masque, comme ceci :
masque = famille_panda_df["ventre"] == 80
pandas_80 = famille_panda_df[masque]
# On écrit plus souvent cela de cette manière :
# pandas_80 = famille_panda_df[famille_panda_df["ventre"] == 80]
pandas_80
pattes poil queue ventre maman 100.0 5.0 20.0 80.0 papa 110.0 6.0 22.0 80.0
Qu'est-ce qu'un masque ?
Lorsque vous mettez un masque sur votre visage, celui-ci cache certaines parties de votre tête, mais en laisse visible d'autres : vos yeux, votre nez et votre bouche. Ici, c'est un peu la même chose : comme nous souhaitons ne garder que certaines lignes du dataframe, et en cacher d'autres, nous appliquons un masque. Un masque est ici une liste de variables booléennes (True
ouFalse
) dans laquelle chaque élément est associé à une ligne du dataframe. Si cet élément estTrue
, cela signfie que nous souhaitons garder la ligne en question. Si nous souhaitons ne pas garder la ligne, cet élément estFalse
.
Pour inverser le masque, il suffit d'utiliser l'opérateur~
, et nous sélectionnons les pandas qui n'ont pas un ventre de 80cm :
famille_panda_df[~masque]
pattes poil queue ventre bebe 50.0 2.5 10.0 40.0
Maintenant, ajoutons des lignes à notre dataframe
. Il y a plusieurs méthodes pour cela, mais voyons ici la plus simple : assemblons ensemble deux dataframes.
quelques_pandas = pd.DataFrame([[105,4,19,80],[100,5,20,80]], # deux nouveaux pandas
columns = famille_panda_df.columns)
# même colonnes que famille_panda_df
tous_les_pandas = famille_panda_df.append(quelques_pandas)
tous_les_pandas
pattes poil queue ventre maman 100.0 5.0 20.0 80.0 bebe 50.0 2.5 10.0 40.0 papa 110.0 6.0 22.0 80.0 0 105.0 4.0 19.0 80.0 1 100.0 5.0 20.0 80.0
Dans le dataframe tous_les_pandas
, il y a des doublons. En effet, le premier panda (maman) et le dernier panda (dont l'index est 1) ont exactement les mêmes mesures. Si nous souhaitions dédoublonner, nous ferions ceci :
tous_les_pandas.drop_duplicates()
pattes poil queue ventre maman 100.0 5.0 20.0 80.0 bebe 50.0 2.5 10.0 40.0 papa 110.0 6.0 22.0 80.0 0 105.0 4.0 19.0 80.0
Allez, encore un petit effort ! Voici quelques autres fonctionnalités en vrac, que nous utiliserons dans le projet :
# accéder aux noms des colonnes
famille_panda_df.columns
# créer une nouvelle colonne, composée de chaînes de caractères
famille_panda_df["sexe"] = ["f", "f", "m"]
# la maman et le bébé sont des femelles, le papa est un mâle
# obtenir le nombre de lignes
len(famille_panda_df)
# obtenir les valeurs distinctes d'une colonne :
# pour la colonne ventre, il y a deux valeurs distinctes : 40 et 80
famille_panda_df.ventre.unique()
array([ 80., 40.])
Lire un fichier CSV avec Pandas
Un fichier CSV (comma separated values), c'est un fichier permettant de représenter des données sous forme de tableau. D'ailleurs, si vous utilisez un logiciel de type tableur, il y a fort à parier qu'il puisse lire et exporter au format CSV !
Vous me voyez venir ? La librairie Pandas est justement spécialisée dans la manipulation de tableaux ! Lire un fichier CSV avec Pandas est donc un jeu d'enfant : il ne suffit que d'une ligne pour créer un dataframe à partir d'un CSV :
data = pd.read_csv("data.csv", sep=";")
C'est fait ! La variabledata
contient maintenant un dataframe contenant les données du fichier csv. Petite particularité ici : dans notre fichier CSV, les valeurs sont séparées par le caractère;
. Par défaut, pd.read_csv
attend des valeurs séparées par une virgule. Nous sommes donc obligés de spécifier sep=";"
. Pour en savoir plus sur read_csv
, faites un petit tour sur la doc ! Pour avoir un exemple d'utilisation, visitez le haut de ce chapitre.