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_csv("houses.csv", index_col=0)

#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 [2]:
df
Out[2]:
address price rooms surface website
0 Rue de Fer 26, 5000 Namur 400000.0 4.0 150 immoweb
2 Porte de Namur 25, Bruxelles 400000.0 3.0 120 immoweb
3 Rue de L'Eglise 42, Charleroi 150000.0 5.0 150 immoweb
4 Rue Saint-ghislain 30, 6224 Fleurus 330000.0 5.0 320 immoweb
6 Rue de la Closière 20, Fleurus 230000.0 3.0 170 immoweb
10 Rue de Bruxelles 42, 5000 Namur 350000.0 4.0 200 immovlan
11 Rue de la Loi 50, Bruxelles 700000.0 3.0 220 immovlan
12 Rue de la Loi 51, Bruxelles 280000.0 3.0 120 immoweb
13 Rue de la Loi 52, Bruxelles 400000.0 4.0 150 immovlan
14 Rue de la Loi 53, Bruxelles 480000.0 5.0 170 immovlan
15 Rue de Fer 27, Namur 280000.0 3.0 140 immoweb
16 Rue de Fer 28, Namur 320000.0 4.0 160 immoweb
17 Rue de Fer 29, Namur 350000.0 5.0 180 immovlan

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 [4]:
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
Out[4]:
address price rooms surface website city price_per_m2 price_per_room
0 Rue de Fer 26, 5000 Namur 400000.0 4.0 150 immoweb Namur 2666.666667 100000.000000
2 Porte de Namur 25, Bruxelles 400000.0 3.0 120 immoweb Bruxelles 3333.333333 133333.333333
3 Rue de L'Eglise 42, Charleroi 150000.0 5.0 150 immoweb Charleroi 1000.000000 30000.000000
4 Rue Saint-ghislain 30, 6224 Fleurus 330000.0 5.0 320 immoweb Fleurus 1031.250000 66000.000000
6 Rue de la Closière 20, Fleurus 230000.0 3.0 170 immoweb Fleurus 1352.941176 76666.666667
10 Rue de Bruxelles 42, 5000 Namur 350000.0 4.0 200 immovlan Namur 1750.000000 87500.000000
11 Rue de la Loi 50, Bruxelles 700000.0 3.0 220 immovlan Bruxelles 3181.818182 233333.333333
12 Rue de la Loi 51, Bruxelles 280000.0 3.0 120 immoweb Bruxelles 2333.333333 93333.333333
13 Rue de la Loi 52, Bruxelles 400000.0 4.0 150 immovlan Bruxelles 2666.666667 100000.000000
14 Rue de la Loi 53, Bruxelles 480000.0 5.0 170 immovlan Bruxelles 2823.529412 96000.000000
15 Rue de Fer 27, Namur 280000.0 3.0 140 immoweb Namur 2000.000000 93333.333333
16 Rue de Fer 28, Namur 320000.0 4.0 160 immoweb Namur 2000.000000 80000.000000
17 Rue de Fer 29, Namur 350000.0 5.0 180 immovlan Namur 1944.444444 70000.000000
In [5]:
#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
Out[5]:
address price rooms surface website city price_per_m2 price_per_room
0 Rue de Fer 26, 5000 Namur 400000.0 4.0 150 immoweb Namur 2666.666667 100000.000000
2 Porte de Namur 25, Bruxelles 400000.0 3.0 120 immoweb Bruxelles 3333.333333 133333.333333
3 Rue de L'Eglise 42, Charleroi 150000.0 5.0 150 immoweb Charleroi 1000.000000 30000.000000
4 Rue Saint-ghislain 30, 6224 Fleurus 330000.0 5.0 320 immoweb Fleurus 1031.250000 66000.000000
6 Rue de la Closière 20, Fleurus 230000.0 3.0 170 immoweb Fleurus 1352.941176 76666.666667
10 Rue de Bruxelles 42, 5000 Namur 350000.0 4.0 200 immovlan Namur 1750.000000 87500.000000
11 Rue de la Loi 50, Bruxelles 700000.0 3.0 220 immovlan Bruxelles 3181.818182 233333.333333
12 Rue de la Loi 51, Bruxelles 280000.0 3.0 120 immoweb Bruxelles 2333.333333 93333.333333
13 Rue de la Loi 52, Bruxelles 400000.0 4.0 150 immovlan Bruxelles 2666.666667 100000.000000
14 Rue de la Loi 53, Bruxelles 480000.0 5.0 170 immovlan Bruxelles 2823.529412 96000.000000
15 Rue de Fer 27, Namur 280000.0 3.0 140 immoweb Namur 2000.000000 93333.333333
16 Rue de Fer 28, Namur 320000.0 4.0 160 immoweb Namur 2000.000000 80000.000000
17 Rue de Fer 29, Namur 350000.0 5.0 180 immovlan Namur 1944.444444 70000.000000
In [6]:
df.groupby("website").mean()
Out[6]:
price rooms surface price_per_m2 price_per_room
website
immovlan 456000.0 4.20 184.00 2473.291741 117366.666667
immoweb 298750.0 3.75 166.25 1964.690564 84083.333333

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

