{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# DS 2500 Day 12 \n",
"\n",
"Feb 21, 2023\n",
"\n",
"content:\n",
"- Ensuring meaningful distances in data\n",
"- K-NN classifier\n",
"\n",
"admin:\n",
"- hw due friday\n",
"- project proposal due next monday\n",
"- workshopping a student project\n",
"- install `plotly` & `sklearn` via pip (see below) for today's notes\n",
"\n",
" pip3 install plotly sklearn\n",
" \n",
" some mac / anaconda students had trouble in the first section via pip but were successful when using anaconda's own installation tool (see piazza for detail)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Representing data (samples & features)\n",
"To describe a collection of **samples** we record a set of **features** for each sample.\n",
"\n",
"For example, when describing penguins:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
species
\n",
"
island
\n",
"
bill_length_mm
\n",
"
bill_depth_mm
\n",
"
flipper_length_mm
\n",
"
body_mass_g
\n",
"
sex
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
Adelie
\n",
"
Torgersen
\n",
"
39.1
\n",
"
18.7
\n",
"
181.0
\n",
"
3750.0
\n",
"
Male
\n",
"
\n",
"
\n",
"
1
\n",
"
Adelie
\n",
"
Torgersen
\n",
"
39.5
\n",
"
17.4
\n",
"
186.0
\n",
"
3800.0
\n",
"
Female
\n",
"
\n",
"
\n",
"
2
\n",
"
Adelie
\n",
"
Torgersen
\n",
"
40.3
\n",
"
18.0
\n",
"
195.0
\n",
"
3250.0
\n",
"
Female
\n",
"
\n",
"
\n",
"
4
\n",
"
Adelie
\n",
"
Torgersen
\n",
"
36.7
\n",
"
19.3
\n",
"
193.0
\n",
"
3450.0
\n",
"
Female
\n",
"
\n",
"
\n",
"
5
\n",
"
Adelie
\n",
"
Torgersen
\n",
"
39.3
\n",
"
20.6
\n",
"
190.0
\n",
"
3650.0
\n",
"
Male
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" species island bill_length_mm bill_depth_mm flipper_length_mm \\\n",
"0 Adelie Torgersen 39.1 18.7 181.0 \n",
"1 Adelie Torgersen 39.5 17.4 186.0 \n",
"2 Adelie Torgersen 40.3 18.0 195.0 \n",
"4 Adelie Torgersen 36.7 19.3 193.0 \n",
"5 Adelie Torgersen 39.3 20.6 190.0 \n",
"\n",
" body_mass_g sex \n",
"0 3750.0 Male \n",
"1 3800.0 Female \n",
"2 3250.0 Female \n",
"4 3450.0 Female \n",
"5 3650.0 Male "
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import seaborn as sns\n",
"\n",
"df_penguin = sns.load_dataset('penguins')\n",
"\n",
"# discard all rows which are missing any data\n",
"df_penguin.dropna(axis=0, inplace=True)\n",
"\n",
"df_penguin.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Each penguin is a sample for which we've observed 7 features:\n",
"\n",
"Quantitative:\n",
"- bill_length_mm\n",
"- bill_depth_mm\n",
"- flipper_length_mm\n",
"- body_mass_g\n",
"\n",
"Nominal:\n",
"- species\n",
"- island\n",
"- sex \n",
"\n",
"Let us represent the quantitative data as an array. \n",
"- We'll return to those Nominal features later"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Samples as vectors"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
bill_length_mm
\n",
"
bill_depth_mm
\n",
"
flipper_length_mm
\n",
"
body_mass_g
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
39.1
\n",
"
18.7
\n",
"
181.0
\n",
"
3750.0
\n",
"
\n",
"
\n",
"
1
\n",
"
40.2
\n",
"
17.9
\n",
"
194.0
\n",
"
3700.0
\n",
"
\n",
"
\n",
"
2
\n",
"
40.3
\n",
"
18.0
\n",
"
195.0
\n",
"
3250.0
\n",
"
\n",
"
\n",
"
4
\n",
"
36.7
\n",
"
19.3
\n",
"
193.0
\n",
"
3450.0
\n",
"
\n",
"
\n",
"
5
\n",
"
39.3
\n",
"
20.6
\n",
"
190.0
\n",
"
3650.0
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" bill_length_mm bill_depth_mm flipper_length_mm body_mass_g\n",
"0 39.1 18.7 181.0 3750.0\n",
"1 40.2 17.9 194.0 3700.0\n",
"2 40.3 18.0 195.0 3250.0\n",
"4 36.7 19.3 193.0 3450.0\n",
"5 39.3 20.6 190.0 3650.0"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# only focus on numerical features (for now)\n",
"col_num_list = 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'\n",
"df_penguin_num = df_penguin.loc[:, col_num_list]\n",
"\n",
"# for pedagogical reasons, we need penguin1 to have slightly different values\n",
"df_penguin_num.iloc[1, :] = [40.2, 17.9, 194.0, 3700]\n",
"\n",
"df_penguin_num.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Individual samples (penguins) are considered, mathematically, as vectors:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([ 39.1, 18.7, 181. , 3750. ])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_penguin_num.iloc[0, :].values"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([ 39.1, 18.7, 181. , 3750. ])"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import numpy as np\n",
"\n",
"penguin0 = np.array(df_penguin_num.iloc[0, :])\n",
"penguin0"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Distances between samples\n",
"Many ML tools require that these vectors have meaningful distances between them. By \"meaningful\", we mean:\n",
"- large distances suggest samples are different\n",
"- small distances suggest samples are similar\n",
"\n",
"Computing distance between two vectors $x = \\begin{bmatrix} x_1 \\\\ x_2 \\end{bmatrix}$ and $x' = \\begin{bmatrix} x_1' \\\\ x_2' \\end{bmatrix}$:\n",
"\n",
"$$||x - x'||_2 = \\sqrt{\\sum_i (x_i - x_i')^2}$$\n",
"\n",
"In words, to compute the distance between two vectors:\n",
"- we square the differences of each element\n",
"- add these values together\n",
"- compute the square root of this sum\n",
"\n",
"How similar is penguin0 to penguin1?"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"bill_length_mm 39.1\n",
"bill_depth_mm 18.7\n",
"flipper_length_mm 181.0\n",
"body_mass_g 3750.0\n",
"Name: 0, dtype: float64"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"penguin0 = df_penguin_num.iloc[0, :]\n",
"penguin0"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"bill_length_mm 40.2\n",
"bill_depth_mm 17.9\n",
"flipper_length_mm 194.0\n",
"body_mass_g 3700.0\n",
"Name: 1, dtype: float64"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"penguin1 = df_penguin_num.iloc[1, :]\n",
"penguin1"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"51.68026702717392"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sq_diff_per_feat = [(39.1 - 40.2) ** 2, (18.7 - 17.9) ** 2, (181 - 194) ** 2, (3750 - 3700) ** 2]\n",
"dist01_slow = sum(sq_diff_per_feat) ** .5\n",
"dist01_slow"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In and of itself, this distance isn't too insightful ... the penguins are 50-ish (units?) apart? \n",
"\n",
"The value becomes more useful when compared to other distances: Is penguin 1 more similar to penguin 0 or penguin 2?"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"distance between penguin0 and penguin1: 51.680\n",
"distance between penguin1 and penguin2: 450.001\n"
]
}
],
"source": [
"vec_penguin0 = np.array(df_penguin_num.iloc[0, :])\n",
"vec_penguin1 = np.array(df_penguin_num.iloc[1, :])\n",
"vec_penguin2 = np.array(df_penguin_num.iloc[2, :])\n",
"\n",
"# a quicker, equivilent way to compute distance\n",
"dist01 = np.linalg.norm(vec_penguin0 - vec_penguin1)\n",
"dist12 = np.linalg.norm(vec_penguin1 - vec_penguin2)\n",
"\n",
"print(f'distance between penguin0 and penguin1: {dist01:.3f}')\n",
"print(f'distance between penguin1 and penguin2: {dist12:.3f}')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Interpretting Distances\n",
"(And cleaning our inputs so they have an appropriate meaning to interpret)\n",
"\n",
"\n",
"Lets recap:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
bill_length_mm
\n",
"
bill_depth_mm
\n",
"
flipper_length_mm
\n",
"
body_mass_g
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
39.1
\n",
"
18.7
\n",
"
181.0
\n",
"
3750.0
\n",
"
\n",
"
\n",
"
1
\n",
"
40.2
\n",
"
17.9
\n",
"
194.0
\n",
"
3700.0
\n",
"
\n",
"
\n",
"
2
\n",
"
40.3
\n",
"
18.0
\n",
"
195.0
\n",
"
3250.0
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" bill_length_mm bill_depth_mm flipper_length_mm body_mass_g\n",
"0 39.1 18.7 181.0 3750.0\n",
"1 40.2 17.9 194.0 3700.0\n",
"2 40.3 18.0 195.0 3250.0"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_penguin_num.head(3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Where penguin0 and penguin1 are more similar since we observed:\n",
"\n",
" distance between penguin0 and penguin1: 51.680\n",
" distance between penguin1 and penguin2: 450.001\n",
" \n",
"Is this satisfying or should penguin1 and penguin2 be considered more similar? Lets break it out by feature:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"bill_length_mm 1.1\n",
"bill_depth_mm -0.8\n",
"flipper_length_mm 13.0\n",
"body_mass_g -50.0\n",
"dtype: float64"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_penguin_num.iloc[1, :] - df_penguin_num.iloc[0, :]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"bill_length_mm -0.1\n",
"bill_depth_mm -0.1\n",
"flipper_length_mm -1.0\n",
"body_mass_g 450.0\n",
"dtype: float64"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_penguin_num.iloc[1, :] - df_penguin_num.iloc[2, :]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The bills and flippers of penguin2 and penguin1 are just about identical ... but their difference in body mass is so large that it yields a large distance.\n",
"\n",
"### Big Idea 1: Distances assume that a change of 1 unit (in any feature) is equally significant\n",
"\n",
"What if we measured the body mass of the penguin in a different unit?"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
bill_length_mm
\n",
"
bill_depth_mm
\n",
"
flipper_length_mm
\n",
"
body_mass_kg
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
39.1
\n",
"
18.7
\n",
"
181.0
\n",
"
3.750000e-13
\n",
"
\n",
"
\n",
"
1
\n",
"
40.2
\n",
"
17.9
\n",
"
194.0
\n",
"
3.700000e-13
\n",
"
\n",
"
\n",
"
2
\n",
"
40.3
\n",
"
18.0
\n",
"
195.0
\n",
"
3.250000e-13
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" bill_length_mm bill_depth_mm flipper_length_mm body_mass_kg\n",
"0 39.1 18.7 181.0 3.750000e-13\n",
"1 40.2 17.9 194.0 3.700000e-13\n",
"2 40.3 18.0 195.0 3.250000e-13"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# replace body_mass_g with body_mass_kg\n",
"df_penguin_num['body_mass_kg'] = df_penguin_num['body_mass_g'] / 10000000000000000\n",
"del df_penguin_num['body_mass_g']\n",
"\n",
"df_penguin_num.head(3)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"new distance between penguin0 and penguin1: 13.071\n",
"new distance between penguin1 and penguin2: 1.010\n"
]
}
],
"source": [
"vec_penguin0 = np.array(df_penguin_num.iloc[0, :])\n",
"vec_penguin1 = np.array(df_penguin_num.iloc[1, :])\n",
"vec_penguin2 = np.array(df_penguin_num.iloc[2, :])\n",
"\n",
"# a quicker way to compute distance\n",
"dist01 = np.linalg.norm(vec_penguin0 - vec_penguin1)\n",
"dist12 = np.linalg.norm(vec_penguin1 - vec_penguin2)\n",
"\n",
"print(f'new distance between penguin0 and penguin1: {dist01:.3f}')\n",
"print(f'new distance between penguin1 and penguin2: {dist12:.3f}')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"These numbers aren't just different, they claim an opposite conclusion: penguin1 and penguin2 are more similar!\n",
"\n",
"- **Distances assume that a change of 1 unit (in any feature) is equally significant**\n",
"- **Distances implicitly weight how important each feature is relative to others according to its variance**\n",
" - a feature with a higher variance is responsible for more of the distances\n",
" \n",
"To wrap all the different features into a single distance we must say *something* about how important one feature is compared to another. "
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"bill_length_mm 2.988886e+01\n",
"bill_depth_mm 3.879347e+00\n",
"flipper_length_mm 1.959126e+02\n",
"body_mass_kg 6.486477e-27\n",
"dtype: float64"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_penguin_num.var()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Scale Normalization:\n",
"How to scale your features so that they're equally important in our distance metric:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"bill_length_mm 2.988886e+01\n",
"bill_depth_mm 3.879347e+00\n",
"flipper_length_mm 1.959126e+02\n",
"body_mass_kg 6.486477e-27\n",
"dtype: float64"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import pandas as pd\n",
"\n",
"df_penguin_num.var()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# by dividing each feature by the standard deviation, outputs will have same std dev\n",
"df_penguin_num_scaled = pd.DataFrame()\n",
"for feat in df_penguin_num.columns:\n",
" df_penguin_num_scaled[f'{feat}_scaled'] = df_penguin_num[feat] / df_penguin_num[feat].std()"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"bill_length_mm_scaled 1.0\n",
"bill_depth_mm_scaled 1.0\n",
"flipper_length_mm_scaled 1.0\n",
"body_mass_kg_scaled 1.0\n",
"dtype: float64"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_penguin_num_scaled.var()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice that in doing so, our units are no longer valid:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
"
],
"text/plain": [
" bill_length_mm bill_depth_mm flipper_length_mm body_mass_g \\\n",
"65 41.6 18.0 192.0 3950.0 \n",
"276 43.8 13.9 208.0 4300.0 \n",
"186 49.7 18.6 195.0 3600.0 \n",
"198 50.1 17.9 190.0 3400.0 \n",
"293 46.5 14.8 217.0 5200.0 \n",
"\n",
" species_Adelie species_Chinstrap species_Gentoo island_Biscoe \\\n",
"65 1 0 0 1 \n",
"276 0 0 1 1 \n",
"186 0 1 0 0 \n",
"198 0 1 0 0 \n",
"293 0 0 1 1 \n",
"\n",
" island_Dream sex_Female sex_Male \n",
"65 0 0 1 \n",
"276 0 1 0 \n",
"186 1 0 1 \n",
"198 1 1 0 \n",
"293 0 1 0 "
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# you can apply one hot encoding to multiple features\n",
"pd.get_dummies(df_penguin, columns=['species', 'island', 'sex'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Notice:\n",
"One advantage of the \"one\"-hot-encoding is that a single sample can belong to multiple categories\n",
"- a penguin which lives on two islands\n",
" - (a penguin which heads to his warmer house in the winter)\n",
" \n",
"- consider a collection of boardgames, we can store their tags via one-hot encoding\n",
" - a single game (row) may have multiple tags:"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
cooperative
\n",
"
includes element of luck
\n",
"
strains even good friendships
\n",
"
\n",
" \n",
" \n",
"
\n",
"
monopoly
\n",
"
0
\n",
"
1
\n",
"
1
\n",
"
\n",
"
\n",
"
pictionary
\n",
"
1
\n",
"
1
\n",
"
0
\n",
"
\n",
"
\n",
"
risk
\n",
"
0
\n",
"
1
\n",
"
1
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" cooperative includes element of luck \\\n",
"monopoly 0 1 \n",
"pictionary 1 1 \n",
"risk 0 1 \n",
"\n",
" strains even good friendships \n",
"monopoly 1 \n",
"pictionary 0 \n",
"risk 1 "
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_board_game = pd.DataFrame({'cooperative': [0, 1, 0], \n",
" 'includes element of luck': [1, 1, 1],\n",
" 'strains even good friendships': [1, 0, 1]}, \n",
" index=['monopoly', 'pictionary', 'risk'])\n",
"\n",
"df_board_game"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# K-Nearest Neighbors "
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## ML overview\n",
"| | Input Features per sample | Output Features per sample | Supervised | Penguin Example |\n",
"|:------------------------:|:-------------------------:|:--------------------------:|:----------:|---------------------------------------------------------------------------------------|\n",
"| Classification | 1+ numerical features | one categorical feature | True | Given `body_weight_g`, `flipper_length_mm` estimate `species` |\n",
"| Regression | 1+ numerical features | one continuous feature | True | Given `body_weight_g`, `bill_depth_mm` estimate `flipper_length_mm` |\n",
"| Clustering | 1+ numerical features | one categorical feature | False | Identify k groups of penguins which have similar `body_weight_g`, `flipper_length_mm` |\n",
"| Dimensionality Reduction | N numerical features | < N numerical features | False | Find 2d vector which best represents all 4 of penguin's body/flipper/beak features |\n",
"\n",
"A **supervised** method is one whose output features are observed in some input data set. Notice:\n",
"- To build a penguin species **classifier**, we must observe the species of penguins in our data set\n",
"- To build a **clustering** of penguins, no output feature needs to be observed"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## K-Nearest Neighbors Classifier (Warm Up)\n",
"\n",
"#### Goal:\n",
"Make a function which estimates `species` from `bill_depth_mm` and `bill_length_mm`.\n",
"\n",
"#### Problem Statement (any classifier):\n",
"\n",
"Given an initial set of \"training\" penguins we observe:\n",
"- `bill_depth_mm`\n",
"- `bill_length_mm`\n",
"- `species` \n",
"\n",
"Given some new penguin, Gerald, who is not in the training set, we observe:\n",
"- `bill_depth_mm`\n",
"- `bill_length_mm`\n",
"\n",
"How can we estimate Gerald's `species`?\n",
"\n",
"#### K-Nearest Neighbors (k-NN) Approach:\n",
"1. We identify the penguins which are the Geradld's $k$ Nearest Neighbors:\n",
"- let us represent each penguin as a vector containing:\n",
" - `bill_depth_mm`\n",
" - `bill_length_mm`\n",
"- the **nearest neighbors** are the vectors which are closest to some target vector (Gerald)\n",
"2. We estimate Gerald's species as the most common species of these $k$ Nearest Neighbors."
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"conf_mat_disp = ConfusionMatrixDisplay(conf_mat, display_labels=np.unique(y_true))\n",
"conf_mat_disp.plot()\n",
"\n",
"plt.gcf().set_size_inches(8, 8)\n",
"\n",
"# seaborn turns on grid by default ... looks best without it\n",
"plt.grid(False)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## In Class Exercise 2\n",
"\n",
"Build a K-NN classifier which estimates whether a passenger on the titanic `survived` given their `age`, `pclass` and `fare` features.\n",
"- Discard any passengers which are missing a feature\n",
"- Be mindful of scale normalization, you may need to adjust data a bit\n",
"- Show the output of your classification as a confusion matrix plot, as shown above"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'1.2.1'"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import sklearn\n",
"sklearn.__version__"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
survived
\n",
"
pclass
\n",
"
sex
\n",
"
age
\n",
"
sibsp
\n",
"
parch
\n",
"
fare
\n",
"
embarked
\n",
"
class
\n",
"
who
\n",
"
adult_male
\n",
"
deck
\n",
"
embark_town
\n",
"
alive
\n",
"
alone
\n",
"
\n",
" \n",
" \n",
"
\n",
"
0
\n",
"
0
\n",
"
3
\n",
"
male
\n",
"
22.0
\n",
"
1
\n",
"
0
\n",
"
7.2500
\n",
"
S
\n",
"
Third
\n",
"
man
\n",
"
True
\n",
"
NaN
\n",
"
Southampton
\n",
"
no
\n",
"
False
\n",
"
\n",
"
\n",
"
1
\n",
"
1
\n",
"
1
\n",
"
female
\n",
"
38.0
\n",
"
1
\n",
"
0
\n",
"
71.2833
\n",
"
C
\n",
"
First
\n",
"
woman
\n",
"
False
\n",
"
C
\n",
"
Cherbourg
\n",
"
yes
\n",
"
False
\n",
"
\n",
"
\n",
"
2
\n",
"
1
\n",
"
3
\n",
"
female
\n",
"
26.0
\n",
"
0
\n",
"
0
\n",
"
7.9250
\n",
"
S
\n",
"
Third
\n",
"
woman
\n",
"
False
\n",
"
NaN
\n",
"
Southampton
\n",
"
yes
\n",
"
True
\n",
"
\n",
"
\n",
"
3
\n",
"
1
\n",
"
1
\n",
"
female
\n",
"
35.0
\n",
"
1
\n",
"
0
\n",
"
53.1000
\n",
"
S
\n",
"
First
\n",
"
woman
\n",
"
False
\n",
"
C
\n",
"
Southampton
\n",
"
yes
\n",
"
False
\n",
"
\n",
"
\n",
"
4
\n",
"
0
\n",
"
3
\n",
"
male
\n",
"
35.0
\n",
"
0
\n",
"
0
\n",
"
8.0500
\n",
"
S
\n",
"
Third
\n",
"
man
\n",
"
True
\n",
"
NaN
\n",
"
Southampton
\n",
"
no
\n",
"
True
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" survived pclass sex age sibsp parch fare embarked class \\\n",
"0 0 3 male 22.0 1 0 7.2500 S Third \n",
"1 1 1 female 38.0 1 0 71.2833 C First \n",
"2 1 3 female 26.0 0 0 7.9250 S Third \n",
"3 1 1 female 35.0 1 0 53.1000 S First \n",
"4 0 3 male 35.0 0 0 8.0500 S Third \n",
"\n",
" who adult_male deck embark_town alive alone \n",
"0 man True NaN Southampton no False \n",
"1 woman False C Cherbourg yes False \n",
"2 woman False NaN Southampton yes True \n",
"3 woman False C Southampton yes False \n",
"4 man True NaN Southampton no True "
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_titanic = sns.load_dataset('titanic')\n",
"df_titanic.head()"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [],
"source": [
"\n",
"df_titanic.dropna(how='any', inplace=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Index(['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare',\n",
" 'embarked', 'class', 'who', 'adult_male', 'deck', 'embark_town',\n",
" 'alive', 'alone'],\n",
" dtype='object')"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df_titanic.columns"
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.neighbors import KNeighborsClassifier\n",
"import pandas as pd\n",
"import seaborn as sns\n",
"\n",
"k = 11\n",
"x_feat_list = ['age', 'pclass', 'fare']\n",
"y_feat = 'survived'\n",
"\n",
"df_titanic = sns.load_dataset('titanic')\n",
"df_titanic.dropna(how='any', inplace=True)\n",
"\n",
"# scale normalization (overwrites old data)\n",
"for feat in x_feat_list:\n",
" df_titanic[feat] = df_titanic[feat] / df_titanic[feat].std()\n",
"\n",
"# extract data into numpy format (for sklearn)\n",
"x = df_titanic.loc[:, x_feat_list].values\n",
"y_true = df_titanic.loc[:, y_feat].values\n",
"\n",
"# initialize a knn_classifier\n",
"knn_classifier = KNeighborsClassifier(n_neighbors=k)\n",
"\n",
"# fit happens \"inplace\", we modify the internal state of knn_classifier to remember all the training samples\n",
"knn_classifier.fit(x, y_true)\n",
"\n",
"# estimate each penguin's species\n",
"y_pred = knn_classifier.predict(x)"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {
"scrolled": false
},
"outputs": [
{
"data": {
"image/png": "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5eVp/fr1at26dambljp16uj555/XpEmTNHLkSP3ud79T8+bNlZmZqeTkZB07dkybNm2yH9+lSxetXbtWY8eO1bXXXqsqVaooJiZGMTExuvrqq/Xkk0/qkUce0YABA9S9e3ddeeWVysvLU2pqqnbs2KHatWtr/fr1kgovuz755JMaPXq0Jk2aVGwdry1btqh79+768ssvy/V9HTFihJYvX6777rtPffr0UVhYmH788Ud9+eWX6tevnz766COnr23SpIn69+9fbB2vw4cP66abbip240Tt2rX10ksvafz48br99tvVpUsXNW3aVDabTWlpadq5c6cyMjIuOb8HwDfReAEeEhISosWLF+vdd9/VmjVrtGHDBuXk5KhevXq64oorNG3aNN1www324202m2bOnKm5c+dqxYoVWrJkicLCwjRkyBCNHz++xPphl3LTTTfpgw8+sN/599VXX6lmzZpq0qSJ7r333mLHPvLII7LZbNqyZYs+//xzFRQUaMKECfY7E2NjYxUdHa0FCxZo69at2rx5s6pVq6awsDD16dNH/fr1K/Z+HTt21NKlSzVjxgz75c62bdtq8eLF2rx5c7kbr+joaC1atEgvvviiPv/8c+Xl5Sk6OlqvvPKKatSoccnGa+bMmZo1a5Y+/PBDHT9+XOHh4Zo4caLGjBlTYlavS5cuWr16tebPn6/Nmzdr+/btCggIUFhYmK6//nr16dOnXJ8D8BosoOpSNsviseMAAKCk/Lx8HT9c/ptefktY43ryr+Lv9vN4AxovAAAAQxiuBwAAMITGCwAAwBAaLwAAAENovAAAAAyh8QIAADCExgsAAMAQGi8AAABDaLwAAAAMofECAAAwhMYLAADAEBovAAAAQ2i8AAAADPl/J9PKTZ/+sP0AAAAASUVORK5CYII=\n",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"conf_mat = confusion_matrix(y_true=y_true, y_pred=y_pred)\n",
"\n",
"conf_mat_disp = ConfusionMatrixDisplay(conf_mat, display_labels=np.unique(y_true))\n",
"conf_mat_disp.plot()\n",
"\n",
"plt.gcf().set_size_inches(7, 7)\n",
"\n",
"# seaborn turns on grid by default ... looks best without it\n",
"plt.grid(False)"
]
}
],
"metadata": {
"celltoolbar": "Slideshow",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.6"
}
},
"nbformat": 4,
"nbformat_minor": 4
}