diff --git a/files/tp_titanic.ipynb b/files/tp_titanic.ipynb new file mode 100644 index 00000000..21236a53 --- /dev/null +++ b/files/tp_titanic.ipynb @@ -0,0 +1,1118 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# TP : Classification des survivants du Titanic\n", + "\n", + "
\n", + "\n", + "\n", + "[**Le TP est à faire de préférence sur Capytale**](https://capytale2.ac-paris.fr/web/c/5ee4-1148472). Sinon, utilisez Pyzo et télécharger [les données](https://raw.githubusercontent.com/cpge-itc/itc2/6fff3c359a4761aab625b2adb8e5b83697d5c72f/titanic.csv) (à mettre dans le même dossier que votre fichier Python). \n", + "\n", + "On souhaite prédire si un passager du Titanic a survécu ou non à l'accident, en utilisant l'algorithme des plus proches voisins. On pourra s'inspirer de l'[exemple du cours sur la classification des iris](https://cpge-itc.github.io/itc2/4_knn/exemple/knn_iris.html).\n", + "\n", + "Voici les informations sur chaque passager : \n", + "- `Survived` : 0 = Non, 1 = Oui\n", + "- `Pclass` : Classe de ticket (1 = 1ère classe, 2 = 2ème, 3 = 3ème) \n", + "- `Sex` : Genre du passager (`male` ou `female`) \n", + "- `Age` : Âge du passager (en années) \n", + "- `Fare` : Tarif du ticket (en dollars)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Chargement des données avec Pandas\n", + "\n", + "Pandas est un module Python qui permet de manipuler des données sous forme de tableau appelé **DataFrame** (qui ressemble à un peu à une table SQL) :" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SurvivedPclassSexAgeFare
003male22.07.2500
111female38.071.2833
213female26.07.9250
311female35.053.1000
403male35.08.0500
\n", + "
" + ], + "text/plain": [ + " Survived Pclass Sex Age Fare\n", + "0 0 3 male 22.0 7.2500\n", + "1 1 1 female 38.0 71.2833\n", + "2 1 3 female 26.0 7.9250\n", + "3 1 1 female 35.0 53.1000\n", + "4 0 3 male 35.0 8.0500" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.read_csv('titanic.csv') # df est un DataFrame\n", + "df.head() # pour afficher les 5 premières lignes" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ainsi `df` est un tableau contient 5 colonnes (`Survived`, `Pclass`, `Sex`, `Age`, `Fare`) et chaque ligne correspondant à un passager du Titanic. On peut obtenir le nombres de lignes avec `len(df)` :" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "891" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(df)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Chaque ligne est identifiée par un index (= nom de la ligne), ici $0, 1, 2, ...$. On peut accéder à la ligne d'indice $i$ avec `df.loc[i]` :" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Survived 0\n", + "Pclass 3\n", + "Sex male\n", + "Age 22.0\n", + "Fare 7.25\n", + "Name: 0, dtype: object" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.loc[0]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`df.loc[i]` donne en fait une series, qui peut être vu comme un tableau à une dimension.\n", + "\n", + "On peut récupérer une colonne (également sous forme de series), par exemple `Age`, avec `df[\"Age\"]` :" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0 22.0\n", + "1 38.0\n", + "2 26.0\n", + "3 35.0\n", + "4 35.0\n", + " ... \n", + "886 27.0\n", + "887 19.0\n", + "888 28.0\n", + "889 26.0\n", + "890 32.0\n", + "Name: Age, Length: 891, dtype: float64" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[\"Age\"]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On peut combiner ces deux méthodes pour récupérer une valeur précise, par exemple l'âge du 3ème passager :" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "26.0" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.loc[2, \"Age\"]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On peut aussi modifier une valeur avec, par exemple, `df.loc[2, \"Age\"] = ...`." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On peut parcourir les indices d'un dataframe avec `df.index`. Par exemple, pour trouver le passager le plus vieux :" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "80.0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "maxi_age = 0\n", + "for i in df.index:\n", + " if df.loc[i, \"Age\"] > maxi_age:\n", + " maxi_age = df.loc[i, \"Age\"]\n", + "maxi_age" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Remarque** : avec Pandas, il faut normalement utiliser au maximum des opérations vectorielles pour que le processeur puisse effectuer les calculs en parallèle. Cependant, comme l'utilisation de Pandas n'est pas au programme, nous allons nous limiter à une approche élémentaire." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Statistiques" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `moyenne(df, c)` qui renvoie la moyenne des valeurs sur la colonne `c` du dataframe `df`. Quelle est l'âge moyen des passagers du Titanic ? Le prix moyen du ticket ?" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Âge moyen : 29.36158249158249\n", + "Prix moyen du billet : 32.2042079685746\n" + ] + } + ], + "source": [ + "def moyenne(df, col):\n", + " m = 0\n", + " for i in df.index:\n", + " m += df.loc[i, col]\n", + " return m / len(df)\n", + "\n", + "print(\"Âge moyen :\", moyenne(df, \"Age\"))\n", + "print(\"Prix moyen du billet :\", moyenne(df, \"Fare\"))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `ecart_type(df, c)` qui renvoie l'écart-type des valeurs de la colonne `c` du dataframe `df`. On rappelle que l'écart-type d'une série de valeurs $x_1, \\ldots, x_n$ est donné par : \n", + "\n", + "$$\\sqrt{\\frac{1}{n} \\sum_{i=1}^n (x_i - \\bar{x})^2}$$\n", + "\n", + "où $\\bar{x}$ est la moyenne des valeurs $x_1, ..., x_n$.\n", + "\n", + "**Remarque : On évitera de calculer plusieurs fois la même moyenne.**" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def ecart_type(df, col):\n", + " m = moyenne(df, col)\n", + " s = 0\n", + " for i in df.index:\n", + " s += (df.loc[i, col] - m)**2\n", + " return (s / len(df))**0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13.01238827279366" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ecart_type(df, \"Age\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Afficher le pourcentage de survivants parmi :\n", + "- les hommes\n", + "- les femmes\n", + "- les passagers de 1ère classe\n", + "- les passagers de 3ème classe" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Taux de survie pour Sex = male : 0.18890814558058924\n", + "Taux de survie pour Sex = female : 0.7420382165605095\n", + "Taux de survie pour Pclass = 1 : 0.6296296296296297\n", + "Taux de survie pour Pclass = 3 : 0.24236252545824846\n" + ] + } + ], + "source": [ + "def survivants(col, val):\n", + " n_survivants = 0\n", + " n = 0\n", + " for i in df.index:\n", + " if df.loc[i, col] == val:\n", + " n_survivants += df.loc[i, \"Survived\"]\n", + " n += 1\n", + " return n_survivants/n\n", + "\n", + "for c, v in [(\"Sex\", \"male\"), (\"Sex\", \"female\"), (\"Pclass\", 1), (\"Pclass\", 3)]:\n", + " print(\"Taux de survie pour\", c, \"=\", v, \":\", survivants(c, v))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Variables catégorielles\n", + "\n", + "Nous souhaitons modéliser chaque passager par un vecteur de $\\mathbb{R}^4$ (car il y a $4$ informations pour chaque passager : âge, genre, classe et prix du ticket). Cependant, le genre est une variable catégorielle qu'il faut transformer en variable numérique :" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SurvivedPclassSexAgeFare
003022.07.2500
111138.071.2833
213126.07.9250
311135.053.1000
403035.08.0500
\n", + "
" + ], + "text/plain": [ + " Survived Pclass Sex Age Fare\n", + "0 0 3 0 22.0 7.2500\n", + "1 1 1 1 38.0 71.2833\n", + "2 1 3 1 26.0 7.9250\n", + "3 1 1 1 35.0 53.1000\n", + "4 0 3 0 35.0 8.0500" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[\"Sex\"] = df[\"Sex\"].map({\"male\": 0, \"female\": 1}) # remplace male par 0 et female par 1\n", + "df.head()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Standardisation\n", + "\n", + "On remarque que les attributs sont sur des échelles très différentes (par exemple, l'âge est entre 0 et 80, alors que la classe du billet est entre 1 et 3). \n", + "Les différences d'âge contribuent alors beaucoup plus dans les calculs de distance, ce qui ferait que l'âge aurait un poids plus important que la classe du billet pour la prédiction. \n", + "Pour éviter cela, on va standardiser les données, c'est-à-dire les transformer de manière à ce que chaque attribut ait une moyenne nulle et un écart-type égal à 1. \n", + "\n", + "Si un attribut $x$ a une moyenne $\\bar{x}$ et un écart-type $\\sigma$, on peut le standardiser en le remplaçant par :\n", + "\n", + "$$\\frac{x - \\bar{x}}{\\sigma}$$" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `standardiser(df, c)` qui standardise la colonne `c` du dataframe `df`. L'utiliser pour standardiser les colonnes `Age`, `Fare`, `Pclass` et `Sex`. On rappelle qu'on peut modifier l'élément sur la ligne `i` et la colonne `c` avec `df.loc[i, c] = ...`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def standardiser(df, col):\n", + " m = moyenne(df, col)\n", + " s = ecart_type(df, col)\n", + " for i in df.index:\n", + " df.loc[i, col] = (df.loc[i, col] - m) / s" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SurvivedPclassSexAgeFare
000.827377-0.737695-0.565736-0.502445
11-1.5661071.3555740.6638610.786845
210.8273771.355574-0.258337-0.488854
31-1.5661071.3555740.4333120.420730
400.827377-0.7376950.433312-0.486337
\n", + "
" + ], + "text/plain": [ + " Survived Pclass Sex Age Fare\n", + "0 0 0.827377 -0.737695 -0.565736 -0.502445\n", + "1 1 -1.566107 1.355574 0.663861 0.786845\n", + "2 1 0.827377 1.355574 -0.258337 -0.488854\n", + "3 1 -1.566107 1.355574 0.433312 0.420730\n", + "4 0 0.827377 -0.737695 0.433312 -0.486337" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "for c in [\"Age\", \"Fare\", \"Pclass\", \"Sex\"]:\n", + " standardiser(df, c)\n", + "df.head()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Distance\n", + "\n", + "Pour la question suivante, on rappelle comment accéder aux attributs d'une donnée :" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-0.565736461074875,\n", + " -0.5024451714361915,\n", + " 0.8273772438659676,\n", + " -0.7376951317802897)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p = df.loc[0] # 1er passager\n", + "p[\"Age\"], p[\"Fare\"], p[\"Pclass\"], p[\"Sex\"] # attributs de p" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `distance(p1, p2)` qui calcule la distance euclidienne entre les passagers `p1` et `p2`. On prendra en compte tous les attributs sauf `Survived`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def distance(p1, p2):\n", + " d = 0\n", + " for c in [\"Pclass\", \"Sex\", \"Age\", \"Fare\"]:\n", + " d += (p1[c] - p2[c])**2\n", + " return d**0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.644820996221396" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "distance(df.loc[0], df.loc[1]) # distance entre les deux premiers passagers" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Séparation des données\n", + "\n", + "On sépare les données en deux : une partie `train` utilisée pour la prédiction, et une partie `test` utilisée pour évaluer la qualité de la prédiction :" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nombre de données dans train : 802\n", + "nombre de données dans test : 89\n" + ] + } + ], + "source": [ + "train = df.sample(frac=0.9,random_state=0)\n", + "test = df.drop(train.index)\n", + "print(\"nombre de données dans train :\", len(train))\n", + "print(\"nombre de données dans test :\", len(test))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Algorithmes des plus proches voisins" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `voisins(x, k)` qui renvoie les indices des $k$ plus proches voisins de `x` dans `train`." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def voisins(x, k):\n", + " indices = sorted(train.index, key=lambda i: distance(x, train.loc[i]))\n", + " return indices[:k]" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[446, 651, 546, 427, 389]" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "voisins(test.iloc[0], 5)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `plus_frequent(L)` qui renvoie l'élément le plus fréquent d'une liste `L`." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def plus_frequent(L): # renvoie la classe qui apparaît le plus souvent dans L\n", + " compte = {}\n", + " for e in L:\n", + " compte[e] = compte.get(e, 0) + 1\n", + " return max(compte, key=compte.get)" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plus_frequent([2, 1, 5, 1, 2, 5, 5])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `knn(x, k)` qui renvoie la prédiction de survie de `x` en utilisant l'algorithme des $k$ plus proches voisins." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def knn(x, k):\n", + " return plus_frequent([train.loc[i, \"Survived\"] for i in voisins(x, k)])" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "knn(test.iloc[0], 5)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analyse des résultats" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `precision(k)` qui renvoie la précision de l'algorithme des $k$ plus proches voisins en utilisant `k` voisins." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def precision(k):\n", + " n = 0\n", + " for i in test.index:\n", + " if knn(test.loc[i], k) == test.loc[i, \"Survived\"]:\n", + " n += 1\n", + " return n / len(test)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.8314606741573034" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "precision(3)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Écrire une fonction `plot_precision(kmax)` qui trace la précision pour $k$ variant de $1$ à `kmax`. Quelle est la meilleure précision obtenue pour k entre 1 et 5 (cela prend environ 20 secondes) ? Quelle est le nombre de voisins optimal ?" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def plot_precision(kmax):\n", + " import matplotlib.pyplot as plt\n", + " R = range(1, kmax)\n", + " plt.plot(R, [precision(k) for k in R])\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_precision(5)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## Kaggle\n", + "\n", + "Ce TP provient d'une compétition Kaggle : [Titanic: Machine Learning from Disaster](www.kaggle.com/c/titanic). \n", + "Avec KNN, j'obtiens un score de $\\approx 0.763$... Ce qui n'est pas terrible car l'exemple de submission basée uniquement sur le genre donne un score de $\\approx 0.765$." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Question** : Soumettre le fichier `submission.csv` obtenu par le code ci-dessus sur [Kaggle](https://www.kaggle.com/competitions/titanic/submissions) et essayer d'obtenir un score plus élevé. On pourra essayer d'implémenter les améliorations suggérées à la [fin du cours](https://cpge-itc.github.io/itc2/4_knn/exemple/knn_iris.html)." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "submission = pd.read_csv('test.csv')\n", + "for c in \"Age\", \"Fare\":\n", + " submission[c].fillna(submission[c].median(), inplace=True)\n", + "submission[\"Sex\"] = submission[\"Sex\"].map({\"male\": 0, \"female\": 1})\n", + "for c in [\"Age\", \"Fare\", \"Pclass\", \"Sex\"]:\n", + " standardiser(submission, c)\n", + " \n", + "submission[\"Survived\"] = [knn(submission.iloc[i], 5) for i in range(len(submission))]\n", + "\n", + "submission[[\"PassengerId\", \"Survived\"]].to_csv('submission.csv', index=False)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2 (default, Feb 28 2021, 17:03:44) \n[GCC 10.2.1 20210110]" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}