{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "6_Data_Mining.ipynb", "provenance": [], "collapsed_sections": [], "toc_visible": true, "include_colab_link": true }, "kernelspec": { "name": "python3", "display_name": "Python 3" } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "view-in-github", "colab_type": "text" }, "source": [ "\"Open" ] }, { "cell_type": "markdown", "metadata": { "id": "DfQ-d2onvooo", "colab_type": "text" }, "source": [ "Mickaël Tits\n", "CETIC\n", "mickael.tits@cetic.be" ] }, { "cell_type": "markdown", "metadata": { "id": "Ug1Bj8_tAVWq", "colab_type": "text" }, "source": [ "# Chapitre 6 - Introduction au Data Mining et à la visualisation de données\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "-ce4y6EYR9Pt", "colab_type": "text" }, "source": [ "Dans ce chapitre, nous allons explorer plus prodonfément les possibilités d'exploration de données (data mining) avec Pandas. \n", "* Nous allons d'abord **extraire de nouvelles caractéristiques** à partir des variables existantes, pour améliorer le dataframe.\n", "* Nous allons tester différentes méthodes d'exploration de base, selon le type de caractéristique (variables **continues** ou **catégorielles**)\n", "* Nous allons détecter les valeurs particulières, souvent appelées **anomalies**, ou outliers.\n", "* Nous allons finalement montrer quelques exemples de **visualisation graphique** des données grâce à la librairie **Matplotlib**\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "fdJpiflmUNkc", "colab_type": "text" }, "source": [ "Chargez d'abord le dataframe préparé lors du chapitre précédent. " ] }, { "cell_type": "code", "metadata": { "id": "5LmLdhShSGzL", "colab_type": "code", "colab": {} }, "source": [ "import pandas as pd\n", "\n", "#Si vous venez d'exécuter le notebook précédent, vous pouvez simplement récupérer le fichier temporaire créé.\n", "#df = pd.read_hdf(\"houses (1).h5\")\n", "\n", "#Vous pouvez aussi récupérer une version du fichier hébergée ici:\n", "df = pd.read_csv(\"https://raw.githubusercontent.com/titsitits/Python_Data_Science/master/Donn%C3%A9es/houses.csv\", index_col=0)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "sEx1UGV2SUk0", "colab_type": "code", "colab": {} }, "source": [ "df" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "ZCMYrQKBXKUt", "colab_type": "text" }, "source": [ "# Extraction de caractéristiques (i.e. augmentation du DataFrame par de nouvelles colonnes)" ] }, { "cell_type": "code", "metadata": { "id": "zWaSDF3HMz4C", "colab_type": "code", "colab": {} }, "source": [ "#Extract city\n", "def get_city(address):\n", "\n", " #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) )\n", " for i in range(len(address)-1,0,-1):\n", " c = address[i]\n", " if c in \"0123456789,\":\n", " #c est un nombre (ou une virgule), on peut sortir de la boucle\n", " break\n", "\n", " #On extrait le nom de la ville\n", " city = address[i+2:]\n", "\n", " return city" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "ggB0pgbStyPj", "colab_type": "code", "colab": {} }, "source": [ "my_address = \"Rue de Bruxelles 42, Namur\"\n", "print(get_city(my_address))\n", "\n", "my_address = \"Namur\"\n", "print(get_city(my_address))" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "Rzxt1LTVkiYo", "colab_type": "code", "colab": {} }, "source": [ "df = df.assign(city = df[\"address\"].apply(get_city))\n", "df = df.assign(price_per_m2 = df.price/df.surface)\n", "df = df.assign(price_per_room = df.price/df.rooms)\n", "df" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "120j9NbNMIQ2", "colab_type": "code", "colab": {} }, "source": [ "#Ou (idem)\n", "df[\"city\"] = df[\"address\"].apply(get_city)\n", "df[\"price_per_m2\"] = df.price/df.surface\n", "df[\"price_per_room\"] = df.price/df.rooms\n", "df" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "siHvkjWKlVDI", "colab_type": "code", "colab": {} }, "source": [ "df.groupby(\"website\").mean()" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "CZaA_8dTlgHw", "colab_type": "text" }, "source": [ "Le prix/m2 et le prix/pièce est en moyenne plus élevé sur immovlan." ] }, { "cell_type": "code", "metadata": { "id": "VXTedsTKlsTz", "colab_type": "code", "colab": {} }, "source": [ "df.groupby(\"city\").mean()" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "NMy1CDKCmA_F", "colab_type": "text" }, "source": [ "Quelques observations:\n", "* Bruxelles serait la ville la plus chère\n", "* Charleroi la ville la moins chère\n", "* Les maisons sont généralement plus grandes à Fleurus\n", "* Les maisons de Fleurus sont plus chères qu'à Charleroi, mais le prix au m2 est presque le même\n", "* Les maisons de Charleroi seraient petites mais avec beaucoup de pièces (toutes petites du coup)" ] }, { "cell_type": "markdown", "metadata": { "id": "QtoIJoNFm964", "colab_type": "text" }, "source": [ "# Exploration selon le type de caractéristiques: variables continues et catégorielles\n", "\n", "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.\n", "\n", "* 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).\n", "\n", "* 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.\n", "\n", "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).\n", "\n", "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.)." ] }, { "cell_type": "markdown", "metadata": { "id": "sABaHctp1ogg", "colab_type": "text" }, "source": [ "## Corrélations entre les variables continues" ] }, { "cell_type": "markdown", "metadata": { "id": "HqlFcFT0CJFL", "colab_type": "text" }, "source": [ "La méthode .corr() permet de calculer les corrélations 2 par 2 pour toutes les variables numériques." ] }, { "cell_type": "code", "metadata": { "id": "eAMhg_OQ1zR2", "colab_type": "code", "colab": {} }, "source": [ "df.corr()" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "jQd4mlrb12S6", "colab_type": "text" }, "source": [ "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." ] }, { "cell_type": "code", "metadata": { "id": "V_XUtX_Iy2Gt", "colab_type": "code", "colab": {} }, "source": [ "bxldf = df[df.city == \"Bruxelles\"]\n", "bxldf" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "GGjgU3Cg0-2Y", "colab_type": "code", "colab": {} }, "source": [ "bxldf.corr()" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "w_uqhLeG35QJ", "colab_type": "text" }, "source": [ "Si on n'analyse que les maisons Bruxelloises, on constate une corrélation forte entre la surface et le prix." ] }, { "cell_type": "markdown", "metadata": { "id": "2F5kVGQG1dQT", "colab_type": "text" }, "source": [ "## Comparaison de catégories: maisons par nombre de pièces" ] }, { "cell_type": "code", "metadata": { "id": "W7QQ8Pm_qgOh", "colab_type": "code", "colab": {} }, "source": [ "df.groupby(\"rooms\").mean()" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "qhmyYJupq-C3", "colab_type": "text" }, "source": [ "* 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." ] }, { "cell_type": "code", "metadata": { "id": "pz8qPP8vs5mY", "colab_type": "code", "colab": {} }, "source": [ "df[df.rooms == 5]" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "mW2k4mbjJKXg", "colab_type": "text" }, "source": [ "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" ] }, { "cell_type": "markdown", "metadata": { "id": "sMx3V1ofPbTB", "colab_type": "text" }, "source": [ "## Comparaison de catégories multiples" ] }, { "cell_type": "code", "metadata": { "id": "ztj9hxDiMQL9", "colab_type": "code", "colab": {} }, "source": [ "cat1 = \"city\"\n", "#cat2 = \"rooms\"\n", "cat2 = \"website\"\n", "count_analysis = df.groupby([cat1,cat2])[\"price\"].count().unstack(fill_value=0).transpose()\n", "count_analysis" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "H2spQ_K5RXlf", "colab_type": "code", "colab": {} }, "source": [ "price_analysis = df.groupby([cat1,cat2])[\"price\"].mean().unstack(fill_value=0).transpose()\n", "price_analysis" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "HwCs3n-mrSRa", "colab_type": "text" }, "source": [ "# Détection/suppression des anomalies ?\n", "\n", "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 ?).\n", "\n", "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.\n", "\n", "Les anomalies peuvent se détecter de deux manières: les statistiques et la visualisation graphique." ] }, { "cell_type": "code", "metadata": { "id": "AeXpEgLo-Zjv", "colab_type": "code", "colab": {} }, "source": [ "df" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "tbygvQ3F8Mav", "colab_type": "code", "colab": {} }, "source": [ "#Inter-quantile range\n", "Q1 = df.price_per_room.quantile(0.25)\n", "Q3 = df.price_per_room.quantile(0.75)\n", "IQR = Q3 - Q1\n", "h = 2\n", "print(\"limits:\", (Q1 - h * IQR), \"to\", (Q3 + h * IQR))\n", "\n", "#Les données s'écartant fortement des quantiles sont potentiellement des anomalies (outlier en anglais)\n", "is_outlier = (df.price_per_room < (Q1 - h * IQR)) | (df.price_per_room > (Q3 + h * IQR))\n", "df.loc[is_outlier]" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "KSpAhzcBYgj7", "colab_type": "code", "colab": {} }, "source": [ "df2 = df[~is_outlier] #détection statistique d'outliers\n", "df2.to_csv(\"houses_features.csv\")\n", "df2" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "C7DGIGdluWig", "colab_type": "text" }, "source": [ "# Visualisation de données avec `Matplotlib`\n", "\n", "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)." ] }, { "cell_type": "code", "metadata": { "id": "jdWUb0mJ0euk", "colab_type": "code", "colab": {} }, "source": [ "from matplotlib import pyplot as plt\n", "\n", "plt.scatter(df.surface, df.price)\n", "plt.xlabel(\"Surface\")\n", "plt.ylabel(\"Price\")" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "E_g3E8o9rY0E", "colab_type": "code", "colab": {} }, "source": [ "from matplotlib import pyplot as plt\n", "\n", "plt.bar(df.address,df.price_per_room)\n", "plt.xticks(rotation=90)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "6qnUZJK2tbMo", "colab_type": "code", "colab": {} }, "source": [ "#méthode intégrée de pandas\n", "df.plot.bar(y=\"price_per_room\")\n", "plt.xticks(rotation=90) " ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "xEUcqzP5vvfH", "colab_type": "code", "colab": {} }, "source": [ "#On retire les données extrêmes\n", "df2 = df.drop([3,11]) #détection manuelle (visuelle)\n", "df2" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "8pd7l-Z3wHAD", "colab_type": "code", "colab": {} }, "source": [ "df2.groupby(\"rooms\").mean()" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "mCSyQ5Lswbl5", "colab_type": "text" }, "source": [ "* Après avoir retiré les donénes extrêmes, l'évolution du prix en fonction du nombre de chambres est plus cohérente.\n", "* On remarque que le prix au m2 augmente, mais que le prix par pièce diminue." ] }, { "cell_type": "code", "metadata": { "id": "zDVI9DvqwxFX", "colab_type": "code", "colab": {} }, "source": [ "result = df2.groupby(\"rooms\").mean()\n", "#Divide each column by its sum (so that sum is 1) - so that multiple bar plots with various orders are visible\n", "result = result/result.sum()\n", "result.plot(kind=\"bar\")" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "ct5UTpmm7ItQ", "colab_type": "code", "colab": {} }, "source": [ "#plus lisible (on transpose pour avoir un graphe par variable plutôt que par nombre de chambres)\n", "result.transpose().plot(kind=\"bar\")" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "8jXwAMMRzJnf", "colab_type": "text" }, "source": [ "On remarque que:\n", "* le prix augmente à peu près\n", "* la surface augmente\n", "* le prix au m2 diminue\n", "* le prix par pièce diminue" ] }, { "cell_type": "code", "metadata": { "id": "oFJhEsNat6io", "colab_type": "code", "colab": {} }, "source": [ "website_comparison = df.groupby(\"website\").mean()\n", "website_comparison.plot.bar(y=\"price\")" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "ad0KrEotvBiZ", "colab_type": "code", "colab": {} }, "source": [ "#df.plot: afficher une courbe. On trie d'abord les maisons par surface croissante pour afficher la courbe (DF.sort_values(\"surface\"))\n", "bxldf.sort_values(\"surface\").plot(\"surface\",\"price\")" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "lnOUpYmJMVn_", "colab_type": "code", "colab": {} }, "source": [ "count_analysis.plot.pie(subplots = True, figsize = (15,15))" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "EZY3bOLpPxuh", "colab_type": "code", "colab": {} }, "source": [ "price_analysis.plot.bar(subplots = True, figsize = (10,10))" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "JmLLbgt2Hg9W", "colab_type": "text" }, "source": [ "# Exercices\n", "\n", "## 1. Recherchez les maisons ayant un prix anormal\n", "\n", "Cherchez les maisons ayant un prix s'écartant fortement du IQR (inter-quantile range).\n", "\n", "Indice: nous avons réalisé un processus similaire plus haut sur le prix par chambre.\n", "\n", "Note: vous devriez trouver une maison à 700000€ (Rue de la Loi 50, Bruxelles).\n", "\n" ] }, { "cell_type": "code", "metadata": { "id": "tiFi_eH-lI7S", "colab_type": "code", "colab": {} }, "source": [ "" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "_-ROwVwUlJfl", "colab_type": "text" }, "source": [ "## 2. Définissez une fonction générique permettant de détecter les anomalies sur n'importe quelle colonne numérique\n", "\n", "Modularisez le code écrit à l'exercice précédent pour le rendre générique.\n", "\n" ] }, { "cell_type": "code", "metadata": { "id": "TaNZaAyWlMmM", "colab_type": "code", "colab": {} }, "source": [ "def detect_outliers(df, column):\n", " \n", " #Modifiez cette fonction de manière à renvoyer un dataframe contenant les outliers\n", " outliers = pd.DataFrame() \n", " \n", " return outliers\n", "\n", "#Tests\n", "out = detect_outliers(df, 'price')\n", "#Vous devriez trouver une maison à 700000€\n", "out = detect_outliers(df, 'surface')\n", "#Vous devriez trouver une maison avec une surface de 320m²" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "exGQnJJclNHE", "colab_type": "text" }, "source": [ "## 3. Définissez une fonction générique permettant de détecter les anomalies sur une colonne catégorielle\n", "\n", "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). \n" ] }, { "cell_type": "code", "metadata": { "id": "W1GdOs4iYr1s", "colab_type": "code", "colab": {} }, "source": [ "#variables catégorielles (le nombre de chambres peut être considéré comme variable catégorielle aussi)\n", "def detect_rare_cat(df, column, nmin = 2):\n", " \n", " \"\"\"\n", " Cette fonction permet de détecter des outliers dans des variables catégorielles\n", " \"\"\"\n", " outliers = pd.DataFrame()\n", " \n", " return outliers\n", "\n", "#Tests\n", "out = detect_rare_cat(df, 'city')\n", "#Vous devriez trouver une maison à Charleroi" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "bgxvdXf9lPzM", "colab_type": "text" }, "source": [ "## 4. Détectez automatiquement le type d'une variable\n", "\n", "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())\n", "Attention: le nombre de chambres 'rooms' peut à la fois être considéré comme catégoriel et numérique. Utilisez la méthode pd.DataFrame. \n", "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.\n" ] }, { "cell_type": "code", "metadata": { "id": "SHne6Oo8lR5d", "colab_type": "code", "colab": {} }, "source": [ "#Modifiez le code ci-dessous\n", "\n", "def is_cat(series, relative_threshold = 0.5, absolute_threshold = 20):\n", " \n", " #nombre de valeurs\n", " nvalues = len(series) \n", " #nombre de valeurs uniques\n", " ncats = len(series.unique())\n", " \n", " #On considère la variable comme catégorielle si le nombre de valeurs uniques et plus petit qu'un seuil relatif ou absolu\n", " return False\n", "\n", "def is_num(series):\n", " \n", " #On peut considérer une variable comme numérique si elle peut être convertie en float (utiliser try/except)\n", " #series.astype(float)\n", " \n", " return False\n", "\n", "#Tests\n", "print(is_cat(df.price))\n", "print(is_cat(df.city))\n", "\n", "#Application sur chaque colonne\n", "is_categorical = df.apply(is_cat)\n", "is_numeric = df.apply(is_num)\n", "print(is_categorical)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "Q0TeASzvlSOt", "colab_type": "text" }, "source": [ "## 5. Recherchez les anomalies de manière systématique" ] }, { "cell_type": "code", "metadata": { "id": "Fn_0vnMilTIl", "colab_type": "code", "colab": {} }, "source": [ "cols = df.columns\n", "\n", "for col in cols:\n", " \n", " print(\"Are there numerical outliers in %s ?\" % col)\n", " \n", " print(\"Are there categorical outliers in %s ?\" % col)\n", " \n", "# On devrait retrouver la maison de Charleroi, la maison de 320m², et la maison à 700000€ \n", "\n", "#Remarque: vous pouvez aussi commencer par extraire les noms des variables catégorielles/numériques, et puis utiliser un ecompréhension de liste dessus" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "wMt4elplxQVi", "colab_type": "text" }, "source": [ "# Fin\n", "Vous pouvez maintenant passer au [Chapitre 7: Introduction au Machine Learning](https://colab.research.google.com/github/titsitits/UNamur_Python_Analytics/blob/master/7_Machine_Learning_Introduction.ipynb)" ] } ] }