Open In Colab

Mickaël Tits CETIC [email protected]

Chapitre 5 - Les librairies Python pour l'analyse de données

Une des grandes forces du langage de programmation Python est l'énorme communauté de développeurs qui développent et maintiennent un grand nombre de librairies, facilitant la vie desprogrammeurs. Ces librairies permettent d'utiliser des nouveaux types d'objets spécialisés, aux applications très variées, dont par exemple l'import/export/le traitement/la visualisation de données spécifiques, de l'analyse mathématique/statistique/scientifique poussée, l'interfaçage et la connecticité avec d'autres outils informatiques, etc.

Dans le domaine de l'analyse de données en particulier, voici une liste de librairies incontourables que nous allons voir dans le cadre de ce cours:

  • numpy : librairie de calcul scientifique (algèbre, calcul matriciel, stochastique, etc.)
  • scipy: librairie de calcul scientifique et statistique (reposant sur numpy)
  • pandas : librairie permettant la représentation, la manipulation et l'analyse de données sous forme de tableaux (ou DataFrames)
  • matplotlib : librairie de visualisation graphique de données (voir Chapitre 6)

Pour importer un package, il faut tout d'abord qu'il soit installé sur la machine (ce qui est déjà fait la plupart du temps dans Google Colab). Ensuite, il faut utiliser les mots-clés suivants:

import package as alias

ou:

from package import subpackage as alias

Numpy

Numpy est une librairie de calcul mathématique. Elle inclut notamment l'objet ndarray (n-dimensional array) qui est une représentation d'un tableau multidimensionnel (un tenseur). On peut notamment utiliser cet objet pour représenter une matrice ou un vecteur. Le package dispose de nombreuses fonctions et méthodes permettant notamment des opérations algébriques sur ces objets, ou la génération de nombres aléatoires.

In [0]:
import numpy as np

price_list = [ 100000, 300000, 250000]

price_array = np.array(price_list)

print(price_array)

print("shape:", price_array.shape)
print("mean:", price_array.mean())
print("argmax:", price_array.argmax())
print("cumsum:", price_array.cumsum())
In [0]:
my_array1 = np.array([[1,2,3]])
my_array2 = np.array([[4,5,6]])

print(my_array1.shape)

#element-wise
print(my_array1*my_array2)
#matrix product
print(my_array1@my_array2.T)
In [0]:
print("Opérations de base sur des listes => concaténation:", (price_list + price_list)*2)
In [0]:
print("Opérations de base sur un array => algèbre:", (price_array + price_array)*2/100%42)

Scipy

Scipy est une librairie de calcul scientifique, et plus particulièrement de statistiques. Elle se base sur Numpy pour la représentation des données mathématiques.

In [0]:
from scipy import stats

#le package random permet de générer des nombres (pseudo-)aléatoires
from numpy import random as rnd

#génération d'un array contenant 100 valeurs aléatoires suivant une distribution gaussienne (randn = normal distribution; rand = uniform distribution)
my_array = rnd.randn(100)

#description statistique
stats.describe(my_array)
In [0]:
#Distribution normale standard:
print(stats.norm.pdf(0), stats.norm.cdf(0))
stats.norm.cdf(1)-stats.norm.cdf(0)

Exemple: simulons une séquence de 100 jets de dés, et analysons les statistiques:

In [0]:
#Jet de dé: distribution uniforme et discrète des nombres [1,2,3,4,5,6]
#tosses = np.ceil(rnd.rand(100)*6).astype(int)
#plus rapide:
tosses = np.random.randint(1,7,100)
#print(tosses)
print(stats.describe(tosses))

Exemple de test statistique: Student's t-test

In [0]:
stats.ttest_ind(rnd.randn(100), rnd.randn(100)+1)
In [0]:
stats.f_oneway(rnd.randn(100), rnd.randn(100)+1)

Pandas

Pandas est une librairie permettant la représentation, la manipulation et l'analyse de données sous forme de tableaux.

La librairie dispose de deux objets centraux:

  • La Series: une série de données à une dimension
  • Le Dataframe: un tableau de données à deux dimensions. (remarque: l'extraction d'une ligne ou d'une colonne d'un DataFrame donne une Series).

Ces deux objets disposent d'un grand nombre de fonctions et méthodes qui peuvent être utilisées pour accéder/manipuler/extraire/modifier/filtrer/ajouter/supprimer des données, et extraire des statistiques (aussi appelés aggrégats).

La liste complète des méthodes peut se trouver ici: DataFrame, Series

Reprenons l'inventaire des biens immobiliers

In [0]:
#Un inventaire de biens immobiliers

