Mickaël Tits CETIC mickael.tits@cetic.be
Dans ce chapitre, nous allons explorer plus prodonfément les possibilités d'exploration de données (data mining) avec Pandas.
Chargez d'abord le dataframe préparé lors du chapitre précédent.
import pandas as pd
#Si vous venez d'exécuter le notebook précédent, vous pouvez simplement récupérer le fichier temporaire créé.
#df = pd.read_hdf("houses (1).h5")
#Vous pouvez aussi récupérer une version du fichier hébergée ici:
df = pd.read_csv("https://raw.githubusercontent.com/titsitits/Python_Data_Science/master/Donn%C3%A9es/houses.csv", index_col=0)
df
#Extract city
def get_city(address):
#Le nom de la rue est la partie après le dernier nombre, ou une virgule si aucun code postal n'est renseigné. On cherche donc un nombre en commençant par la fin ( range(len(address),0,-1) )
for i in range(len(address)-1,0,-1):
c = address[i]
if c in "0123456789,":
#c est un nombre (ou une virgule), on peut sortir de la boucle
break
#On extrait le nom de la ville
city = address[i+2:]
return city
my_address = "Rue de Bruxelles 42, Namur"
print(get_city(my_address))
my_address = "Namur"
print(get_city(my_address))
df = df.assign(city = df["address"].apply(get_city))
df = df.assign(price_per_m2 = df.price/df.surface)
df = df.assign(price_per_room = df.price/df.rooms)
df
#Ou (idem)
df["city"] = df["address"].apply(get_city)
df["price_per_m2"] = df.price/df.surface
df["price_per_room"] = df.price/df.rooms
df
df.groupby("website").mean()
Le prix/m2 et le prix/pièce est en moyenne plus élevé sur immovlan.
df.groupby("city").mean()
Quelques observations:
On peut identifier deux types de variables: des variables continues (tel que le prix ou la surface), et des variables catégorielles, tel que la ville ou la plateforme.
Les variables continues permettent d'extraire toutes sortes de statistiques, et peuvent être comparées entre elles, par exemple par une analyse de corrélation ou une comparaison d'histogrammes, ou par des modèles prédictifs (e.g.: régression linéaire).
Les variables catégorielles permettent quand à elles une analyse comparative de catégories (une analyse factorielle), par comparaison des statistiques extraites sur les variables continues pour chaque catégorie.
Certaines variables peuvent également être considérées de plusieurs manières: le nombre de pièces est une variable discrète. Etant donné que le nombre de valeurs différentes est très limité, on pourrait la considérer comme une variable catégorielle (plus spécifiquement comme une variable ordinale, i.e. une échelle).
A l'inverse, l'adresse pourrait être considérée comme une variable continue si on la traduisait en coordonnées GPS, ou en une distance (à vol d'oiseau ou par la route) à un lieu de référence (distance au magasin le plus proche, l'autoroute la plus proche, à la capitale, à la frontière, etc.).
La méthode .corr() permet de calculer les corrélations 2 par 2 pour toutes les variables numériques.
df.corr()
Les corrélations entre les variables continues peuvent être fortement influencées par d'autres facteurs (catégoriels par exemple). Par exemple, on s'attend à ce que le prix augmente avec la surface pour des maisons donc les autres caractéristiques sont semblables, mais il est évident qu'une maison à la capitale est généralement plus chère qu'une maison de la même surface à la campagne.
bxldf = df[df.city == "Bruxelles"]
bxldf
bxldf.corr()
Si on n'analyse que les maisons Bruxelloises, on constate une corrélation forte entre la surface et le prix.
df.groupby("rooms").mean()
df[df.rooms == 5]
On remarque que ces résultats sont principalement dus à une maison particulière, celle de Charleroi: le prix par nombre de chambres est anormalement bas, en comparaison à toutes les autres maisons du dataset. Nous verrons plus loin comment gérer les données inhabituelles
cat1 = "city"
#cat2 = "rooms"
cat2 = "website"
count_analysis = df.groupby([cat1,cat2])["price"].count().unstack(fill_value=0).transpose()
count_analysis
price_analysis = df.groupby([cat1,cat2])["price"].mean().unstack(fill_value=0).transpose()
price_analysis
Les anomalies sont des observations très différentes de la masse des données. Par exemple, si pour un dataset de 100 maisons, toutes sont entre 200000 et 400000 et une seule est à 100000, elle peut-être considérée comme une anomalie. Ou si on a 100 maisons à Bruxelles et une seule à Philippeville, on peut également considérer cette dernière comme anormale. Elle risque en effet d'être tellement différente des autres qu'elle aura une forte influence sur les statistiques. Lors d'une analyse statistique exploratoire, il est donc pertinent d'omettre ces données inhabituelles. A l'inverse, il est également parfois intéressant de détecter les valeurs exceptionnelles, permettant par exemple d'identifier des causes de problèmes (sur des données issues de capteurs d'usines par exemples); ou dans le présent contexte des éventuelles bonnes affaires immobilières (ou des fraudeurs: blanchiment d'argent ou arnaque ?).
En l'occurrence, dans notre dataset de test, on pourrait considérer la maison de Charleroi comme anormale: son prix par pièce est beaucoup moins élevé que celui de toutes les autres.
Les anomalies peuvent se détecter de deux manières: les statistiques et la visualisation graphique.
df
#Inter-quantile range
Q1 = df.price_per_room.quantile(0.25)
Q3 = df.price_per_room.quantile(0.75)
IQR = Q3 - Q1
h = 2
print("limits:", (Q1 - h * IQR), "to", (Q3 + h * IQR))
#Les données s'écartant fortement des quantiles sont potentiellement des anomalies (outlier en anglais)
is_outlier = (df.price_per_room < (Q1 - h * IQR)) | (df.price_per_room > (Q3 + h * IQR))
df.loc[is_outlier]
df2 = df[~is_outlier] #détection statistique d'outliers
df2.to_csv("houses_features.csv")
df2
Matplotlib
¶Matplotlib est une librairie permettent la visualisation graphique des données. Elle est habituellement utilisée avec les trois librairies présentées plus haut (Numpy, Scipy, Pandas).
from matplotlib import pyplot as plt
plt.scatter(df.surface, df.price)
plt.xlabel("Surface")
plt.ylabel("Price")
from matplotlib import pyplot as plt
plt.bar(df.address,df.price_per_room)
plt.xticks(rotation=90)
#méthode intégrée de pandas
df.plot.bar(y="price_per_room")
plt.xticks(rotation=90)
#On retire les données extrêmes
df2 = df.drop([3,11]) #détection manuelle (visuelle)
df2
df2.groupby("rooms").mean()
result = df2.groupby("rooms").mean()
#Divide each column by its sum (so that sum is 1) - so that multiple bar plots with various orders are visible
result = result/result.sum()
result.plot(kind="bar")
#plus lisible (on transpose pour avoir un graphe par variable plutôt que par nombre de chambres)
result.transpose().plot(kind="bar")
On remarque que:
website_comparison = df.groupby("website").mean()
website_comparison.plot.bar(y="price")
#df.plot: afficher une courbe. On trie d'abord les maisons par surface croissante pour afficher la courbe (DF.sort_values("surface"))
bxldf.sort_values("surface").plot("surface","price")
count_analysis.plot.pie(subplots = True, figsize = (15,15))
price_analysis.plot.bar(subplots = True, figsize = (10,10))
Cherchez les maisons ayant un prix s'écartant fortement du IQR (inter-quantile range).
Indice: nous avons réalisé un processus similaire plus haut sur le prix par chambre.
Note: vous devriez trouver une maison à 700000€ (Rue de la Loi 50, Bruxelles).
Modularisez le code écrit à l'exercice précédent pour le rendre générique.
def detect_outliers(df, column):
#Modifiez cette fonction de manière à renvoyer un dataframe contenant les outliers
outliers = pd.DataFrame()
return outliers
#Tests
out = detect_outliers(df, 'price')
#Vous devriez trouver une maison à 700000€
out = detect_outliers(df, 'surface')
#Vous devriez trouver une maison avec une surface de 320m²
Pour les variables catégorielles, vous pouvez simplement détecter une catégorie particulièrement rare (nombre d'occurrences plus petit qu'un seuil nmin).
#variables catégorielles (le nombre de chambres peut être considéré comme variable catégorielle aussi)
def detect_rare_cat(df, column, nmin = 2):
"""
Cette fonction permet de détecter des outliers dans des variables catégorielles
"""
outliers = pd.DataFrame()
return outliers
#Tests
out = detect_rare_cat(df, 'city')
#Vous devriez trouver une maison à Charleroi
Indice: vous pouvez aussi définir une variable comme catégorielle si elle compte un nombre limité de valeurs uniques. (Utilisez la méthode pd.Series.unique()) Attention: le nombre de chambres 'rooms' peut à la fois être considéré comme catégoriel et numérique. Utilisez la méthode pd.DataFrame. Indice: pour vérifier si une colonne est numérique, vous pouvez essayer de la convertir en float: series.astype(float) et rediriger l'erreur.
#Modifiez le code ci-dessous
def is_cat(series, relative_threshold = 0.5, absolute_threshold = 20):
#nombre de valeurs
nvalues = len(series)
#nombre de valeurs uniques
ncats = len(series.unique())
#On considère la variable comme catégorielle si le nombre de valeurs uniques et plus petit qu'un seuil relatif ou absolu
return False
def is_num(series):
#On peut considérer une variable comme numérique si elle peut être convertie en float (utiliser try/except)
#series.astype(float)
return False
#Tests
print(is_cat(df.price))
print(is_cat(df.city))
#Application sur chaque colonne
is_categorical = df.apply(is_cat)
is_numeric = df.apply(is_num)
print(is_categorical)
cols = df.columns
for col in cols:
print("Are there numerical outliers in %s ?" % col)
print("Are there categorical outliers in %s ?" % col)
# On devrait retrouver la maison de Charleroi, la maison de 320m², et la maison à 700000€
#Remarque: vous pouvez aussi commencer par extraire les noms des variables catégorielles/numériques, et puis utiliser un ecompréhension de liste dessus
Vous pouvez maintenant passer au Chapitre 7: Introduction au Machine Learning