In [7]:
df.groupby("city").mean()
Out[7]:
price rooms surface price_per_m2 price_per_room
city
Bruxelles 452000.0 3.6 156 2867.736185 131200.000000
Charleroi 150000.0 5.0 150 1000.000000 30000.000000
Fleurus 280000.0 4.0 245 1192.095588 71333.333333
Namur 340000.0 4.0 166 2072.222222 86166.666667

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, mais auraient peu de pièces (très grandes du coup)
  • 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 [8]:
df.corr()
Out[8]:
price rooms surface price_per_m2 price_per_room
price 1.000000 -0.160783 0.233623 0.750157 0.890961
rooms -0.160783 1.000000 0.410615 -0.411927 -0.553127
surface 0.233623 0.410615 1.000000 -0.397963 0.049693
price_per_m2 0.750157 -0.411927 -0.397963 1.000000 0.753682
price_per_room 0.890961 -0.553127 0.049693 0.753682 1.000000

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 [9]:
bxldf = df[df.city == "Bruxelles"]
bxldf
Out[9]:
address price rooms surface website city price_per_m2 price_per_room
2 Porte de Namur 25, Bruxelles 400000.0 3.0 120 immoweb Bruxelles 3333.333333 133333.333333
11 Rue de la Loi 50, Bruxelles 700000.0 3.0 220 immovlan Bruxelles 3181.818182 233333.333333
12 Rue de la Loi 51, Bruxelles 280000.0 3.0 120 immoweb Bruxelles 2333.333333 93333.333333
13 Rue de la Loi 52, Bruxelles 400000.0 4.0 150 immovlan Bruxelles 2666.666667 100000.000000
14 Rue de la Loi 53, Bruxelles 480000.0 5.0 170 immovlan Bruxelles 2823.529412 96000.000000
In [10]:
bxldf.corr()
Out[10]:
price rooms surface price_per_m2 price_per_room
price 1.000000 0.007169 0.949677 0.618822 0.874520
rooms 0.007169 1.000000 0.147841 -0.201748 -0.478613
surface 0.949677 0.147841 1.000000 0.347179 0.761512
price_per_m2 0.618822 -0.201748 0.347179 1.000000 0.642289
price_per_room 0.874520 -0.478613 0.761512 0.642289 1.000000

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 [11]:
df.groupby("rooms").mean()
Out[11]:
price surface price_per_m2 price_per_room
rooms
3.0 378000.0 154 2440.285205 126000.0
4.0 367500.0 165 2270.833333 91875.0
5.0 327500.0 205 1699.805964 65500.0
  • 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 [12]:
