{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "faafb9a0",
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import os\n",
    "import sys\n",
    "\n",
    "sys.path.append(os.path.abspath(\"..\"))\n",
    "\n",
    "from IPython.core.display_functions import DisplayHandle\n",
    "from pandas import read_csv\n",
    "from sklearn.model_selection import train_test_split, cross_val_score\n",
    "from sklearn.preprocessing import MinMaxScaler, StandardScaler\n",
    "from sklearn.pipeline import make_pipeline\n",
    "from sklearn.linear_model import LinearRegression\n",
    "from IPython.display import Markdown, display\n",
    "from src.cleaning import main as cleaning\n",
    "from src.scraper import main as scraper\n",
    "from sklearn.tree import DecisionTreeRegressor\n",
    "from sklearn.neighbors import KNeighborsRegressor\n",
    "from sklearn.decomposition import PCA\n",
    "from seaborn import heatmap\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "\n",
    "\n",
    "class Tableau:\n",
    "    \"\"\"\n",
    "    classe pour afficher le tableau des scores automatiquements.\n",
    "    \"\"\"\n",
    "    def __init__(self, score_title: str = \"R²\") -> None:\n",
    "        self._table: dict[str, float] = dict[str, float]()\n",
    "        self._score_title: str = score_title\n",
    "\n",
    "    def ajoutligne(self, methode: str, score: float) -> None:\n",
    "        self._table[methode] = score\n",
    "\n",
    "    def dessiner(self) -> DisplayHandle | None:\n",
    "        table = f\"| Méthode | {self._score_title} |\\n| :---: | :---: |\\n\"\n",
    "\n",
    "        for key, value in self._table.items():\n",
    "            table+= f\"|{key}|{value:0.6f}|\\n\"\n",
    "\n",
    "        return display(Markdown(table))\n",
    "\n",
    "\n",
    "_filename_scraped = \"../data.csv\"\n",
    "_filename_cleaned = \"../clean.csv\" \n",
    "\n",
    "_url = \"bordeaux.html\"\n",
    "\n",
    "try:\n",
    "    if not os.path.exists(_filename_scraped):\n",
    "        print(f\"{_filename_scraped} est absent. Lancement du scraper (~10min):\")\n",
    "        scraper(_filename_scraped, _url)\n",
    "    cleaning(_filename_scraped)\n",
    "except Exception as e:\n",
    "    print(f\"ERREUR: {e}\")\n",
    "\n",
    "dataset = read_csv(_filename_cleaned)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed96ca6c",
   "metadata": {},
   "source": [
    "## Troisième jalon : Apprentissage\n",
    "\n",
    "Nous sommes prêts à passer à l’apprentissage à proprement parler. Pour ce faire, nous allons utiliser la bibliothèque scikit-learn.\n",
    "\n",
    "### Préparation de données d’apprentissage et de test\n",
    "Le but est d’estimer le prix d’un produit à partir des attributs. La colonne “Prix” correspond ainsi aux étiquettes (\"targets\"), tandis que les autres colonnes sont les attributs (\"features\")."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27e82783",
   "metadata": {},
   "source": [
    "### Question 15\n",
    "Préparez la matrice de données et le vecteur des étiquettes pour votre jeu de données. Séparez les données en un jeu d’entraînement et un jeu de test, sur un base 75%/25%, en utilisant\n",
    "le paramètre random_state = 49.\n",
    "\n",
    "Nous utiliserons les notations suivantes :\n",
    "\n",
    "- X_train (resp. y_train) fait référence aux données d’apprentissage (resp. à leur étiquette),\n",
    "\n",
    "- X_test (resp. y_test) fait référence aux données de test (resp. à leur étiquette)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8342340f",
   "metadata": {},
   "outputs": [],
   "source": [
    "X = dataset.drop(\"Prix\", axis=1).values\n",
    "y = dataset[\"Prix\"].values\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y, test_size=0.25, random_state=49\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67685584",
   "metadata": {},
   "source": [
    "## Premier modèle : Régression Linéaire\n",
    "\n",
    "Dans un premier temps, nous allons appliquer une régression linéaire [LinearRegression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html) sur nos données."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "9dfdc01f",
   "metadata": {},
   "outputs": [],
   "source": [
    "lr_table: Tableau = Tableau()\n",
    "\n",
    "\n",
    "def get_model_name(model) -> str:\n",
    "    return (\n",
    "        model.steps[-2][1].__class__.__name__\n",
    "        if hasattr(model, \"steps\")\n",
    "        else model.__class__.__name__\n",
    "    )\n",
    "\n",
    "\n",
    "def dessiner(lr, X_test, y_test):\n",
    "    x_test_pred = lr.predict(X_test)\n",
    "\n",
    "    plt.figure(figsize=(8, 6))\n",
    "    plt.scatter(x_test_pred, y_test, label=\"Vins\")\n",
    "\n",
    "    mn = min(x_test_pred.min(), y_test.min())\n",
    "    mx = max(x_test_pred.max(), y_test.max())\n",
    "\n",
    "    plt.plot([mn, mx], [mn, mx], color=\"red\", linestyle=\"--\", label=\"estim_LR=y_test\")\n",
    "\n",
    "    plt.xlabel(\"estim_LR\")\n",
    "    plt.ylabel(\"y_test\")\n",
    "    plt.legend()\n",
    "    plt.grid(True, linestyle=\":\", alpha=0.6)\n",
    "\n",
    "    name = get_model_name(lr)\n",
    "    plt.title(name)\n",
    "\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b6909c3",
   "metadata": {},
   "source": [
    "### Question 16\n",
    "Évaluez ce modèle sur le jeu de test par r2_score. Rappelons qu’il s’agit de la mesure par défaut pour la fonction score() des méthodes de régression."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "99de3ed7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LR score[r^2]: 0.234788\n"
     ]
    }
   ],
   "source": [
    "lr = LinearRegression()\n",
    "lr.fit(X_train, y_train)\n",
    "score: float = lr.score(X_test, y_test)\n",
    "\n",
    "# lr_table.ajoutligne(\"LR\", score)\n",
    "print(f\"LR score[r^2]: {score:.6f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f807cb05",
   "metadata": {},
   "source": [
    "### Question 17\n",
    "Nous allons Faire une comparaison entre les estimations (estim_LR) faites par le modèle pour les données de test et les prix y_test. Dessinez une figure où l’axe x représente estim_LR et l’axe y est y_test. Pensez à utiliser la fonction scatter pour représenter les points. Nous appellerons dans la suite figure de visualisation cette représentation. Rappelons que si votre modèle fonctionne bien pour ce jeu de données, vous devriez avoir les points autour de la diagonale\n",
    "où estim_LR est égal à y_test. Que constatez-vous ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "09eca16d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 59884 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "dessiner(lr, X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4189c565",
   "metadata": {},
   "source": [
    "On constate des bouteilles hors de prix et donc l'algorithme de regression linéaire ne prend pas suffisamment en compte ces valeurs là."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8889cde7",
   "metadata": {},
   "source": [
    "### Question 18\n",
    "Nous allons maintenant évaluer l’impact du pré-traitement sur nos données. En utilisant la commande make_pipeline, normalisez d’abord les données avant de lancer l’apprentissage, et évaluez le score sur le jeu de test. Répétez l’expérience, cette fois-ci en standardisant les données avant l’apprentissage. Dessinez les figures de visualisation pour les deux cas. Observez-vous les mêmes choses qu’à la question précédente ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "b94a89f2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Normalisation + LR score[r^2]: 0.234788\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 59360 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Standardisation + LR score[r^2]: 0.234788\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 59632 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "scaler_methods = [MinMaxScaler, StandardScaler]\n",
    "scaler_names = [\"Normalisation\", \"Standardisation\"]\n",
    "\n",
    "for name, scaler in zip(scaler_names, scaler_methods):\n",
    "    pipeline = make_pipeline(scaler(), LinearRegression())\n",
    "    pipeline.fit(X_train, y_train)\n",
    "\n",
    "    x_test_pred = pipeline.predict(X_test)\n",
    "    score: float = pipeline.score(X_test, y_test)\n",
    "\n",
    "    # lr_table.ajoutligne(f\"{name} + LR\", score)\n",
    "    print(f\"{name} + LR score[r^2]: {score:.6f}\")\n",
    "    \n",
    "    dessiner(pipeline, X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6857d2aa",
   "metadata": {},
   "source": [
    "On constate aucune différence entre la normalisation, la standardisation et sans prétraitement."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92a737f0",
   "metadata": {},
   "source": [
    "### Question 19\n",
    "Trouvez les valeurs minimales et maximales pour les prix dans votre dataset. Que peut-on dire de la dispersion des prix dans votre dataset ?\n",
    "Dans le cas où les étiquettes sont trop dispersées, une solution pourrait être de modifier l’échelle des étiquettes. Pour ce faire, on pourrait transformer les prix de sorte que les prix utilisés dans\n",
    "l’apprentissage soient ln(y[i]) à l’aide des fonctions ln et exp de la bibliothèque numphy. Quelles sont les nouvelles valeurs pour le prix minimum et maximum de votre dataset ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "4f1c169f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prix Min : 6.20€\n",
      "Prix Max : 17280.00€\n",
      "Log Min : 1.8245\n",
      "Log Max : 9.7573\n"
     ]
    }
   ],
   "source": [
    "print(f\"Prix Min : {y.min():.2f}€\")\n",
    "print(f\"Prix Max : {y.max():.2f}€\")\n",
    "\n",
    "y_log = np.log(y)\n",
    "\n",
    "print(f\"Log Min : {y_log.min():.4f}\")\n",
    "print(f\"Log Max : {y_log.max():.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7feda3af",
   "metadata": {},
   "source": [
    "### Question 20\n",
    "Après avoir éventuellement transformé les prix, remplissez le tableau suivant avec les résultats obtenus grâce à la régression linéaire, avec ou sans pré-traitement. Constatez-vous une amélioration des prédictions due au pré-traitement pour ce jeu de données et cette méthode ?\n",
    "Dessinez la figure de visualisation pour la méthode qui donne le meilleur score. Que constatez-vous ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "91cedffb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "| Méthode | R² |\n",
       "| :---: | :---: |\n",
       "|LR|0.452908|\n",
       "|Normalisation + LR|0.452908|\n",
       "|Standardisation + LR|0.452908|\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 78596 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y_log, test_size=0.25, random_state=49\n",
    ")\n",
    "\n",
    "scaler_methods = [None, MinMaxScaler, StandardScaler]\n",
    "scaler_names = [\"LR\", \"Normalisation + LR\", \"Standardisation + LR\"]\n",
    "\n",
    "best_models = []\n",
    "\n",
    "for name, scaler in zip(scaler_names, scaler_methods):\n",
    "    model = (\n",
    "        LinearRegression()\n",
    "        if scaler is None\n",
    "        else make_pipeline(scaler(), LinearRegression())\n",
    "    )\n",
    "    \n",
    "    _ = model.fit(X_train, y_train)\n",
    "    score: float = model.score(X_test, y_test)\n",
    "    \n",
    "    lr_table.ajoutligne(f\"{name}\", score)\n",
    "    best_models.append((model, score))\n",
    "\n",
    "best_model_lr, best_score_lr = best_models[0]\n",
    "for model, score in best_models:\n",
    "    if score > best_score_lr:\n",
    "        best_score_lr = score\n",
    "        best_model_lr = model\n",
    "\n",
    "lr_table.dessiner()\n",
    "dessiner(best_model_lr, X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5ec320d",
   "metadata": {},
   "source": [
    "L'application d'une transformation logarithmique sur les prix ont permis d'obtenir une prédiction bien plus précise."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f42b94f7",
   "metadata": {},
   "source": [
    "## Deuxième modèle : Arbre de Décision\n",
    "\n",
    "Dans la suite, vous utiliserez les prix transformés ou non en fonction de votre dataset. Le deuxième modèle d’apprentissage que nous allons considérer sont les arbres de décision [DecisionTreeRegressor](https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeRegressor.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3602df5",
   "metadata": {},
   "source": [
    "### Question 21\n",
    "En utilisant la méthode de validation croisée (cv) avec cv = 5, déterminez la valeur de la meilleure profondeur maximale (h) parmi max_depth= {3, 4, 5} sur les données X_train (sans pré-traitement).\n",
    "\n",
    "- Remplissez à nouveau un tableau de trois lignes (une pour la méthode “AD”, une pour “Normalisation + AD” et une pour “Standardisation + AD”) avec les résultats que vous avez obtenus grâce aux arbres de décision avec la profondeur maximale, h, avec ou sans pré-traitement pour les données de test.\n",
    "\n",
    "- Constatez-vous une amélioration significative suite au pré-traitement ? Les scores obtenus sont-ils satisfaisants ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4c21cd56",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "depth:  3\n"
     ]
    },
    {
     "data": {
      "text/markdown": [
       "| Méthode | R² |\n",
       "| :---: | :---: |\n",
       "|AD|0.393010|\n",
       "|Normalisation + AD|0.393010|\n",
       "|Standardisation + AD|0.393034|\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "depth:  4\n"
     ]
    },
    {
     "data": {
      "text/markdown": [
       "| Méthode | R² |\n",
       "| :---: | :---: |\n",
       "|AD|0.403744|\n",
       "|Normalisation + AD|0.403744|\n",
       "|Standardisation + AD|0.403761|\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "depth:  5\n"
     ]
    },
    {
     "data": {
      "text/markdown": [
       "| Méthode | R² |\n",
       "| :---: | :---: |\n",
       "|AD|0.400791|\n",
       "|Normalisation + AD|0.400791|\n",
       "|Standardisation + AD|0.400821|\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "best score= 0.40376125774243815  depth= 4  method= Standardisation + AD\n"
     ]
    }
   ],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y_log, test_size=0.25, random_state=49\n",
    ")\n",
    "\n",
    "best_score_ad = -np.inf\n",
    "best_depth = None\n",
    "best_scaler_name = None\n",
    "best_model_ad = None\n",
    "\n",
    "max_depth = [3, 4, 5]\n",
    "scaler_methods = [None, MinMaxScaler, StandardScaler]\n",
    "scaler_names = [\"AD\", \"Normalisation + AD\", \"Standardisation + AD\"]\n",
    "\n",
    "for depth in max_depth:\n",
    "    ad_table = Tableau()\n",
    "    \n",
    "    for name, scaler in zip(scaler_names, scaler_methods):\n",
    "        model = (\n",
    "            DecisionTreeRegressor(max_depth=depth)\n",
    "            if scaler is None\n",
    "            else make_pipeline(scaler(), DecisionTreeRegressor(max_depth=depth))\n",
    "        )\n",
    "        model.fit(X_train, y_train)\n",
    "        score: float = cross_val_score(model, X, y_log, cv=5).mean()\n",
    "        ad_table.ajoutligne(f\"{name}\", score)\n",
    "\n",
    "        if score > best_score_ad:\n",
    "            best_score_ad = score\n",
    "            best_depth = depth\n",
    "            best_scaler_name = name\n",
    "            best_model_ad = model\n",
    "    \n",
    "    print(\"depth: \", depth)\n",
    "    ad_table.dessiner()\n",
    "\n",
    "print(\"best score=\",  best_score_ad, \" depth=\", best_depth, \" method=\", best_scaler_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4345eb59",
   "metadata": {},
   "source": [
    "Les scores avec KNN sans prétraitement, avec normalisation et standardisation sont satisfaisant, cependant aucune amélioration significative entre les profondeur."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3004e1ca",
   "metadata": {},
   "source": [
    "## Troisième modèle : Nplus proches voisins\n",
    "En guise de troisième méthode d’apprentissage, nous allons nous pencher sur la méthode des plus proches voisins [KNeighborsRegressor](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsRegressor.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "770e793d",
   "metadata": {},
   "source": [
    "### Question 22\n",
    "Évaluez ce modèle sur le jeu de test, en employant dans une premier temps le paramètre n_neighbors = 4. Testez ensuite l’impact du pré-traitement, comme pour les modèles précédents. Constatez-vous une amélioration significative suite au pré-traitement ? Les scores obtenus sont-ils satisfaisants ?\n",
    "\n",
    "Refaites l’expérience avec un paramètre n_neigbors=5. L’augmentation du nombre de voisins considérés permet-elle de meilleurs résultats ? Si cette amélioration n’est pas significative, nous conserverons n_neighbors = 4 pour la suite, par souci de simplicité."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72869cf6",
   "metadata": {},
   "source": [
    "### Question 23\n",
    "Remplissez à nouveau un tableau de trois lignes (une pour la méthode “KNN”, une pour “Normalisation + KNN” et une pour “Standardisation + KNN”) avec les résultats que vous avez obtenus grâce à la méthode des plus proches voisins, avec ou sans pré-traitement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f72f499f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "neighbor:  4\n"
     ]
    },
    {
     "data": {
      "text/markdown": [
       "| Méthode | R² |\n",
       "| :---: | :---: |\n",
       "|KNN|0.371305|\n",
       "|Normalisation + KNN|0.392151|\n",
       "|Standardisation + KNN|0.392397|\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "neighbor:  5\n"
     ]
    },
    {
     "data": {
      "text/markdown": [
       "| Méthode | R² |\n",
       "| :---: | :---: |\n",
       "|KNN|0.391092|\n",
       "|Normalisation + KNN|0.412238|\n",
       "|Standardisation + KNN|0.402197|\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "best score= 0.4122380532367001  neighbor= 5  method= Normalisation + KNN\n"
     ]
    }
   ],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y_log, test_size=0.25, random_state=49\n",
    ")\n",
    "\n",
    "best_score_knn = -np.inf\n",
    "best_neighbor = None\n",
    "best_scaler_name = None\n",
    "best_model_knn = None\n",
    "\n",
    "m_neighbors = [4, 5]\n",
    "scaler_methods = [None, MinMaxScaler, StandardScaler]\n",
    "scaler_names = [\"KNN\", \"Normalisation + KNN\", \"Standardisation + KNN\"]\n",
    "\n",
    "for n in m_neighbors:\n",
    "    knn_table = Tableau()\n",
    "    \n",
    "    for name, scaler in zip(scaler_names, scaler_methods):\n",
    "        model = (\n",
    "            KNeighborsRegressor(n_neighbors=n)\n",
    "            if scaler is None\n",
    "            else make_pipeline(scaler(), KNeighborsRegressor(n_neighbors=n))\n",
    "        )\n",
    "        model.fit(X_train, y_train)\n",
    "        score: float = cross_val_score(model, X, y_log, cv=5).mean()\n",
    "        knn_table.ajoutligne(f\"{name}\", score)\n",
    "\n",
    "        if score > best_score_ad:\n",
    "            best_score_knn = score\n",
    "            best_neighbor = n\n",
    "            best_scaler_name = name\n",
    "            best_model_knn = model\n",
    "    \n",
    "    print(\"neighbor: \", n)\n",
    "    knn_table.dessiner()\n",
    "\n",
    "print(\"best score=\",  best_score_knn, \" neighbor=\", best_neighbor, \" method=\", best_scaler_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d73080e2",
   "metadata": {},
   "source": [
    "## Discussions sur le jeu de données\n",
    "\n",
    "Nous avons considéré à ce stade trois méthodes d’apprentissage, à savoir la régression linéaire (LR), les arbres de décision (AD) et la méthode des plus proches voisins (KNN). En gardant uniquement le meilleur score obtenu pour chacune de ces trois méthodes (avec pré-traitement ou non), nous obtenons un tableau de 3 lignes :\n",
    "\n",
    "|Méthode|R²|\n",
    "|:---:|:---:|\n",
    "|LR||\n",
    "|AD||\n",
    "|KNN||"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25fd6607",
   "metadata": {},
   "source": [
    "### Question 24\n",
    "Replissez le tableau, et comparez les scores obtenus pour ce jeu de données grâceà ces différentes méthodes. Que constatez-vous ? Dans la suite, nous noterons M la méthode qui donne le meilleur score pour les données de test."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9f764f3a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "| Méthode | R² |\n",
       "| :---: | :---: |\n",
       "|LR|0.452908|\n",
       "|AD|0.403761|\n",
       "|KNN|0.412238|\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "best_model= LR best_scaler= MinMaxScaler\n"
     ]
    }
   ],
   "source": [
    "comparaison = {\n",
    "    best_score_lr: (best_model_lr, \"LR\"),\n",
    "    best_score_ad: (best_model_ad, \"AD\"),\n",
    "    best_score_knn: (best_model_knn, \"KNN\")\n",
    "}\n",
    "\n",
    "tableau = Tableau()\n",
    "for score, (_, name) in comparaison.items():\n",
    "    tableau.ajoutligne(name, score)\n",
    "tableau.dessiner()\n",
    "\n",
    "M, model_name = comparaison[max(comparaison.keys())]\n",
    "print(\"best_model=\", model_name, \"best_scaler=\", get_model_name(M))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b343950d",
   "metadata": {},
   "source": [
    "## Réduction le nombre d’attributs\n",
    "Nous souhaitons maintenant réduire le nombre d’attributs de notre jeu de données en utilisant la méthode [PCA](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04d778ac",
   "metadata": {},
   "source": [
    "### Question 25\n",
    "Réduisez le nombre de composantes à 5. La modélisation avec 5 composantes principales est-elle satisfaisante pour ce jeu de données ? Justifiez."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "9084e87e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-4.56902336 -1.48348032  0.12179827 -0.12058014 -0.04324375]\n",
      "explained_variance_ratio [0.61800918 0.31336979 0.05010568 0.00388997 0.00234596] \n",
      "explained_variance_ratio_sum 0.987720578902822 \n",
      "SATISFAISANT\n"
     ]
    }
   ],
   "source": [
    "pca = PCA(n_components=5)\n",
    "pca.fit(X)\n",
    "X_pca= pca.transform(X)\n",
    "print(X_pca[0])\n",
    "print(\"explained_variance_ratio {} \".format(pca.explained_variance_ratio_))\n",
    "sum_ratio = pca.explained_variance_ratio_.sum()\n",
    "print(\"explained_variance_ratio_sum {} \".format(sum_ratio))\n",
    "print(\"SATISFAISANT\" if sum_ratio >= 0.80 else \"PAS SATISFAISANT\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b8c8513",
   "metadata": {},
   "source": [
    "### Question 26 \n",
    "Lancez la méthode M sur les données obtenues après réduction des composantes, et comparez les nouveaux scores aux scores précédents. Que constatez-vous ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "fdcdfb17",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.24194691318111572"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X_pca, y_log, test_size=0.25, random_state=49\n",
    ")\n",
    "\n",
    "M.fit(X_train, y_train)\n",
    "M.score(X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "502baa49",
   "metadata": {},
   "source": [
    "On constate que le score avec PCA avec 5 éléments est très inférieur au score obtenu précédemment avec M."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5dec0753",
   "metadata": {},
   "source": [
    "## Matrice de corrélation\n",
    "Nous allons tenter de comprendre dans quelle mesure chaque attribut permet de prédire l’étiquette"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63115a21",
   "metadata": {},
   "source": [
    "### Question 27\n",
    "Établissez la matrice de corrélation DataFrame.corr() du jeu de données. On pourra trouver un exemple de création et d’affichage de matrice de corrélation à cette [adresse](https://www.delftstack.com/howto/python-pandas/pandas-correlation-matrix/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "c4f6c27f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 690304 characters]",
      "text/plain": [
       "<Figure size 3100x3100 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "correlation_matrix= dataset.corr()\n",
    "# taille du heatmap\n",
    "n = len(correlation_matrix.columns)\n",
    "plt.figure(figsize=(n, n))\n",
    "heatmap(correlation_matrix, annot=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67821f8a",
   "metadata": {},
   "source": [
    "### Question 28 \n",
    "Quels sont les attributs dont la connaissance nous renseigne le plus sur le prix ?\n",
    "Pour déterminer cela, on regardera la colonne (ou la ligne) “Prix” de la matrice, qui indique la corrélation entre le prix et chacun des autres attributs, considérés individuellement. Plus le résultat est grand, plus la corrélation entre le prix et cet attribut est grande. À l’inverse, plus ce résultat est grand dans les négatifs, plus la corrélation inverse est grande. Enfin, un résultat proche de zéro signifie qu’il y a une faible corrélation entre le prix et cet attribut."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "2e512052",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Prix</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Robert</th>\n",
       "      <td>0.208953</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Robinson</th>\n",
       "      <td>0.158168</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Suckling</th>\n",
       "      <td>0.105477</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Pauillac</th>\n",
       "      <td>0.095258</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Sauternes</th>\n",
       "      <td>0.047033</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Graves</th>\n",
       "      <td>0.034793</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Margaux</th>\n",
       "      <td>0.033802</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Saint-Emilion</th>\n",
       "      <td>0.025754</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Saint-Julien</th>\n",
       "      <td>0.003441</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Côtes de Bourg</th>\n",
       "      <td>-0.003414</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Saint-Estèphe</th>\n",
       "      <td>-0.004453</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Saint-Georges-Saint-Emilion</th>\n",
       "      <td>-0.007867</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Bordeaux Supérieur</th>\n",
       "      <td>-0.009016</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Pessac-Léognan</th>\n",
       "      <td>-0.010667</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Francs Côtes de Bordeaux</th>\n",
       "      <td>-0.013484</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Vin de France</th>\n",
       "      <td>-0.015546</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Puisseguin-Saint-Emilion</th>\n",
       "      <td>-0.016300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Canon-Fronsac</th>\n",
       "      <td>-0.016991</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Bordeaux Blancs Secs</th>\n",
       "      <td>-0.017457</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Montagne-Saint-Emilion</th>\n",
       "      <td>-0.019821</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Bordeaux</th>\n",
       "      <td>-0.022210</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Listrac</th>\n",
       "      <td>-0.022918</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Lalande de Pomerol</th>\n",
       "      <td>-0.024407</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Barsac</th>\n",
       "      <td>-0.025269</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Fronsac</th>\n",
       "      <td>-0.026336</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Médoc</th>\n",
       "      <td>-0.028587</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Moulis</th>\n",
       "      <td>-0.029468</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Castillon Côtes de Bordeaux</th>\n",
       "      <td>-0.034267</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Pomerol</th>\n",
       "      <td>-0.040492</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>App_Haut-Médoc</th>\n",
       "      <td>-0.070383</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                     Prix\n",
       "Robert                           0.208953\n",
       "Robinson                         0.158168\n",
       "Suckling                         0.105477\n",
       "App_Pauillac                     0.095258\n",
       "App_Sauternes                    0.047033\n",
       "App_Graves                       0.034793\n",
       "App_Margaux                      0.033802\n",
       "App_Saint-Emilion                0.025754\n",
       "App_Saint-Julien                 0.003441\n",
       "App_Côtes de Bourg              -0.003414\n",
       "App_Saint-Estèphe               -0.004453\n",
       "App_Saint-Georges-Saint-Emilion -0.007867\n",
       "App_Bordeaux Supérieur          -0.009016\n",
       "App_Pessac-Léognan              -0.010667\n",
       "App_Francs Côtes de Bordeaux    -0.013484\n",
       "App_Vin de France               -0.015546\n",
       "App_Puisseguin-Saint-Emilion    -0.016300\n",
       "App_Canon-Fronsac               -0.016991\n",
       "App_Bordeaux Blancs Secs        -0.017457\n",
       "App_Montagne-Saint-Emilion      -0.019821\n",
       "App_Bordeaux                    -0.022210\n",
       "App_Listrac                     -0.022918\n",
       "App_Lalande de Pomerol          -0.024407\n",
       "App_Barsac                      -0.025269\n",
       "App_Fronsac                     -0.026336\n",
       "App_Médoc                       -0.028587\n",
       "App_Moulis                      -0.029468\n",
       "App_Castillon Côtes de Bordeaux -0.034267\n",
       "App_Pomerol                     -0.040492\n",
       "App_Haut-Médoc                  -0.070383"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "correlation_matrix_price = correlation_matrix[['Prix']].drop(\"Prix\").sort_values(by=\"Prix\", ascending=False)\n",
    "correlation_matrix_price"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54eb466e",
   "metadata": {},
   "source": [
    "### Question 29\n",
    "Modifiez le jeu de données en ne conservant que les cinq attributs qui sont le plus corrélés (positivement ou négativement) au prix. Appliquez la méthode M à ce nouveau jeu de données, et comparez le score de prédiction aux résultats obtenus précédemment. Que constatez-vous ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "4010aa12",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.24551258031003886"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "best_score_correlation_5 = correlation_matrix_price[0:5].index.to_list()\n",
    "\n",
    "X = dataset[best_score_correlation_5].values\n",
    "y = y_log\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y, test_size=0.25, random_state=49\n",
    ")\n",
    "\n",
    "M.fit(X_train, y_train)\n",
    "M.score(X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9d42a7c",
   "metadata": {},
   "source": [
    "On constate que le score avec seulement 5 attributs est très proche au score obtenu précédemment avec l'ensemble des données."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "abafb65f",
   "metadata": {},
   "source": [
    "## Quatrième modèle : une méthode ensembliste\n",
    "Pour terminer, vous allez explorer une méthode qui n’a pas été étudiée dans le cours."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "687016a1",
   "metadata": {},
   "source": [
    "### Question 30\n",
    "On se penche sur l’apprentissage via une méthode ensembliste, comme le Gradient Boosting ou les Random Forests.\n",
    "Choisissez une de ces méthodes, et refaites l’apprentissage en l’utilisant. Remplissez à nouveau un tableau de trois lignes (une pour la méthode “ens”, une pour “Normalisation + ens” et une pour “Standardisation + ens”) avec les résultats que vous avez obtenus grâce à la méthode ensembliste que vous avez choisie, avec ou sans pré-traitement. Que constatez-vous ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1510f763",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "| Méthode | R² |\n",
       "| :---: | :---: |\n",
       "|Random Forest|0.493344|\n",
       "|Normalisation + RF|0.493113|\n",
       "|Standardisation + RF|0.493782|\n"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Random Forests\n",
    "\n",
    "X = dataset.drop(\"Prix\", axis=1).values\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y_log, test_size=0.25, random_state=49\n",
    ")\n",
    "\n",
    "rf_table = Tableau()\n",
    "\n",
    "scaler_methods = [None, MinMaxScaler, StandardScaler]\n",
    "scaler_names = [\"Random Forest\", \"Normalisation + RF\", \"Standardisation + RF\"]\n",
    "\n",
    "for name, scaler in zip(scaler_names, scaler_methods):\n",
    "    rf_model = RandomForestRegressor()\n",
    "\n",
    "    model = (\n",
    "        rf_model if scaler is None \n",
    "        else make_pipeline(scaler(), rf_model)\n",
    "    )\n",
    "\n",
    "    model.fit(X_train, y_train)\n",
    "    score = model.score(X_test, y_test)\n",
    "    rf_table.ajoutligne(name, score)\n",
    "\n",
    "rf_table.dessiner()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48a380ab",
   "metadata": {},
   "source": [
    "On constate qu'entre le modèle M et le random forest, le modèle random forest surpasse le modèle M dans cette ensemble de donnée."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "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.10.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
