Open In Colab

Mickaël Tits CETIC [email protected]

Chapitre 6 - Introduction au Data Mining et à la visualisation de données

Dans ce chapitre, nous allons explorer plus prodonfément les possibilités d'exploration de données (data mining) avec Pandas.

  • Nous allons d'abord extraire de nouvelles caractéristiques à partir des variables existantes, pour améliorer le dataframe.
  • Nous allons tester différentes méthodes d'exploration de base, selon le type de caractéristique (variables continues ou catégorielles)
  • Nous allons détecter les valeurs particulières, souvent appelées anomalies, ou outliers.
  • Nous allons finalement montrer quelques exemples de visualisation graphique des données grâce à la librairie Matplotlib

Chargez d'abord le dataframe préparé lors du chapitre précédent.

In [0]:
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)
In [0]:
df

Extraction de caractéristiques (i.e. augmentation du DataFrame par de nouvelles colonnes)

In [0]:
#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
In [0]:
my_address = "Rue de Bruxelles 42, Namur"
print(get_city(my_address))

my_address = "Namur"
print(get_city(my_address))
In [0]:
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
In [0]:
#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
In [0]:
df.groupby("website").mean()

Le prix/m2 et le prix/pièce est en moyenne plus élevé sur immovlan.

In [0]:
df.groupby("city").mean()

Quelques observations:

  • Bruxelles serait la ville la plus chère
  • Charleroi la ville la moins chère
  • Les maisons sont généralement plus grandes à Fleurus
  • Les maisons de Fleurus sont plus chères qu'à Charleroi, mais le prix au m2 est presque le même
  • Les maisons de Charleroi seraient petites mais avec beaucoup de pièces (toutes petites du coup)

Exploration selon le type de caractéristiques: variables continues et catégorielles

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.).

Corrélations entre les variables continues

La méthode .corr() permet de calculer les corrélations 2 par 2 pour toutes les variables numériques.

In [0]:
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.

In [0]:
bxldf = df[df.city == "Bruxelles"]
bxldf
In [0]:
bxldf.corr()

Si on n'analyse que les maisons Bruxelloises, on constate une corrélation forte entre la surface et le prix.

Comparaison de catégories: maisons par nombre de pièces

In [0]:
df.groupby("rooms").mean()
  • En l'occurence, on remarque étrangement que le prix des maisons semble diminuer, et que les maisons à 5 pièces sont beaucoup moins chères.
In [0]:
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

Comparaison de catégories multiples

In [0]:
cat1 = "city"
#cat2 = "rooms"
cat2 = "website"
count_analysis = df.groupby([cat1,cat2])["price"].count().unstack(fill_value=0).transpose()
count_analysis
In [0]:
price_analysis = df.groupby([cat1,cat2])["price"].mean().unstack(fill_value=0).transpose()
price_analysis

Détection/suppression des anomalies ?

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.

In [0]:
df
In [0]:
#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]
In [0]:
df2 = df[~is_outlier] #détection statistique d'outliers
df2.to_csv("houses_features.csv")
df2

Visualisation de données avec 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).

In [0]:
from matplotlib import pyplot as plt

plt.scatter(df.surface, df.price)
plt.xlabel("Surface")
plt.ylabel("Price")
In [0]:
from matplotlib import pyplot as plt

plt.bar(df.address,df.price_per_room)
plt.xticks(rotation=90)
In [0]:
#méthode intégrée de pandas
df.plot.bar(y="price_per_room")
plt.xticks(rotation=90)           
In [0]:
#On retire les données extrêmes
df2 = df.drop([3,11]) #détection manuelle (visuelle)
df2
In [0]:
df2.groupby("rooms").mean()
  • Après avoir retiré les donénes extrêmes, l'évolution du prix en fonction du nombre de chambres est plus cohérente.
  • On remarque que le prix au m2 augmente, mais que le prix par pièce diminue.
In [0]:
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")
In [0]:
#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:

  • le prix augmente à peu près
  • la surface augmente
  • le prix au m2 diminue
  • le prix par pièce diminue
In [0]:
website_comparison = df.groupby("website").mean()
website_comparison.plot.bar(y="price")
In [0]:
#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")
In [0]:
count_analysis.plot.pie(subplots = True, figsize = (15,15))
In [0]:
price_analysis.plot.bar(subplots = True, figsize = (10,10))

Exercices

1. Recherchez les maisons ayant un prix anormal

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).

In [0]:
 

2. Définissez une fonction générique permettant de détecter les anomalies sur n'importe quelle colonne numérique

Modularisez le code écrit à l'exercice précédent pour le rendre générique.

In [0]:
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²

3. Définissez une fonction générique permettant de détecter les anomalies sur une colonne catégorielle

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).

In [0]:
#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

4. Détectez automatiquement le type d'une variable

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.

In [0]:
#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)

5. Recherchez les anomalies de manière systématique

In [0]:
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

Fin

Vous pouvez maintenant passer au Chapitre 7: Introduction au Machine Learning