df[df.rooms == 5]
Out[12]:
address price rooms surface website city price_per_m2 price_per_room
3 Rue de L'Eglise 42, Charleroi 150000.0 5.0 150 immoweb Charleroi 1000.000000 30000.0
4 Rue Saint-ghislain 30, 6224 Fleurus 330000.0 5.0 320 immoweb Fleurus 1031.250000 66000.0
14 Rue de la Loi 53, Bruxelles 480000.0 5.0 170 immovlan Bruxelles 2823.529412 96000.0
17 Rue de Fer 29, Namur 350000.0 5.0 180 immovlan Namur 1944.444444 70000.0

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 [13]:
cat1 = "city"
cat2 = "rooms"
#cat2 = "website"
count_analysis = df.groupby([cat1,cat2])["price"].count().unstack(fill_value=0).transpose()
count_analysis
Out[13]:
city Bruxelles Charleroi Fleurus Namur
rooms
3.0 3 0 1 1
4.0 1 0 0 3
5.0 1 1 1 1
In [14]:
price_analysis = df.groupby([cat1,cat2])["price"].mean().unstack(fill_value=0).transpose()
price_analysis
Out[14]:
city Bruxelles Charleroi Fleurus Namur
rooms
3.0 460000.0 0.0 230000.0 280000.000000
4.0 400000.0 0.0 0.0 356666.666667
5.0 480000.0 150000.0 330000.0 350000.000000

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 [15]:
df
Out[15]:
address price rooms surface website city price_per_m2 price_per_room
0 Rue de Fer 26, 5000 Namur 400000.0 4.0 150 immoweb Namur 2666.666667 100000.000000
2 Porte de Namur 25, Bruxelles 400000.0 3.0 120 immoweb Bruxelles 3333.333333 133333.333333
3 Rue de L'Eglise 42, Charleroi 150000.0 5.0 150 immoweb Charleroi 1000.000000 30000.000000
4 Rue Saint-ghislain 30, 6224 Fleurus 330000.0 5.0 320 immoweb Fleurus 1031.250000 66000.000000
6 Rue de la Closière 20, Fleurus 230000.0 3.0 170 immoweb Fleurus 1352.941176 76666.666667
10 Rue de Bruxelles 42, 5000 Namur 350000.0 4.0 200 immovlan Namur 1750.000000 87500.000000
11 Rue de la Loi 50, Bruxelles 700000.0 3.0 220 immovlan Bruxelles 3181.818182 233333.333333
12 Rue de la Loi 51, Bruxelles 280000.0 3.0 120 immoweb Bruxelles 2333.333333 93333.333333
13 Rue de la Loi 52, Bruxelles 400000.0 4.0 150 immovlan Bruxelles 2666.666667 100000.000000
14 Rue de la Loi 53, Bruxelles 480000.0 5.0 170 immovlan Bruxelles 2823.529412 96000.000000
15 Rue de Fer 27, Namur 280000.0 3.0 140 immoweb Namur 2000.000000 93333.333333
16 Rue de Fer 28, Namur 320000.0 4.0 160 immoweb Namur 2000.000000 80000.000000
17 Rue de Fer 29, Namur 350000.0 5.0 180 immovlan Namur 1944.444444 70000.000000
In [16]:
#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 outliers
is_outlier = (df.price_per_room < (Q1 - h * IQR)) | (df.price_per_room > (Q3 + h * IQR))
df.loc[is_outlier]
limits: 30000.000000000015 to 146666.66666666666
Out[16]:
address price rooms surface website city price_per_m2 price_per_room
3 Rue de L'Eglise 42, Charleroi 150000.0 5.0 150 immoweb Charleroi 1000.000000 30000.000000
11 Rue de la Loi 50, Bruxelles 700000.0 3.0 220 immovlan Bruxelles 3181.818182 233333.333333
In [17]:
df2 = df[~is_outlier] #détection statistique d'outliers
df2.to_csv("houses_features.csv")
df2
Out[17]:
address price rooms surface website city price_per_m2 price_per_room
0 Rue de Fer 26, 5000 Namur 400000.0 4.0 150 immoweb Namur 2666.666667 100000.000000
2 Porte de Namur 25, Bruxelles 400000.0 3.0 120 immoweb Bruxelles 3333.333333 133333.333333
4 Rue Saint-ghislain 30, 6224 Fleurus 330000.0 5.0 320 immoweb Fleurus 1031.250000 66000.000000
6 Rue de la Closière 20, Fleurus 230000.0 3.0 170 immoweb Fleurus 1352.941176 76666.666667
10 Rue de Bruxelles 42, 5000 Namur 350000.0 4.0 200 immovlan Namur 1750.000000 87500.000000
12 Rue de la Loi 51, Bruxelles 280000.0 3.0 120 immoweb Bruxelles 2333.333333 93333.333333
13 Rue de la Loi 52, Bruxelles 400000.0 4.0 150 immovlan Bruxelles 2666.666667 100000.000000
14 Rue de la Loi 53, Bruxelles 480000.0 5.0 170 immovlan Bruxelles 2823.529412 96000.000000
15 Rue de Fer 27, Namur 280000.0 3.0 140 immoweb Namur 2000.000000 93333.333333
16 Rue de Fer 28, Namur 320000.0 4.0 160 immoweb Namur 2000.000000 80000.000000
17 Rue de Fer 29, Namur 350000.0 5.0 180 immovlan Namur 1944.444444 70000.000000

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 [18]:
from matplotlib import pyplot as plt

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