houses_dataset = {"address":["Rue de Fer 26, 5000 Namur",
                             "Rue de Bruxelles 42, 5000 Namur",
                      "Porte de Namur 25, Bruxelles",
                      "Rue de L'Eglise 42, Charleroi",
                      "Rue Saint-ghislain 30, 6224 Fleurus",
                     "Rue de la Closière 20, Fleurus",
                     "Rue de la Closière 20, Fleurus",
                     "Rue de Fer 25, 5000 Namur",
                     "Rue du Luxembourg 15, 1000 Bruxelles",
                     "NaN",
                     "Rue de Bruxelles 42, 5000 Namur",
                     "Rue de la Loi 50, Bruxelles"],
          "website":["immoweb","immoweb","immoweb","immoweb","immoweb","immoweb","immoweb","immovlan","immovlan","immovlan","immovlan","immovlan"],
           "price": [400000,
                     350000,
                     400000,
                     150000,
                     "330000",
                     230000,
                     230000,
                     0,
                     -100,
                     "cent mille",
                     350000,
                     700000],
           "surface":[150,
                      200,
                      120,
                      150,
                      320,
                      175,
                      170,
                      170,
                      100,
                      100,
                      200,
                      220],
                 "rooms":[4,5,3,5,5,2,3,3,"two",0,4,3]}

print(houses_dataset)

Préparation du dataframe

In [0]:
import pandas as pd

df = pd.DataFrame(houses_dataset)
#print(df) #moins joli
display(df) #plus joli
In [0]:
#convertir certaines colonnes en valeurs numériques (les valeurs invalides deviennent des NaN - not a number)
str_cols = df.columns[:2] #address, website
num_cols = df.columns[2:]# price, surface, rooms

for col in num_cols:
  df[col] = pd.to_numeric(df[col], errors = "coerce") #errors = "coerce" transforme en NaN les données qui ne peuvent être converties en nombre

df0 = df

df

Concaténation de DataFrames

In [0]:
newhouses = pd.DataFrame({ "address": ["Rue de la Loi 51, Bruxelles", "Rue de la Loi 52, Bruxelles", "Rue de la Loi 53, Bruxelles", "Rue de Fer 27, Namur", "Rue de Fer 28, Namur", "Rue de Fer 29, Namur"], 
                          "website" : ["immoweb","immovlan","immovlan","immoweb","immoweb","immovlan"], 
                          "surface" : [120, 150, 170, 140, 160, 180], 
                          "price" : [280000, 400000, 480000, 280000, 320000, 350000], 
                          "rooms" : [3,4,5,3,4,5]})

df = pd.concat([df0, newhouses], ignore_index = True)

df1 = df#on le garde pour plus tard

df

Accès aux données d'un dataframe

In [0]:
#Les labels du dataframe (i.e. les titres des lignes et des colonnes)
print(df.index) # labels des lignes
print(df.index.to_list()) # labels des lignes
In [0]:
print(df.columns) #labels des colonnes
print(df.columns.to_list()) #labels des colonnes
In [0]:
df.transpose() #permet de transposer le tableau
In [0]:
df.set_index("address") #permet de modifier l'index
In [0]:
#Accès à une colonne
print(df["price"])
#ou
df.price
In [0]:
#Accès à plusieurs colonnes
df[["price","surface"]]
In [0]:
#Accès à une ligne
#par label: loc
print(df.loc[0])
In [0]:
#par un entier: iloc (dans ce cas ça revient au même puisque l'index correspond à des entiers de 0 à 11)
df.iloc[0]
In [0]:
#Accès à une ligne
print(df.set_index("address").loc["NaN"])
In [0]:
df.set_index("address").iloc[0]
In [0]:
#Accès à un/plusieurs élément(s)
df.iloc[0,0]
In [0]:
df.iloc[0:2,0:2]
In [0]:
df.loc[0,"price"]
In [0]:
df.loc[0,["price","surface"]]
In [0]:
df.loc[0:3,["price","surface"]]

Exploration statistique du dataframe

Avant même de corriger le dataframe, il peut être intéressant de réaliser une première exploration statistique, qui permet parfois de se rendre compte directement des données à corriger.

df.describe() permet de calculer différents aggrégats (des statistiques de base) pour toutes les colonnes numériques.

