L’une des choses les plus importantes en data science ; c’est de pouvoir explorer, transformer, visualiser, comprendre vos données afin d’en retirer le maximum d’informations. C’est donc le rôle de pandas une bibliothèque python permettant l’analyse et la manipulation de données.
Pandas est donc un puissant outil d’analyse et de manipulation de données (open source) facile à utiliser, écrit en python.
Dans cet article, nous présenterons les structures de données de bases dans pandas et montrerons comment les utiliser afin de manipuler aisément nos données.
1. Installer et importer Pandas
Si vous avez déjà installé la distribution Anaconda (sinon cliquez ici savoir comment l’installer rapidement) il vous suffit d’installer Pandas comme suit :
conda install pandas
ou via l’installateur de paquet python pip comme suit :
pip install pandas
Tout comme nous importons NumPy sous l’alias np, nous importerons Pandas sous l’alias pd (c’est un peu comme une convention) :
import pandas as pd
2. Les éléments de bases : les Series et les DataFrames
En effet, comme toute autre bibliothèque, pandas a ses propres structures de données que sont les series et les dataFrames. Ces structures de données peuvent être considérées comme des versions améliorées de tableaux structurés de NumPy.
Les lignes suivantes ont alors tous leurs sens (si on veut travailler avec pandas il faut bien évidement importer Numpy).
import numpy as np
import pandas as pd
Les Series
Une série dans pandas n’est juste qu’un tableau NumPy à une dimension (voir l’article sur NumPy). Elle peut être créée assez aisément comme suit :
data = pd.Series([1.0, 2.0 , 2.25, 3.5])
Pour accéder aux données contenues dans cette série on utilise l’attribut « values » comme suit :
data.values
et on obtient :
array([1. , 2. , 2.25, 3.5 ])
L’une des fonctionnalités assez intéressante de pandas est de pouvoir associer ses propres indexes aux données. Pour ce faire, on utilise l’attribut « index«
data.index = ['a','b','c','d']
et on obtient :
a 1.00
b 2.00
c 2.25
d 3.50
dtype: float64
On peut ainsi dire qu’une série pandas est une sorte de dictionnaire (clé/valeur) où les clés sont les indexes et les valeurs sont les données de la série.
Il est donc possible de créer une série à partir d’un dictionnaire python.
population_dict = {'Paris': 2187526,
'Marseille': 863310,
'Lyon': 516092,
'Toulouse': 479553,
'Nice': 340017}
population = pd.Series(population_dict)
Les DataFrames
Un dataFrame est une structure de données très importante dans pandas. Un dataFrame est un tableau à deux dimensions. On peut même dire que c’est une collection de « Series pandas ».
Afin d’étayer mes propos, je crée une nouvelle ‘Series’ qui contiendra les superficies (km2) des villes citées dans l’exemple plus haut.
area_dict = {'Paris': 105.4,
'Marseille': 240.6,
'Lyon': 47.87,
'Toulouse': 118.3,
'Nice': 71.92}
area = pd.Series(area_dict)
La création d’un dataFrame pandas se fait simplement avec « pd.dataFrame() » comme suit :
cities = pd.DataFrame({'population': population,'area': area})
Dans le cas où on n’a pas de Series sous la main, on peut créer un dataFrame directement à partir du jeu de données.
Supposons qu’on ait le jeu de données suivant :
#un dictionnaire comportant uniquement les données sur la population
#et les superficies de chaque ville
data = { 'population': [2187526,863310,516092,479553,340017],
'area': [105.4, 240.6,47.87,118.3, 71.92]
}
Pour créer notre dataFrame nous pouvons procéder ainsi
#en précisant les indexes
cities = pd.DataFrame(data,index=['Paris','Marseille','Lyon','Toulouse','Nice'])
On obtient le même dataFrame
population | aerea | |
Paris | 2187526 | 105.40 |
---|---|---|
Marseille | 863310 | 240.60 |
Lyon | 516092 | 47.87 |
Toulouse | 479553 | 118.30 |
Nice | 340017 | 71.92 |
3. Les Opérations de base avec Pandas
Pandas nous permet de manipuler aisément les données d’une série ou d’un dataFrame, de lire des fichiers, d’appliquer des fonctions aux données …
Accéder aux données
On reprend l’exemple précédent de notre dataFrame.
On a .head() qui nous permet de d’accéder par défaut aux premières lignes de notre dataFrame. (Si on ne met pas de paramètres il nous retourne les 5 premières lignes).
# Les 3 premières lignes
cities.head(3)
Ensuite, on peut accéder uniquement à une colonne de notre dataFrame ou à un sous-ensemble du dataFrame :
# Seulement la colonne population
cities["population"]
# Le dataFrame à partir de la 3e colonne
cities[2:]
Il est aussi possible d’accéder aux données du tableau par le biais de leur position grâce à .iloc()
# la 1e ligne
cities.iloc[0]
# Les 1e et 2e lignes
cities.iloc[[0, 1]]
Filtrer, trier, ranger
Un truc dans pandas qui est vraiment cool, c’est la possibilité de filtrer les données à la volée.
Ex: On veut voir uniquement les villes dont la population est supérieure à 700 000. On le fait comme suit :
cities[ cities["population"] > 700000 ]
De plus, il est aussi possible d’ordonner le dataFrame en fonction d’une colonne.
En supposant que je voudrai par exemple classer dans l’ordre décroissant les villes en fonction de leur superficie.
cities.sort_values(by="area", ascending=False)
Ouvrir et enregistrer des fichiers avec pandas
Ouvrir un CSV : rien de plus simple !
# On suppose que vous avez un fichier fichier.csv
df = pd.read_csv('fichier.csv')
Pareil pour les fichiers JSON
# On suppose que vous avez un fichier fichier.json
df = pd.read_json('fichier.json')
Il est aussi possible de se connecter à une base de données avec pandas. Cependant nous n’aborderons pas ce point dans cet article.
C’est bien de pouvoir lire des données mais c’est encore mieux de pouvoir les exporter. Pandas nous permet d’exporter notre dataFrame en CSV, JSON…
# Notre dataFrame d'exemple
cities.to_csv('cities.csv') #en csv
cities.to_json('cities.json') #en json
Obtenir des informations sur des données
Nous utiliserons à partir de maintenant un dataset des données historiques de la NBA (disponible ici).
# On importe notre dataset nba.csv
nba = pd.read_csv("nba.csv")
La première chose à faire avec un dataFrame pandas (à mon avis) c’est d’utiliser .info()
nba.info()
Et on obtient un bon paquet d’infos sur notre dataFrame.
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 126314 entries, 0 to 126313
Data columns (total 23 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 gameorder 126314 non-null int64
1 game_id 126314 non-null object
2 lg_id 126314 non-null object
3 _iscopy 126314 non-null int64
4 year_id 126314 non-null int64
5 date_game 126314 non-null object
6 seasongame 126314 non-null int64
7 is_playoffs 126314 non-null int64
8 team_id 126314 non-null object
9 fran_id 126314 non-null object
10 pts 126314 non-null int64
11 elo_i 126314 non-null float64
12 elo_n 126314 non-null float64
13 win_equiv 126314 non-null float64
14 opp_id 126314 non-null object
15 opp_fran 126314 non-null object
16 opp_pts 126314 non-null int64
17 opp_elo_i 126314 non-null float64
18 opp_elo_n 126314 non-null float64
19 game_location 126314 non-null object
20 game_result 126314 non-null object
21 forecast 126314 non-null float64
22 notes 5424 non-null object
dtypes: float64(6), int64(7), object(10)
memory usage: 22.2+ MB
.info() fournit des détails essentiels sur notre dataFrame pandas, tels que le nombre de lignes et de colonnes, le nombre de valeurs non nulles, le type de données de chaque colonne et la quantité de mémoire utilisée par notre DataFrame.
On peut déjà remarquer à ce niveau que la colonne 22 (notes) a un grand nombre de valeurs nulles.
Une autre information importante de notre tableau c’est la forme du tableau. En gros, le nombre lignes et le nombre de colonnes. On obtient cela par le biais de .shape.
nba.shape
(126314, 23)
Afin de mieux comprendre notre jeu de données, on peut aussi utiliser .describe() qui pourra nous afficher certains détails statistiques de base tels que la distribution des données, la moyenne, la variance, etc.
nba.describe()
gameorder | _iscopy | year_id | seasongame | is_playoffs | pts | elo_i | elo_n | win_equiv | opp_pts | opp_elo_i | opp_elo_n | forecast | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 | 126314.000000 |
mean | 31579.000000 | 0.500000 | 1988.200374 | 43.533733 | 0.063857 | 102.729982 | 1495.236055 | 1495.236055 | 41.707889 | 102.729982 | 1495.236055 | 1495.236055 | 0.500000 |
std | 18231.927643 | 0.500002 | 17.582309 | 25.375178 | 0.244499 | 14.814845 | 112.139945 | 112.461687 | 10.627332 | 14.814845 | 112.139945 | 112.461687 | 0.215252 |
min | 1.000000 | 0.000000 | 1947.000000 | 1.000000 | 0.000000 | 0.000000 | 1091.644500 | 1085.774400 | 10.152501 | 0.000000 | 1091.644500 | 1085.774400 | 0.020447 |
25% | 15790.000000 | 0.000000 | 1975.000000 | 22.000000 | 0.000000 | 93.000000 | 1417.237975 | 1416.994900 | 34.103035 | 93.000000 | 1417.237975 | 1416.994900 | 0.327989 |
50% | 31579.000000 | 0.500000 | 1990.000000 | 43.000000 | 0.000000 | 103.000000 | 1500.945550 | 1500.954400 | 42.113357 | 103.000000 | 1500.945550 | 1500.954400 | 0.500000 |
75% | 47368.000000 | 1.000000 | 2003.000000 | 65.000000 | 0.000000 | 112.000000 | 1576.060000 | 1576.291625 | 49.635328 | 112.000000 | 1576.060000 | 1576.291625 | 0.672011 |
max | 63157.000000 | 1.000000 | 2015.000000 | 108.000000 | 1.000000 | 186.000000 | 1853.104500 | 1853.104500 | 71.112038 | 186.000000 | 1853.104500 | 1853.104500 | 0.979553 |
4. Traiter les valeurs nulles et manipuler vos données avec pandas
Lorsque nous traitons des données, il est impératif de s’assurer qu’on a des données fiables (pas de valeurs nulles, pas de valeurs aberrantes, …).
Pandas nous propose de nombreux outils pour faire face à ces problèmes.
Traiter les valeurs nulles
En effet, traiter les valeurs peut se présenter comme un grand dilemme. Faut-il supprimer les lignes où il y a des null
? Ou faut-il les remplacer par d’autres valeurs (imputation) ?
Il n’y a pas de remède miracle 🙃 cependant, l’idée sera de ne pas créer un biais dans les données.
Alors comment supprimer les null
dans pandas ?
# On supprime les *null* avec dropna
nba_dropna = nba.dropna()
# regardons le nombre de données que nous avons maintenant
nba_dropna.shape
(5424, 23)
On voit qu’on perd beaucoup de lignes si l’on procède ainsi. Analysons les null
par colonne.
nba.isnull().sum()
gameorder 0
game_id 0
lg_id 0
_iscopy 0
year_id 0
date_game 0
seasongame 0
is_playoffs 0
team_id 0
fran_id 0
pts 0
elo_i 0
elo_n 0
win_equiv 0
opp_id 0
opp_fran 0
opp_pts 0
opp_elo_i 0
opp_elo_n 0
game_location 0
game_result 0
forecast 0
notes 120890
dtype: int64
On peut supprimer cette colonne mais nous allons essayer de les garder.
# On essaie de voir ce que contient cette colonne
nba["notes"].dropna().head(10)
930 at Baltimore's Fifth Regiment Armory
931 at Baltimore's Fifth Regiment Armory
1084 Tiebreaker
1085 Tiebreaker
1088 Tiebreaker
1089 Tiebreaker
1212 at Chicago IL
1213 at Chicago IL
1264 at Chicago IL
1265 at Chicago IL
Name: notes, dtype: object
Je crois qu’on a le lieu où s’est déroulé la rencontre. Cette information pourrait être importante. Nous allons faire de l’imputation.
Comment faire de l’imputation dans pandas ?
L’imputation est le processus de remplacement des données manquantes avec des valeurs substituées. (wikpédia)
Lorsqu’il s’agit de données numériques (le plus souvent), on peut remplacer les données manquantes par la moyenne, la médiane…
Pour le cas des données textuelles, on peut aller chercher une autre source de données qui nous permettra compléter le dataset.
On peut aussi (le cas des données textuelles) remplacer les null
par une valeur par défaut (chose que nous allons faire ici).
On utilise .fillna() pour remplacer les valeurs nulles.
# On remplace les *null* par 'unknown'
nba["notes"].fillna("unknown", inplace=True)
Notez qu’en utilisant inplace=True
nous avons modifié notre dataFrame de base.
Et lorsqu’on essaie de compter les null
. On observe qu’on n’en a plus !
nba.isnull().sum()
gameorder 0
game_id 0
lg_id 0
_iscopy 0
year_id 0
date_game 0
seasongame 0
is_playoffs 0
team_id 0
fran_id 0
pts 0
elo_i 0
elo_n 0
win_equiv 0
opp_id 0
opp_fran 0
opp_pts 0
opp_elo_i 0
opp_elo_n 0
game_location 0
game_result 0
forecast 0
notes 0
dtype: int64
Appliquer des fonctions à son dataFrame pandas
Lorsque je veux itérer sur un dataFrame avec (for , while,…), on se rend compte que l’exécution est parfois lente pour les jeux de données assez volumineux.
Pandas nous propose alors la méthode .apply() qui permettra d’appliquer une fonction sur chaque ligne (ou colonne) de notre dataFrame.
Ex: On a plus haut remplacé les null
par unknown
; on peut utiliser apply pour remplacer les valeurs unknown
par le nom de l’équipe où s’est déroulé la rencontre. On définit une fonction pour effectuer ce traitement puis on l’applique au dataFrame pandas avec .apply().
# Notre fonction
def replaceUnknown(row):
if row["notes"] == "unknown" :
if row["game_location"] == "H" : #home
return row["fran_id"]
elif row["game_location"] == "A": #away
return row["opp_fran"]
return row["notes"]
Puis
nba["notes"]=nba.apply(lambda x : replaceUnknown(x), axis=1)
En effet, la méthode .apply() peut permettre de faire tout ce qu’on veut (pratiquement) avec notre dataFrame pandas.
Ressources complémentaires
La documentation officielle de pandas
10 minutes to pandas
Le repository GitHub de l’article
Conclusion
Nous avons présenté dans cet article la librairie python pandas qui est souvent utilisée dans le monde de la data. Nous avons aussi présenté certaines fonctionnalités de la lib.
Cet article est vraiment introductif à pandas, je vous invite donc à visiter d’autres ressources afin d’améliorer vos connaissances sur le sujet. N’hesitez pas à laisser des commentaires pour les questions et suggestions 🙃.