{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "f503b4b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import dependencies and packages needed\n",
    "from sklearn import neighbors, datasets\n",
    "from sklearn.model_selection import train_test_split\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sn\n",
    "import matplotlib.pyplot as plt\n",
    "# import the dataset (stored in a variable)\n",
    "mnist = datasets.load_digits()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "86919d4f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'algorithm': 'auto',\n",
       " 'leaf_size': 30,\n",
       " 'metric': 'minkowski',\n",
       " 'metric_params': None,\n",
       " 'n_jobs': None,\n",
       " 'n_neighbors': 5,\n",
       " 'p': 2,\n",
       " 'weights': 'uniform'}"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# this step is just to demonstrate the adjustable parameters within the algorithm and not a necessary step\n",
    "neighbors.KNeighborsClassifier().get_params()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "e6d9fc14",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['__abstractmethods__',\n",
       " '__class__',\n",
       " '__delattr__',\n",
       " '__dict__',\n",
       " '__dir__',\n",
       " '__doc__',\n",
       " '__eq__',\n",
       " '__format__',\n",
       " '__ge__',\n",
       " '__getattribute__',\n",
       " '__getstate__',\n",
       " '__gt__',\n",
       " '__hash__',\n",
       " '__init__',\n",
       " '__init_subclass__',\n",
       " '__le__',\n",
       " '__lt__',\n",
       " '__module__',\n",
       " '__ne__',\n",
       " '__new__',\n",
       " '__reduce__',\n",
       " '__reduce_ex__',\n",
       " '__repr__',\n",
       " '__setattr__',\n",
       " '__setstate__',\n",
       " '__sizeof__',\n",
       " '__str__',\n",
       " '__subclasshook__',\n",
       " '__weakref__',\n",
       " '_abc_impl',\n",
       " '_check_algorithm_metric',\n",
       " '_check_feature_names',\n",
       " '_check_n_features',\n",
       " '_estimator_type',\n",
       " '_fit',\n",
       " '_get_param_names',\n",
       " '_get_tags',\n",
       " '_kneighbors_reduce_func',\n",
       " '_more_tags',\n",
       " '_pairwise',\n",
       " '_repr_html_',\n",
       " '_repr_html_inner',\n",
       " '_repr_mimebundle_',\n",
       " '_validate_data',\n",
       " 'fit',\n",
       " 'get_params',\n",
       " 'kneighbors',\n",
       " 'kneighbors_graph',\n",
       " 'predict',\n",
       " 'predict_proba',\n",
       " 'score',\n",
       " 'set_params']"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# this step is just to demonstrate the api methods and not a necessary step\n",
    "dir(neighbors.KNeighborsClassifier)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "9be92ae9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "training data points: 1212\n",
      "validation data points: 135\n",
      "testing data points: 450\n"
     ]
    }
   ],
   "source": [
    "# take the MNIST data and construct the training and testing split, using 75% of the\n",
    "# data for training and 25% for testing\n",
    "(trainData, testData, trainLabels, testLabels) = train_test_split(np.array(mnist.data),\n",
    "mnist.target, test_size=0.25, random_state=42)\n",
    "\n",
    "# Further take 10% of the training data and use that for validation\n",
    "(trainData, valData, trainLabels, valLabels) = train_test_split(trainData, trainLabels,\n",
    "test_size=0.1, random_state=84)\n",
    "\n",
    "# show the sizes of each data split\n",
    "print(\"training data points: {}\".format(len(trainLabels)))\n",
    "print(\"validation data points: {}\".format(len(valLabels)))\n",
    "print(\"testing data points: {}\".format(len(testLabels)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "7080b640",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "KNeighborsClassifier(n_neighbors=3)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Create a model for K-Nearest Neighbours and include parameters\n",
    "model = neighbors.KNeighborsClassifier(n_neighbors=3)\n",
    "# fit the data to the model, both features and labels to train it\n",
    "model.fit(trainData, trainLabels)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "871813e9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([6, 9, 3, 7, 2, 1, 5, 2, 5, 2, 1, 9, 4, 0, 4, 2, 3, 7, 8, 8, 4, 3,\n",
       "       9, 7, 5, 6, 3, 5, 6, 3, 4, 9, 1, 4, 4, 6, 9, 4, 7, 6, 6, 9, 1, 3,\n",
       "       6, 1, 3, 0, 6, 5, 5, 1, 3, 5, 6, 0, 9, 0, 0, 1, 0, 4, 5, 2, 4, 5,\n",
       "       7, 0, 7, 5, 9, 5, 5, 4, 7, 0, 4, 5, 5, 9, 9, 0, 2, 3, 8, 0, 6, 4,\n",
       "       4, 9, 1, 2, 8, 3, 5, 2, 9, 0, 4, 4, 4, 3, 5, 3, 1, 3, 5, 9, 4, 2,\n",
       "       7, 7, 4, 4, 1, 9, 2, 7, 8, 7, 2, 6, 9, 4, 0, 7, 2, 7, 5, 8, 7, 5,\n",
       "       7, 9, 0, 6, 6, 4, 2, 8, 0, 9, 4, 6, 8, 9, 6, 9, 0, 3, 5, 6, 6, 0,\n",
       "       6, 4, 3, 9, 3, 4, 7, 2, 9, 0, 4, 5, 3, 6, 5, 9, 9, 8, 4, 2, 1, 3,\n",
       "       7, 7, 2, 2, 3, 9, 8, 0, 3, 2, 2, 5, 6, 9, 9, 4, 1, 5, 4, 2, 3, 6,\n",
       "       4, 8, 5, 9, 5, 7, 1, 9, 4, 8, 1, 5, 4, 4, 9, 6, 1, 8, 6, 0, 4, 5,\n",
       "       2, 7, 4, 6, 4, 5, 6, 0, 3, 2, 3, 6, 7, 1, 5, 1, 4, 7, 6, 8, 8, 5,\n",
       "       5, 1, 6, 2, 8, 8, 9, 5, 7, 6, 2, 2, 2, 3, 4, 8, 8, 3, 6, 0, 9, 7,\n",
       "       7, 0, 1, 0, 4, 5, 1, 5, 3, 6, 0, 4, 1, 0, 0, 3, 6, 5, 9, 7, 3, 5,\n",
       "       5, 9, 9, 8, 5, 3, 3, 2, 0, 5, 8, 3, 4, 0, 2, 4, 6, 4, 3, 4, 5, 0,\n",
       "       5, 2, 1, 3, 1, 4, 1, 1, 7, 0, 1, 5, 2, 1, 2, 8, 7, 0, 6, 4, 8, 8,\n",
       "       5, 1, 8, 4, 5, 8, 7, 9, 8, 5, 0, 6, 2, 0, 7, 9, 8, 9, 5, 2, 7, 7,\n",
       "       1, 8, 7, 4, 3, 8, 3, 5, 6, 0, 0, 3, 0, 5, 0, 0, 4, 1, 2, 8, 4, 5,\n",
       "       9, 6, 3, 1, 8, 8, 4, 2, 3, 8, 9, 8, 8, 5, 0, 6, 3, 3, 7, 1, 6, 4,\n",
       "       1, 2, 1, 1, 6, 4, 7, 4, 8, 3, 4, 0, 5, 1, 9, 4, 5, 7, 6, 3, 7, 0,\n",
       "       5, 9, 7, 5, 9, 7, 4, 2, 1, 9, 0, 7, 5, 3, 3, 6, 3, 9, 6, 9, 5, 0,\n",
       "       1, 5, 5, 8, 3, 3, 6, 2, 6, 5])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# use the model to classify the datapoints within the test set\n",
    "y_pred2=model.predict(testData)\n",
    "# print predicted classifications\n",
    "y_pred2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "e492c2d2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([6, 9, 3, 7, 2, 1, 5, 2, 5, 2, 1, 9, 4, 0, 4, 2, 3, 7, 8, 8, 4, 3,\n",
       "       9, 7, 5, 6, 3, 5, 6, 3, 4, 9, 1, 4, 4, 6, 9, 4, 7, 6, 6, 9, 1, 3,\n",
       "       6, 1, 3, 0, 6, 5, 5, 1, 9, 5, 6, 0, 9, 0, 0, 1, 0, 4, 5, 2, 4, 5,\n",
       "       7, 0, 7, 5, 9, 5, 5, 4, 7, 0, 4, 5, 5, 9, 9, 0, 2, 3, 8, 0, 6, 4,\n",
       "       4, 9, 1, 2, 8, 3, 5, 2, 9, 0, 4, 4, 4, 3, 5, 3, 1, 3, 5, 9, 4, 2,\n",
       "       7, 7, 4, 4, 1, 9, 2, 7, 8, 7, 2, 6, 9, 4, 0, 7, 2, 7, 5, 8, 7, 5,\n",
       "       7, 7, 0, 6, 6, 4, 2, 8, 0, 9, 4, 6, 9, 9, 6, 9, 0, 3, 5, 6, 6, 0,\n",
       "       6, 4, 3, 9, 3, 9, 7, 2, 9, 0, 4, 5, 3, 6, 5, 9, 9, 8, 4, 2, 1, 3,\n",
       "       7, 7, 2, 2, 3, 9, 8, 0, 3, 2, 2, 5, 6, 9, 9, 4, 1, 5, 4, 2, 3, 6,\n",
       "       4, 8, 5, 9, 5, 7, 8, 9, 4, 8, 1, 5, 4, 4, 9, 6, 1, 8, 6, 0, 4, 5,\n",
       "       2, 7, 4, 6, 4, 5, 6, 0, 3, 2, 3, 6, 7, 1, 5, 1, 4, 7, 6, 8, 8, 5,\n",
       "       5, 1, 6, 2, 8, 8, 9, 9, 7, 6, 2, 2, 2, 3, 4, 8, 8, 3, 6, 0, 9, 7,\n",
       "       7, 0, 1, 0, 4, 5, 1, 5, 3, 6, 0, 4, 1, 0, 0, 3, 6, 5, 9, 7, 3, 5,\n",
       "       5, 9, 9, 8, 5, 3, 3, 2, 0, 5, 8, 3, 4, 0, 2, 4, 6, 4, 3, 4, 5, 0,\n",
       "       5, 2, 1, 3, 1, 4, 1, 1, 7, 0, 1, 5, 2, 1, 2, 8, 7, 0, 6, 4, 8, 8,\n",
       "       5, 1, 8, 4, 5, 8, 7, 9, 8, 5, 0, 6, 2, 0, 7, 9, 8, 9, 5, 2, 7, 7,\n",
       "       1, 8, 7, 4, 3, 8, 3, 5, 6, 0, 0, 3, 0, 5, 0, 0, 4, 1, 2, 8, 4, 5,\n",
       "       9, 6, 3, 1, 8, 8, 4, 2, 3, 8, 9, 8, 8, 5, 0, 6, 3, 3, 7, 1, 6, 4,\n",
       "       1, 2, 1, 1, 6, 4, 7, 4, 8, 3, 4, 0, 5, 1, 9, 4, 5, 7, 6, 3, 7, 0,\n",
       "       5, 9, 7, 5, 9, 7, 4, 2, 1, 9, 0, 7, 5, 3, 3, 6, 3, 9, 6, 9, 5, 0,\n",
       "       1, 5, 5, 8, 3, 3, 6, 2, 6, 5])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# print test labels (ground truth) that is compared to classifier\n",
    "testLabels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "eaf265c9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[43  0  0  0  0  0  0  0  0  0]\n",
      " [ 0 37  0  0  0  0  0  0  0  0]\n",
      " [ 0  0 38  0  0  0  0  0  0  0]\n",
      " [ 0  0  0 46  0  0  0  0  0  0]\n",
      " [ 0  0  0  0 55  0  0  0  0  0]\n",
      " [ 0  0  0  0  0 59  0  0  0  0]\n",
      " [ 0  0  0  0  0  0 45  0  0  0]\n",
      " [ 0  0  0  0  0  0  0 40  0  1]\n",
      " [ 0  1  0  0  0  0  0  0 37  0]\n",
      " [ 0  0  0  1  1  1  0  0  1 44]]\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0      1.000     1.000     1.000        43\n",
      "           1      0.974     1.000     0.987        37\n",
      "           2      1.000     1.000     1.000        38\n",
      "           3      0.979     1.000     0.989        46\n",
      "           4      0.982     1.000     0.991        55\n",
      "           5      0.983     1.000     0.992        59\n",
      "           6      1.000     1.000     1.000        45\n",
      "           7      1.000     0.976     0.988        41\n",
      "           8      0.974     0.974     0.974        38\n",
      "           9      0.978     0.917     0.946        48\n",
      "\n",
      "    accuracy                          0.987       450\n",
      "   macro avg      0.987     0.987     0.987       450\n",
      "weighted avg      0.987     0.987     0.987       450\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# display the metrics of the model's classification through confusion matrix and classification report\n",
    "# (accuracy / precision / recall / f1)\n",
    "from sklearn import metrics\n",
    "print(metrics.confusion_matrix(testLabels, y_pred2))\n",
    "\n",
    "\n",
    "print(metrics.classification_report(testLabels, y_pred2, digits=3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "69f17eb5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "     Actual  Predicted\n",
      "0         6          6\n",
      "1         9          9\n",
      "2         3          3\n",
      "3         7          7\n",
      "4         2          2\n",
      "..      ...        ...\n",
      "445       3          3\n",
      "446       6          6\n",
      "447       2          2\n",
      "448       6          6\n",
      "449       5          5\n",
      "\n",
      "[450 rows x 2 columns]\n"
     ]
    }
   ],
   "source": [
    "# use Pandas to create a dataframe to view the ground truth and classifications side by side\n",
    "data = {'Actual': testLabels,\n",
    "        'Predicted': y_pred2}\n",
    "df = pd.DataFrame(data)\n",
    "print(df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f66273ba",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "              precision    recall  f1-score   support\n",
      "\n",
      "           0      1.000     1.000     1.000        43\n",
      "           1      0.974     1.000     0.987        37\n",
      "           2      1.000     1.000     1.000        38\n",
      "           3      0.979     1.000     0.989        46\n",
      "           4      0.982     1.000     0.991        55\n",
      "           5      0.983     1.000     0.992        59\n",
      "           6      1.000     1.000     1.000        45\n",
      "           7      1.000     0.976     0.988        41\n",
      "           8      0.974     0.974     0.974        38\n",
      "           9      0.978     0.917     0.946        48\n",
      "\n",
      "    accuracy                          0.987       450\n",
      "   macro avg      0.987     0.987     0.987       450\n",
      "weighted avg      0.987     0.987     0.987       450\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Use Seaborn to create a confusion matrix heatmap and MatPlotLib to display\n",
    "confusion_matrix = pd.crosstab(df['Actual'], df['Predicted'], rownames=['Actual'], colnames=['Predicted'])\n",
    "sn.heatmap(confusion_matrix, annot=True)\n",
    "plt.show()\n",
    "print(metrics.classification_report(testLabels, y_pred2, digits=3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "2daca0f3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create an iterative grid search to tune the parameters\n",
    "import joblib\n",
    "from sklearn.model_selection import GridSearchCV"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "e515ab2a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create a function to show the results of the classifier under different parameter values\n",
    "def print_results(results):\n",
    "    print('Best Parameters: {}\\n'.format(results.best_params_))\n",
    "    \n",
    "    means = results.cv_results_['mean_test_score']\n",
    "    stds = results.cv_results_['std_test_score']\n",
    "    for mean, std, params in zip(means, stds, results.cv_results_['params']):\n",
    "        print('{} (+/-{}) for {}'.format(round(mean, 3), round(std * 2, 3), params))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "9455bf40",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Best Parameters: {'n_neighbors': 3}\n",
      "\n",
      "0.983 (+/-0.014) for {'n_neighbors': 3}\n",
      "0.978 (+/-0.019) for {'n_neighbors': 5}\n",
      "0.976 (+/-0.017) for {'n_neighbors': 7}\n",
      "0.974 (+/-0.02) for {'n_neighbors': 9}\n",
      "0.968 (+/-0.024) for {'n_neighbors': 11}\n"
     ]
    }
   ],
   "source": [
    "# Create a model using the KNN algorithm\n",
    "knn = neighbors.KNeighborsClassifier()\n",
    "# Set the parameters to be tuned\n",
    "parameters = {\n",
    "    'n_neighbors' : [3, 5, 7, 9, 11]\n",
    "}\n",
    "# instantiate the grid search with 5 fold cross validation\n",
    "cv = GridSearchCV(knn, parameters, cv=5)\n",
    "# fit the training data to the model\n",
    "cv.fit(trainData, trainLabels.ravel())\n",
    "# display the model evaluation with the different paramter values (K value)\n",
    "print_results(cv)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "1544fd54",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'algorithm': 'auto',\n",
       " 'leaf_size': 30,\n",
       " 'metric': 'minkowski',\n",
       " 'metric_params': None,\n",
       " 'n_jobs': None,\n",
       " 'n_neighbors': 3,\n",
       " 'p': 2,\n",
       " 'weights': 'uniform'}"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# show the best paramters for classification (not a necesary step unless you are saving best parameter models to compare)\n",
    "cv.best_estimator_.get_params()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "76e701bf",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "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.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
