Mickaël Tits CETIC mickael.tits@cetic.be
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:
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.
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())
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)
print("Opérations de base sur des listes => concaténation:", (price_list + price_list)*2)
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.
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)
#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:
#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
stats.ttest_ind(rnd.randn(100), rnd.randn(100)+1)
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:
Series
: une série de données à une dimensionDataframe
: 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
#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)
import pandas as pd
df = pd.DataFrame(houses_dataset)
#print(df) #moins joli
display(df) #plus joli
#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
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
#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
print(df.columns) #labels des colonnes
print(df.columns.to_list()) #labels des colonnes
df.transpose() #permet de transposer le tableau
df.set_index("address") #permet de modifier l'index
#Accès à une colonne
print(df["price"])
#ou
df.price
#Accès à plusieurs colonnes
df[["price","surface"]]
#Accès à une ligne
#par label: loc
print(df.loc[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]
#Accès à une ligne
print(df.set_index("address").loc["NaN"])
df.set_index("address").iloc[0]
#Accès à un/plusieurs élément(s)
df.iloc[0,0]
df.iloc[0:2,0:2]
df.loc[0,"price"]
df.loc[0,["price","surface"]]
df.loc[0:3,["price","surface"]]
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.
df.describe()
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.
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
df.groupby("website").count()
#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
#On reprend le dataframe non-corrigé pour comparer
df = df1
df
#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
#Elimine les lignes contenant des NaN
df = df.dropna()
df
#Retirer les valeurs numériques incohérentes
df = df[(df.price > 0) & (df.rooms > 0)]
df
#On stocke la version avec doublons pour plus tard
dfdup = df
#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
#filtrer les données d'immoweb
df[df.website == "immoweb"]
df.mean()
df[df.website == "immoweb"].mean()
#On remarque que les maisons d'immoweb sont en moyenne moins chère que la moyenne totale
df.count()
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
#Comparer les deux plateformes: c'est direct avec la méthode "groupby"
display(df.groupby("website").count())
display(df.groupby("website").mean())
#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.
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)
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
#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()
#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)
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
#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.
Vous pouvez maintenant passer au Chapitre 6: Introduction au Data Mining et à la visualisation de données