{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# TP : Classification de voitures\n", "\n", "## Chargement des données\n", "Dans ce TP, nous voulons classifier des voitures, selon leur type (sportive, citadine, familiale...). Commençons par charger les données dans Basthon :\n", "\n", "1. Télécharger les données (clic droit ici puis enregistrer la cible du lien sous). \n", "2. Dans Basthon, cliquer sur Fichier puis Ouvrir et sélectionner le fichier téléchargé.\n", "3. Exécuter le code ci-dessous, en modifiant titanic.csv si vous avez utilisé un autre nom de fichier." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
nommarquecylindréechevauxlongueurlargeurhauteurpoidsvitesse_max0_100consommation
0pandafiat124269365164155103016415.05.7
1ttaudi200020040417613514002357.07.0
2208peugeot1200100398174146105016314.03.8
3c3citroën11246138516715299815716.05.4
4berlingocitroën136075411172180137814916.07.4
5clio 4renault114975406173145110517712.05.0
6mégane 1renault199811541317014210851979.77.3
7espacerenault1870117466186173189717614.09.7
82008peugeot13986841617415612001909.55.7
9capturrenault133390422190158135218010.06.5
10carrera gtporsche573361246119211713853304.018.0
11f40ferrari293647844319811311003244.616.0
12scénicrenault1461105449181164170019011.07.0
\n", "
" ], "text/plain": [ " nom marque cylindrée chevaux longueur largeur hauteur \\\n", "0 panda fiat 1242 69 365 164 155 \n", "1 tt audi 2000 200 404 176 135 \n", "2 208 peugeot 1200 100 398 174 146 \n", "3 c3 citroën 1124 61 385 167 152 \n", "4 berlingo citroën 1360 75 411 172 180 \n", "5 clio 4 renault 1149 75 406 173 145 \n", "6 mégane 1 renault 1998 115 413 170 142 \n", "7 espace renault 1870 117 466 186 173 \n", "8 2008 peugeot 1398 68 416 174 156 \n", "9 captur renault 1333 90 422 190 158 \n", "10 carrera gt porsche 5733 612 461 192 117 \n", "11 f40 ferrari 2936 478 443 198 113 \n", "12 scénic renault 1461 105 449 181 164 \n", "\n", " poids vitesse_max 0_100 consommation \n", "0 1030 164 15.0 5.7 \n", "1 1400 235 7.0 7.0 \n", "2 1050 163 14.0 3.8 \n", "3 998 157 16.0 5.4 \n", "4 1378 149 16.0 7.4 \n", "5 1105 177 12.0 5.0 \n", "6 1085 197 9.7 7.3 \n", "7 1897 176 14.0 9.7 \n", "8 1200 190 9.5 5.7 \n", "9 1352 180 10.0 6.5 \n", "10 1385 330 4.0 18.0 \n", "11 1100 324 4.6 16.0 \n", "12 1700 190 11.0 7.0 " ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "voitures = pd.read_csv(\"voitures.csv\")\n", "voitures" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Pour éviter d'utiliser des dataframe pandas, nous allons utiliser des listes `attributs` et `noms` tels que `attributs[i]` contienne les caractéristiques (cyindrée, chevaux, longueur...) et `noms[i]` le nom de la voiture $i$ :" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['panda', 'fiat']" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "noms = voitures.iloc[:, :2].to_numpy().tolist()\n", "noms[0] # affiche le nom de la première voiture" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1242.0, 69.0, 365.0, 164.0, 155.0, 1030.0, 164.0, 15.0, 5.7]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "attributs = voitures.iloc[:, 2:].to_numpy().tolist()\n", "attributs[0] # affiche les valeurs des attributs de la première voiture" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Pour obtenir la signification des attributs :" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['cylindrée',\n", " 'chevaux',\n", " 'longueur',\n", " 'largeur',\n", " 'hauteur',\n", " 'poids',\n", " 'vitesse_max',\n", " '0_100',\n", " 'consommation']" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "attributs_noms = voitures.columns[2:].to_numpy().tolist()\n", "attributs_noms" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Standardisation des données" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `moyenne(j)` qui renvoie la moyenne de l'attribut `j` (c'est-à-dire la moyenne de `attributs[i][j]`, pour tous les `i` possibles).\n", "````" ] }, { "cell_type": "markdown", "execution_count": 5, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def moyenne(j):\n", " return sum([x[j] for x in attributs])/len(attributs)\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10.984615384615385" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "moyenne(7) # nombre moyen de secondes pour aller de 0 à 100 km/h" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `ecart_type(j)` qui renvoie l'écart-type de l'attribut `j` (c'est-à-dire $\\sigma = \\sqrt{\\sum_i \\frac{(x_i - \\mu)^2}{n}}$ où les $x_i$ sont les valeurs pour l'attribut `j` et $\\mu$ est la moyenne de l'attribut `j`).\n", "````" ] }, { "cell_type": "markdown", "execution_count": 7, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def ecart_type(j):\n", " m = moyenne(j)\n", " return (sum([(x[j] - m)**2 for x in attributs])/len(attributs))**0.5\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1206.7697505196386" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ecart_type(0)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Si des données $x_1, ..., x_n$ ont une moyenne $\\mu$ et un écart-type $\\sigma$, on peut standardiser ces données, c'est-à-dire se ramener à une moyenne à $0$ et un écart-type à $1$, en remplaçant chaque $x_i$ par :\n", "\n", "$$\\frac{x_i - \\mu}{\\sigma}$$" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Définir une matrice `X` qui contient les caractéristiques standardisées des voitures.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 9, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "X = []\n", "for i in range(len(attributs)):\n", " X.append([(attributs[i][j] - moyenne(j))/ecart_type(j) for j in range(len(attributs[i]))])\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[-0.5518865547576234,\n", " -0.5838129204459943,\n", " -1.881194060479367,\n", " -1.4284117337371103,\n", " 0.3256520809467957,\n", " -0.9620792816591849,\n", " -0.6746501884297091,\n", " 1.034391681977312,\n", " -0.5737524192504039]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X[0]" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " On aura aussi besoin de réaliser l'opération inverse de la précédente. Écrire une fonction `inverse_standardisation(y)` qui, pour une voiture `y` (c'est-à-dire la liste de ses attributs), renvoie une liste `x` telle que $x_i = y_i \\sigma_i + \\mu_i$, où $\\mu_i$ et $\\sigma_i$ sont la moyenne et l'écart-type de l'attribut $i$.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 11, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def inverse_standardisation(y):\n", " return [y[i]*ecart_type(i) + moyenne(i) for i in range(len(y))]\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1242.0, 69.0, 365.0, 164.0, 155.0, 1030.0, 164.0, 15.0, 5.7]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "inverse_standardisation(X[0]) # doit être égal à attributs[0]" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Algorithme des k-moyennes" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `d(x, y)` qui calcule la distance euclidienne entre deux attributs de voitures.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 13, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def d(x, y):\n", " s = 0\n", " for i in range(len(x)):\n", " s += (x[i] - y[i])**2\n", " return s**0.5\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.6573045403567335" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d(X[0], X[1])" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `centre(indices)` qui renvoie le centre des `X[i]` pour `i` dans la liste `indices`.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 15, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def centre(indices):\n", " c = [0]*len(X[0])\n", " for i in indices:\n", " for j in range(len(c)):\n", " c[j] += X[i][j]\n", " if len(indices) != 0:\n", " for j in range(len(c)):\n", " c[j] /= len(indices)\n", " return c\n", "\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[-0.3900219295884706,\n", " -0.42619570979982285,\n", " -0.30720748345964605,\n", " -0.35774636214316785,\n", " 0.48641703230027744,\n", " 0.16190614861559313,\n", " -0.6103335371327435,\n", " 0.8626535994012577,\n", " -0.4020041621721581]" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "centre([0, 2, 7]) # centre des voitures 0, 2 et 7" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Dans la suite, on utilisera une liste `classes` telle que `classes[i]` est la liste des indices `j` tels que `X|j]` est dans la classe `i`. \n", "Par exemple, si `classes = [[0, 8, 11], [2, 7]]` alors la classe numéro $0$ contient les voitures $0$, $8$, $11$ (dont les valeurs sont `X[0]`, `X[8]` et `X[11]`)." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `calculer_centres(classes)` qui renvoie une liste contenant les centres de chaque classe. Il faut donc que `centres[i]` soit le centre de la classe `i`.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 17, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def calculer_centres(classes):\n", " centres = []\n", " for c in classes:\n", " centres.append(centre(c))\n", " return centres\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[-0.04088048553760174,\n", " 0.2302101421317011,\n", " -0.36593832588575514,\n", " 0.04375315220456059,\n", " -0.4067215863301765,\n", " -0.6579569555419957,\n", " 0.4128859153189823,\n", " -0.3309260745023202,\n", " 0.2686318892762309],\n", " [-0.30908961700389415,\n", " -0.34738710447673715,\n", " 0.47978580505021445,\n", " 0.1775863236538034,\n", " 0.5667995079770183,\n", " 0.7238988637529822,\n", " -0.5781752114842607,\n", " 0.7767845581132305,\n", " -0.31613003363303516]]" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "calculer_centres([[0, 8, 11], [2, 7]])" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "On initialisera l'algorithme des k-moyennes avec des centres aléatoires en utilisant la fonction suivante :" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[0.417022004702574,\n", " 0.7203244934421581,\n", " 0.00011437481734488664,\n", " 0.30233257263183977,\n", " 0.14675589081711304,\n", " 0.0923385947687978,\n", " 0.1862602113776709,\n", " 0.34556072704304774,\n", " 0.39676747423066994],\n", " [0.538816734003357,\n", " 0.4191945144032948,\n", " 0.6852195003967595,\n", " 0.20445224973151743,\n", " 0.8781174363909454,\n", " 0.027387593197926163,\n", " 0.6704675101784022,\n", " 0.41730480236712697,\n", " 0.5586898284457517]]" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def centres_aléatoires(k):\n", " np.random.seed(1)\n", " return np.random.rand(k, len(X[0])).tolist()\n", "\n", "centres_aléatoires(2) # exemple de 2 centres aléatoires" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `plus_proche(i, centres)` qui renvoie l'indice du centre le plus proche de la voiture `X[i]`. Il faut donc renvoyer `j` tel que `d(X[i], centres[j])` est minimum.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 20, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def plus_proche(i, centres):\n", " jmin = 0\n", " for j in range(1, len(centres)):\n", " d1 = d(X[i], centres[j])\n", " if d1 < d(X[i], centres[jmin]):\n", " jmin = j\n", " return jmin\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "plus_proche(0, centres_aléatoires(4))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `calculer_classes(centres)` qui renvoie une liste `classes` de même taille que `centres` et contenant les classes obtenues en associant chaque `X[j]` au centre le plus proche. \n", "Ainsi, `classes[i]` doit contenir les indices `j` tel que le centre le plus proche de `X[j]` est `centres[i]`.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 22, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def calculer_classes(centres):\n", " classes = [[] for c in centres]\n", " for i in range(len(X)):\n", " classes[plus_proche(i, centres)].append(i)\n", " return classes\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[0, 1, 2, 3, 5, 6, 8, 9, 10, 11], [4], [7, 12]]" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "calculer_classes(centres_aléatoires(3))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `k_moyennes(k)` qui renvoie les classes obtenues par l'algorithme des k-moyennes appliqué aux données `X`.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 24, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def k_moyennes(k):\n", " centres = centres_aléatoires(k)\n", " while True:\n", " classes = calculer_classes(centres)\n", " centres2 = calculer_centres(classes)\n", " if centres == centres2:\n", " return classes, centres\n", " centres = centres2\n", "```\n", "````" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[2.010739827506566,\n", " 2.2652677985759393,\n", " 1.1845559141635222,\n", " 1.683209502457785,\n", " -1.8178806037662938,\n", " -0.15425435291040127,\n", " 2.1845173101353987,\n", " -1.7220045433683606,\n", " 2.198755159298423],\n", " [-0.39288770686855584,\n", " -0.42594631547918027,\n", " -0.6566559958949947,\n", " -0.7006938639818525,\n", " 0.13139443139467197,\n", " -0.4840370002937282,\n", " -0.41153661494212246,\n", " 0.36461315993070004,\n", " -0.5216145554945079],\n", " [-0.29279266668822856,\n", " -0.3743216911061462,\n", " 0.9613787129443082,\n", " 0.7463773023130854,\n", " 0.861535252125068,\n", " 1.393601569390209,\n", " -0.3589139002446052,\n", " 0.1757012690970402,\n", " -0.07486462488026106]]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "classes, centres = k_moyennes(3)\n", "centres" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Écrire une fonction `inertie(classes, centres)` qui renvoie la somme des carrés des distances des voitures aux centres de leurs classes.\n", "````" ] }, { "cell_type": "markdown", "execution_count": 26, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "def inertie(classes, centres):\n", " s = 0\n", " for i in range(len(classes)):\n", " for j in classes[i]:\n", " s += d(X[j], centres[i])**2\n", " return s\n", "```\n", "````" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Exécuter le code suivant pour afficher l'inertie en fonction du nombre de classes $k$. Quel $k$ vous semble optimal ?\n", "````" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "image/png": "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VXFxsdnRLCU5OVnPP/+8tmzZos2bN2vw4MEaMWKEdu7caXY0y0pPT9err76q7t27mx3Fsi677DIdO3bM+1i7dq3ZkSzn5MmTGjBggIKCgvTRRx/pm2++0YsvvqiWLVs2exa2i2gCN9xwg2644QazY1jexx9/XOvPc+fOVVxcnLZs2aKrr77apFTWM3z48Fp//tOf/qRZs2Zpw4YNuuyyy0xKZV1FRUUaN26cXn/9dU2dOtXsOJblcDiUkJBgdgxLmz59ulJSUjRnzhzvsfbt25uShSs7sAyXyyVJiomJMTmJdVVWVmr+/PkqLi5WWlqa2XEsaeLEibrppps0dOhQs6NY2r59+5SUlKQOHTpo3LhxOnz4sNmRLGfx4sXq06ePRo8erbi4OPXs2VOvv/66KVm4sgNLcLvdmjJligYMGKCuXbuaHcdytm/frrS0NJWUlCgiIkKLFi1Sly5dzI5lOfPnz9fWrVuVnp5udhRL69+/v+bOnatOnTrp2LFjeuaZZ3TVVVdpx44dioyMNDueZRw4cECzZs3SQw89pCeeeELp6em6//77FRwcrPHjxzdrFsoOLGHixInasWMH992bSKdOnbRt2za5XC69++67Gj9+vFatWkXhaUQZGRl64IEH9Nlnnyk0NNTsOJZ25jCD7t27q3///mrXrp3efvtt3X333SYmsxa3260+ffroueeekyT17NlTO3bs0OzZs5u97HAbC35v0qRJWrp0qVauXKnk5GSz41hScHCwOnbsqN69e2vatGnq0aOH/vd//9fsWJayZcsW5eTkqFevXnI4HHI4HFq1apVmzJghh8OhyspKsyNaVnR0tC699FLt37/f7CiWkpiYeNY/iDp37mzKLUOu7MBveTweTZ48WYsWLdLnn39u2sC3QOR2u1VaWmp2DEsZMmSItm/fXuvYr371K6Wmpuqxxx6T3W43KZn1FRUV6dtvv9UvfvELs6NYyoABA85aDmTv3r1q165ds2eh7DSBoqKiWv9COHjwoLZt26aYmBi1bdvWxGTWMnHiRM2bN08ffPCBIiMjlZWVJUlyOp0KCwszOZ11PP7447rhhhvUtm1bFRYWat68efr888/1ySefmB3NUiIjI88abxYeHq7Y2FjGoTWyRx55RMOHD1e7du2UmZmpp59+Wna7XXfccYfZ0SzlwQcf1JVXXqnnnntOt99+uzZt2qTXXntNr732WvOH8aDRrVy50iPprMf48ePNjmYp5/qMJXnmzJljdjRL+fWvf+1p166dJzg42NO6dWvPkCFDPJ9++qnZsQLCz372M88DDzxgdgzLGTNmjCcxMdETHBzsadOmjWfMmDGe/fv3mx3LkpYsWeLp2rWrJyQkxJOamup57bXXTMlheDweT/NXLAAAgObBAGUAAGBplB0AAGBplB0AAGBplB0AAGBplB0AAGBplB0AAGBplB0AAGBplB0AAGBplB0AlnTNNddoypQpZscA4AMoOwAAwNIoOwAAwNIoOwACwocffiin06k333zT7CgAmpnD7AAA0NTmzZune++9V/PmzdOwYcPMjgOgmXFlB4ClzZw5U/fdd5+WLFlC0QECFFd2AFjWu+++q5ycHK1bt059+/Y1Ow4Ak3BlB4Bl9ezZU61bt9Y//vEPeTwes+MAMAllB4BlXXzxxVq5cqU++OADTZ482ew4AEzCbSwAlnbppZdq5cqVuuaaa+RwOPTyyy+bHQlAM6PsALC8Tp06acWKFbrmmmtkt9v14osvmh0JQDMyPNzIBgAAFsaYHQAAYGmUHQAAYGmUHQAAYGmUHQAAYGmUHQAAYGmUHQAAYGmUHQAAYGmUHQAAYGmUHQAAYGmUHQAAYGmUHQAAYGn/H5zs27uuvwxBAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "kmax = 6\n", "I = []\n", "for k in range(1, kmax+1):\n", " classes,centres = k_moyennes(k)\n", " I.append(inertie(classes, centres))\n", "plt.plot(range(1, kmax+1), I)\n", "plt.xlabel(\"k\")\n", "plt.ylabel(\"inertie\")\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " On prend la valeur de $k$ obtenue à la question précédente. Pour chaque classe, afficher les voitures de cette classe ainsi que le centre (en appliquant `inverse_standardisation` dessus).\n", "````" ] }, { "cell_type": "markdown", "execution_count": 28, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "classes, centres = k_moyennes(4)\n", "for i in range(len(centres)):\n", " print(\"Centre\", i, \":\", inverse_standardisation(centres[i]))\n", " for j in classes[i]:\n", " print(\" \", noms[j])\n", " print()\n", "```\n", "````" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Question\n", " Pour savoir ce qui différencie deux classes, on peut regarder les coordonnées du vecteur dont les extrémités sont les centres des deux classes. Quelles sont les caractéristiques qui différencient les voitures familiale des voitures citadines ? Les voitures sportives des voitures citadines ?\n", "````" ] }, { "cell_type": "markdown", "execution_count": 29, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "attributs_noms\n", "```\n", "``` \n", "['cylindrée',\n", " 'chevaux',\n", " 'longueur',\n", " 'largeur',\n", " 'hauteur',\n", " 'poids',\n", " 'vitesse_max',\n", " '0_100',\n", " 'consommation']\n", "\n", "```\n", "````" ] }, { "cell_type": "markdown", "execution_count": 30, "metadata": { "tags": [ "cor" ] }, "source": [ "````{admonition} Solution\n", ":class: tip, dropdown\n", "``` python\n", "np.array(centres[1]) - np.array(centres[3])\n", "```\n", "``` \n", "array([-0.31896446, -0.1361693 , -1.88526004, -1.02047793, -1.22359991,\n", " -2.32875335, -0.11255414, 0.15027082, -0.75037631])\n", "\n", "```\n", "````" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.2" }, "vscode": { "interpreter": { "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" } } }, "nbformat": 4, "nbformat_minor": 2 }