In [ ]:
from __future__ import division, print_function # Python 3
from sympy import init_printing
init_printing(use_latex='mathjax',use_unicode=False) # Affichage des résultats
Les données massives jouent et continueront de jouer un rôle important dans la société du 21e siècle. Dans ce chapitre, nous ferons une introduction à une librairie de l'environnement Python qui permet de représenter et analyser des données. Cette librairie s'appelle pandas, contraction des termes anglais "panel" et "data". Dans pandas, un tableau 3-dimensionnel de données est appelé un "panel". La librairie pandas joue le même rôle qu'un tableur comme Microsoft Excel, LibreOffice calc ou celui qu'on retrouve dans GeoGebra.
Les principales structures de données de pandas sont les Series
(pour stocker des données selon une dimension) et les DataFrame
(pour stocker des données selon 2 dimensions - lignes et colonnes). On peut aussi représenter des données selon trois dimensions ou plus avec Panel
et Panel4D
.
Dans ce chapitre nous décrivons les tableaux de données à une et deux dimensions. Nous verrons comment faire des calculs statistiques et créer des graphiques à partir de celles-ci. Nous verrons comment importer et exporter des données. Finalement, nous ferons un exemple basé sur le site de données de la Belgique: http://data.gov.be/. On trouvera plus d'informations dans la documentation en ligne de pandas incluant une introduction en 10 minutes, les notions de base et quelques tutoriels.
En utilisant sympy, construisons une liste de 0 et de 1 telle qu'un 1 est à la position i
si et seulement si i
est un nombre premier:
In [ ]:
from sympy import isprime
L = [isprime(i) for i in range(15)]
L
Out[ ]:
La librairie pandas permet de représenter les tableaux unidimensionnels de données appelés séries. Faisons un premier exemple. La liste Python ci-haut peut être transformée en une série de pandas en faisant comme suit:
In [ ]:
from pandas import Series
s = Series(L)
s
Out[ ]:
Par défaut, les indices sont les nombres de 0
à n-1
où n
est la taille de la liste. On peut accéder aux éléments de la série de la même façon qu'on le fait pour les éléments d'une liste:
In [ ]:
s[0]
Out[ ]:
In [ ]:
s[7]
Out[ ]:
L'intérêt des séries de pandas par rapport aux listes Python de base est qu'un grand nombres de fonctions utiles sont disponibles sur les séries de pandas et qui retournent souvent d'autres séries. Par exemple, on peut obtenir une brève description statistique des éléments d'une série avec la méthode describe()
:
In [ ]:
s.describe()
Out[ ]:
Ci-haut, cela nous indique qu'il y a deux valeurs distinctes dans la série et que False
est la plus fréquence avec 9 apparitions sur 15. En effet, il y 6 nombres premiers inférieurs à 15.
On peut obtenir la séries des sommes cumulées d'une série avec la méthode cumsum()
. Ici False
vaut zéro et True
vaut 1
:
In [ ]:
s.cumsum()
Out[ ]:
In [ ]:
t = s.cumsum()
t * 1000 + 43
Out[ ]:
On peut aussi appliquer une fonction aux éléments d'une série avec la méthode apply
:
In [ ]:
def carre_plus_trois(x):
Out[ ]:
In [ ]:
t.apply(carre_plus_trois)
Out[ ]:
In [ ]:
from pandas import concat
concat([s, s.cumsum()])
Out[ ]:
La concaténation a été faite une en-dessous de l'autre et cela a aussi eu pour effet de transformer les valeurs booléennes en nombres entiers, car les données d'une même colonne doivent avoir le même type. Ce n'est pas exactement ce qu'on voulait. Pour spécifier que la concaténation doit être faite en colonnes, il faut spécifier dans quelle direction (axe) ou veut concaténer les données. On donne alors une valeur 1
à l'argument axis
plutôt que 0
(la valeur par défaut) pour obtenir ce que l'on veut:
In [ ]:
concat([s, s.cumsum()], axis=1)
Out[ ]:
Pour donner des titres plus parlant aux colonnes, il s'agit de spécifier une liste de titres via l'argument keys
. Comme le nombre de nombres entiers inférieur à x
est souvent dénoté $\pi(x)$, on utilise 'pi_x'
pour le nom de la deuxième colonne:
In [ ]:
keys = ['isprime', 'pi_x']
df = concat([s, s.cumsum()], axis=1, keys=keys)
df
Out[ ]:
Le type du tableau ci-haut est DataFrame
pour tableau de données:
In [ ]:
type(df)
Out[ ]:
In [ ]:
from pandas import DataFrame
D'abord, on calcule en Python la liste des sommes cumulées de la liste L
:
In [ ]:
L = [isprime(i) for i in range(15)]
L_cumsum = [sum(L[:i]) for i in range(1,len(L)+1)]
L_cumsum
Out[ ]:
On crée un dictionnaire qui associe des noms de colonnes à des valeurs:
In [ ]:
d = {'isprime':L, 'pi_x':L_cumsum}
d
Out[ ]:
On crée un objet de type DataFrame
à partir de ce dictionnaire:
In [ ]:
df = DataFrame(d)
df
Out[ ]:
Comme pour les séries, on peut obtenir les statistiques simples pour les données de chaque colonne d'un tableau de données avec la méthode describe()
:
In [ ]:
df.describe()
Out[ ]:
Il est aussi possible de créer des tableaux de données en dimensions supérieures, mais cela dépasse le cadre de ce cours:
In [ ]:
from pandas import Panel,Panel4D
In [ ]:
df.pi_x
Out[ ]:
Comme pour un dictionnaire, on peut aussi accéder à une colonne avec les crochets. Il faut alors spécifier le nom de la colonne entre guillemets:
In [ ]:
df['pi_x']
Out[ ]:
Cela peut se combiner avec d'autres méthodes comme l'affichage de statistiques df.pi_x.describe()
ou encore des calculs:
In [ ]:
df.pi_x * 100
Out[ ]:
In [ ]:
L = [isprime(i) for i in range(1000)]
s = Series(L)
d = {'isprime':s, 'pi_x':s.cumsum()}
df = DataFrame(d)
Pour afficher les cinq premières lignes d'un tableau de données, on utilise la méthode head()
:
In [ ]:
df.head()
Out[ ]:
Pour afficher les cinq dernières lignes d'un tableau de données, on utilise la méthode tail()
:
In [ ]:
df.tail()
Out[ ]:
Les deux méthodes head
et tail
peuvent prendre un nombre entier en argument pour indiquer le nombre de lignes à afficher si on veut en voir plus ou moins:
In [ ]:
df.tail(10)
Out[ ]:
In [ ]:
df[500:520]
Out[ ]:
Pour accéder à une donnée particulière dans le tableau, on utilise la méthode at
en spécifiant l'indice de la ligne puis le nom de la colonne entre crochets:
In [ ]:
df.at[510, 'x_logx']
Out[ ]:
In [ ]:
df.at[510, 'pi_x']
Out[ ]:
Supposons que l'on veuille ajouter une colonne à un tableau. Cela se fait avec la méthode insert()
.
Johann Carl Friedrich Gauss avait deviné au 19e siècle que $\pi(x)$, le nombre de nombres premiers inférieurs à $x$, était approximativement $x/\log(x)$. Construisons une série qui calcule cette fonction pour les 1000 premiers nombres entiers:
In [ ]:
from math import log
def x_sur_log_x(x):
Out[ ]:
In [ ]:
t = Series(range(1000)).apply(x_sur_log_x)
On ajoute la nouvelle colonne avec la méthode insert
en spécifiant la position où on veut l'insérer, le titre de la colonne et les données:
In [ ]:
df.insert(2, 'x_logx', t)
df['x_logx'] = t # equivalent, notation comme les dictionnaires Python
En 1838, Dirichlet a contacté Gauss pour lui dire qu'il avait trouvé une meilleure approximation de la fontion $\pi(x)$ en utilisant l'intégrale de l'inverse de la fonction $\log(x)$, c'est-à-dire par la fonction $Li(x)=\int_2^x {1\over\log(t)} dt$.
En utilisant sympy, calculons les 1000 premières valeurs de $Li(x)$ et ajoutons cette colonne dans le tableau:
In [ ]:
from sympy import Li
K = [Li(x).n() for x in range(1000)]
df['Li_x'] = Series(K, dtype='float64')
On peut afficher les premières et dernières lignes du tableau à quatre colonnes:
In [ ]:
df.head()
Out[ ]:
In [ ]:
df.tail()
Out[ ]:
In [ ]:
%matplotlib inline
Pour visualiser les données, il suffit d'utiliser la commande plot
:
In [ ]:
df.plot()
On voit bien que $\pi(x)$, le nombre de nombres premiers inférieurs à $x$, se trouve bien entre les fonctions $\pi(x)$ et $Li(x)$ sur l'intervalle [0,1000]
.
On peut visualiser qu'une partie par exemple l'intervalle [0,100]
en choisissant d'abord un sous-tableau:
In [ ]:
df[:100].plot()
D'autres types de graphiques peuvent être plus adaptées dans d'autres situations (histogrammes, tartes, etc.). Voici la liste méthodes disponibles:
In [ ]:
df.plot.area df.plot.box df.plot.hist df.plot.pie
df.plot.bar df.plot.density df.plot.kde df.plot.scatter
df.plot.barh df.plot.hexbin df.plot.line
On trouvera des exemples d'utilisation de ces méthodes de visualisation de données dans la documentation de pandas:
http://pandas.pydata.org/pandas-docs/stable/visualization.html#visualization
Il est possible d'exporter un tableau de données de pandas vers plusieurs formats:
In [ ]:
df.to_[TOUCHE_TABULATION]
Out[ ]:
Pour exporter vers le format .xlsx
on fait:
In [ ]:
from pandas import ExcelWriter
writer = ExcelWriter('tableau.xlsx')
df.to_excel(writer,'Feuille 1')
writer.save()
On peut vérifier que Excel ouvre bien ce fichier qui se trouve dans le même répertoire que le notebook Jupyter (utiliser la commande pwd
, abbréviation de "present working directory" en anglais, pour connaître ce répertoire en cas de doute).
Pour exporter vers le format .csv
on fait:
In [ ]:
df.to_csv('tableau.csv')
In [ ]:
import pandas as pd
df = pd.read_excel('tableau.xlsx')
df.head()
Out[ ]:
Parfois, un fichier Excel est corrompu et il vaut mieux passer par le format .csv
. On procède alors ainsi:
In [ ]:
df = pandas.read_csv('tableau.csv')
df.head()
Out[ ]:
Parfois, la ligne de titre n'est pas sur la première ligne. À ce moment là, on peut spécifier la valeur de l'argument header
pour dire où commencer la lecture du fichier en entrée:
In [ ]:
df = pandas.read_csv('tableau.csv', header=56)
df.head()
Out[ ]:
Le site web http://data.gov.be/ contient des centaines de données de toutes sortes de sujet sur la Belgique. Par exemple, à la page
http://data.gov.be/fr/dataset/4fd7a1cf-f959-46ff-83d0-807778fe3438
on retrouve des données météorologiques de Ostende depuis 2010. Sur cette page, on peut y télécharger le fichier meteoostende.xls
au format excel. On peut l'importer dans pandas facilement:
In [ ]:
df = pandas.read_excel('meteoostende.xls')
Il est possible d'écrire l'URL directement ce qui évite d'avoir à télécharger le fichier:
In [ ]:
url = ("http://opendata.digitalwallonia.be/dataset/"
Out[ ]:
In [ ]:
df = pandas.read_excel(url)
Ce tableau de données comporte 1461 lignes:
In [ ]:
len(df)
Out[ ]:
et 10 colonnes dont les titres sont:
In [ ]:
df.columns
Out[ ]:
Les premières lignes permettent de se donner une idées des données. On peut aussi utiliser df.describe()
:
In [ ]:
df.head()
Out[ ]:
Pour voir ce qu'il y a à la 100e ligne du tableau, on utilise la méthode iloc
. Ce sont les données météo du 11 avril 2010:
In [ ]:
df.iloc[100]
Out[ ]:
Pour afficher les moyennes par colonnes, on utilise la méthode mean()
:
In [ ]:
df.mean()
Out[ ]:
Pour étudier une colonne en particulier, par exemple la pression atmosphérique, c'est-à-dire la septième colonne, on peut procéder ainsi:
In [ ]:
s = df.icol(7)
s.head()
Out[ ]:
In [ ]:
s.describe()
Out[ ]:
Finalement, on peut dessiner l'évolution de la pression atmosphérique en fonction de la date:
In [ ]:
date = df.columns[1]
pression = df.columns[7]
df.plot(x=date, y=pression)
Pour afficher un histogramme de la pression atmosphérique, il s'agit d'utiliser df.plot.hist
avec les mêmes arguments:
In [ ]:
df.plot.hist(x=date, y=pression)
Parfois, il est pertinent de filtrer les lignes d'un tableau df
. La façon de faire est d'abord de créer une série s_vrai_faux
avec le même nombre de lignes contenant des valeurs booléennes en utilisant True
pour les lignes que l'on veut garder et False
sinon. La syntaxe est la suivante: df[s_vrai_faux]
qui retourne un tableau filtré.
Voici un premier exemple facile où on veut afficher que les nombres multiples de 3 d'une série:
In [ ]:
In [32]: s = Series(range(10))
In [31]: s
Out[31]:
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
dtype: int64
On crée une série de la même longueur qui teste si les entrées sont multiples de trois ou non:
In [ ]:
In [29]: s % 3 == 0
Out[29]:
0 True
1 False
2 False
3 True
4 False
5 False
6 True
7 False
8 False
9 True
dtype: bool
On utilise la précédent série de booléen pour filtrer les lignes de la première série:
In [ ]:
In [30]: s[s % 3 == 0]
Out[30]:
0 0
3 3
6 6
9 9
dtype: int64
Faisons maintenant un exemple au sujet de la météo de Ostende. Supposons qu'on s'intéresse à la température moyenne les jours de Noël à Ostende. D'abord, on crée une fonction qui teste si une date est bien le jour de Noël:
In [ ]:
est_noel = lambda date:date.day==25 and date.month==12
On applique cette fonction au tableau. On obtient une série de vrai ou faux:
In [ ]:
s_vrai_faux = df['Date'].apply(est_noel)
s_vrai_faux.tail(10)
Out[ ]:
Finalement, on filtre le tableau avec cette série. Et on affiche que les deux colonnes qui nous intéressent (la date et la température):
In [ ]:
df_noel = df[s_vrai_faux]
df_noel.icol([1,2])
Out[ ]:
Les outils Python tels que la librairie pandas sont utilisés par les gens qui analysent des données comme le média alternatif BuzzFeedNews qui a mis au jour en janvier 2016 [TennisRacket]_ le fait que des matchs de tennis de l'ATP avaient été truqués. Les données ainsi que les notebook Jupyter réalisés par BuzzFeedNews sont disponibles sur github à l'adresse http://github.com/BuzzFeedNews/everything. On y trouvera d'autres analyses de données tels que les tremblements de terre reliés à l'exploitation des gaz de schiste aux États-Unis, les mouvements des donateurs de la campagne présidentielle américaine lorsqu'un candidat sort de la course, ou une analyse du placement des enfants dans les crèches.
Le lecteur désirant en savoir plus sur pandas est invité à lire les tutoriels en ligne sur pandas. La librairie pandas est utilisée par la librairie Python de statistiques StatsModels qui permet de faire encore plus comme des modèles statistiques, des estimations et des tests statistiques.