In [0]:
df.describe()
  • On remarque directement des données incohérentes: le prix minimum est négatif, et le nombre de pièces minimum est 0. Il faut donc au moins corriger ces deux caractéristiques.
  • Il manque des valeurs pour les colonnes price et rooms (17 au lieu de 18). (Note: df.dropna() permet d'éliminer ces lignes invalides)
  • Les valeurs moyennes et maximales semble cohérentes.

La méthode "groupby" permet de grouper les données selon leur adresse (on obtient un groupe par addresse unique), et la méthode "count" permet ensuite de compter les valeurs pour chaque groupe. Ceci permet de directement identifier les doublons, et les données non-valides.

In [0]:
df.groupby("address").count()
  • On distingue 2 doublons: Rue de Bruxelles 42, 5000 Namur et Rue de la Closière 20, Fleurus

  • On remarque aussi des données invalides pour deux adresses: il manque le prix pour "NaN", de le nombre de pièces pour "Rue du Luxembourg 15, 1000 Bruxelles"

  • On aperçoit une adresse invalide: "NaN", qu'on peut enlever manuellement

Note: df.dropna() permet d'éliminer directement ces données invalides (des NaN)

On peut réaliser le même processus pour l'autre colonne textuelle: website

  • On remarque en l'occurrence que plus de données viennent d'immoweb, et plus particulièrement que les données invalides viennent toutes de immovlan
In [0]:
df.groupby("website").count()

Correction du dataframe en une seule instruction

  • Retire les données invalides:
    • string à "NaN","" ou " "
    • nombre négatif
    • nombre défini à NaN
  • Retire les doublons
In [0]:
#Correction du dataframe en une seule instruction: retire toutes les données invalides et les doublons
df = df[~df.address.isin(["NaN",""," "]) & \
        (df.price > 0) & (df.rooms > 0)]\
  .dropna()\
  .drop_duplicates("address")

df

Correction étape par étape

In [0]:
#On reprend le dataframe non-corrigé pour comparer
df = df1
df
In [0]:
#Pour les colonnes de texte, retirer les valeurs invalides (NaN ou "" ou " ")

#df = df[df.address != "NaN"]
#Ou pus complet
df = df[~df.address.isin(["NaN",""," "])]
df
In [0]:
#Elimine les lignes contenant des NaN
df = df.dropna()
df
In [0]:
#Retirer les valeurs numériques incohérentes
df = df[(df.price > 0) & (df.rooms > 0)]
df
In [0]:
#On stocke la version avec doublons pour plus tard
dfdup = df
In [0]:
#Retirer les doublons

#df =df.drop_duplicates("address")
#Par défaut, le premier élément est gardé. On peut aussi garder le dernier (ce qui gardera notamment plus d'éléments d'immovlan)
df = dfdup.drop_duplicates("address", keep = "last")
df
In [0]:
#filtrer les données d'immoweb
df[df.website == "immoweb"]

Aggrégats

In [0]:
df.mean()
In [0]:
df[df.website == "immoweb"].mean()
#On remarque que les maisons d'immoweb sont en moyenne moins chère que la moyenne totale
In [0]:
df.count()

Comparer deux groupes (aggrégats groupés)

Avec Pandas, il est très facile de comparer différents groupes d'échantillons, grâce à la méthode pandas.DataFrame.groupby, qui permet de faire des aggrégats groupés.

Remarque: la méthode pandas.DataFrame.groupby renvoie en fait un objet d'un nouveau type (GroupBy), qui permet de gérer des groupements de données, et dispose de ses propres méthodes dont la liste complète peut se trouver ici: https://pandas.pydata.org/pandas-docs/stable/reference/groupby.html

In [0]:
#Comparer les deux plateformes: c'est direct avec la méthode "groupby"
display(df.groupby("website").count())
display(df.groupby("website").mean())
In [0]:
#Idem en gardant les doublons

display(dfdup.groupby("website").count())
display(dfdup.groupby("website").mean())

Les chiffres* montrent que les maisons sur immovlan sont généralement plus grandes et plus chères.

* (le dataset est bien évidemment simulé et trop petit pour donner des résultats significatifs)

Nous pouvons vérifier cette observation par un test statistique (e.g., Student's t-test), avec scipy.

In [0]:
from scipy import stats

stats.ttest_ind(df[df.website == "immoweb"].price, df[df.website == "immovlan"].price)
stats.f_oneway(df[df.website == "immoweb"].price, df[df.website == "immovlan"].price)

Exercice: les maisons plus grandes sont-elles plus chères ?

Divisez les maisons en deux groupes égaux (de part et d'autre de la surface médiane) et utilisez la méthode .describe() pour comparer les prix

In [0]:
 

Solution

In [0]:
#df["large_houses"] = df.surface > df.surface.median()
#df.groupby("large_houses")["price"].describe()

#ou: (il n'est pas nécessaire d'ajouter une colonne dans le dataframe, 
#il suffit de donner comme argument du groupby une series de la même longueur que le dataframe)

is_large = df.surface > df.surface.median()
df.groupby(is_large)["price"].describe()

#ou directement:
df.groupby(df.surface > df.surface.median())["price"].describe()
In [0]:
#Anova

#Pour avoir un code générique, ou extrait tous les groupes obtenus sous forme d'une liste
groups = df.groupby(is_large)["price"]
group_list = [g[1].values for g in groups]

#La méthode f_oneway prend autant d'arguments que d'échantillon à comparer. Pour transformer une liste en arguments, on utilise le symbole *
stats.f_oneway(*group_list)

Sauvegarder/charger un dataframe

In [0]:
df.to_csv("houses.csv") #tableau ascii (comma separated values)
df.to_hdf("houses.h5","df") #"Hierarchical Data Format". Format standard plus générique pour les ensembles de données
df.to_parquet("houses.parquet") #Format plus léger (compressé) et rapide à lire pour de gros volumes de données
In [0]:
#Dans un notebook, on peut appeler une ligne de commande linux grâce au symbole "!"
#le comande "ls" permet de lister les éléments du dossier courant. 
#Les lettre après le symbole "-" sont des options de la commande: 
#"l" permet un affichage plus détaillé, et "h" rend les données plus lisibles (affichage des tailles en Ko et Mo)
!ls -lh

Vous pouvez également utiliser l'interface graphique de Google Colab pour accéder aux fichiers temporairement stockés dans la machine virtuelle.

Vous pouvez également utiliser cette interface pour télécharger (clic-droit sur le fichier) ou importer des fichiers.

Accès aux fichiers dans Google Colab