{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8797cb42",
   "metadata": {},
   "source": [
    "# Étape 1 — Chargement et exploration initiale"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "391c54d2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(116, 10)\n",
      "   Age        BMI  Glucose  Insulin      HOMA   Leptin  Adiponectin  Resistin  \\\n",
      "0   48  23.500000       70    2.707  0.467409   8.8071     9.702400   7.99585   \n",
      "1   83  20.690495       92    3.115  0.706897   8.8438     5.429285   4.06405   \n",
      "2   82  23.124670       91    4.498  1.009651  17.9393    22.432040   9.27715   \n",
      "3   68  21.367521       77    3.226  0.612725   9.8827     7.169560  12.76600   \n",
      "4   86  21.111111       92    3.549  0.805386   6.6994     4.819240  10.57635   \n",
      "\n",
      "     MCP.1  Classification  \n",
      "0  417.114               1  \n",
      "1  468.786               1  \n",
      "2  554.697               1  \n",
      "3  928.220               1  \n",
      "4  773.920               1  \n",
      "<class 'pandas.core.frame.DataFrame'>\n",
      "RangeIndex: 116 entries, 0 to 115\n",
      "Data columns (total 10 columns):\n",
      " #   Column          Non-Null Count  Dtype  \n",
      "---  ------          --------------  -----  \n",
      " 0   Age             116 non-null    int64  \n",
      " 1   BMI             116 non-null    float64\n",
      " 2   Glucose         116 non-null    int64  \n",
      " 3   Insulin         116 non-null    float64\n",
      " 4   HOMA            116 non-null    float64\n",
      " 5   Leptin          116 non-null    float64\n",
      " 6   Adiponectin     116 non-null    float64\n",
      " 7   Resistin        116 non-null    float64\n",
      " 8   MCP.1           116 non-null    float64\n",
      " 9   Classification  116 non-null    int64  \n",
      "dtypes: float64(7), int64(3)\n",
      "memory usage: 9.2 KB\n",
      "Les variables explicative sont : ['Age', 'BMI', 'Glucose', 'Insulin', 'HOMA', 'Leptin', 'Adiponectin', 'Resistin', 'MCP.1']\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 79116 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 12 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Import des bibliothèques\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "# Chargement des données\n",
    "data = pd.read_csv('breast+cancer+coimbra/dataR2.csv')\n",
    "\n",
    "# Aperçu des données\n",
    "print(data.shape)\n",
    "print(data.head())\n",
    "data.info()\n",
    "\n",
    "# Liste des variables explicatives\n",
    "features = data.columns[:-1]\n",
    "print(\"Les variables explicative sont :\", list(features))\n",
    "target = 'Classification'\n",
    "\n",
    "# Histogrammes des variables\n",
    "data.hist(bins=50, figsize=(12, 8))\n",
    "plt.suptitle(\"Histogrammes des variables\", fontsize=16)\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a62b9137",
   "metadata": {},
   "source": [
    "Nous utilisons ici les données du dataset *Breast Cancer Coimbra*, qui contient des mesures biométriques de 116 patientes. L’objectif est de prédire si une patiente est atteinte d’un cancer du sein ou non, à partir de neuf variables biologiques.\n",
    "\n",
    "### Aperçu du dataset\n",
    "\n",
    "Le jeu de données contient 116 observations et 10 variables, dont :\n",
    "- **9 variables explicatives** continues : `Age`, `BMI`, `Glucose`, `Insulin`, `HOMA`, `Leptin`, `Adiponectin`, `Resistin`, `MCP.1`.\n",
    "- **1 variable cible** : `Classification`, qui prend la valeur `1` si la patiente est atteinte d’un cancer (positive) et `2` sinon (négative).\n",
    "\n",
    "Les données ne présentent pas de valeurs manquantes, ce qui permet de procéder directement à l’analyse exploratoire.\n",
    "\n",
    "### Distribution des variables\n",
    "\n",
    "La distribution des variables a été visualisée à l’aide d’histogrammes :\n",
    "\n",
    "- Certaines variables présentent une **distribution asymétrique** avec des valeurs extrêmes, notamment `Insulin`, `HOMA`, `Leptin`, `Adiponectin`, `Resistin` et `MCP.1`. Cela pourra motiver une **transformation des données** ou une **normalisation** avant apprentissage.\n",
    "- La variable `Classification` est **déséquilibrée**, avec une majorité de patientes saines (`2`). Cette information est importante car elle aura un impact sur le **choix des métriques d’évaluation** : une simple précision (`accuracy`) ne sera pas suffisante pour juger de la qualité des modèles.\n",
    "\n",
    "Nous allons désormais préparer les données pour l'entraînement des modèles, en séparant les features et la cible, et en effectuant un **split stratifié** du jeu de données.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9cb261cd",
   "metadata": {},
   "source": [
    "# Étape 2 — Split du dataset en train et test\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3eddff01",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train : (92, 9) (92,)\n",
      "Test  : (24, 9) (24,)\n"
     ]
    }
   ],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "# Séparation des features et de la cible\n",
    "X = data.drop(columns='Classification')\n",
    "y = data['Classification'].map({2: 0, 1: 1})\n",
    "# Mapping : 1 = patient malade (positif), 2 = patient sain (négatif)\n",
    "# On recode pour avoir : 1 → malade (classe positive), 0 → sain\n",
    "\n",
    "\n",
    "# Split stratifié pour préserver la distribution des classes\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y,\n",
    "    test_size=0.2,\n",
    "    random_state=42,\n",
    "    stratify=y)\n",
    "\n",
    "# Vérification de la forme\n",
    "print(\"Train :\", X_train.shape, y_train.shape)\n",
    "print(\"Test  :\", X_test.shape, y_test.shape)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2eec1d6e",
   "metadata": {},
   "source": [
    "Nous commençons par séparer les variables explicatives (features) de la variable cible (`Classification`), en recodant cette dernière pour faciliter l'interprétation des résultats :\n",
    "- `1` : patiente malade (classe positive),\n",
    "- `0` : patiente saine (classe négative).\n",
    "\n",
    "Un **split stratifié** a été effectué à l’aide de la fonction `train_test_split` de `scikit-learn`, afin de préserver la proportion initiale des classes dans les ensembles d’entraînement et de test. Cela est particulièrement important ici compte tenu du **déséquilibre de classes** observé.\n",
    "\n",
    "Les tailles obtenues sont :\n",
    "- **Ensemble d'entraînement** : 92 observations\n",
    "- **Ensemble de test** : 24 observations\n",
    "\n",
    "Ce découpage correspond à une répartition classique de 80% / 20%.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02d2e734",
   "metadata": {},
   "source": [
    "# Étape 3 — Visualisation des distributions par classe du dataset d'entrainement"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "ac59107b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 119592 characters]",
      "text/plain": [
       "<Figure size 1500x1200 with 9 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 134612 characters]",
      "text/plain": [
       "<Figure size 1500x1200 with 9 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 221632 characters]",
      "text/plain": [
       "<Figure size 1500x1200 with 9 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Fusion temporaire pour les visualisations\n",
    "train_data = X_train.copy()\n",
    "train_data['Classification'] = y_train\n",
    "\n",
    "variables = X_train.columns.tolist()\n",
    "n_cols = 3\n",
    "n_rows = int(np.ceil(len(variables) / n_cols))\n",
    "\n",
    "\n",
    "# Histogrammes\n",
    "fig, axes = plt.subplots(n_rows, n_cols, figsize=(5 * n_cols, 4 * n_rows))\n",
    "axes = axes.flatten()\n",
    "\n",
    "for i, col in enumerate(variables):\n",
    "    for classe in y_train.unique():\n",
    "        axes[i].hist(X_train[y_train == classe][col], bins=30, alpha=0.5, label=f\"Classe {classe}\")\n",
    "    axes[i].set_title(f\"{col} selon la classe\")\n",
    "    axes[i].legend()\n",
    "    \n",
    "for j in range(i + 1, len(axes)):\n",
    "    fig.delaxes(axes[j])\n",
    "\n",
    "fig.suptitle(\"Histogrammes par classe (avant tranformation)\", fontsize=16)\n",
    "plt.tight_layout(rect=[0, 0, 1, 0.97])\n",
    "plt.show()\n",
    "\n",
    "# Boxplots\n",
    "fig, axes = plt.subplots(n_rows, n_cols, figsize=(5 * n_cols, 4 * n_rows))\n",
    "axes = axes.flatten()\n",
    "\n",
    "for i, col in enumerate(variables):\n",
    "    sns.boxplot(x=y_train, y=X_train[col], ax=axes[i])\n",
    "    axes[i].set_title(f\"Boxplot de {col}\")\n",
    "    axes[i].set_xlabel(\"Classe\")\n",
    "    axes[i].set_ylabel(col)\n",
    "\n",
    "for j in range(i + 1, len(axes)):\n",
    "    fig.delaxes(axes[j])\n",
    "\n",
    "fig.suptitle(\"Boxplots par classe (avant tranformation)\", fontsize=16)\n",
    "plt.tight_layout(rect=[0, 0, 1, 0.97])\n",
    "plt.show()\n",
    "\n",
    "# Scatter plots\n",
    "fig, axes = plt.subplots(n_rows, n_cols, figsize=(5 * n_cols, 4 * n_rows))\n",
    "axes = axes.flatten()\n",
    "\n",
    "for i, col in enumerate(variables):\n",
    "    for classe in y_train.unique():\n",
    "        axes[i].scatter(\n",
    "            X_train[y_train == classe][col],\n",
    "            np.random.normal(loc=classe, scale=0.02, size=X_train[y_train == classe][col].shape),\n",
    "            alpha=0.5,\n",
    "            label=f\"Classe {classe}\"\n",
    "        )\n",
    "    axes[i].set_title(f\"{col} (scatter jittered)\")\n",
    "    axes[i].legend()\n",
    "\n",
    "for j in range(i + 1, len(axes)):\n",
    "    fig.delaxes(axes[j])\n",
    "\n",
    "fig.suptitle(\"Scatter plots (jittered) par classe (avant tranformation)\", fontsize=16)\n",
    "plt.tight_layout(rect=[0, 0, 1, 0.97])\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "066377f6",
   "metadata": {},
   "source": [
    "Afin de mieux comprendre les différences entre les patientes malades (`Classe 1`) et les patientes saines (`Classe 0`), nous avons représenté les distributions des variables explicatives en les segmentant par classe. Trois types de visualisations complémentaires ont été produits : histogrammes, boxplots et scatter plots jittered.\n",
    "\n",
    "### Histogrammes par classe\n",
    "\n",
    "Les histogrammes permettent d'observer la distribution des valeurs au sein de chaque classe. Plusieurs variables présentent des décalages notables entre les deux groupes, notamment :\n",
    "- **Age** : les patientes malades ont tendance à être légèrement plus âgées.\n",
    "- **Resistin**, **MCP.1** et **Adiponectin** semblent présenter des décalages de densité entre les deux classes, suggérant un potentiel pouvoir discriminant.\n",
    "- À l'inverse, des variables comme **BMI** ou **Leptin** montrent une forte superposition entre les classes.\n",
    "\n",
    "Ces observations qualitatives suggèrent que certaines variables pourraient être informatives, mais **aucune ne permet à elle seule une séparation nette**.\n",
    "\n",
    "### Boxplots par classe\n",
    "\n",
    "Les boxplots permettent une évaluation visuelle plus précise des différences de distribution :\n",
    "- La majorité des variables présentent une **forte dispersion** avec de nombreux **outliers** visibles dans les deux classes, en particulier pour `Insulin`, `Leptin` et `MCP.1`.\n",
    "- **Les médianes sont proches**, mais on note quelques différences de tendance (ex. `Resistin` plus élevée pour la classe 1).\n",
    "\n",
    "Ces éléments indiquent une **variance importante intra-classe**, ce qui pourrait affecter les performances de modèles sensibles aux échelles non standardisées.\n",
    "\n",
    "### Scatter plots (jittered)\n",
    "\n",
    "Ces visualisations montrent explicitement la séparation verticale entre les classes (0 ou 1) :\n",
    "- Aucune variable seule ne suffit à séparer totalement les classes.\n",
    "- Certains regroupements apparaissent (ex. des valeurs faibles de `Insulin`, `HOMA`, `Adiponectin` associées à la classe 0), mais avec un **chevauchement significatif**.\n",
    "\n",
    "---\n",
    "\n",
    "###  Bilan de l’analyse exploratoire\n",
    "\n",
    "- Les variables semblent contenir **un signal partiel**, mais nécessiteront d’être **combinées dans un modèle multivarié** pour exploiter au mieux les interactions.\n",
    "- La présence d’**outliers**, de **distributions asymétriques** et de **valeurs sur des échelles très différentes** justifie :\n",
    "  - une **standardisation préalable** des variables,\n",
    "  - et éventuellement une **transformation** logarithmique pour certaines (ex. `Insulin`, `Leptin`, `HOMA`).\n",
    "- Enfin, la forte **superposition inter-classe** implique que la séparation linéaire pourrait être limitée, ce qui motivera l’usage de modèles non linéaires.\n",
    "\n",
    "Cette étape exploratoire permet donc de motiver rigoureusement les choix de **prétraitement** et de **modélisation supervisée** qui suivront.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09e7c056",
   "metadata": {},
   "source": [
    "# Étape 4 — Matrice de corrélation et scatter matrix (train uniquement)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "9ff82466",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 110644 characters]",
      "text/plain": [
       "<Figure size 1000x800 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 463160 characters]",
      "text/plain": [
       "<Figure size 1200x800 with 81 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from pandas.plotting import scatter_matrix\n",
    "# Matrice de corrélation (sur les données d'entraînement)\n",
    "corr_matrix = X_train.corr()\n",
    "\n",
    "plt.figure(figsize=(10, 8))\n",
    "sns.heatmap(corr_matrix, annot=True, fmt=\".2f\", cmap='coolwarm', square=True)\n",
    "plt.title(\"Matrice de corrélation (X_train)\")\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# DataFrame combiné pour visualisation colorée\n",
    "X_train_visu = X_train.copy()\n",
    "X_train_visu['Classification'] = y_train.values\n",
    "\n",
    "# Scatter matrix colorée\n",
    "scatter_matrix(X_train_visu.drop(columns='Classification'),\n",
    "               figsize=(12, 8),\n",
    "               diagonal='kde',\n",
    "               c=X_train_visu['Classification'],\n",
    "               cmap='coolwarm',\n",
    "               alpha=0.7)\n",
    "plt.suptitle(\"Matrice de dispersion colorée (X_train)\", y=1.02)\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a358c6aa",
   "metadata": {},
   "source": [
    "Afin d’examiner la redondance potentielle entre les variables et d’anticiper les risques de multicolinéarité, nous avons étudié les relations linéaires entre les variables explicatives à l’aide de deux outils complémentaires :\n",
    "\n",
    "### Matrice de corrélation\n",
    "\n",
    "La matrice de corrélation permet d’identifier les relations linéaires entre les variables continues. On observe notamment :\n",
    "\n",
    "- Une **corrélation très forte** entre `Insulin` et `HOMA` (ρ ≈ 0.93), ce qui est attendu puisque HOMA est une fonction de l’insuline et de la glycémie (`HOMA = (Glucose × Insulin) / 405`).\n",
    "- Des corrélations modérées entre :\n",
    "  - `Glucose` et `HOMA` (ρ ≈ 0.70),\n",
    "  - `BMI` et `Leptin` (ρ ≈ 0.60).\n",
    "- Les autres paires présentent des corrélations relativement faibles (ρ < 0.4), ce qui suggère que **la majorité des variables apportent une information complémentaire**.\n",
    "\n",
    "Cette analyse nous incite à envisager éventuellement une **réduction de dimension** ou une **sélection de variables**, en particulier si certaines méthodes sont sensibles à la colinéarité (ex. régression logistique).\n",
    "\n",
    "### Matrice de dispersion colorée\n",
    "\n",
    "La scatter matrix, colorée selon la classe (`Classification`), donne une vision globale des interactions deux-à-deux entre variables, tout en mettant en évidence les regroupements selon la classe.\n",
    "\n",
    "- On note un **chevauchement significatif** entre les deux classes dans la majorité des projections planes.\n",
    "- Quelques combinaisons de variables, comme (`Resistin`, `MCP.1`) ou (`Adiponectin`, `HOMA`), montrent des séparations partielles entre les classes, sans pour autant être suffisantes pour une séparation nette.\n",
    "\n",
    "---\n",
    "\n",
    "### Conclusion de cette étape\n",
    "\n",
    "- Il n’existe pas de **séparation linéaire évidente** entre les deux classes sur les paires de variables.\n",
    "- La structure du jeu de données suggère que des méthodes multivariées pourront tirer profit de **relations complexes**, éventuellement **non linéaires**.\n",
    "- La **standardisation** sera nécessaire avant d’appliquer certaines méthodes sensibles aux échelles (SVM, k-NN, PCA...).\n",
    "\n",
    "Nous passons maintenant à l’étape de prétraitement (standardisation des données), avant de comparer différentes méthodes d’apprentissage supervisé.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "556f426b",
   "metadata": {},
   "source": [
    "# Étape 5 — Transformation logarithmique "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af21af16",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 43544 characters]",
      "text/plain": [
       "<Figure size 1200x400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 47584 characters]",
      "text/plain": [
       "<Figure size 1200x400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 45600 characters]",
      "text/plain": [
       "<Figure size 1200x400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 50112 characters]",
      "text/plain": [
       "<Figure size 1200x400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 119356 characters]",
      "text/plain": [
       "<Figure size 1500x1200 with 9 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 135072 characters]",
      "text/plain": [
       "<Figure size 1500x1200 with 9 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 223016 characters]",
      "text/plain": [
       "<Figure size 1500x1200 with 9 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Liste des variables à transformer (asymétriques)\n",
    "variables_log = ['Insulin', 'HOMA', 'MCP.1', 'Resistin']\n",
    "\n",
    "# Comparaison avant / après (1 figure par variable)\n",
    "for col in variables_log:\n",
    "    fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n",
    "\n",
    "    # Avant transformation\n",
    "    axes[0].hist(X_train[col], bins=30, alpha=0.6, color='salmon', edgecolor='black')\n",
    "    axes[0].set_title(f\"{col} (avant transformation)\")\n",
    "    axes[0].set_xlabel(col)\n",
    "    axes[0].set_ylabel(\"Fréquence\")\n",
    "\n",
    "    # Après transformation log1p\n",
    "    transformed = np.log1p(X_train[col])\n",
    "    axes[1].hist(transformed, bins=30, alpha=0.6, color='steelblue', edgecolor='black')\n",
    "    axes[1].set_title(f\"{col} après transformation log({col} + 1)\")\n",
    "    axes[1].set_xlabel(f\"log1p({col})\")\n",
    "    axes[1].set_ylabel(\"Fréquence\")\n",
    "\n",
    "    plt.suptitle(f\"Comparaison avant / après transformation : {col}\", fontsize=14)\n",
    "    plt.tight_layout(rect=[0, 0, 1, 0.93])\n",
    "    plt.show()\n",
    "\n",
    "# Application de la transformation à X_train\n",
    "X_train_transformed = X_train.copy()\n",
    "for col in variables_log:\n",
    "    X_train_transformed[col + '_log'] = np.log1p(X_train[col])\n",
    "X_train_transformed = X_train_transformed.drop(columns=variables_log)\n",
    "\n",
    "# Mise à jour des features transformées\n",
    "features = X_train_transformed.columns.tolist()\n",
    "\n",
    "# Fusion avec y_train pour faciliter les visualisations\n",
    "train_data2 = X_train_transformed.copy()\n",
    "train_data2['Classification'] = y_train.values\n",
    "\n",
    "# Histogrammes par classe\n",
    "n_cols = 3\n",
    "n_rows = int(np.ceil(len(features) / n_cols))\n",
    "\n",
    "fig, axes = plt.subplots(n_rows, n_cols, figsize=(5 * n_cols, 4 * n_rows))\n",
    "axes = axes.flatten()\n",
    "\n",
    "for i, col in enumerate(features):\n",
    "    for classe in [0, 1]:\n",
    "        axes[i].hist(train_data2[train_data2['Classification'] == classe][col], \n",
    "                     bins=30, alpha=0.5, label=f\"Classe {classe}\")\n",
    "    axes[i].set_title(f\"{col} selon la classe\")\n",
    "    axes[i].legend()\n",
    "\n",
    "for j in range(i + 1, len(axes)):\n",
    "    fig.delaxes(axes[j])\n",
    "\n",
    "fig.suptitle(\"Histogrammes par classe (après transformation)\", fontsize=16)\n",
    "plt.tight_layout(rect=[0, 0, 1, 0.97])\n",
    "plt.show()\n",
    "\n",
    "# Boxplots par classe\n",
    "\n",
    "fig, axes = plt.subplots(n_rows, n_cols, figsize=(5 * n_cols, 4 * n_rows))\n",
    "axes = axes.flatten()\n",
    "\n",
    "for i, col in enumerate(features):\n",
    "    sns.boxplot(x='Classification', y=col, data=train_data2, ax=axes[i])\n",
    "    axes[i].set_title(f\"Boxplot de {col}\")\n",
    "    axes[i].set_xlabel(\"Classe\")\n",
    "    axes[i].set_ylabel(col)\n",
    "\n",
    "for j in range(i + 1, len(axes)):\n",
    "    fig.delaxes(axes[j])\n",
    "\n",
    "fig.suptitle(\"Boxplots par classe (après transformation)\", fontsize=16)\n",
    "plt.tight_layout(rect=[0, 0, 1, 0.97])\n",
    "plt.show()\n",
    "\n",
    "# Scatter plots jittered par classe\n",
    "\n",
    "fig, axes = plt.subplots(n_rows, n_cols, figsize=(5 * n_cols, 4 * n_rows))\n",
    "axes = axes.flatten()\n",
    "\n",
    "for i, col in enumerate(features):\n",
    "    for classe in [0, 1]:\n",
    "        values = train_data2[train_data2['Classification'] == classe][col]\n",
    "        jittered_y = np.random.normal(loc=classe, scale=0.02, size=values.shape)\n",
    "        axes[i].scatter(values, jittered_y, alpha=0.5, label=f\"Classe {classe}\")\n",
    "    axes[i].set_title(f\"{col} (scatter jittered)\")\n",
    "    axes[i].legend()\n",
    "\n",
    "for j in range(i + 1, len(axes)):\n",
    "    fig.delaxes(axes[j])\n",
    "\n",
    "fig.suptitle(\"Scatter plots (jittered) par classe (après transformation)\", fontsize=16)\n",
    "plt.tight_layout(rect=[0, 0, 1, 0.97])\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c582a520",
   "metadata": {},
   "source": [
    "\n",
    "### Objectif\n",
    "\n",
    "L'analyse exploratoire initiale a révélé une **asymétrie marquée** (distribution fortement étalée à droite) pour plusieurs variables : `Insulin`, `HOMA`, `MCP.1` et `Resistin`.  \n",
    "Ces distributions comportaient de nombreuses valeurs extrêmes susceptibles de nuire à l’apprentissage de certains modèles (régression logistique, k-NN, SVM...) sensibles aux écarts de grandeur ou à la non-normalité.\n",
    "\n",
    "Afin de rendre ces distributions plus symétriques et de réduire l’impact des valeurs extrêmes, nous avons appliqué une **transformation logarithmique** de type `log(x + 1)` à l’aide de la fonction `np.log1p`.\n",
    "\n",
    "---\n",
    "\n",
    "### Comparaison avant / après transformation\n",
    "\n",
    "Pour chaque variable transformée, nous avons comparé la distribution **avant et après transformation** à l’aide d’un histogramme :\n",
    "\n",
    "- On observe que les longues traînes à droite sont **fortement atténuées**.\n",
    "- Les distributions deviennent **plus compactes, moins asymétriques** et souvent plus proches d’une forme gaussienne.\n",
    "- Cela améliore l’homogénéité du jeu de données et facilite les étapes de **standardisation** ultérieures.\n",
    "\n",
    "Ces figures constituent une **justification visuelle explicite** de l’intérêt de la transformation logarithmique.\n",
    "\n",
    "---\n",
    "\n",
    "### Mise à jour du jeu de données\n",
    "\n",
    "Les nouvelles variables transformées (`Insulin_log`, `HOMA_log`, `MCP.1_log`, `Resistin_log`) ont été ajoutées au jeu de données, et les anciennes supprimées pour éviter toute redondance.\n",
    "\n",
    "---\n",
    "\n",
    "### Visualisation des variables transformées par classe\n",
    "\n",
    "Afin d’observer l’effet de cette transformation sur la répartition des valeurs dans les deux classes (malade / sain), nous avons reproduit plusieurs visualisations sur les variables transformées :\n",
    "\n",
    "#### • Histogrammes par classe  \n",
    "Ces histogrammes permettent de visualiser les densités relatives dans chaque classe, après transformation.  \n",
    "On constate que certaines variables, notamment `Insulin_log` et `Resistin_log`, présentent toujours des différences de densité entre les classes, mais avec une **distribution beaucoup plus régulière**.\n",
    "\n",
    "#### • Boxplots par classe  \n",
    "La transformation log réduit clairement l'impact des **outliers visibles** dans les boxplots.  \n",
    "Les médianes sont plus représentatives et les intervalles interquartiles moins éclatés.\n",
    "\n",
    "#### • Scatter plots jittered  \n",
    "Ces visualisations montrent que les points restent bien séparés verticalement entre les classes 0 et 1, tout en bénéficiant d’une **distribution horizontale plus contrôlée**, ce qui est favorable aux modèles à base de distance.\n",
    "\n",
    "---\n",
    "\n",
    "### Conclusion\n",
    "\n",
    "L’application de la transformation logarithmique a permis de :\n",
    "- Réduire les effets des valeurs extrêmes,\n",
    "- Stabiliser la variance,\n",
    "- Améliorer la structure géométrique du jeu de données en vue de la standardisation et de l'apprentissage.\n",
    "\n",
    "Ces transformations constituent une **étape essentielle du pipeline de prétraitement** avant la construction des modèles.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "184a592b",
   "metadata": {},
   "source": [
    "# Étape 6 — Standardisation (StandardScaler)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b77fbe5",
   "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>mean</th>\n",
       "      <th>std</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Age</th>\n",
       "      <td>1.037817e-16</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>BMI</th>\n",
       "      <td>-8.447349e-17</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Glucose</th>\n",
       "      <td>1.995309e-16</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Leptin</th>\n",
       "      <td>6.033821e-17</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Adiponectin</th>\n",
       "      <td>1.436049e-16</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Insulin_log</th>\n",
       "      <td>2.292852e-16</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>HOMA_log</th>\n",
       "      <td>-3.891814e-16</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>MCP.1_log</th>\n",
       "      <td>-7.406515e-16</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Resistin_log</th>\n",
       "      <td>-1.680419e-16</td>\n",
       "      <td>1.005479</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                      mean       std\n",
       "Age           1.037817e-16  1.005479\n",
       "BMI          -8.447349e-17  1.005479\n",
       "Glucose       1.995309e-16  1.005479\n",
       "Leptin        6.033821e-17  1.005479\n",
       "Adiponectin   1.436049e-16  1.005479\n",
       "Insulin_log   2.292852e-16  1.005479\n",
       "HOMA_log     -3.891814e-16  1.005479\n",
       "MCP.1_log    -7.406515e-16  1.005479\n",
       "Resistin_log -1.680419e-16  1.005479"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "# Initialisation du scaler\n",
    "scaler = StandardScaler()\n",
    "\n",
    "# Entraînement du scaler sur le jeu transformé\n",
    "X_train_scaled = pd.DataFrame(\n",
    "    scaler.fit_transform(X_train_transformed),\n",
    "    columns=X_train_transformed.columns,\n",
    "    index=X_train_transformed.index\n",
    ")\n",
    "\n",
    "X_train_scaled.describe().T[['mean', 'std']]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e525eb2",
   "metadata": {},
   "source": [
    "### Objectif\n",
    "\n",
    "Après transformation logarithmique des variables asymétriques, nous appliquons une **standardisation** à l’ensemble des variables explicatives du jeu d’entraînement.  \n",
    "Cette étape vise à **mettre toutes les variables sur une même échelle**, en les centrant autour de 0 et en les réduisant à une variance unitaire.\n",
    "\n",
    "La standardisation est particulièrement importante pour les modèles sensibles aux distances ou aux écarts d’échelle, tels que :\n",
    "- la régression logistique (convergence plus stable),\n",
    "- le SVM,\n",
    "- les k plus proches voisins (k-NN),\n",
    "- les méthodes de régularisation (Ridge, Lasso),\n",
    "- ou encore les techniques de réduction de dimension (PCA, etc.).\n",
    "\n",
    "---\n",
    "### Méthode\n",
    "\n",
    "La transformation utilisée est la standardisation classique (Z-score), via la formule :\n",
    "\n",
    "$$\n",
    "X^{(i)}_{\\text{std}} = \\frac{X^{(i)} - \\mu^{(i)}}{\\sigma^{(i)}}\n",
    "$$\n",
    "\n",
    "où \\( \\mu^{(i)} \\) et \\( \\sigma^{(i)} \\) désignent respectivement la moyenne et l’écart-type de la variable \\( X^{(i)} \\) sur l’ensemble d’entraînement.\n",
    "\n",
    "Nous utilisons l’outil `StandardScaler` de `scikit-learn`, appliqué uniquement sur les données d’entraînement (`fit_transform`) pour éviter toute fuite d’information.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d374b93f",
   "metadata": {},
   "source": [
    "# Étape 7 — Préparation de X_test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28a4ffe0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Copie de X_test\n",
    "X_test_transformed = X_test.copy()\n",
    "\n",
    "# Appliquer les mêmes log-transforms\n",
    "for col in variables_log:\n",
    "    X_test_transformed[col + '_log'] = np.log1p(X_test_transformed[col])\n",
    "\n",
    "# Supprimer les colonnes originales\n",
    "X_test_transformed = X_test_transformed.drop(columns=variables_log)\n",
    "\n",
    "# Standardiser avec le scaler déjà entraîné sur X_train\n",
    "X_test_scaled = pd.DataFrame(\n",
    "    scaler.transform(X_test_transformed),\n",
    "    columns=X_test_transformed.columns,\n",
    "    index=X_test_transformed.index\n",
    ")\n",
    "\n",
    "# Vérifie que les colonnes de X_test sont identiques (et dans le même ordre) à celles de X_train, pour éviter des erreurs de prédiction\n",
    "if list(X_train_scaled.columns) != list(X_test_scaled.columns):\n",
    "    print( \"Mismatch between train and test columns\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4a5689a",
   "metadata": {},
   "source": [
    "Les transformations précédemment appliquées sur le jeu d'entraînement (logarithmisation + standardisation) doivent être reproduites **exactement à l’identique** sur le jeu de test, sans réestimer les paramètres.\n",
    "\n",
    "### Étapes réalisées :\n",
    "1. **Transformation logarithmique** sur les mêmes variables (`Insulin`, `HOMA`, `MCP.1`, `Resistin`), via `log(x + 1)` ;\n",
    "2. **Suppression des variables brutes** pour éviter la redondance ;\n",
    "3. **Standardisation avec les mêmes paramètres** (`μ`, `σ`) appris sur `X_train`, à l’aide de `scaler.transform()`.\n",
    "\n",
    "Nous avons également vérifié que les **colonnes de `X_test_scaled` sont strictement les mêmes (et dans le même ordre)** que celles de `X_train_scaled`, afin de garantir la compatibilité avec les modèles d’apprentissage.\n",
    "\n",
    "---\n",
    "\n",
    "Cette étape complète le pipeline de prétraitement et nous permet désormais de passer à la **modélisation supervisée**.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5fd9b3f0",
   "metadata": {},
   "source": [
    "# Étape 8 — Modélisation avec LogisticRegression"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "d58417ad",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Évaluation du modèle simple (régression logistique)\n",
      "Accuracy : 0.792\n",
      "F1-score : 0.783\n",
      "\n",
      "Classification report :\n",
      "               precision    recall  f1-score   support\n",
      "\n",
      "           0       0.83      0.77      0.80        13\n",
      "           1       0.75      0.82      0.78        11\n",
      "\n",
      "    accuracy                           0.79        24\n",
      "   macro avg       0.79      0.79      0.79        24\n",
      "weighted avg       0.80      0.79      0.79        24\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import accuracy_score, f1_score, classification_report\n",
    "\n",
    "# Initialisation et entraînement du modèle sans régularisation explicite\n",
    "logreg_simple = LogisticRegression(random_state=42)\n",
    "logreg_simple.fit(X_train_scaled, y_train)\n",
    "\n",
    "# Prédictions sur le jeu de test\n",
    "y_pred_simple = logreg_simple.predict(X_test_scaled)\n",
    "\n",
    "# Évaluation des performances de base\n",
    "print(\"Évaluation du modèle simple (régression logistique)\")\n",
    "print(\"Accuracy :\", round(accuracy_score(y_test, y_pred_simple), 3))\n",
    "print(\"F1-score :\", round(f1_score(y_test, y_pred_simple), 3))\n",
    "print(\"\\nClassification report :\\n\", classification_report(y_test, y_pred_simple))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76d3da3a",
   "metadata": {},
   "source": [
    "Nous commençons par entraîner un modèle de régression logistique simple, sans régularisation explicite, sur les données prétraitées (après transformation log et standardisation).\n",
    "\n",
    "### Objectif\n",
    "\n",
    "Prédire la probabilité qu’une patiente soit atteinte d’un cancer à partir des neuf variables explicatives.  \n",
    "La régression logistique est un modèle linéaire interprétable, particulièrement adapté pour une première approche supervisée.\n",
    "\n",
    "### Résultats sur le jeu de test\n",
    "\n",
    "Le modèle a été évalué sur l’ensemble de test après entraînement sur les données standardisées. Les résultats sont les suivants :\n",
    "\n",
    "- **Accuracy** : 0.792\n",
    "- **F1-score** : 0.783\n",
    "\n",
    "### Détails du rapport de classification :\n",
    "\n",
    "| Classe | Précision | Rappel | F1-score | Support |\n",
    "|--------|-----------|--------|----------|---------|\n",
    "| 0 (sain) | 0.83 | 0.77 | 0.80 | 13 |\n",
    "| 1 (malade) | 0.75 | 0.82 | 0.78 | 11 |\n",
    "\n",
    "- **Macro avg F1** : 0.79\n",
    "- **Weighted avg F1** : 0.79\n",
    "\n",
    "Ces résultats montrent un bon équilibre entre précision et rappel, ce qui est important dans un contexte médical.  \n",
    "Cependant, ce modèle simple ne prend pas en compte d’éventuelles interactions entre variables ni d’optimisation des hyperparamètres, ce qui sera abordé dans les étapes suivantes.\n",
    "\n",
    "---\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b7c1cdf",
   "metadata": {},
   "source": [
    "# Étape 9 — Validation croisée sur les données d'entraînement\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8804f23",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Validation croisée (10 folds)\n",
      "F1-score moyen : 0.696 ± 0.187\n",
      "Accuracy moyen : 0.731 ± 0.175\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 57280 characters]",
      "text/plain": [
       "<Figure size 800x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.model_selection import cross_val_score\n",
    "\n",
    "# Validation croisée (10 folds) sur le modèle simple\n",
    "f1_scores = cross_val_score(logreg_simple, X_train_scaled, y_train, cv=10, scoring='f1')\n",
    "acc_scores = cross_val_score(logreg_simple, X_train_scaled, y_train, cv=10, scoring='accuracy')\n",
    "\n",
    "# Résumé\n",
    "print(\"Validation croisée (10 folds)\")\n",
    "print(f\"F1-score moyen : {f1_scores.mean():.3f} ± {f1_scores.std():.3f}\")\n",
    "print(f\"Accuracy moyen : {acc_scores.mean():.3f} ± {acc_scores.std():.3f}\")\n",
    "\n",
    "# Visualisation des scores par fold\n",
    "folds = range(1, len(f1_scores) + 1)\n",
    "plt.figure(figsize=(8, 5))\n",
    "plt.plot(folds, f1_scores, marker='o', label='F1-score')\n",
    "plt.plot(folds, acc_scores, marker='s', label='Accuracy')\n",
    "plt.title(\"Scores par fold (validation croisée)\")\n",
    "plt.xlabel(\"Fold\")\n",
    "plt.ylabel(\"Score\")\n",
    "plt.ylim(0, 1)\n",
    "plt.grid(True)\n",
    "plt.legend()\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8407908d",
   "metadata": {},
   "source": [
    "Afin d'évaluer la stabilité du modèle de régression logistique et sa capacité de généralisation, nous avons effectué une **validation croisée à 10 plis** (k-fold CV).  \n",
    "Deux métriques principales ont été suivies : **le F1-score** (adapté au contexte de déséquilibre de classes) et **l'accuracy** (exactitude globale).\n",
    "\n",
    "### Résultats de la validation croisée\n",
    "\n",
    "- **F1-score moyen** : 0.696 ± 0.187\n",
    "- **Accuracy moyenne** : 0.731 ± 0.175\n",
    "\n",
    "### Analyse\n",
    "\n",
    "La dispersion importante des scores (écarts types > 0.17) reflète une **sensibilité à la composition des folds**, probablement due à la **petite taille du jeu d'entraînement** (92 observations).  \n",
    "Cela justifie l'intérêt d’une future **régularisation ou sélection de variables**.\n",
    "\n",
    "### Visualisation\n",
    "\n",
    "La figure suivante présente les scores individuels obtenus sur chaque fold. On note une **variabilité significative d’un pli à l’autre**, notamment sur les F1-scores (certains folds tombant à 0.4–0.5, d'autres dépassant 0.8), ce qui confirme le besoin d’optimiser le modèle pour gagner en robustesse.\n",
    "\n",
    "---\n",
    "\n",
    "Cette étape permet de constituer un point de référence pour les performances du modèle simple avant d’envisager une **optimisation par recherche d’hyperparamètres**.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c8f1ec9",
   "metadata": {},
   "source": [
    "# Étape 10 — Optimisation des hyperparamètres (GridSearchCV)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "ba6b4fd9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Best parameters : {'C': 100.0, 'penalty': 'l1', 'solver': 'liblinear'}\n",
      "Best F1-score (cross-val) : 0.737\n"
     ]
    }
   ],
   "source": [
    "from sklearn.model_selection import GridSearchCV\n",
    "\n",
    "# Grille d’hyperparamètres\n",
    "param_grid = {\n",
    "    'penalty': ['l1', 'l2'],\n",
    "    'C': np.logspace(-2, 4, 10),  # de 0.01 à 10000 (échelle log)\n",
    "    'solver': ['liblinear']  # nécessaire pour 'l1'\n",
    "}\n",
    "\n",
    "# Initialisation du modèle + recherche par validation croisée\n",
    "logreg_grid = LogisticRegression(random_state=42, max_iter=1000)\n",
    "grid_search = GridSearchCV(logreg_grid, param_grid, cv=10, scoring='f1')\n",
    "grid_search.fit(X_train_scaled, y_train)\n",
    "\n",
    "# Meilleur modèle\n",
    "best_logreg = grid_search.best_estimator_\n",
    "\n",
    "print(\"Best parameters :\", grid_search.best_params_)\n",
    "print(\"Best F1-score (cross-val) :\", round(grid_search.best_score_, 3))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "090475a0",
   "metadata": {},
   "source": [
    "Afin d'améliorer les performances du modèle, nous avons mis en œuvre une **recherche par grille (GridSearchCV)** pour identifier la meilleure combinaison d’hyperparamètres de la régression logistique.  \n",
    "L’objectif est de sélectionner automatiquement le niveau optimal de régularisation, afin d’équilibrer la capacité d’apprentissage du modèle et sa généralisation.\n",
    "\n",
    "---\n",
    "\n",
    "### Grille d’hyperparamètres testée\n",
    "\n",
    "Les combinaisons suivantes ont été évaluées dans le cadre d’une validation croisée à 10 plis :\n",
    "\n",
    "- **Type de régularisation (`penalty`)** :  \n",
    "  `'l1'` (Lasso, régularisation favorisant la sparsité) et `'l2'` (Ridge, régularisation quadratique classique),\n",
    "- **Coefficient de régularisation (`C`)** :  \n",
    "  `[0.01, 0.1, 1, 10, 100]`  \n",
    "  *(plus `C` est grand, plus la pénalisation est faible ; `C` est l’inverse du poids de régularisation)*,\n",
    "- **Solveur (`solver`)** :  \n",
    "  `'liblinear'`, choisi car c’est le seul compatible avec la régularisation `l1` dans `scikit-learn`.\n",
    "\n",
    "La métrique d’évaluation choisie pour guider la sélection est le **F1-score**, bien adaptée aux problèmes de classification binaire avec déséquilibre de classes.\n",
    "\n",
    "---\n",
    "\n",
    "### Résultats de la recherche\n",
    "\n",
    "- **Meilleurs hyperparamètres identifiés** :  \n",
    "  `{'C': 100, 'penalty': 'l1', 'solver': 'liblinear'}`\n",
    "\n",
    "- **F1-score moyen en validation croisée (10 folds)** :  \n",
    "  **0.737**\n",
    "\n",
    "---\n",
    "\n",
    "### Analyse et interprétation\n",
    "\n",
    "Le modèle optimal utilise :\n",
    "- une **régularisation L1 faible** (`C = 100`), ce qui réduit l’effet de la pénalisation tout en permettant la mise à zéro automatique de certains coefficients (sparsité),\n",
    "- une formulation **plus parcimonieuse**, limitant potentiellement le surapprentissage sur un petit jeu de données,\n",
    "- une structure **interprétable**, les coefficients non nuls pouvant être analysés directement comme poids des variables.\n",
    "\n",
    "Le fait que la meilleure valeur de `C` se situe à l’extrémité supérieure de la grille initiale a motivé une extension du domaine de recherche, mais celle-ci a confirmé que `C = 100` restait optimal. Cela suggère que la régularisation, bien que contrôlée, **n’a pas d’impact significatif** sur les performances dans ce contexte.\n",
    "\n",
    "---\n",
    "\n",
    "### Impact attendu\n",
    "\n",
    "Le modèle régularisé ainsi obtenu sera évalué dans l’étape suivante (étape 11) sur le jeu de test.  \n",
    "L’objectif est de vérifier si cette optimisation permet **un gain significatif de performance** par rapport au modèle de base (non régularisé) de l’étape 8.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57f03133",
   "metadata": {},
   "source": [
    "# Étape 11 — Évaluation du modèle optimisé"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd749019",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Évaluation sur le jeu de test :\n",
      "Accuracy : 0.792\n",
      "F1-score : 0.762\n",
      "\n",
      "Classification Report :\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0       0.79      0.85      0.81        13\n",
      "           1       0.80      0.73      0.76        11\n",
      "\n",
      "    accuracy                           0.79        24\n",
      "   macro avg       0.79      0.79      0.79        24\n",
      "weighted avg       0.79      0.79      0.79        24\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 18088 characters]",
      "text/plain": [
       "<Figure size 600x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 45100 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.metrics import confusion_matrix, roc_curve, auc\n",
    "\n",
    "# Prédictions\n",
    "y_pred_best = best_logreg.predict(X_test_scaled)\n",
    "\n",
    "# Évaluation globale\n",
    "print(\"\\nÉvaluation sur le jeu de test :\")\n",
    "print(\"Accuracy :\", round(accuracy_score(y_test, y_pred_best), 3))\n",
    "print(\"F1-score :\", round(f1_score(y_test, y_pred_best), 3))\n",
    "print(\"\\nClassification Report :\")\n",
    "print(classification_report(y_test, y_pred_best))\n",
    "\n",
    "# Matrice de confusion\n",
    "cm = confusion_matrix(y_test, y_pred_best)\n",
    "plt.figure(figsize=(6, 5))\n",
    "sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False,\n",
    "            xticklabels=['Négatif', 'Positif'], yticklabels=['Négatif', 'Positif'])\n",
    "plt.title(\"Matrice de confusion\")\n",
    "plt.xlabel(\"Prédictions\")\n",
    "plt.ylabel(\"Réel\")\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# Courbe ROC et AUC\n",
    "y_proba_best = best_logreg.predict_proba(X_test_scaled)[:, 1]\n",
    "fpr, tpr, thresholds = roc_curve(y_test, y_proba_best)\n",
    "roc_auc = auc(fpr, tpr)\n",
    "\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.plot(fpr, tpr, label=f'AUC = {roc_auc:.2f}', color='blue')\n",
    "plt.plot([0, 1], [0, 1], linestyle='--', color='red')\n",
    "plt.title(\"Courbe ROC - Modèle optimisé\")\n",
    "plt.xlabel(\"Taux de faux positifs (FPR)\")\n",
    "plt.ylabel(\"Taux de vrais positifs (TPR)\")\n",
    "plt.grid(True)\n",
    "plt.legend()\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6bcbfdc8",
   "metadata": {},
   "source": [
    "Après avoir sélectionné le meilleur modèle de régression logistique par validation croisée (cf. étape 10), nous l’avons évalué sur le **jeu de test**, resté totalement indépendant durant l’entraînement.\n",
    "\n",
    "---\n",
    "\n",
    "### Résultats globaux\n",
    "\n",
    "- **Accuracy** : 0.792  \n",
    "- **F1-score** : 0.762\n",
    "\n",
    "**Rapport de classification :**\n",
    "\n",
    "| Classe | Précision | Rappel | F1-score | Support |\n",
    "|--------|-----------|--------|----------|---------|\n",
    "| 0 (sain)   | 0.79 | 0.85 | 0.81 | 13 |\n",
    "| 1 (malade) | 0.80 | 0.73 | 0.76 | 11 |\n",
    "| **Moyenne (macro)** | 0.79 | 0.79 | 0.79 | 24 |\n",
    "\n",
    "Le **compromis entre précision et rappel est bien équilibré**, et les performances globales sont comparables (légèrement inférieures en F1) à celles obtenues avec le modèle simple (étape 8), malgré une régularisation L1 imposée.\n",
    "\n",
    "---\n",
    "\n",
    "### Matrice de confusion\n",
    "\n",
    "La matrice ci-dessous permet de visualiser les performances de prédiction classe par classe :\n",
    "\n",
    "|         | Prédit : 0 | Prédit : 1 |\n",
    "|---------|------------|------------|\n",
    "| **Réel : 0** | 11         | 2          |\n",
    "| **Réel : 1** | 3          | 8          |\n",
    "\n",
    "- **11 vrais négatifs** et **8 vrais positifs** correctement identifiés\n",
    "- **2 faux positifs** et **3 faux négatifs**\n",
    "\n",
    "Le modèle présente une **bonne capacité à identifier les deux classes**, même si quelques cas malades sont encore manqués (ce qui est toujours critique dans un contexte médical).\n",
    "\n",
    "---\n",
    "\n",
    "### Courbe ROC et AUC\n",
    "\n",
    "La courbe ROC du modèle sur le jeu de test confirme une **bonne capacité discriminante**, avec une **aire sous la courbe (AUC) de 0.81**.\n",
    "\n",
    "Cela signifie qu’en moyenne, le modèle classe un individu malade avec une probabilité plus élevée qu’un individu sain dans **81 % des cas**.\n",
    "\n",
    "La courbe ROC est bien au-dessus de la diagonale (modèle aléatoire), ce qui valide la qualité du modèle.\n",
    "\n",
    "---\n",
    "\n",
    "### Conclusion\n",
    "\n",
    "Le modèle optimisé offre un **compromis robuste entre performance et parcimonie**, avec :\n",
    "- une régularisation L1 qui limite le surajustement,\n",
    "- une stabilité démontrée par la validation croisée,\n",
    "- des performances solides sur le test.\n",
    "\n",
    "Cette évaluation confirme que la régression logistique constitue un **modèle fiable et interprétable** sur ce jeu de données, bien qu’il soit possible d’envisager des modèles non linéaires pour améliorer encore les résultats.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "918960cc",
   "metadata": {},
   "source": [
    "# Étape 12 — Comparaison des variantes de régression logistique\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9c2a95f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                        Modèle  Accuracy  F1-score    AUC\n",
      "0                LogReg simple     0.792     0.783  0.811\n",
      "1  LogReg optimisé (L1, C=100)     0.792     0.762  0.811\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 55228 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 1000x600 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 46336 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 32776 characters]",
      "text/plain": [
       "<Figure size 700x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "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>y_test</th>\n",
       "      <th>Pred_simple</th>\n",
       "      <th>Pred_optimisé</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>66</th>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>80</th>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>16</th>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>103</th>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>13</th>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "     y_test  Pred_simple  Pred_optimisé\n",
       "66        0            1              1\n",
       "5         1            0              0\n",
       "80        0            1              0\n",
       "16        1            1              0\n",
       "103       0            1              1\n",
       "13        1            0              0"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.metrics import (roc_auc_score)\n",
    "\n",
    "# 1. Calcul des probabilités \n",
    "y_proba_simple = logreg_simple.predict_proba(X_test_scaled)[:, 1]\n",
    "y_proba_best = best_logreg.predict_proba(X_test_scaled)[:, 1]\n",
    "\n",
    "# 2. Récapitulatif des scores\n",
    "results = pd.DataFrame({\n",
    "    'Modèle': ['LogReg simple', 'LogReg optimisé (L1, C=100)'],\n",
    "    'Accuracy': [\n",
    "        accuracy_score(y_test, y_pred_simple),\n",
    "        accuracy_score(y_test, y_pred_best)\n",
    "    ],\n",
    "    'F1-score': [\n",
    "        f1_score(y_test, y_pred_simple),\n",
    "        f1_score(y_test, y_pred_best)\n",
    "    ],\n",
    "    'AUC': [\n",
    "        roc_auc_score(y_test, y_proba_simple),\n",
    "        roc_auc_score(y_test, y_proba_best)\n",
    "    ]\n",
    "})\n",
    "print(results.round(3))\n",
    "\n",
    "# 3. Courbes ROC superposées\n",
    "fpr_simple, tpr_simple, _ = roc_curve(y_test, y_proba_simple)\n",
    "fpr_best, tpr_best, _ = roc_curve(y_test, y_proba_best)\n",
    "\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.plot(fpr_simple, tpr_simple, '--', label=f'Simple (AUC={roc_auc_score(y_test, y_proba_simple):.2f})')\n",
    "plt.plot(fpr_best, tpr_best, '-', label=f'Optimisé (AUC={roc_auc_score(y_test, y_proba_best):.2f})')\n",
    "plt.plot([0, 1], [0, 1], linestyle=':', color='grey')\n",
    "plt.title(\"Comparaison des courbes ROC\")\n",
    "plt.xlabel(\"Taux de faux positifs (FPR)\")\n",
    "plt.ylabel(\"Taux de vrais positifs (TPR)\")\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# 4. Comparaison des coefficients\n",
    "coef_df = pd.DataFrame({\n",
    "    'Feature': X_train_scaled.columns,\n",
    "    'LogReg simple': logreg_simple.coef_[0],\n",
    "    'LogReg optimisé (L1)': best_logreg.coef_[0]\n",
    "}).set_index('Feature')\n",
    "\n",
    "plt.figure(figsize=(10, 6))\n",
    "coef_df.T.plot(kind='barh', colormap='viridis')\n",
    "plt.axvline(x=0, color='black', linestyle='--')\n",
    "plt.title(\"Comparaison des coefficients entre les deux modèles\")\n",
    "plt.xlabel(\"Coefficient\")\n",
    "plt.ylabel(\"Modèle / Feature\")\n",
    "plt.grid(True)\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# 5. Distribution des probabilités prédites (classe 1)\n",
    "plt.figure(figsize=(7, 5))\n",
    "plt.hist(y_proba_simple, bins=10, alpha=0.5, label=\"Simple\", density=True)\n",
    "plt.hist(y_proba_best, bins=10, alpha=0.5, label=\"Optimisé\", density=True)\n",
    "plt.title(\"Distribution des probabilités prédites (classe positive)\")\n",
    "plt.xlabel(\"Probabilité prédite\")\n",
    "plt.ylabel(\"Densité\")\n",
    "plt.legend()\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# 6. Analyse des erreurs par individu\n",
    "erreurs = pd.DataFrame({\n",
    "    'y_test': y_test.values,\n",
    "    'Pred_simple': y_pred_simple,\n",
    "    'Pred_optimisé': y_pred_best\n",
    "}, index=y_test.index)\n",
    "\n",
    "# Afficher uniquement les cas mal classés par au moins un des deux modèles\n",
    "diff = erreurs.query(\"Pred_simple != y_test or Pred_optimisé != y_test\")\n",
    "display(diff)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a30c65a7",
   "metadata": {},
   "source": [
    "Nous avons comparé deux versions du modèle de régression logistique :\n",
    "- un **modèle simple**, sans régularisation explicite (paramètres par défaut),\n",
    "- un **modèle optimisé**, avec régularisation L1 (`penalty='l1'`, `C=100`), déterminé via validation croisée.\n",
    "\n",
    "---\n",
    "\n",
    "### Performances globales\n",
    "\n",
    "| Modèle                      | Accuracy | F1-score | AUC   |\n",
    "|-----------------------------|----------|----------|-------|\n",
    "| LogReg simple               | 0.792    | 0.783    | 0.811 |\n",
    "| LogReg optimisé (L1, C=100) | 0.792    | 0.762    | 0.811 |\n",
    "\n",
    "Les deux modèles atteignent une **précision identique (0.792)** et une **capacité discriminante équivalente (AUC = 0.811)**.  \n",
    "Le **modèle simple obtient un F1-score légèrement supérieur**, indiquant un meilleur équilibre entre rappel et précision sur la classe positive.\n",
    "\n",
    "---\n",
    "\n",
    "### Courbes ROC\n",
    "\n",
    "Les courbes ROC confirment que les deux modèles sont très proches en termes de performance globale.  \n",
    "Ils possèdent la même AUC (0.81), et suivent des trajectoires similaires sur l’ensemble de test.\n",
    "\n",
    "---\n",
    "\n",
    "### Comparaison des coefficients\n",
    "\n",
    "Le graphe ci-dessous compare les poids attribués à chaque variable par les deux modèles.\n",
    "\n",
    "- Le modèle optimisé par L1 **modifie fortement certains coefficients**, en particulier ceux de `Insulin_log` et `HOMA_log`, ce qui indique une régularisation efficace.\n",
    "- D'autres variables comme `Glucose` ou `BMI` conservent un rôle dominant dans les deux modèles.\n",
    "\n",
    "Cette comparaison renforce l’idée que le **modèle régularisé est plus parcimonieux**, tout en conservant l’essentiel du signal prédictif.\n",
    "\n",
    "---\n",
    "\n",
    "### Distribution des probabilités `predict_proba`\n",
    "\n",
    "Nous avons visualisé les distributions des probabilités prédites pour la classe positive par les deux modèles.\n",
    "\n",
    "- Les deux distributions couvrent des plages similaires, mais le modèle régularisé produit **des prédictions légèrement plus extrêmes** (plus proches de 0 ou 1),\n",
    "- Cela peut indiquer une **plus grande confiance dans certaines décisions**, ou une **meilleure séparation des classes**.\n",
    "\n",
    "---\n",
    "\n",
    "### Comparaison des erreurs\n",
    "\n",
    "Nous avons identifié les cas où les prédictions diffèrent entre les deux modèles.  \n",
    "Seulement quelques individus sont classés différemment, ce qui explique les scores très proches.  \n",
    "Exemple :\n",
    "\n",
    "| id | Vérité | Simple | Optimisé |\n",
    "|----|--------|--------|----------|\n",
    "| 80 | 0      | 1      | 0        |\n",
    "| 5  | 1      | 0      | 0        |\n",
    "\n",
    "Cela confirme que la **structure des erreurs est différente**, même si les performances agrégées sont proches.\n",
    "\n",
    "---\n",
    "\n",
    "### Analyse finale\n",
    "\n",
    "Les deux modèles sont proches en termes de performance, mais présentent :\n",
    "- des **différences structurelles dans les coefficients**,\n",
    "- des **légères différences de comportement** sur les individus en bord de décision,\n",
    "- et une **forme de décision plus régulière** dans le cas du modèle L1.\n",
    "\n",
    "---\n",
    "\n",
    "### Conclusion\n",
    "\n",
    "| Modèle                     | Avantage principal |\n",
    "|---------------------------|--------------------|\n",
    "| **LogReg simple**         | Légère supériorité en F1-score |\n",
    "| **LogReg optimisé (L1)**  | Parcimonie, coefficients régularisés, meilleure interprétabilité |\n",
    "\n",
    "En pratique, les deux modèles peuvent être retenus selon le compromis souhaité entre performance brute et simplicité structurelle.  \n",
    "Le modèle L1 est particulièrement pertinent dans un objectif de généralisation ou de sélection automatique de variables.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d11139d7",
   "metadata": {},
   "source": [
    "# Étape 13 (bonus): application de l'ACP pour essayer d'améliorer les modèles"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "b3a8341b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 54416 characters]",
      "text/plain": [
       "<Figure size 800x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Meilleurs hyperparamètres : {'C': 1.6681005372000592, 'penalty': 'l2', 'solver': 'liblinear'}\n",
      "F1-score moyen (validation croisée) : 0.715\n",
      "\n",
      " Évaluation sur le jeu de test :\n",
      "Accuracy : 0.833\n",
      "F1-score : 0.818\n",
      "AUC : 0.832\n",
      "\n",
      "Classification Report :\n",
      "               precision    recall  f1-score   support\n",
      "\n",
      "           0       0.85      0.85      0.85        13\n",
      "           1       0.82      0.82      0.82        11\n",
      "\n",
      "    accuracy                           0.83        24\n",
      "   macro avg       0.83      0.83      0.83        24\n",
      "weighted avg       0.83      0.83      0.83        24\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 18824 characters]",
      "text/plain": [
       "<Figure size 600x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 46188 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.decomposition import PCA\n",
    "\n",
    "\n",
    "# Étape 1 — Sélection du meilleur nombre de composantes via validation croisée\n",
    "scores = []\n",
    "components_range = range(2, X_train_scaled.shape[1] + 1)\n",
    "\n",
    "for n in components_range:\n",
    "    pca = PCA(n_components=n, svd_solver='full', random_state=42)\n",
    "    X_train_pca = pca.fit_transform(X_train_scaled)\n",
    "    model = LogisticRegression(random_state=42, max_iter=1000)\n",
    "    score = cross_val_score(model, X_train_pca, y_train, cv=10, scoring='f1').mean()\n",
    "    scores.append(score)\n",
    "\n",
    "# Tracé des performances\n",
    "plt.figure(figsize=(8, 5))\n",
    "plt.plot(components_range, scores, marker='o')\n",
    "plt.title(\"Sélection du nombre de composantes pour PCA\")\n",
    "plt.xlabel(\"Nombre de composantes principales\")\n",
    "plt.ylabel(\"F1-score (validation croisée)\")\n",
    "plt.grid(True)\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# Étape 2 — Application du PCA avec le meilleur n\n",
    "n_best = components_range[np.argmax(scores)]\n",
    "pca_final = PCA(n_components=n_best, svd_solver='full', random_state=42)\n",
    "X_train_pca = pca_final.fit_transform(X_train_scaled)\n",
    "X_test_pca = pca_final.transform(X_test_scaled)\n",
    "\n",
    "# Étape 3 — Optimisation du modèle via GridSearchCV\n",
    "param_grid = {\n",
    "    'penalty': ['l1', 'l2'],\n",
    "    'C': np.logspace(-2, 3, 10),\n",
    "    'solver': ['liblinear']  # nécessaire pour l1\n",
    "}\n",
    "logreg = LogisticRegression(random_state=42, max_iter=1000)\n",
    "grid_search = GridSearchCV(logreg, param_grid, cv=10, scoring='f1')\n",
    "grid_search.fit(X_train_pca, y_train)\n",
    "\n",
    "best_model = grid_search.best_estimator_\n",
    "print(\"Meilleurs hyperparamètres :\", grid_search.best_params_)\n",
    "print(\"F1-score moyen (validation croisée) :\", round(grid_search.best_score_, 3))\n",
    "\n",
    "# Étape 4 — Évaluation finale sur le jeu de test\n",
    "y_pred_pca = best_model.predict(X_test_pca)\n",
    "y_proba_pca = best_model.predict_proba(X_test_pca)[:, 1]\n",
    "\n",
    "print(\"\\n Évaluation sur le jeu de test :\")\n",
    "print(\"Accuracy :\", round(accuracy_score(y_test, y_pred_pca), 3))\n",
    "print(\"F1-score :\", round(f1_score(y_test, y_pred_pca), 3))\n",
    "print(\"AUC :\", round(roc_auc_score(y_test, y_proba_pca), 3))\n",
    "print(\"\\nClassification Report :\\n\", classification_report(y_test, y_pred_pca))\n",
    "\n",
    "# Matrice de confusion\n",
    "cm = confusion_matrix(y_test, y_pred_pca)\n",
    "plt.figure(figsize=(6, 5))\n",
    "sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False,\n",
    "            xticklabels=['Sain', 'Malade'], yticklabels=['Sain', 'Malade'])\n",
    "plt.xlabel(\"Prédictions\")\n",
    "plt.ylabel(\"Réel\")\n",
    "plt.title(\"Matrice de confusion (PCA)\")\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "# Courbe ROC\n",
    "fpr, tpr, _ = roc_curve(y_test, y_proba_pca)\n",
    "auc_value = roc_auc_score(y_test, y_proba_pca)\n",
    "\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.plot(fpr, tpr, label=f\"AUC = {auc_value:.2f}\", color='blue')\n",
    "plt.plot([0, 1], [0, 1], linestyle='--', color='red')\n",
    "plt.xlabel(\"Taux de faux positifs (FPR)\")\n",
    "plt.ylabel(\"Taux de vrais positifs (TPR)\")\n",
    "plt.title(\"Courbe ROC — Modèle après PCA\")\n",
    "plt.grid(True)\n",
    "plt.legend()\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "394a2a73",
   "metadata": {},
   "source": [
    "\n",
    "L'objectif de cette étape est d'examiner si une réduction de dimension par Analyse en Composantes Principales (PCA) peut améliorer les performances du modèle de classification.\n",
    "\n",
    "---\n",
    "\n",
    "### Justification\n",
    "\n",
    "Même après transformation logarithmique et standardisation, les variables explicatives peuvent contenir des corrélations redondantes ou du bruit peu informatif. Le PCA permet de :\n",
    "- projeter les données dans un nouvel espace de dimension réduite,\n",
    "- concentrer l’essentiel de la variance sur quelques composantes principales,\n",
    "- stabiliser l’apprentissage sur petits jeux de données en atténuant la variance inutile.\n",
    "\n",
    "---\n",
    "\n",
    "### Méthodologie\n",
    "\n",
    "1. Le PCA est appliqué sur les données standardisées (`X_train_scaled`) via `sklearn.decomposition.PCA`.\n",
    "2. Les performances de classification sont évaluées en fonction du nombre de composantes principales, de 2 à 9 (limite fixée par le nombre de variables d'origine).\n",
    "3. À chaque itération, un modèle de régression logistique est entraîné avec validation croisée (cv=10) pour mesurer le F1-score moyen.\n",
    "4. Le nombre optimal de composantes est sélectionné en maximisant ce score.\n",
    "5. Enfin, le modèle optimal est réentraîné sur l’ensemble réduit et évalué sur le jeu de test.\n",
    "\n",
    "---\n",
    "\n",
    "### Sélection du nombre de composantes\n",
    "\n",
    "La figure ci-dessous montre l'évolution du F1-score moyen en fonction du nombre de composantes principales :\n",
    "\n",
    "> *Figure – Sélection du nombre de composantes (PCA)*\n",
    "\n",
    "Le maximum est atteint à 7 composantes, avec un F1-score de validation croisée de 0.715. Au-delà, le score stagne, ce qui indique que les dernières composantes contiennent peu d'information discriminante utile.\n",
    "\n",
    "---\n",
    "\n",
    "### Résultats finaux sur le jeu de test\n",
    "\n",
    "Après réduction à 7 composantes, puis optimisation des hyperparamètres de la régression logistique (`C`, `penalty`) via `GridSearchCV`, les résultats obtenus sont les suivants :\n",
    "\n",
    "- Accuracy : 0.833\n",
    "- F1-score : 0.818\n",
    "- AUC : 0.832\n",
    "\n",
    "#### Classification report :\n",
    "\n",
    "| Classe       | Précision | Rappel | F1-score | Support |\n",
    "|--------------|-----------|--------|----------|---------|\n",
    "| 0 (sain)     | 0.85      | 0.85   | 0.85     | 13      |\n",
    "| 1 (malade)   | 0.82      | 0.82   | 0.82     | 11      |\n",
    "| Macro avg    | 0.83      | 0.83   | 0.83     | 24      |\n",
    "\n",
    "---\n",
    "\n",
    "### Matrice de confusion\n",
    "\n",
    "|               | Prédit : sain | Prédit : malade |\n",
    "|---------------|----------------|------------------|\n",
    "| Réel : sain   | 11             | 2                |\n",
    "| Réel : malade | 2              | 9                |\n",
    "\n",
    "Le modèle après PCA parvient à bien équilibrer les deux classes, avec un nombre réduit de faux positifs et de faux négatifs.\n",
    "\n",
    "---\n",
    "\n",
    "### Courbe ROC\n",
    "\n",
    "La courbe ROC confirme une capacité discriminante élevée, avec une AUC de 0.83. Cela signifie que le modèle est capable, dans 83 % des cas, d’attribuer une probabilité plus élevée à une observation positive qu’à une observation négative.\n",
    "\n",
    "---\n",
    "\n",
    "### Conclusion\n",
    "\n",
    "L’application du PCA classique avec 7 composantes principales :\n",
    "- améliore légèrement les performances globales par rapport aux modèles sans réduction de dimension,\n",
    "- stabilise l’apprentissage sur un jeu de données restreint,\n",
    "- conserve une bonne capacité discriminante tout en simplifiant la représentation des données.\n",
    "\n",
    "Ces résultats valident l’intérêt de la réduction de dimension par PCA linéaire, et justifient son intégration finale dans le pipeline supervisé.\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.11.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
