\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",
+ "
Survived
\n",
+ "
Pclass
\n",
+ "
Sex
\n",
+ "
Age
\n",
+ "
Fare
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
0
\n",
+ "
3
\n",
+ "
male
\n",
+ "
22.0
\n",
+ "
7.2500
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
1
\n",
+ "
1
\n",
+ "
female
\n",
+ "
38.0
\n",
+ "
71.2833
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
1
\n",
+ "
3
\n",
+ "
female
\n",
+ "
26.0
\n",
+ "
7.9250
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
1
\n",
+ "
1
\n",
+ "
female
\n",
+ "
35.0
\n",
+ "
53.1000
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
0
\n",
+ "
3
\n",
+ "
male
\n",
+ "
35.0
\n",
+ "
8.0500
\n",
+ "
\n",
+ " \n",
+ "
\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",
+ "
Survived
\n",
+ "
Pclass
\n",
+ "
Sex
\n",
+ "
Age
\n",
+ "
Fare
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
0
\n",
+ "
3
\n",
+ "
0
\n",
+ "
22.0
\n",
+ "
7.2500
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
1
\n",
+ "
1
\n",
+ "
1
\n",
+ "
38.0
\n",
+ "
71.2833
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
1
\n",
+ "
3
\n",
+ "
1
\n",
+ "
26.0
\n",
+ "
7.9250
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
1
\n",
+ "
1
\n",
+ "
1
\n",
+ "
35.0
\n",
+ "
53.1000
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
0
\n",
+ "
3
\n",
+ "
0
\n",
+ "
35.0
\n",
+ "
8.0500
\n",
+ "
\n",
+ " \n",
+ "
\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",
+ "
Survived
\n",
+ "
Pclass
\n",
+ "
Sex
\n",
+ "
Age
\n",
+ "
Fare
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
0
\n",
+ "
0.827377
\n",
+ "
-0.737695
\n",
+ "
-0.565736
\n",
+ "
-0.502445
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
1
\n",
+ "
-1.566107
\n",
+ "
1.355574
\n",
+ "
0.663861
\n",
+ "
0.786845
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
1
\n",
+ "
0.827377
\n",
+ "
1.355574
\n",
+ "
-0.258337
\n",
+ "
-0.488854
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
1
\n",
+ "
-1.566107
\n",
+ "
1.355574
\n",
+ "
0.433312
\n",
+ "
0.420730
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
0
\n",
+ "
0.827377
\n",
+ "
-0.737695
\n",
+ "
0.433312
\n",
+ "
-0.486337
\n",
+ "
\n",
+ " \n",
+ "
\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": [
+ "