{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Machine learning 3 - Classification (FR) v2_1 (version étudiant)\n\n```python\n# import de modules vus dans \"Machine learning 2 - Regression\"\n\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nimport pandas as pd\n\nimport plotly.express as px\nimport seaborn as sns\n\nfrom sklearn.metrics import mean_absolute_error, mean_squared_error, mean_absolute_percentage_error, r2_score\nfrom sklearn.model_selection import train_test_split, cross_val_score\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.pipeline import Pipeline\n\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.neighbors import KNeighborsRegressor\n```\n","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import de modules vus dans \"Machine learning 2 - Regression\"\n\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nimport pandas as pd\n\nimport plotly.express as px\nimport seaborn as sns\n\nfrom sklearn.metrics import *\nfrom sklearn.model_selection import train_test_split, cross_val_score\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.pipeline import Pipeline\n\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.neighbors import KNeighborsRegressor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:17.645026Z","iopub.execute_input":"2026-05-08T00:04:17.645278Z","iopub.status.idle":"2026-05-08T00:04:23.545729Z","shell.execute_reply.started":"2026-05-08T00:04:17.645253Z","shell.execute_reply":"2026-05-08T00:04:23.544885Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Le dataset `titanic_mini`\n\nDans ce notebook, nous allons utiliser le jeu de données **`titanic_mini`**, une version volontairement simplifiée du dataset du Titanic.\n\nCe dataset est conçu pour **illustrer les bases de la classification supervisée**, sans complexité liée au nettoyage ou à l’encodage des variables.\n\n![Titanic](https://upload.wikimedia.org/wikipedia/commons/f/fd/RMS_Titanic_3.jpg)\n\n### Objectif du problème\n\nL’objectif est de **prédire la survie d’un passager** lors du naufrage du Titanic.\n\n* **Type de problème** : classification binaire\n* **Variable cible** : `Survived`\n* **Valeurs possibles** :\n\n  * `0` → le passager n’a pas survécu\n  * `1` → le passager a survécu\n\n\n### Colonnes du dataset\n\nLe dataset contient **4 variables**, toutes déjà numériques :\n\n| Colonne      | Type            | Description                                                                  |\n| ------------ | --------------- | ---------------------------------------------------------------------------- |\n| `Sex`        | binaire (0 / 1) | Sexe du passager : <br>• `0` = homme <br>• `1` = femme                       |\n| `Children`   | binaire (0 / 1) | Indique si le passager est un enfant : <br>• `0` = adulte <br>• `1` = enfant |\n| `FirstClass` | binaire (0 / 1) | Classe du billet : <br>• `1` = 1ʳᵉ classe <br>• `0` = autre classe           |\n| `Survived`   | binaire (0 / 1) | **Variable cible** : survie du passager                                      |\n\n```python\ndf = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/titanic_mini.csv')\ndf.head()\n```\n","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/titanic_mini.csv')\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.548012Z","iopub.execute_input":"2026-05-08T00:04:23.548708Z","iopub.status.idle":"2026-05-08T00:04:23.606360Z","shell.execute_reply.started":"2026-05-08T00:04:23.548671Z","shell.execute_reply":"2026-05-08T00:04:23.605335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/titanic_mini.csv')\n\nX = df.drop(columns=['Survived'])\ny = df['Survived']\n\nX_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=42)\n\nmodel = LinearRegression()\nmodel.fit(X_train,y_train)\ny_hat = model.predict(X_test)\n\nprint(f\"MAE : {mean_absolute_error(y_test,y_hat):.2f}\")\nprint(f\"RMSE : {root_mean_squared_error(y_test,y_hat):.2f}\")\nprint(f\"MAPE : {mean_absolute_percentage_error(y_test,y_hat):.2f}\")\nprint(f\"R2 score : {r2_score(y_test,y_hat):.2f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.607631Z","iopub.execute_input":"2026-05-08T00:04:23.607893Z","iopub.status.idle":"2026-05-08T00:04:23.652279Z","shell.execute_reply.started":"2026-05-08T00:04:23.607869Z","shell.execute_reply":"2026-05-08T00:04:23.651223Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_hat = (y_hat>0.5).astype(int)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.653374Z","iopub.execute_input":"2026-05-08T00:04:23.653978Z","iopub.status.idle":"2026-05-08T00:04:23.659945Z","shell.execute_reply.started":"2026-05-08T00:04:23.653931Z","shell.execute_reply":"2026-05-08T00:04:23.658158Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"1 - np.mean(abs(y_test.values - y_hat))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.661810Z","iopub.execute_input":"2026-05-08T00:04:23.662211Z","iopub.status.idle":"2026-05-08T00:04:23.682143Z","shell.execute_reply.started":"2026-05-08T00:04:23.662169Z","shell.execute_reply":"2026-05-08T00:04:23.681283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df[(df.Sex==1)&(df.FirstClass==1)][\"Survived\"].mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.683416Z","iopub.execute_input":"2026-05-08T00:04:23.683790Z","iopub.status.idle":"2026-05-08T00:04:23.705191Z","shell.execute_reply.started":"2026-05-08T00:04:23.683751Z","shell.execute_reply":"2026-05-08T00:04:23.704225Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df[(df.Sex==0)&(df.FirstClass==0)][\"Survived\"].mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.707929Z","iopub.execute_input":"2026-05-08T00:04:23.708608Z","iopub.status.idle":"2026-05-08T00:04:23.728246Z","shell.execute_reply.started":"2026-05-08T00:04:23.708554Z","shell.execute_reply":"2026-05-08T00:04:23.727150Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Filtrer un DataFrame pandas avec des conditions booléennes\n\nEn `pandas`, on peut sélectionner des lignes d’un DataFrame en utilisant **des conditions logiques** appliquées aux colonnes.\n\n\n\n### Filtrage avec une seule condition\n\n```python\ndf[df[\"Sex\"] == 1]\n```\n\nSélectionne toutes les lignes où la colonne `Sex` vaut `1` (ici: femmes).\n\n\n### Combiner plusieurs conditions\n\nPour combiner des conditions, on utilise les **opérateurs logiques bit-à-bit** :\n\n| Opérateur | Signification |    |\n| --------- | ------------- | -- |\n| `&`       | ET            |    |\n| `         | `             | OU |\n| `~`       | NON           |    |\n\nChaque condition doit être **entre parenthèses**.\n\n\n\n### Exemple: femmes en 1ere classe\n\n```python\ndf[(df[\"Sex\"] == 1) & (df[\"FirstClass\"] == 1)]\n```\n\nLignes pour lesquelles :\n\n* le passager est une femme\n* **et** il voyage en 1ʳᵉ classe\n\n\n\n### Exemple avec la négation\n\n```python\ndf[df[\"FirstClass\"] == 0]\n```\n\nPassagers **qui ne sont pas en 1ere classe**.\n\n```python\ndf[(df[\"Sex\"] == 0) & (df[\"FirstClass\"] == 0)]\n```\n\nHommes **non** en 1ere classe.\n\n\n\n### Le résultat est toujours un DataFrame\n\n```python\ntype(df[(df[\"Sex\"] == 1) & (df[\"FirstClass\"] == 1)])\n```\n\nLe filtrage retourne **un DataFrame**, sur lequel on peut ensuite :\n\n* compter (`.shape`)\n* résumer (`.value_counts()`)\n* calculer des statistiques (`.mean()`)\n\n\n\n### Moyenne d’une variable binaire\n\nSi une colonne contient des valeurs `0/1`, sa **moyenne correspond à une proportion**.\n\n```python\ndf[(df[\"Sex\"] == 1) & (df[\"FirstClass\"] == 1)][\"Survived\"].mean()\n```\n\n","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nfemme_1ere = df[(df['Sex'] == 1) & (df['FirstClass'] == 1)]\np_survie_femme_1ere = femme_1ere['Survived'].mean()\np_survie_femme_1ere\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice — Calcul empirique de probabilités de survie\n>\n> On considère le jeu de données `titanic_mini`, qui contient les variables suivantes :\n>\n> * `Sex`\n>\n>   * `0` : homme\n>   * `1` : femme\n> * `FirstClass`\n>\n>   * `1` : passager en 1ʳᵉ classe\n>   * `0` : passager non en 1ʳᵉ classe\n> * `Survived`\n>\n>   * `0` : n’a pas survécu\n>   * `1` : a survécu\n>\n> Toutes les variables sont binaires et déjà encodées.\n>\n> L’objectif de cet exercice est de calculer **empiriquement**, à partir des données, la probabilité de survie :\n>\n> 1. d’une femme en 1ʳᵉ classe\n> 2. d’un homme non en 1ʳᵉ classe\n>\n> À l’aide d’un filtrage par conditions booléennes avec `pandas` :\n>\n> * sélectionnez les passagers correspondant à chacun des deux profils\n> * calculez, pour chaque sous-population, la proportion de passagers ayant survécu\n> * comparez les deux probabilités obtenues\n>\n> Indication :\n> La variable `Survived` étant binaire (0/1), sa moyenne correspond directement à une probabilité empirique.\n","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Principe du classifieur Naive Bayes\n\nLe classifieur **Naive Bayes** est un modèle de classification probabiliste basé sur le théorème de Bayes.\n\nOn cherche à estimer, pour une observation $x$, la probabilité :\n\n$$\nP(Y = y \\mid X = x)\n$$\n\nLe théorème de Bayes donne :\n\n$$\nP(Y = y \\mid X = x)\n\\propto\nP(Y = y),P(X = x \\mid Y = y)\n$$\n\nLe terme “naive” provient de l’hypothèse suivante, appelée indépendance conditionnelle des variables explicatives sachant la classe :\n\n$$\nP(X_1, \\dots, X_d \\mid Y) = \n\\prod_{j=1}^d P(X_j \\mid Y)\n$$\n\nOn obtient alors :\n\n$$\nP(Y = y \\mid X_1, \\dots, X_d)\n\\propto\nP(Y = y)\\prod_{j=1}^d P(X_j \\mid Y = y)\n$$\n\nLa classe prédite est celle qui maximise cette quantité, appelée estimateur MAP (Maximum A Posteriori) :\n\n$$\n\\hat{y} = \n\\arg\\max_y\n; P(Y = y)\\prod_{j=1}^d P(X_j \\mid Y = y)\n$$\n\n### Naive Bayes Bernoulli pour des variables binaires\n\nDans le dataset `titanic_mini`, les variables explicatives sont binaires : `Sex`, `Children` et `FirstClass` prennent les valeurs 0 ou 1.\n\nLe modèle **Bernoulli Naive Bayes** suppose que, pour chaque variable $X_j$ et chaque classe $y$, la loi conditionnelle est une loi de Bernoulli :\n\n$$\nP(X_j = 1 \\mid Y = y) = \\theta_{j \\mid y}\n\\qquad\nP(X_j = 0 \\mid Y = y) = 1 - \\theta_{j \\mid y}\n$$\n\nPour une observation $x \\in {0,1}^d$, on a alors :\n\n$$\nP(X = x \\mid Y = y) = \n\\prod_{j=1}^d\n\\theta_{j \\mid y}^{x_j}\n(1 - \\theta_{j \\mid y})^{1 - x_j}\n$$\n\nLes paramètres $\\theta_{j \\mid y}$ sont estimés à partir des données.\nDans la pratique, on utilise un **lissage de Laplace** afin d’éviter les probabilités nulles :\n\n$$\n\\theta_{j \\mid y}\n\\frac{N_{j=1,y} + \\alpha}\n{N_y + 2\\alpha}\n$$\n\noù :\n\n* $N_{j=1,y}$ est le nombre d’exemples de classe $y$ tels que $X_j = 1$\n* $N_y$ est le nombre total d’exemples de la classe $y$\n* $\\alpha$ est le paramètre de lissage (argument `alpha` dans `sklearn`)\n\n\n\n### Implémentation avec `sklearn` (BernoulliNB)\n\n```python\nfrom sklearn.naive_bayes import BernoulliNB\n\nmodel = BernoulliNB(alpha=1.0)\nmodel.fit(X_train, y_train)\n```\n","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nfrom sklearn.naive_bayes import BernoulliNB\nmodel = BernoulliNB(alpha=1.0)\nmodel.fit(X_train, y_train)\ny_hat = model.predict(X_test)\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Prédire la survie d'un passager avec `BernoulliNB`\n>\n> Est-ce que les métriques de régression comme `mean_absolute_error`ont un sens ici ?\n>\n> Comment évaluer la performance de la prédiction ?","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"from sklearn.naive_bayes import BernoulliNB\nfrom sklearn.metrics import *\n\ndf = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/titanic_mini.csv')\n\nX = df.drop(columns=['Survived'])\ny = df['Survived']\n\nX_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=42)\n\nmodel = BernoulliNB()\nmodel.fit(X_train,y_train)\ny_hat = model.predict(X_test)\n\nprint(f\"Accuracy : {accuracy_score(y_test,y_hat):.2f}\")\n\nprint(confusion_matrix(y_test, y_hat))\n\nprint(classification_report(y_test, y_hat))\n\nfrom sklearn.metrics import roc_curve, roc_auc_score\n\ny_score = model.predict_proba(X_test)[:, 1]\nfpr, tpr, thresholds = roc_curve(y_test, y_score)\nauc = roc_auc_score(y_test, y_score)\n\nprint(f\"AUC : {auc:.2f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.729787Z","iopub.execute_input":"2026-05-08T00:04:23.730138Z","iopub.status.idle":"2026-05-08T00:04:23.792177Z","shell.execute_reply.started":"2026-05-08T00:04:23.730095Z","shell.execute_reply":"2026-05-08T00:04:23.791084Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Métriques d’évaluation en classification\n\nEn classification supervisée, on cherche à mesurer la qualité des prédictions d’un modèle en comparant :\n\n* les valeurs réelles $y$\n* les valeurs prédites $\\hat{y}$\n\nDans le cadre du Titanic :\n\n* classe positive : `Survived = 1`\n* classe négative : `Survived = 0`\n\n\n\n### Accuracy (taux de bonne classification)\n\nL’**accuracy** mesure la proportion de prédictions correctes.\n\nOn note :\n\n* $y \\in {0,1}^n$ les vraies étiquettes\n* $\\hat{y} \\in {0,1}^n$ les prédictions\n\n#### Définition\n\n$$\n\\text{Accuracy} = \\frac{\\text{nombre de prédictions correctes}}\n{\\text{nombre total de prédictions}}\n$$\n\n\n#### Écriture équivalente pour des classes binaires (0 / 1)\n\nLorsque les classes sont codées en 0 et 1, on observe que :\n\n* si $\\hat{y}_i = y_i$, alors $|\\hat{y}_i - y_i| = 0$\n* si $\\hat{y}_i \\neq y_i$, alors $|\\hat{y}_i - y_i| = 1$\n\nLa quantité\n$$\n\\sum_{i=1}^n |\\hat{y}_i - y_i|\n$$\ncorrespond exactement au **nombre d’erreurs de classification**.\n\nOn peut donc écrire :\n\n$$\n\\text{Accuracy} = 1 - \\frac{1}{n}\\sum_{i=1}^n |\\hat{y}_i - y_i|\n$$\n\nCette expression montre que l’accuracy est simplement **1 moins la proportion d’erreurs**.\n\n\n#### Calcul avec `sklearn`\n\n```python\nfrom sklearn.metrics import accuracy_score\n\naccuracy_score(y_test, y_hat)\n```\n\n\n### Matrice de confusion\n\nL’accuracy donne une vision globale, mais ne permet pas de comprendre **la nature des erreurs**.\nPour cela, on utilise la **matrice de confusion**.\n\nPour une classification binaire :\n\n$$\n\\begin{array}{c|cc}\n & \\text{Prédit }0 & \\text{Prédit }1 \\\\\n\\hline\n\\text{Réel }0 & TN & FP \\\\\n\\text{Réel }1 & FN & TP\n\\end{array}\n$$\n\n\noù :\n\n* $TP$ : vrais positifs\n* $TN$ : vrais négatifs\n* $FP$ : faux positifs\n* $FN$ : faux négatifs\n\nLa matrice de confusion est la base de toutes les métriques suivantes.\n\n\n\n#### Calcul avec `sklearn`\n\n```python\nfrom sklearn.metrics import confusion_matrix\n\nconfusion_matrix(y_test, y_hat)\n```\n\n\n\n### Precision\n\nLa **précision** mesure la fiabilité des prédictions positives.\n\n$$\n\\text{Precision} = \\frac{TP}{TP + FP}\n$$\n\nInterprétation :\n\n* parmi les observations prédites comme positives, quelle proportion est réellement positive ?\n\nLa précision est importante lorsque les **faux positifs sont coûteux**.\n\n\n\n#### Calcul avec `sklearn`\n\n```python\nfrom sklearn.metrics import precision_score\n\nprecision_score(y_test, y_hat)\n```\n\n\n\n### Recall (rappel ou sensibilité)\n\nLe **recall** mesure la capacité du modèle à détecter les vrais positifs.\n\n$$\n\\text{Recall} = \\frac{TP}{TP + FN}\n$$\n\nInterprétation :\n\n* parmi les vrais positifs, quelle proportion est correctement détectée ?\n\nLe recall est crucial lorsque les **faux négatifs sont coûteux**.\n\n\n\n#### Calcul avec `sklearn`\n\n```python\nfrom sklearn.metrics import recall_score\n\nrecall_score(y_test, y_hat)\n```\n\n\n### F1-score\n\nLe **F1-score** combine précision et rappel via leur moyenne harmonique.\n\n$$\n\\text{F1} = 2 \\cdot\n\\frac{\\text{Precision} \\cdot \\text{Recall}}\n{\\text{Precision} + \\text{Recall}}\n$$\n\nIl pénalise fortement un modèle qui aurait :\n\n* une précision élevée mais un rappel faible\n* ou l’inverse\n\nLe F1-score est souvent utilisé comme **métrique globale équilibrée**.\n\n\n\n#### Calcul avec `sklearn`\n\n```python\nfrom sklearn.metrics import f1_score\n\nf1_score(y_test, y_hat)\n```\n\n\n### Rapport de classification\n\n`sklearn` fournit un résumé complet des métriques par classe.\n\n```python\nfrom sklearn.metrics import classification_report\n\nprint(classification_report(y_test, y_hat))\n```\n\nCe rapport inclut :\n\n* precision\n* recall\n* f1-score\n* support (nombre d’exemples par classe)","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nfrom sklearn.metrics import *\nmodel = BernoulliNB(alpha=1.0)\nmodel.fit(X_train, y_train)\ny_hat = model.predict(X_test)\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Compléter l'exercice précédent en affichant l'accuracy et le classification report","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Scores de probabilité et seuil de décision\n\nDe nombreux classifieurs (Naive Bayes, régression logistique, etc.) ne prédisent pas directement une classe, mais une **probabilité de la classe positive**.\n\nDans le cas du Titanic, le modèle estime :\n\n$$\n\\hat{p} = P(\\text{Survived} = 1 \\mid X)\n$$\n\nCes probabilités sont ensuite transformées en classes par l’application d’un **seuil de décision**.\n\n\n\n### `predict_proba` avec `sklearn`\n\nLa méthode `predict_proba` retourne, pour chaque observation, les probabilités estimées pour chaque classe.\n\n```python\nprobas = model.predict_proba(X_test)\nprobas[:5]\n```\n\nLe résultat est un tableau de taille `(n_samples, 2)` :\n\n* colonne 0 : $P(Y=0 \\mid X)$\n* colonne 1 : $P(Y=1 \\mid X)$\n\nOn extrait généralement la probabilité de la classe positive :\n\n```python\ny_score = probas[:, 1]\n```\n\n\n\n### Prédiction avec un seuil fixé\n\nÀ partir des probabilités $\\hat{p}$, on définit la prédiction binaire :\n\n$$\n\\hat{y} =\n\\begin{cases}\n1 & \\text{si } \\hat{p} \\ge t \\\\\n0 & \\text{sinon}\n\\end{cases}\n$$\n\n\noù $t \\in [0,1]$ est le **seuil de décision**.\n\n\n\n#### Exemple avec un seuil différent de 0.5\n\n```python\nimport numpy as np\n\nt = 0.7\ny_hat = (y_score >= t).astype(int)\n```\n\nIci :\n\n* seules les observations avec une probabilité de survie d’au moins 70 % sont classées comme survivantes\n* les autres sont classées comme non-survivantes\n\n\n\n### Effet du seuil sur les prédictions\n\nModifier le seuil change le comportement du classifieur :\n\n* seuil élevé :\n\n  * peu de prédictions positives\n  * peu de faux positifs\n  * plus de faux négatifs\n* seuil faible :\n\n  * beaucoup de prédictions positives\n  * plus de faux positifs\n  * moins de faux négatifs\n\nLe seuil contrôle donc le **compromis entre détection et fausses alertes**.\n\n```python\nprobas = model.predict_proba(X_test)\nprobas[:5]\n```\n\n```python\ny_score = probas[:, 1]\ny_score\n```\n","metadata":{}},{"cell_type":"code","source":"y_proba = model.predict_proba(X_test)[:,1]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.793435Z","iopub.execute_input":"2026-05-08T00:04:23.793808Z","iopub.status.idle":"2026-05-08T00:04:23.802790Z","shell.execute_reply.started":"2026-05-08T00:04:23.793767Z","shell.execute_reply":"2026-05-08T00:04:23.801660Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"(y_proba>1).astype(int)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:04:23.803948Z","iopub.execute_input":"2026-05-08T00:04:23.804403Z","iopub.status.idle":"2026-05-08T00:04:23.827828Z","shell.execute_reply.started":"2026-05-08T00:04:23.804361Z","shell.execute_reply":"2026-05-08T00:04:23.826751Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Prédiction avec un seuil fixé\n\nÀ partir des probabilités $\\hat{p}$, on définit la prédiction binaire :\n\n$$\n\\hat{y} =\n\\begin{cases}\n1 & \\text{si } \\hat{p} \\ge t \\\\\n0 & \\text{sinon}\n\\end{cases}\n$$\n\n\noù $t \\in [0,1]$ est le **seuil de décision**.\n\n\n\n#### Exemple avec un seuil différent de 0.5\n\n```python\nimport numpy as np\n\nt = 0.7\ny_hat = (y_score >= t).astype(int)\n```\n\nIci :\n\n* seules les observations avec une probabilité de survie d’au moins 70 % sont classées comme survivantes\n* les autres sont classées comme non-survivantes\n\n\n\n### Effet du seuil sur les prédictions\n\nModifier le seuil change le comportement du classifieur :\n\n* seuil élevé :\n\n  * peu de prédictions positives\n  * peu de faux positifs\n  * plus de faux négatifs\n* seuil faible :\n\n  * beaucoup de prédictions positives\n  * plus de faux positifs\n  * moins de faux négatifs\n\nLe seuil contrôle donc le **compromis entre détection et fausses alertes**.\n\n```python\nt = 0.7\ny_hat = (y_score >= t).astype(int)\ny_hat\n```\n","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Courbe ROC et AUC\n\nJusqu’ici, nous avons vu que les prédictions binaires dépendent d’un **seuil de décision** $t$ appliqué aux probabilités estimées par le modèle.\n\nLa **courbe ROC** permet d’analyser le comportement du classifieur **pour tous les seuils possibles**, et donc indépendamment du choix d’un seuil particulier.\n\n\n### Taux utilisés pour la courbe ROC\n\nPour un seuil $t$ donné, on obtient une matrice de confusion et on définit deux quantités fondamentales.\n\nTaux de vrais positifs (TPR), aussi appelé recall :\n\n$$\nTPR(t) = \\frac{TP(t)}{TP(t) + FN(t)}\n$$\n\nTaux de faux positifs (FPR) :\n\n$$\nFPR(t) = \\frac{FP(t)}{FP(t) + TN(t)}\n$$\n\nUn **point de la courbe ROC** est alors le couple :\n\n$$\n\\big(FPR(t),; TPR(t)\\big)\n$$\n\n\n\n### Construction de la courbe ROC\n\nLa courbe ROC est construite en :\n\n1. faisant varier le seuil $t$ de 1 à 0\n2. calculant, pour chaque valeur de $t$, les quantités $TPR(t)$ et $FPR(t)$\n3. traçant les points $(FPR(t), TPR(t))$\n\nChaque point correspond donc à une stratégie de décision différente.\n\n\n\n### Interprétation de la courbe ROC\n\n* le point $(0,0)$ correspond à un seuil très élevé (aucune prédiction positive)\n* le point $(1,1)$ correspond à un seuil très faible (tout est prédit positif)\n* la diagonale $TPR = FPR$ correspond à un classifieur aléatoire\n* plus la courbe est proche du coin supérieur gauche, meilleur est le modèle\n\n\n\n### AUC (Area Under the Curve)\n\nL’**AUC** est l’aire sous la courbe ROC.\n\n$$\n\\text{AUC} \\in [0,1]\n$$\n\nInterprétation clé :\n\n> L’AUC est la probabilité qu’un exemple positif reçoive un score plus élevé qu’un exemple négatif tiré au hasard.\n\n\n\n### Valeurs typiques de l’AUC\n\n* AUC = 0.5 : modèle aléatoire\n* 0.7 à 0.8 : modèle correct\n* 0.8 à 0.9 : bon modèle\n* $> 0.9$ : très bon modèle\n\n\n\n### Calcul avec `sklearn`\n\nLa courbe ROC et l’AUC se calculent à partir des **probabilités**, et non des classes prédites.\n\n```python\nfrom sklearn.metrics import roc_curve, roc_auc_score\n\ny_score = model.predict_proba(X_test)[:, 1]\n\nfpr, tpr, thresholds = roc_curve(y_test, y_score)\nauc = roc_auc_score(y_test, y_score)\n\nauc\n```","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import roc_curve, roc_auc_score\ny_score = model.predict_proba(X_test)[:, 1]\nt = 0.7\ny_hat_07 = (y_score >= t).astype(int)\nTP = np.sum((y_test == 1) & (y_hat_07 == 1))\nFP = np.sum((y_test == 0) & (y_hat_07 == 1))\nFN = np.sum((y_test == 1) & (y_hat_07 == 0))\nTN = np.sum((y_test == 0) & (y_hat_07 == 0))\nTPR_07 = TP / (TP + FN) if TP + FN > 0 else 0.0\nFPR_07 = FP / (FP + TN) if FP + TN > 0 else 0.0\n(fpr, tpr, thresholds) = roc_curve(y_test, y_score)\nauc = roc_auc_score(y_test, y_score)\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice — Calculer $TPR(0.7)$ et tracer la courbe ROC (avec AUC)\n>\n> On dispose :\n>\n> * des vraies étiquettes `y_test` (0/1)\n> * des probabilités de la classe positive `y_score = model.predict_proba(X_test)[:, 1]`\n>\n> On fixe un seuil $t = 0.7$ et on définit la prédiction binaire :\n> $$\n \\hat{y} =\n \\begin{cases}\n 1 & \\text{si } \\hat{p} \\ge t  \\\\ \n 0 & \\text{sinon}\n \\end{cases}\n $$\n>\n> Travail demandé :\n>\n> 1. Calculer $TPR(0.7)$ à partir de `y_test` et de la prédiction au seuil 0.7\n> 2. Calculer aussi $FPR(0.7)$ (utile pour situer le point sur la ROC)\n> 3. Tracer la courbe ROC à partir de `y_test` et `y_score`\n> 4. Afficher la valeur de l’AUC\n>\n> Rappels :\n> $$\nTPR(t) = \\frac{TP(t)}{TP(t) + FN(t)}\n\\qquad\nFPR(t) = \\frac{FP(t)}{FP(t) + TN(t)}\n$$\n>\n> Pour calculer le nombre de vrais positifs  :  \n> ```python\nnp.sum((y_test == 1) & (y_hat == 1))\n```","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Arbres de décision — principe et construction\n \nUn **arbre de décision** est un modèle de classification basé sur une succession de **questions simples** posées sur les variables explicatives.\n \nÀ chaque étape, l’arbre :\n \n \n- choisit une variable\n \n- sépare le dataset en sous-ensembles\n \n- répète le processus sur chaque sous-ensemble\n \n\n \nL’objectif est de créer des groupes de plus en plus **purs** du point de vue de la variable cible.\n  \n### Indice de Gini\n \nPour une variable cible binaire $Y \\in {0,1}$, on définit l’**indice de Gini** par :\n ` G = 2p(1-p) ` \noù $p = P(Y=1)$ est la proportion de la classe positive dans le groupe considéré.\n \n \n- $G$ est maximal pour $p = 0.5$\n \n- $G = 0$ lorsque le groupe est pur ($p = 0$ ou $p = 1$)\n \n\n \nL’indice de Gini mesure donc l’**impureté** d’un ensemble.\n  \n### Choix de la coupure\n \nÀ un nœud donné, on dispose d’un sous-dataset $D$.\n \nPour chaque variable candidate $X$ :\n \n \n1. on sépare $D$ selon les valeurs possibles de $X$\n \n2. on calcule l’impureté de Gini dans chaque sous-groupe\n \n3. on calcule l’impureté moyenne pondérée après coupure :\n \n$$\n  G(Y \\mid X) = \\sum_k P(X = k)\\,G(Y \\mid X = k) \n$$\n\nLa variable choisie est celle qui **minimise** $G(Y \\mid X)$.\n \nCette variable devient le **test du nœud**.\n  \n### Séparation du dataset\n \nUne fois la variable $X^*$ choisie :\n \n \n- le dataset est séparé en sous-datasets\n \n- chaque sous-dataset correspond à une branche\n \n- chaque branche est traitée indépendamment\n \n\n \nSur `titanic_mini`, une première séparation typique peut être :\n \n \n- `Children = 1`\n \n- `Children = 0`\n \n\n  \n### Construction récursive de l’arbre\n \nLe même processus est appliqué **récursivement** à chaque sous-dataset :\n \n \n- recalcul du Gini\n \n- choix de la meilleure variable\n \n- nouvelle séparation\n \n\n \nL’arbre se développe donc de la racine vers les feuilles.\n  \n### Critères d’arrêt\n \nLa construction s’arrête dans les cas suivants.\n \n#### Pureté parfaite\n \nSi un sous-dataset contient uniquement une seule classe :\n ` G = 0 ` \nLe nœud devient une **feuille**.\n  \n#### Absence de gain\n \nSi aucune variable ne permet de réduire l’impureté, on arrête la séparation.\n  \n#### Contraintes structurelles\n \nPour éviter le sur-apprentissage, on impose souvent :\n \n \n- une profondeur maximale (`max_depth`)\n \n- un nombre minimum d’exemples dans un nœud (`min_samples_split`)\n \n- un nombre minimum d’exemples dans une feuille (`min_samples_leaf`)\n \n\n \nCes critères forcent l’arbre à rester **simple et généralisable**.\n  \n### Décision dans une feuille\n \nUne feuille contient un sous-dataset $D_{\\text{leaf}}$.\n \nLa décision est prise ainsi :\n \n \n- **classe prédite** : la classe majoritaire\n \n- **probabilité prédite** :\n \n\n ` P(Y=1 \\mid \\text{leaf}) = \\frac{\\text{nombre de } Y=1}{|D_{\\text{leaf}}|} ` \nDans `sklearn`, cette probabilité correspond exactement à `predict_proba`.\n  \n### Lecture complète sur le Titanic mini\n \nUn arbre appris sur `titanic_mini` correspond typiquement à une logique du type :\n \n \n- si `Children = 1` → survivant\n \n- sinon si `Sex = 1` → survivant\n \n- sinon si `FirstClass = 1` → survivant\n \n- sinon → non-survivant\n \n\n \nChaque règle correspond à une **branche** menant à une **feuille**.\n  \n","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\ndf_fc0 = df[df['FirstClass'] == 0]\np_fc0 = df_fc0['Survived'].mean()\ngini_fc0 = 2 * p_fc0 * (1 - p_fc0)\ndf_fc1 = df[df['FirstClass'] == 1]\np_fc1 = df_fc1['Survived'].mean()\ngini_fc1 = 2 * p_fc1 * (1 - p_fc1)\nw_fc0 = len(df_fc0) / len(df)\nw_fc1 = len(df_fc1) / len(df)\ngini_firstclass = w_fc0 * gini_fc0 + w_fc1 * gini_fc1\ngini_firstclass\n```\n","metadata":{}},{"cell_type":"code","source":"df_fc0 = df[df['FirstClass'] == 0]\np_fc0 = df_fc0['Survived'].mean()\ngini_fc0 = 2 * p_fc0 * (1 - p_fc0)\ngini_fc0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:15:25.942087Z","iopub.execute_input":"2026-05-08T00:15:25.942409Z","iopub.status.idle":"2026-05-08T00:15:25.952136Z","shell.execute_reply.started":"2026-05-08T00:15:25.942382Z","shell.execute_reply":"2026-05-08T00:15:25.950872Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_fc1 = df[df['FirstClass'] == 1]\np_fc1 = df_fc1['Survived'].mean()\ngini_fc1 = 2 * p_fc1 * (1 - p_fc1)\ngini_fc1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:15:51.422325Z","iopub.execute_input":"2026-05-08T00:15:51.422803Z","iopub.status.idle":"2026-05-08T00:15:51.430830Z","shell.execute_reply.started":"2026-05-08T00:15:51.422774Z","shell.execute_reply":"2026-05-08T00:15:51.429974Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"w_fc0 = len(df_fc0) / len(df)\nw_fc1 = len(df_fc1) / len(df)\ngini_firstclass = w_fc0 * gini_fc0 + w_fc1 * gini_fc1\ngini_firstclass","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:16:13.678106Z","iopub.execute_input":"2026-05-08T00:16:13.679213Z","iopub.status.idle":"2026-05-08T00:16:13.685927Z","shell.execute_reply.started":"2026-05-08T00:16:13.679172Z","shell.execute_reply":"2026-05-08T00:16:13.685003Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> ### EXERCICE — Calcul de l’indice de Gini et choix de la racine de l’arbre\n>\n>\n> 1. Calculer l’indice de Gini global du dataset\n> 2. Calculer l’indice de Gini après coupure pour chacune des variables :\n>    - Sex\n>    - Children\n>    - FirstClass\n> 3. Comparer les valeurs obtenues\n> 4. En déduire la variable choisie comme racine de l’arbre\n>\n>\n> Indication — Calcul du Gini pour FirstClass\n>\n> Le code ci-dessous montre comment calculer l’indice de Gini après une coupure selon FirstClass\n>\n> ```python\n> # FirstClass = 0\n> df_fc0 = df[df[\"FirstClass\"] == 0]\n> p_fc0 = df_fc0[\"Survived\"].mean()\n> gini_fc0 = 2 * p_fc0 * (1 - p_fc0)\n>\n> # FirstClass = 1\n> df_fc1 = df[df[\"FirstClass\"] == 1]\n> p_fc1 = df_fc1[\"Survived\"].mean()\n> gini_fc1 = 2 * p_fc1 * (1 - p_fc1)\n>\n> # Pondérations\n> w_fc0 = len(df_fc0) / len(df)\n> w_fc1 = len(df_fc1) / len(df)\n>\n> # Gini après coupure\n> gini_firstclass = w_fc0 * gini_fc0 + w_fc1 * gini_fc1\n>\n> gini_firstclass\n> ```\n\n>\n> \n> - Reproduire le même calcul pour Sex et Children\n> - Comparer les trois valeurs de Gini\n> - Identifier la variable correspondant au Gini minimal\n> - Conclure sur le choix de la racine de l’arbre\n>\n","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"from sklearn.tree import DecisionTreeClassifier, plot_tree\n\ndf = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/titanic_mini.csv')\n\nX = df.drop(columns=['Survived'])\ny = df['Survived']\n\nX_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=42)\n\nmodel = DecisionTreeClassifier()\nmodel.fit(X_train,y_train)\ny_hat = model.predict(X_test)\n\nprint(f\"Accuracy : {accuracy_score(y_test,y_hat):.2f}\")\n\nprint(confusion_matrix(y_test, y_hat))\n\nprint(classification_report(y_test, y_hat))\n\nfrom sklearn.metrics import roc_curve, roc_auc_score\n\ny_score = model.predict_proba(X_test)[:, 1]\nfpr, tpr, thresholds = roc_curve(y_test, y_score)\nauc = roc_auc_score(y_test, y_score)\n\nprint(f\"AUC : {auc:.2f}\")\n\nplt.figure(figsize=(14, 6))\nplot_tree(\n     model,\n     feature_names=X.columns,\n     class_names=[\"0\", \"1\"],\n     filled=True,     rounded=True,\n     impurity=True )\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:20:08.912797Z","iopub.execute_input":"2026-05-08T00:20:08.913229Z","iopub.status.idle":"2026-05-08T00:20:09.474596Z","shell.execute_reply.started":"2026-05-08T00:20:08.913196Z","shell.execute_reply":"2026-05-08T00:20:09.473296Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Arbres de décision avec `sklearn`\n\nL’implémentation de référence dans `sklearn` est la classe `DecisionTreeClassifier`.\n \nLes paramètres principaux sont :\n \n \n- `criterion` : mesure de l’impureté (`\"gini\"` ou `\"entropy\"`)\n \n- `max_depth` : profondeur maximale de l’arbre\n \n- `min_samples_split` : nombre minimum d’observations pour séparer un nœud\n \n- `min_samples_leaf` : nombre minimum d’observations dans une feuille\n\n\n```python\nfrom sklearn.tree import DecisionTreeClassifier\n\nmodel = DecisionTreeClassifier(max_depth=3)\nmodel.fit(X_train, y_train)\n```\n  \n### Affichage graphique de l’arbre\n \nL’arbre appris peut être visualisé directement dans le notebook.\n\n```python\nimport matplotlib.pyplot as plt\nfrom sklearn.tree import plot_tree\n\nplt.figure(figsize=(14, 6))\nplot_tree(\n     model,\n     feature_names=X.columns,\n     class_names=[\"0\", \"1\"],\n     filled=True,     rounded=True,\n     impurity=True )\nplt.show()\n```\n \nChaque nœud affiche :\n \n \n- la règle de séparation (ex. `Sex <= 0.5`)\n \n- l’indice de Gini du nœud\n \n- le nombre d’observations\n \n- la répartition des classes\n \n- la classe majoritaire (couleur dominante)\n \n\n  \n### Affichage textuel des règles de décision\n \nUne représentation textuelle des règles peut être obtenue :\n\n```python\nfrom sklearn.tree import export_text\n\nrules = export_text(model, feature_names=list(X.columns))\nprint(rules)\n```\n\nCette sortie correspond exactement à la structure de l’arbre et permet de lire les règles sous forme hiérarchique.\n  \n  \n### Remarque sur les variables binaires\n \nMême lorsque les variables sont binaires (0 / 1), `sklearn` traite les variables comme numériques. Les seuils affichés sont donc généralement de la forme `<= 0.5`, ce qui correspond à une séparation entre les valeurs 0 et 1.\n  \n","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nfrom sklearn.tree import DecisionTreeClassifier\nmodel = DecisionTreeClassifier(max_depth=3)\nmodel.fit(X_train, y_train)\ny_hat = model.predict(X_test)\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Utiliser un decision_tree_classifier sur le dataset titanic_mini\n>\n> Afficher l'arbre de décision\n>\n> Afficher les règles","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/cancer_mini.csv')\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:24:03.057661Z","iopub.execute_input":"2026-05-08T00:24:03.058370Z","iopub.status.idle":"2026-05-08T00:24:03.086633Z","shell.execute_reply.started":"2026-05-08T00:24:03.058296Z","shell.execute_reply":"2026-05-08T00:24:03.084911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sns.kdeplot(df, x='radius', hue='diagnosis')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:27:40.188518Z","iopub.execute_input":"2026-05-08T00:27:40.188880Z","iopub.status.idle":"2026-05-08T00:27:40.422347Z","shell.execute_reply.started":"2026-05-08T00:27:40.188855Z","shell.execute_reply":"2026-05-08T00:27:40.421338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sns.scatterplot(df, x='radius', y='texture', hue='diagnosis')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:31:29.733360Z","iopub.execute_input":"2026-05-08T00:31:29.734470Z","iopub.status.idle":"2026-05-08T00:31:29.955534Z","shell.execute_reply.started":"2026-05-08T00:31:29.734416Z","shell.execute_reply":"2026-05-08T00:31:29.954408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sns.pairplot(df, hue='diagnosis')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:32:32.172989Z","iopub.execute_input":"2026-05-08T00:32:32.173833Z","iopub.status.idle":"2026-05-08T00:32:35.649250Z","shell.execute_reply.started":"2026-05-08T00:32:32.173798Z","shell.execute_reply":"2026-05-08T00:32:35.647817Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Coupure d’une variable numérique : exemple sur `cancer_mini`\n\nOn considère le dataset `cancer_mini.csv`, issu du **Breast Cancer Wisconsin Dataset (UCI)**.\n\n![Cancer](https://storage.googleapis.com/kaggle-datasets-images/180/384/3da2510581f9d3b902307ff8d06fe327/dataset-cover.jpg)\n\n### Description du dataset\n\n* **Variables explicatives (numériques continues)** :\n\n  * `radius`\n  * `perimeter`\n  * `area`\n  * `texture`\n\n* **Variable cible** :\n\n  * `diagnosis`\n\n    * 0 : bénin\n    * 1 : malin\n\n\n### Focus sur la variable `texture`\n\nLa variable `texture` représente **l’écart-type des niveaux de gris** dans l’image du noyau cellulaire.\n\n* faible valeur :\n\n  * surface relativement lisse\n  * souvent associée à des tumeurs bénignes\n\n* valeur élevée :\n\n  * surface plus rugueuse ou irrégulière\n  * signe fréquent de malignité\n\nCette variable est donc particulièrement adaptée pour illustrer une **coupure numérique** dans un arbre de décision.\n\n\n\n### Principe de la coupure sur une variable numérique\n\nDans un arbre de décision, une variable numérique $X$ est utilisée via une règle de la forme :\n\n* $X \\le t$\n* $X > t$\n\noù $t$ est un **seuil appris automatiquement à partir des données**.\n\nPour `texture`, une règle possible serait par exemple :\n\n* `texture <= 18.6` → branche gauche\n* `texture > 18.6` → branche droite\n\n\n\n### Comment le seuil est-il choisi ?\n\nSoit un noeud contenant un sous-dataset $D$.\n\n1. On trie les valeurs observées de `texture`\n2. On considère des **seuils candidats** entre deux valeurs consécutives\n3. Pour chaque seuil $t$, on sépare :\n\n   * $D_L = {x \\mid \\text{texture} \\le t}$\n   * $D_R = {x \\mid \\text{texture} > t}$\n4. On calcule l’impureté après coupure\n5. On retient le seuil qui **minimise l’impureté**\n\n\n### Interprétation d’une coupure\n\nSupposons que l’arbre apprenne la règle suivante à la racine :\n\n```\ntexture <= 19.2\n```\n\n* **branche gauche (`texture <= 19.2`)** :\n\n  * noyaux relativement homogènes\n  * majorité de tumeurs bénignes\n  * faible proportion de diagnostics malins\n\n* **branche droite (`texture > 19.2`)** :\n\n  * forte hétérogénéité de texture\n  * proportion élevée de tumeurs malignes\n\nL’arbre a donc appris une **règle simple mais cliniquement cohérente**.\n\n\n\n### Construction récursive\n\nUne fois cette première coupure effectuée :\n\n* chaque sous-groupe est traité indépendamment\n* l’arbre peut ensuite tester :\n\n  * `radius`\n  * `perimeter`\n  * `area`\n* avec de nouveaux seuils, spécifiques à chaque branche\n\n\n\n### Décision dans une feuille\n\nDans une feuille de l’arbre :\n\n* la **classe prédite** est la classe majoritaire (bénin ou malin)\n* la **probabilité prédite** est la proportion de cette classe dans la feuille\n\nPar exemple :\n\n* 18 malins sur 20 observations\n* probabilité prédite = 0.9\n\n\n","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nmodel = DecisionTreeClassifier(max_depth=3)\nmodel.fit(X_train, y_train)\ny_hat = model.predict(X_test)\nimport matplotlib.pyplot as plt\nfrom sklearn.tree import plot_tree\nplot_tree(model, feature_names=X.columns, class_names=['0', '1'], filled=True, rounded=True, impurity=True)\nfrom sklearn.tree import export_text\nrules = export_text(model, feature_names=list(X.columns))\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Utiliser un decision_tree_classifier sur le dataset cancer_mini\n>\n> Afficher l'arbre de décision et les règles","metadata":{}},{"cell_type":"code","source":"from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text\n\ndf = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/cancer_mini.csv')\n\nX = df[['radius','texture']]\ny = df['diagnosis']\n\nX_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=42)\n\nmodel = DecisionTreeClassifier(max_depth=5)\nmodel.fit(X_train,y_train)\ny_hat = model.predict(X_test)\n\nprint(f\"Accuracy : {accuracy_score(y_test,y_hat):.2f}\")\n\nprint(confusion_matrix(y_test, y_hat))\n\nprint(classification_report(y_test, y_hat))\n\ny_score = model.predict_proba(X_test)[:, 1]\nfpr, tpr, thresholds = roc_curve(y_test, y_score)\nauc = roc_auc_score(y_test, y_score)\n\nprint(f\"AUC : {auc:.2f}\")\n\nplt.figure(figsize=(30, 15))\nplot_tree(\n     model,\n     feature_names=X.columns,\n     class_names=[\"0\", \"1\"],\n     filled=True,     rounded=True,\n     impurity=True )\nplt.show()\n\nrules = export_text(model, feature_names=list(X.columns))\nprint(rules)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T00:47:46.343043Z","iopub.execute_input":"2026-05-08T00:47:46.343477Z","iopub.status.idle":"2026-05-08T00:47:47.584420Z","shell.execute_reply.started":"2026-05-08T00:47:46.343449Z","shell.execute_reply":"2026-05-08T00:47:47.583034Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Tester plusieurs valeurs du paramètre `max_depth`\n>\n> Comment varie la performance ? ","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Validation croisée\n\nPour obtenir une estimation plus stable, on utilise la validation croisée, déjà introduite précédemment.\n\n**Principe** :\n\n* on découpe les données en $K$ plis\n\n* chaque pli joue successivement le rôle de jeu de test\n\n* on moyenne les performances obtenues","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\ncv_scores = []\nfor d in range(1, 30):\n    model = DecisionTreeClassifier(max_depth=d)\n    scores = cross_val_score(model, X, y, cv=10, scoring='accuracy')\n    cv_scores.append(scores.mean())\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Tester plusieurs valeurs du paramètre `max_depth` avec une validation croisée `cross_val_score`\n>\n> On pourra utiliser une liste cv_scores pour accumuler les résultats :\n>\n> ```python\n> cv_scores = []\n>\n> for ...\n>    ...\n>    cv_scores.append(scores.mean())\n>\n> plt.plot(cv_scores)\n```\n","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Forêts aléatoires (Random Forests)\n\nLes **forêts aléatoires** sont des modèles de classification et de régression basés sur l’**agrégation d’un grand nombre d’arbres de décision**.\n\nElles ont été introduites pour répondre à une limite majeure des arbres de décision individuels :\nleur **forte variance**, c’est-à-dire leur sensibilité aux fluctuations des données.\n\n![Random forest](https://upload.wikimedia.org/wikipedia/commons/d/d8/Decision_Tree_vs._Random_Forest.png)\n\n### Idée fondamentale\n\nPlutôt que d’apprendre **un seul arbre**, potentiellement instable, on apprend :\n\n> un ensemble d’arbres différents, entraînés sur des versions légèrement différentes des données, puis on combine leurs prédictions.\n\nCette idée repose sur un principe statistique simple :\n\n> la moyenne de modèles instables mais peu biaisés est souvent plus stable et plus performante qu’un modèle unique.\n\n\n\n### Deux sources d’aléa\n\nUne forêt aléatoire introduit de l’aléatoire à deux niveaux.\n\n#### 1. Bootstrap des observations\n\nChaque arbre est entraîné sur un **échantillon bootstrap** du dataset :\n\n* tirage aléatoire avec remise\n* même taille que le dataset original\n* certaines observations apparaissent plusieurs fois\n* d’autres n’apparaissent pas\n\nChaque arbre voit donc une version légèrement différente des données.\n\n\n\n#### 2. Sous-échantillonnage des variables\n\nÀ chaque nœud de chaque arbre :\n\n* seule une **sous-partie des variables** est considérée pour la coupure\n* les variables candidates sont choisies aléatoirement\n\nCela empêche une variable très dominante d’être systématiquement choisie en tête et favorise la diversité des arbres.\n\n\n\n### Agrégation des prédictions\n\n#### En classification\n\nChaque arbre produit :\n\n* une classe prédite\n* éventuellement une probabilité\n\nLa forêt agrège par :\n\n* **vote majoritaire** pour la classe\n* **moyenne des probabilités** pour `predict_proba`\n\n\n#### En régression\n\nLa prédiction finale est la **moyenne** des prédictions des arbres.\n\n\n\n\n### Pourquoi les forêts fonctionnent bien\n\nLes forêts aléatoires :\n\n* conservent le **faible biais** des arbres\n* réduisent fortement la **variance**\n* sont robustes au bruit\n* nécessitent peu de réglage fin\n* fonctionnent bien sur des données hétérogènes\n\nElles constituent souvent un **excellent point de référence** en pratique.\n\n\n\n### Implémentation avec `sklearn`\n\nL’implémentation standard est `RandomForestClassifier`.\n\n```python\nfrom sklearn.ensemble import RandomForestClassifier\n\nmodel = RandomForestClassifier(\n    n_estimators=200,\n    max_depth=5\n)\n\nrf.fit(X_train, y_train)\n```\n\n\n### Hyperparamètres principaux\n\n* `n_estimators` : nombre d’arbres\n* `max_depth` : profondeur maximale de chaque arbre\n* `max_features` : nombre de variables testées à chaque nœud\n* `min_samples_leaf` : taille minimale d’une feuille\n* `bootstrap` : activation du bootstrap\n\nEn pratique :\n\n* augmenter `n_estimators` stabilise le modèle\n* `max_depth` contrôle toujours le sur-apprentissage\n\n","metadata":{},"attachments":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nfrom sklearn.ensemble import RandomForestClassifier\nmodel = RandomForestClassifier(n_estimators=200, max_depth=5)\nmodel.fit(X_train, y_train)\ny_hat = model.predict(X_test)\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Tester les forêts aléatoires sur cancer_mini et/ou titanic_mini\n> \n```\n","metadata":{}},{"cell_type":"code","source":"from sklearn.ensemble import RandomForestClassifier\n\ndf = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/cancer_mini.csv')\n\nX = df.drop(columns=['diagnosis'])\ny = df['diagnosis']\n\nX_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=42)\n\nmodel = RandomForestClassifier(n_estimators=10, max_depth=4)\nmodel.fit(X_train,y_train)\ny_hat = model.predict(X_test)\n\nprint(f\"Accuracy : {accuracy_score(y_test,y_hat):.2f}\")\n\nprint(confusion_matrix(y_test, y_hat))\n\nprint(classification_report(y_test, y_hat))\n\ny_score = model.predict_proba(X_test)[:, 1]\nfpr, tpr, thresholds = roc_curve(y_test, y_score)\nauc = roc_auc_score(y_test, y_score)\n\nprint(f\"AUC : {auc:.2f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T01:03:23.279577Z","iopub.execute_input":"2026-05-08T01:03:23.280169Z","iopub.status.idle":"2026-05-08T01:03:23.371176Z","shell.execute_reply.started":"2026-05-08T01:03:23.280038Z","shell.execute_reply":"2026-05-08T01:03:23.368851Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nmodel = RandomForestClassifier(n_estimators=100, max_depth=6)\nscores = cross_val_score(model, X, y, cv=10)\nprint(scores.mean())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T01:02:03.748324Z","iopub.execute_input":"2026-05-08T01:02:03.748802Z","iopub.status.idle":"2026-05-08T01:02:05.922367Z","shell.execute_reply.started":"2026-05-08T01:02:03.748772Z","shell.execute_reply":"2026-05-08T01:02:05.921184Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Gradient Boosting \n\nLe **gradient boosting** est une méthode d’ensemble qui combine **plusieurs petits arbres de décision**, construits **séquentiellement**, afin d’améliorer progressivement la qualité des prédictions.\n\nL’idée centrale est la suivante :\n\n> chaque nouvel arbre **n’apporte pas une prédiction indépendante**, mais **améliore la prédiction actuelle** en corrigeant les erreurs restantes.\n\n![Image](https://www.ibm.com/adobe/dynamicmedia/deliver/dm-aid--fc4f06fb-b27c-424b-9180-3163e7d2825e/ensemble-learning-boosting.png?preferwebp=true)\n\n\n### Principe général\n\nLe gradient boosting procède par **améliorations successives**.\n\n1. **Modèle initial**\n   Un premier arbre fournit une estimation de départ, généralement imparfaite.\n\n2. **Analyse des erreurs**\n   On identifie où et comment cette estimation est incorrecte.\n\n3. **Nouvel arbre**\n   Un arbre simple est entraîné pour **corriger ces erreurs**.\n\n4. **Mise à jour du modèle**\n   La prédiction globale est **ajustée** en tenant compte de cette correction.\n\nCe processus est répété plusieurs fois, chaque étape affinant la précédente.\n\n\n### Comment interpréter la combinaison des arbres\n\nIl est préférable d’éviter l’expression « somme des arbres ».\nUne formulation plus précise est :\n\n> le modèle final est le résultat d’une **combinaison progressive de corrections**, chacune apprise par un arbre simple.\n\n### Rôle du learning rate\n\nChaque correction est appliquée avec une **intensité contrôlée** par le *learning rate*.\n\n* learning rate élevé :\n\n  * corrections rapides\n  * risque de sur-ajustement\n\n* learning rate faible :\n\n  * corrections progressives\n  * meilleure stabilité et généralisation\n\nLe gradient boosting privilégie généralement :\n\n* des corrections modestes\n* un nombre plus élevé d’arbres\n\n\n### Différence avec une forêt aléatoire\n\n* **Forêt aléatoire** :\n\n  * arbres indépendants\n  * entraînés en parallèle\n  * prédictions combinées par moyenne ou vote\n\n* **Gradient boosting** :\n\n  * arbres dépendants\n  * entraînés séquentiellement\n  * chaque arbre améliore la prédiction existante\n\n### Avec `sklearn`\n\n```python\nfrom sklearn.ensemble import GradientBoostingClassifier\n\nmodel = GradientBoostingClassifier(\n    n_estimators=200,      # nombre d'arbres (étapes de correction)\n    learning_rate=0.05,    # intensité de chaque correction\n    max_depth=5            # profondeur des arbres (weak learners)\n)\n```","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nfrom sklearn.ensemble import GradientBoostingClassifier\nmodel = RandomForestClassifier(n_estimators=200, max_depth=5)\nscores = cross_val_score(model, X, y, cv=10)\nmodel = GradientBoostingClassifier(n_estimators=200, learning_rate=0.05, max_depth=5)\nscores = cross_val_score(model, X, y, cv=10)\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Comparer les forêts aléatoires et le gradient boosting avec une validation croisée","metadata":{}},{"cell_type":"code","source":"from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier\n\ndf = pd.read_csv('/kaggle/input/datasets/pyim59/mini-datasets/cancer_mini.csv')\n\nX = df.drop(columns=['diagnosis'])\ny = df['diagnosis']\n\nX_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.2, random_state=42)\n\nmodel = GradientBoostingClassifier()\nmodel.fit(X_train,y_train)\ny_hat = model.predict(X_test)\n\nprint(f\"Accuracy : {accuracy_score(y_test,y_hat):.2f}\")\n\nprint(confusion_matrix(y_test, y_hat))\n\nprint(classification_report(y_test, y_hat))\n\ny_score = model.predict_proba(X_test)[:, 1]\nfpr, tpr, thresholds = roc_curve(y_test, y_score)\nauc = roc_auc_score(y_test, y_score)\n\nprint(f\"AUC : {auc:.2f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T01:09:18.228362Z","iopub.execute_input":"2026-05-08T01:09:18.229214Z","iopub.status.idle":"2026-05-08T01:09:18.478912Z","shell.execute_reply.started":"2026-05-08T01:09:18.229180Z","shell.execute_reply":"2026-05-08T01:09:18.477620Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LightGBM (Light Gradient Boosting Machine)\n\n**LightGBM** est une implémentation optimisée du **gradient boosting** basée sur des arbres de décision.\nElle est conçue pour être **rapide**, **efficace en mémoire** et **performante**, en particulier sur des jeux de données tabulaires.\n\nSa spécificité principale est la **construction des arbres par feuille (leaf-wise)** :\nà chaque étape, LightGBM développe en priorité la feuille qui permet la plus forte réduction de la perte, plutôt que de construire l’arbre niveau par niveau.\n\nCette stratégie permet :\n\n* une convergence plus rapide\n* de bonnes performances avec moins d’arbres\n* au prix d’un risque accru de sur-apprentissage si la complexité n’est pas contrôlée\n\n### Principaux hyperparamètres\n\n* `n_estimators` : nombre d’arbres (étapes de correction)\n* `learning_rate` : intensité de chaque correction\n* `num_leaves` : nombre maximal de feuilles (paramètre clé)\n* `max_depth` : profondeur maximale de l’arbre\n* `subsample` : sous-échantillonnage des observations\n* `colsample_bytree` : sous-échantillonnage des variables\n\n\n### Implémentation Python\n\n```python\nfrom lightgbm import LGBMClassifier\n\nmodel = LGBMClassifier(\n    n_estimators=300,\n    learning_rate=0.05,\n    verbosity=-1         # Pour éviter certains warnings ...\n)\n```\n","metadata":{}},{"cell_type":"markdown","source":"## XGBoost (eXtreme Gradient Boosting)\n\n**XGBoost** est une implémentation avancée du **gradient boosting**, conçue pour améliorer à la fois la **performance prédictive** et la **robustesse** du modèle.\n\nIl conserve le principe fondamental du gradient boosting — améliorer progressivement une prédiction par corrections successives — tout en introduisant des mécanismes supplémentaires pour mieux contrôler la complexité et accélérer l’apprentissage.\n\nLes principales améliorations apportées par XGBoost sont :\n\n* une **régularisation explicite** des arbres pour limiter le sur-apprentissage\n* une **optimisation plus précise** des corrections\n* une **gestion efficace de la complexité** des modèles\n* une **implémentation hautement optimisée** pour le calcul\n\n\n### Principaux hyperparamètres\n\n* `n_estimators` : nombre d’arbres (étapes de correction)\n* `learning_rate` : intensité de chaque correction\n* `max_depth` : profondeur maximale des arbres\n* `subsample` : sous-échantillonnage des observations\n* `colsample_bytree` : sous-échantillonnage des variables\n* `reg_alpha`, `reg_lambda` : régularisation L1 et L2\n\n\n### Implémentation Python (classification)\n\n```python\nfrom xgboost import XGBClassifier\n\nmodel = XGBClassifier(\n    n_estimators=300,\n    learning_rate=0.05,\n    max_depth=3\n)\n```\n\n### XGBoost avec accélération GPU\n\nXGBoost peut exploiter le GPU pour accélérer l’entraînement, en particulier sur des jeux de données volumineux.\nL’activation du GPU se fait via le paramètre **`tree_method=\"gpu_hist\"`**.\n\n```python\nfrom xgboost import XGBClassifier\n\nmodel = XGBClassifier(\n    n_estimators=300,\n    learning_rate=0.05,\n    max_depth=3,\n    tree_method=\"gpu_hist\"\n)\n```","metadata":{}},{"cell_type":"markdown","source":"### 💡 Syntaxe à reprendre\n\n_Repère syntaxique pour aborder l'exercice ci-dessous._\n\n```python\nmodel = RandomForestClassifier(n_estimators=200, max_depth=5)\nscores = cross_val_score(model, X, y, cv=10)\nmodel = GradientBoostingClassifier(n_estimators=200, learning_rate=0.05, max_depth=5)\nscores = cross_val_score(model, X, y, cv=10)\nfrom lightgbm import LGBMClassifier\nmodel = LGBMClassifier(n_estimators=300, learning_rate=0.05, verbosity=-1)\nscores = cross_val_score(model, X, y, cv=10)\nfrom xgboost import XGBClassifier\nmodel = XGBClassifier(n_estimators=300, learning_rate=0.05, max_depth=3)\nscores = cross_val_score(model, X, y, cv=10)\n```\n","metadata":{}},{"cell_type":"markdown","source":"> #### Exercice\n>\n> Ajouter LightGBM et XGBoost à la comparaison des modèles","metadata":{}},{"cell_type":"code","source":"from lightgbm import LGBMClassifier\nfrom xgboost import XGBClassifier\n\nmodel = DecisionTreeClassifier()\nscores = cross_val_score(model, X, y, cv=10)\nscore_dt = scores.mean()\n\nmodel = RandomForestClassifier()\nscores = cross_val_score(model, X, y, cv=10)\nscore_rf = scores.mean()\n\nmodel = GradientBoostingClassifier()\nscores = cross_val_score(model, X, y, cv=10)\nscore_gb = scores.mean()\n\nmodel = LGBMClassifier(verbose=-1)\nscores = cross_val_score(model, X, y, cv=10)\nscore_lgbm = scores.mean()\n\nmodel = XGBClassifier()\nscores = cross_val_score(model, X, y, cv=10)\nscore_xgb = scores.mean()\n\nprint(f\"Decision tree : {score_dt:.2f}\")\nprint(f\"Random forest : {score_rf:.2f}\")\nprint(f\"Gradient boosting : {score_gb:.2f}\")\nprint(f\"Light GBM : {score_lgbm:.2f}\")\nprint(f\"XGBoost : {score_xgb:.2f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-08T01:18:53.204696Z","iopub.execute_input":"2026-05-08T01:18:53.205828Z","iopub.status.idle":"2026-05-08T01:18:59.319117Z","shell.execute_reply.started":"2026-05-08T01:18:53.205788Z","shell.execute_reply":"2026-05-08T01:18:59.317867Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}