plt.bar(df.address,df.price_per_room)
plt.xticks(rotation=90)
Out[19]:
([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
 <a list of 13 Text xticklabel objects>)
In [20]:
#méthode intégrée de pandas
df.plot.bar(y="price_per_room")
plt.xticks(rotation=90)           
Out[20]:
(array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]),
 <a list of 13 Text xticklabel objects>)
In [21]:
#On retire les données extrêmes
df2 = df.drop([3,11]) #détection manuelle (visuelle)
df2
Out[21]:
address price rooms surface website city price_per_m2 price_per_room
0 Rue de Fer 26, 5000 Namur 400000.0 4.0 150 immoweb Namur 2666.666667 100000.000000
2 Porte de Namur 25, Bruxelles 400000.0 3.0 120 immoweb Bruxelles 3333.333333 133333.333333
4 Rue Saint-ghislain 30, 6224 Fleurus 330000.0 5.0 320 immoweb Fleurus 1031.250000 66000.000000
6 Rue de la Closière 20, Fleurus 230000.0 3.0 170 immoweb Fleurus 1352.941176 76666.666667
10 Rue de Bruxelles 42, 5000 Namur 350000.0 4.0 200 immovlan Namur 1750.000000 87500.000000
12 Rue de la Loi 51, Bruxelles 280000.0 3.0 120 immoweb Bruxelles 2333.333333 93333.333333
13 Rue de la Loi 52, Bruxelles 400000.0 4.0 150 immovlan Bruxelles 2666.666667 100000.000000
14 Rue de la Loi 53, Bruxelles 480000.0 5.0 170 immovlan Bruxelles 2823.529412 96000.000000
15 Rue de Fer 27, Namur 280000.0 3.0 140 immoweb Namur 2000.000000 93333.333333
16 Rue de Fer 28, Namur 320000.0 4.0 160 immoweb Namur 2000.000000 80000.000000
17 Rue de Fer 29, Namur 350000.0 5.0 180 immovlan Namur 1944.444444 70000.000000
In [22]:
df2.groupby("rooms").mean()
Out[22]:
price surface price_per_m2 price_per_room
rooms
3.0 297500.000000 137.500000 2254.901961 99166.666667
4.0 367500.000000 165.000000 2270.833333 91875.000000
5.0 386666.666667 223.333333 1933.074619 77333.333333
  • 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 [23]:
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")
Out[23]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f7293705c88>
In [24]:
#plus lisible (on transpose pour avoir un graphe par varible plutôt que par nombre de chambres)
result.transpose().plot(kind="bar")
Out[24]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f72937c8e48>

On remarque que:

  • le prix augmente à peu près
  • la surface augmente
  • le prix au m2 diminue
  • le prix par pièce diminue
In [25]:
website_comparison = df.groupby("website").mean()
website_comparison.plot.bar(y="price")
Out[25]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f72935993c8>
In [26]:
#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")
Out[26]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f72935f6518>
In [27]:
count_analysis.plot.pie(subplots = True, figsize = (15,15))
Out[27]:
array([<matplotlib.axes._subplots.AxesSubplot object at 0x7f72934e2d30>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x7f72935035c0>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x7f72934ada20>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x7f7293454e80>],
      dtype=object)
In [28]:
price_analysis.plot.bar(subplots = True, figsize = (10,10))
Out[28]:
array([<matplotlib.axes._subplots.AxesSubplot object at 0x7f72933c9390>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x7f7293405860>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x7f72933accc0>,
       <matplotlib.axes._subplots.AxesSubplot object at 0x7f7293361160>],
      dtype=object)

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