KNN Classifier Tutorial in Python
KNN Classifier Tutorial in Python
Hello friends,
kNN or k-Nearest Neighbours Classifier is a very simple and easy to understand machine learning
algorithm. In this kernel, I build a k Nearest Neighbours classifier to classify the patients suffering
from Breast Cancer.
In kNN classification, the output is a class membership. The given data point is classified based on
the majority of type of its neighbours. The data point is assigned to the most frequent class among
its k nearest neighbours. Usually k is a small positive integer. If k=1, then the data point is simply
assigned to the class of that single nearest neighbour.
In kNN regression, the output is simply some property value for the object. This value is the average
of the values of k nearest neighbours.
kNN is a type of instance-based learning or lazy learning. Lazy learning means it does not require
any training data points for model generation. All training data will be used in the testing phase. This
makes training faster and testing slower and costlier. So, the testing phase requires more time and
memory resources.
In kNN, the neighbours are taken from a set of objects for which the class or the object property
value is known. This can be thought of as the training set for the kNN algorithm, though no explicit
training step is required. In both classification and regression kNN algorithm, we can assign weight
to the contributions of the neighbours. So, nearest neighbours contribute more to the average than
the more distant ones.
Now, we will see kNN algorithm in action. Suppose, we have a dataset with two variables which are
classified as Red and Blue.
In kNN algorithm, k is the number of nearest neighbours. Generally, k is an odd number because it
helps to decide the majority of the class. When k=1, then the algorithm is known as the nearest
neighbour algorithm.
Now, we want to classify a new data point X into Blue class or Red class. Suppose the value of k is
3. The kNN algorithm starts by calculating the distance between X and all the other data points. It
then finds the 3 nearest points with least distance to point X.
In the final step of the kNN algorithm, we assign the new data point X to the majority of the class of
the 3 nearest points. If 2 of the 3 nearest points belong to the class Red while 1 belong to the
class Blue, then we classify the new data point as Red.
3. How to decide the number of neighbours in
kNN
Table of Contents
While building the kNN classifier model, one question that come to my mind is what should be the
value of nearest neighbours (k) that yields highest accuracy. This is a very important question
because the classification accuracy depends upon our choice of k.
The number of neighbours (k) in kNN is a parameter that we need to select at the time of model
building. Selecting the optimal value of k in kNN is the most critical problem. A small value of k
means that noise will have higher influence on the result. So, probability of overfitting is very high. A
large value of k makes it computationally expensive in terms of time to build the kNN model. Also, a
large value of k will have a smoother decision boundary which means lower variance but higher bias.
The data scientists choose an odd value of k if the number of classes is even. We can apply the
elbow method to select the value of k. To optimize the results, we can use Cross Validation
technique. Using the cross-validation technique, we can test the kNN algorithm with different values
of k. The model which gives good accuracy can be considered to be an optimal choice. It depends
on individual cases and at times best process is to run through each possible value of k and test our
result.
Lazy learning means there is no need for learning or training of the model and all of the data points
are used at the time of prediction. Lazy learners wait until the last minute before classifying any data
point. They merely store the training dataset and waits until classification needs to perform. Lazy
learners are also known as instance-based learners because lazy learners store the training points
or instances, and all learning is based on instances.
Unlike eager learners, lazy learners do less work in the training phase and more work in the testing
phase to make a classification.
5. Import libraries
Table of Contents
In [1]:
# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python docker image: [Link]
python
# For example, here's several helpful packages to load in
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import [Link] as plt # for data visualization purposes
import seaborn as sns # for data visualization
%matplotlib inline
import os
for dirname, _, filenames in [Link]('/kaggle/input'):
for filename in filenames:
print([Link](dirname, filename))
# Any results you write to the current directory are saved as output.
/kaggle/input/uci-breast-cancer-wisconsin-original/breast-cancer-
[Link]
In [2]:
import warnings
[Link]('ignore')
6. Import dataset
Table of Contents
In [3]:
data = '/kaggle/input/uci-breast-cancer-wisconsin-original/breast-cancer-
[Link]'
df = pd.read_csv(data, header=None)
In [4]:
# view dimensions of dataset
[Link]
Out[4]:
(699, 11)
We can see that there are 699 instances and 11 attributes in the data set.
In the dataset description, it is given that there are 10 attributes and 1 Class which is the target
variable. So, we have 10 attributes and 1 target variable.
View top 5 rows of dataset
In [5]:
# preview the dataset
[Link]()
Out[5]:
1
0 1 2 3 4 5 6 7 8 9
0
10000
0 5 1 1 1 2 1 3 1 1 2
25
10029 1
1 5 4 4 5 7 3 2 1 2
45 0
10154
2 3 1 1 1 2 2 3 1 1 2
25
10162
3 6 8 8 1 3 4 3 7 1 2
77
10170
4 4 1 1 3 2 1 3 1 1 2
23
Rename column names
We can see that the dataset does not have proper column names. The columns are merely labelled
as 0,1,2.... and so on. We should give proper names to the columns. I will do it as follows:-
In [6]:
col_names = ['Id', 'Clump_thickness', 'Uniformity_Cell_Size', 'Uniformity_Cell_Shape',
'Marginal_Adhesion',
'Single_Epithelial_Cell_Size', 'Bare_Nuclei', 'Bland_Chromatin',
'Normal_Nucleoli', 'Mitoses', 'Class']
[Link] = col_names
[Link]
Out[6]:
Index(['Id', 'Clump_thickness', 'Uniformity_Cell_Size',
'Uniformity_Cell_Shape', 'Marginal_Adhesion',
'Single_Epithelial_Cell_Size', 'Bare_Nuclei', 'Bland_Chromatin',
'Normal_Nucleoli', 'Mitoses', 'Class'],
dtype='object')
We can see that the column names are renamed. Now, the columns have meaningful names.
In [7]:
# let's agian preview the dataset
[Link]()
Out[7]:
Clump Uniformi Uniformit Margin Single_Epit Bare Bland_ Norma Mit Cl
Id _thick ty_Cell_ y_Cell_S al_Adh helial_Cell _Nuc Chrom l_Nucl os a
ness Size hape esion _Size lei atin eoli es ss
10
00
0 5 1 1 1 2 1 3 1 1 2
02
5
1 10 5 4 4 5 7 10 3 2 1 2
02
Clump Uniformi Uniformit Margin Single_Epit Bare Bland_ Norma Mit Cl
Id _thick ty_Cell_ y_Cell_S al_Adh helial_Cell _Nuc Chrom l_Nucl os a
ness Size hape esion _Size lei atin eoli es ss
94
5
10
15
2 3 1 1 1 2 2 3 1 1 2
42
5
10
16
3 6 8 8 1 3 4 3 7 1 2
27
7
10
17
4 4 1 1 3 2 1 3 1 1 2
02
3
Drop redundant columns
We should drop any redundant columns from the dataset which does not have any predictive power.
Here, Id is the redundant column. So, I will drop it first.
In [8]:
# drop Id column from dataset
[Link]()
<class '[Link]'>
RangeIndex: 699 entries, 0 to 698
Data columns (total 10 columns):
Clump_thickness 699 non-null int64
Uniformity_Cell_Size 699 non-null int64
Uniformity_Cell_Shape 699 non-null int64
Marginal_Adhesion 699 non-null int64
Single_Epithelial_Cell_Size 699 non-null int64
Bare_Nuclei 699 non-null object
Bland_Chromatin 699 non-null int64
Normal_Nucleoli 699 non-null int64
Mitoses 699 non-null int64
Class 699 non-null int64
dtypes: int64(9), object(1)
memory usage: 54.7+ KB
We can see that the Id column has been removed from the dataset.
We can see that there are 9 numerical variables and 1 categorical variable in the dataset. I will
check the frequency distribution of values in the variables to confirm the same.
print(df[var].value_counts())
1 145
5 130
3 108
4 80
10 69
2 50
8 46
6 34
7 23
9 14
Name: Clump_thickness, dtype: int64
1 384
10 67
3 52
2 45
4 40
5 30
8 29
6 27
7 19
9 6
Name: Uniformity_Cell_Size, dtype: int64
1 353
2 59
10 58
3 56
4 44
5 34
7 30
6 30
8 28
9 7
Name: Uniformity_Cell_Shape, dtype: int64
1 407
3 58
2 58
10 55
4 33
8 25
5 23
6 22
7 13
9 5
Name: Marginal_Adhesion, dtype: int64
2 386
3 72
4 48
1 47
6 41
5 39
10 31
8 21
7 12
9 2
Name: Single_Epithelial_Cell_Size, dtype: int64
1 402
10 132
5 30
2 30
3 28
8 21
4 19
? 16
9 9
7 8
6 4
Name: Bare_Nuclei, dtype: int64
2 166
3 165
1 152
7 73
4 40
5 34
8 28
10 20
9 11
6 10
Name: Bland_Chromatin, dtype: int64
1 443
10 61
3 44
2 36
8 24
6 22
5 19
4 18
9 16
7 16
Name: Normal_Nucleoli, dtype: int64
1 579
2 35
3 33
10 14
4 12
7 9
8 8
5 6
6 3
Name: Mitoses, dtype: int64
2 458
4 241
Name: Class, dtype: int64
The distribution of values shows that data type of Bare_Nuclei is of type integer. But the summary
of the dataframe shows that it is type object. So, I will explicitly convert its data type to integer.
Convert data type of Bare_Nuclei to integer
In [11]:
df['Bare_Nuclei'] = pd.to_numeric(df['Bare_Nuclei'], errors='coerce')
Check data types of columns of dataframe
In [12]:
[Link]
Out[12]:
Clump_thickness int64
Uniformity_Cell_Size int64
Uniformity_Cell_Shape int64
Marginal_Adhesion int64
Single_Epithelial_Cell_Size int64
Bare_Nuclei float64
Bland_Chromatin int64
Normal_Nucleoli int64
Mitoses int64
Class int64
dtype: object
Now, we can see that all the columns of the dataframe are of type numeric.
Summary of variables
There are 10 numerical variables in the dataset.
All of the variables are of discrete type.
Out of all the 10 variables, the first 9 variables are feature variables and last
variable Class is the target variable.
Explore problems within variables
Now, I will explore problems within variables.
[Link]().sum()
Out[13]:
Clump_thickness 0
Uniformity_Cell_Size 0
Uniformity_Cell_Shape 0
Marginal_Adhesion 0
Single_Epithelial_Cell_Size 0
Bare_Nuclei 16
Bland_Chromatin 0
Normal_Nucleoli 0
Mitoses 0
Class 0
dtype: int64
We can see that the Bare_Nuclei column contains missing values. We need to dig deeper to find
the frequency distribution of values of Bare_Nuclei.
In [14]:
# check `na` values in the dataframe
[Link]().sum()
Out[14]:
Clump_thickness 0
Uniformity_Cell_Size 0
Uniformity_Cell_Shape 0
Marginal_Adhesion 0
Single_Epithelial_Cell_Size 0
Bare_Nuclei 16
Bland_Chromatin 0
Normal_Nucleoli 0
Mitoses 0
Class 0
dtype: int64
We can see that the Bare_Nuclei column contains 16 nan values.
In [15]:
# check frequency distribution of `Bare_Nuclei` column
df['Bare_Nuclei'].value_counts()
Out[15]:
1.0 402
10.0 132
5.0 30
2.0 30
3.0 28
8.0 21
4.0 19
9.0 9
7.0 8
6.0 4
Name: Bare_Nuclei, dtype: int64
In [16]:
# check unique values in `Bare_Nuclei` column
df['Bare_Nuclei'].unique()
Out[16]:
array([ 1., 10., 2., 4., 3., 9., 7., nan, 5., 8., 6.])
We can see that there are nan values in the Bare_Nuclei column.
In [17]:
# check for nan values in `Bare_Nuclei` column
df['Bare_Nuclei'].isna().sum()
Out[17]:
16
We can see that there are 16 nan values in the dataset. I will impute missing values after dividing
the dataset into training and test set.
check frequency distribution of target variable Class
In [18]:
# view frequency distribution of values in `Class` variable
df['Class'].value_counts()
Out[18]:
2 458
4 241
Name: Class, dtype: int64
check percentage of frequency distribution of Class
In [19]:
# view percentage of frequency distribution of values in `Class` variable
df['Class'].value_counts()/[Link](len(df))
Out[19]:
2 0.655222
4 0.344778
Name: Class, dtype: float64
We can see that the Class variable contains 2 class labels - 2 and 4. 2 stands for benign
and 4 stands for malignant cancer.
Outliers in numerical variables
In [20]:
# view summary statistics in numerical variables
print(round([Link](),2))
Clump_thickness Uniformity_Cell_Size Uniformity_Cell_Shape \
count 699.00 699.00 699.00
mean 4.42 3.13 3.21
std 2.82 3.05 2.97
min 1.00 1.00 1.00
25% 2.00 1.00 1.00
50% 4.00 1.00 1.00
75% 6.00 5.00 5.00
max 10.00 10.00 10.00
8. Data Visualization
Table of Contents
Now, we have a basic understanding of our data. I will supplement it with some data visualization to
get better understanding of our data.
Univariate plots
Check the distribution of variables
Now, I will plot the histograms to check variable distributions to find out if they are normal or skewed.
In [21]:
# plot histograms of the variables
[Link]['[Link]']=(30,25)
[Link]()
We can see that all the variables in the dataset are positively skewed.
Multivariate plots
Estimating correlation coefficients
Our dataset is very small. So, we can compute the standard correlation coefficient (also called
Pearson's r) between every pair of attributes. We can compute it using the [Link]() method as
follows:-
In [22]:
correlation = [Link]()
Our target variable is Class. So, we should check how each attribute correlates with
the Class variable. We can do it as follows:-
In [23]:
correlation['Class'].sort_values(ascending=False)
Out[23]:
Class 1.000000
Bare_Nuclei 0.822696
Uniformity_Cell_Shape 0.818934
Uniformity_Cell_Size 0.817904
Bland_Chromatin 0.756616
Clump_thickness 0.716001
Normal_Nucleoli 0.712244
Marginal_Adhesion 0.696800
Single_Epithelial_Cell_Size 0.682785
Mitoses 0.423170
Name: Class, dtype: float64
Interpretation
The correlation coefficient ranges from -1 to +1.
When it is close to +1, this signifies that there is a strong positive correlation. So, we can see that
there is a strong positive correlation
between Class and Bare_Nuclei, Class and Uniformity_Cell_Shape, Class and Uniform
ity_Cell_Size.
When it is clsoe to -1, it means that there is a strong negative correlation. When it is close to 0, it
means that there is no correlation.
We can see that all the variables are positively correlated with Class variable. Some variables are
strongly positive correlated while some variables are negatively correlated.
Discover patterns and relationships
An important step in EDA is to discover patterns and relationships between variables in the dataset. I
will use the seaborn heatmap to explore the patterns and relationships in the dataset.
Interpretation
From the above correlation heat map, we can conclude that :-
y = df['Class']
X_train.shape, X_test.shape
Out[27]:
((559, 9), (140, 9))
11. Feature Engineering
Table of Contents
Feature Engineering is the process of transforming raw data into useful features that help us to
understand our model better and increase its predictive power. I will carry out feature engineering on
different types of variables.
In [28]:
# check data types in X_train
X_train.dtypes
Out[28]:
Clump_thickness int64
Uniformity_Cell_Size int64
Uniformity_Cell_Shape int64
Marginal_Adhesion int64
Single_Epithelial_Cell_Size int64
Bare_Nuclei float64
Bland_Chromatin int64
Normal_Nucleoli int64
Mitoses int64
dtype: object
Engineering missing values in variables
In [29]:
# check missing values in numerical variables in X_train
X_train.isnull().sum()
Out[29]:
Clump_thickness 0
Uniformity_Cell_Size 0
Uniformity_Cell_Shape 0
Marginal_Adhesion 0
Single_Epithelial_Cell_Size 0
Bare_Nuclei 13
Bland_Chromatin 0
Normal_Nucleoli 0
Mitoses 0
dtype: int64
In [30]:
# check missing values in numerical variables in X_test
X_test.isnull().sum()
Out[30]:
Clump_thickness 0
Uniformity_Cell_Size 0
Uniformity_Cell_Shape 0
Marginal_Adhesion 0
Single_Epithelial_Cell_Size 0
Bare_Nuclei 3
Bland_Chromatin 0
Normal_Nucleoli 0
Mitoses 0
dtype: int64
In [31]:
# print percentage of missing values in the numerical variables in training set
I will impute missing values with the appropriate statistical measures of the data, in this case
median. Imputation should be done over the training set, and then propagated to the test set. It
means that the statistical measures to be used to fill missing values both in train and test set, should
be extracted from the train set only. This is to avoid overfitting.
In [32]:
# impute missing values in X_train and X_test with respective column median in X_train
In [33]:
# check again missing values in numerical variables in X_train
X_train.isnull().sum()
Out[33]:
Clump_thickness 0
Uniformity_Cell_Size 0
Uniformity_Cell_Shape 0
Marginal_Adhesion 0
Single_Epithelial_Cell_Size 0
Bare_Nuclei 0
Bland_Chromatin 0
Normal_Nucleoli 0
Mitoses 0
dtype: int64
In [34]:
# check missing values in numerical variables in X_test
X_test.isnull().sum()
Out[34]:
Clump_thickness 0
Uniformity_Cell_Size 0
Uniformity_Cell_Shape 0
Marginal_Adhesion 0
Single_Epithelial_Cell_Size 0
Bare_Nuclei 0
Bland_Chromatin 0
Normal_Nucleoli 0
Mitoses 0
dtype: int64
We can see that there are no missing values in X_train and X_test.
In [35]:
X_train.head()
Out[35]:
Clump_ Uniformit Uniformity Marginal Single_Epith Bare_ Bland_ Normal Mit
thickne y_Cell_Siz _Cell_Shap _Adhesio elial_Cell_Siz Nucle Chroma _Nucleo ose
ss e e n e i tin li s
2
9 10 4 4 6 2 10.0 2 3 1
3
6
9 10 10 1 10 8.0 3 3 1
2
4
8 1 1 1 3 1 3.0 1 1 1
5
4
2 4 3 3 1 2 1.0 3 3 1
2
3
3 5 2 2 2 2 1.0 2 2 1
2
In [36]:
X_test.head()
Out[36]:
Clump_ Uniformit Uniformity Marginal Single_Epith Bare_ Bland_ Normal Mit
thickne y_Cell_Siz _Cell_Shap _Adhesio elial_Cell_Siz Nucle Chroma _Nucleo ose
ss e e n e i tin li s
4
7 4 1 2 1 2 1.0 1 1 1
6
5 4 2 2 1 2 1.0 2 1 1
Clump_ Uniformit Uniformity Marginal Single_Epith Bare_ Bland_ Normal Mit
thickne y_Cell_Siz _Cell_Shap _Adhesio elial_Cell_Siz Nucle Chroma _Nucleo ose
ss e e n e i tin li s
3
1
4
6 6 6 9 6 1.0 7 8 1
0
4
3 5 1 1 1 2 1.0 2 2 1
2
1
8 7 5 10 7 9.0 5 5 4
4
We now have training and testing set ready for model building. Before that, we should map all the
feature variables onto the same scale. It is called feature scaling. I will do it as follows.
12. Feature Scaling
Table of Contents
In [37]:
cols = X_train.columns
In [38]:
from [Link] import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = [Link](X_test)
In [39]:
X_train = [Link](X_train, columns=[cols])
In [40]:
X_test = [Link](X_test, columns=[cols])
In [41]:
X_train.head()
Out[41]:
Clump_ Uniformit Uniformity Marginal Single_Epith Bare_ Bland_C Normal Mit
thickne y_Cell_Siz _Cell_Shap _Adhesio elial_Cell_Siz Nucle hromati _Nucleo ose
ss e e n e i n li s
-
-
2.0283 1.11907 1.85 0.0412 0.3
0 0.299506 0.289573 -0.546543 0.5777
83 7 8357 41 242
74
58
-
- -
1.6694 1.29 0.0412 0.3
1 2.257680 2.304569 0.62247 3.106879 0.1599
51 7589 41 242
1 53
58
242
05 4329 95 65
58
-
- - - -
- 0.0412 0.3
3 0.1252 -0.046260 0.62247 -0.546543 0.66 0.1599
0.026856 41 242
09 1 5096 53
58
-
- - - -
0.2337 - 0.3
4 -0.382092 0.27416 -0.546543 0.66 0.5777 0.2834
23 0.353219 242
1 5096 74 62
58
We now have X_train dataset ready to be fed into the Logistic Regression classifier. I will do it as
follows.
13. Fit K Neighbours Classifier to the training
eet
Table of Contents
In [42]:
# import KNeighbors ClaSSifier from sklearn
from [Link] import KNeighborsClassifier
y_pred
Out[43]:
array([2, 2, 4, 2, 4, 2, 4, 2, 4, 2, 2, 2, 4, 4, 4, 2, 2, 4, 4, 2, 4, 4,
2, 2, 2, 4, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2,
4, 4, 2, 4, 2, 4, 4, 2, 2, 4, 2, 2, 2, 2, 2, 2, 4, 2, 2, 4, 4, 4,
4, 2, 2, 4, 2, 2, 4, 4, 2, 2, 2, 2, 4, 2, 2, 2, 4, 2, 2, 2, 4, 2,
4, 4, 2, 2, 2, 4, 2, 2, 2, 4, 2, 4, 4, 2, 2, 2, 4, 2, 2, 2, 2, 2,
4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 4, 2, 4, 2, 2, 4, 4, 4, 4, 4, 2,
2, 4, 4, 2, 2, 4, 2, 2])
predict_proba method
predict_proba method gives the probabilities for the target variable(2 and 4) in this case, in array
form.
2 is for probability of benign cancer and 4 is for probability of malignant
cancer.
In [44]:
# probability of getting output as 2 - benign cancer
knn.predict_proba(X_test)[:,0]
Out[44]:
array([1. , 1. , 0.33333333, 1. , 0. ,
1. , 0. , 1. , 0. , 0.66666667,
1. , 1. , 0. , 0.33333333, 0. ,
1. , 1. , 0. , 0. , 1. ,
0. , 0. , 1. , 1. , 1. ,
0. , 1. , 1. , 0. , 0. ,
1. , 1. , 1. , 1. , 1. ,
0.66666667, 1. , 0. , 1. , 1. ,
1. , 1. , 1. , 1. , 0. ,
0. , 1. , 0. , 1. , 0. ,
0. , 1. , 1. , 0. , 1. ,
1. , 1. , 1. , 0.66666667, 1. ,
0. , 1. , 1. , 0. , 0. ,
0.33333333, 0. , 1. , 1. , 0. ,
1. , 1. , 0. , 0. , 1. ,
1. , 1. , 1. , 0. , 1. ,
1. , 1. , 0. , 1. , 1. ,
1. , 0. , 1. , 0. , 0. ,
1. , 1. , 0.66666667, 0. , 1. ,
1. , 1. , 0. , 1. , 0. ,
0. , 1. , 1. , 1. , 0. ,
1. , 1. , 1. , 1. , 1. ,
0. , 0.33333333, 0. , 1. , 1. ,
1. , 1. , 1. , 0. , 0. ,
0. , 0.33333333, 1. , 0. , 1. ,
1. , 0.33333333, 0.33333333, 0. , 0. ,
0. , 1. , 1. , 0.33333333, 0. ,
1. , 1. , 0. , 1. , 1. ])
In [45]:
# probability of getting output as 4 - malignant cancer
knn.predict_proba(X_test)[:,1]
Out[45]:
array([0. , 0. , 0.66666667, 0. , 1. ,
0. , 1. , 0. , 1. , 0.33333333,
0. , 0. , 1. , 0.66666667, 1. ,
0. , 0. , 1. , 1. , 0. ,
1. , 1. , 0. , 0. , 0. ,
1. , 0. , 0. , 1. , 1. ,
0. , 0. , 0. , 0. , 0. ,
0.33333333, 0. , 1. , 0. , 0. ,
0. , 0. , 0. , 0. , 1. ,
1. , 0. , 1. , 0. , 1. ,
1. , 0. , 0. , 1. , 0. ,
0. , 0. , 0. , 0.33333333, 0. ,
1. , 0. , 0. , 1. , 1. ,
0.66666667, 1. , 0. , 0. , 1. ,
0. , 0. , 1. , 1. , 0. ,
0. , 0. , 0. , 1. , 0. ,
0. , 0. , 1. , 0. , 0. ,
0. , 1. , 0. , 1. , 1. ,
0. , 0. , 0.33333333, 1. , 0. ,
0. , 0. , 1. , 0. , 1. ,
1. , 0. , 0. , 0. , 1. ,
0. , 0. , 0. , 0. , 0. ,
1. , 0.66666667, 1. , 0. , 0. ,
0. , 0. , 0. , 1. , 1. ,
1. , 0.66666667, 0. , 1. , 0. ,
0. , 0.66666667, 0.66666667, 1. , 1. ,
1. , 0. , 0. , 0.66666667, 1. ,
0. , 0. , 1. , 0. , 0. ])
15. Check accuracy score
Table of Contents
In [46]:
from [Link] import accuracy_score
In [47]:
y_pred_train = [Link](X_train)
In [48]:
print('Training-set accuracy score: {0:0.4f}'. format(accuracy_score(y_train,
y_pred_train)))
Training-set accuracy score: 0.9821
Check for overfitting and underfitting
In [49]:
# print the scores on training and test set
In [50]:
# check class distribution in test set
y_test.value_counts()
Out[50]:
2 85
4 55
Name: Class, dtype: int64
We can see that the occurences of most frequent class is 85. So, we can calculate null accuracy by
dividing 85 by total number of occurences.
In [51]:
# check null accuracy score
null_accuracy = (85/(85+55))
With k=6,7,8 we get accuracy score of 0.9786. So, it results in performance improvement.
Now, based on the above analysis we can conclude that our classification model accuracy is very
good. Our model is doing a very good job in terms of predicting the class labels.
But, it does not give the underlying distribution of values. Also, it does not tell anything about the
type of errors our classifer is making.
We have another tool called Confusion matrix that comes to our rescue.
17. Confusion matrix
Table of Contents
A confusion matrix is a tool for summarizing the performance of a classification algorithm. A
confusion matrix will give us a clear picture of classification model performance and the types of
errors produced by the model. It gives us a summary of correct and incorrect predictions broken
down by each category. The summary is represented in a tabular form.
Four types of outcomes are possible while evaluating a classification model performance. These four
outcomes are described below:-
True Positives (TP) – True Positives occur when we predict an observation belongs to a certain
class and the observation actually belongs to that class.
True Negatives (TN) – True Negatives occur when we predict an observation does not belong
to a certain class and the observation actually does not belong to that class.
False Positives (FP) – False Positives occur when we predict an observation belongs to a
certain class but the observation actually does not belong to that class. This type of error is
called Type I error.
False Negatives (FN) – False Negatives occur when we predict an observation does not belong
to a certain class but the observation actually belongs to that class. This is a very serious error and it
is called Type II error.
These four outcomes are summarized in a confusion matrix given below.
In [57]:
# Print the Confusion Matrix with k =3 and slice it into four pieces
cm = confusion_matrix(y_test, y_pred)
[[83 2]
[ 2 53]]
True Positives(TP) = 83
True Negatives(TN) = 53
False Positives(FP) = 2
False Negatives(FN) = 2
The confusion matrix shows 83 + 53 = 136 correct predictions and 2 + 2 = 4
incorrect predictions.
In this case, we have
[[83 2]
[ 1 54]]
True Positives(TP) = 83
True Negatives(TN) = 54
False Positives(FP) = 2
False Negatives(FN) = 1
The above confusion matrix shows 83 + 54 = 137 correct predictions and 2 + 1 = 4
incorrect predictions.
In this case, we have
In [59]:
# visualize confusion matrix with seaborn heatmap
[Link](figsize=(6,4))
In [60]:
from [Link] import classification_report
print(classification_report(y_test, y_pred_7))
precision recall f1-score support
2 0.99 0.98 0.98 85
4 0.96 0.98 0.97 55
Classification accuracy
In [61]:
TP = cm_7[0,0]
TN = cm_7[1,1]
FP = cm_7[0,1]
FN = cm_7[1,0]
In [62]:
# print classification accuracy
print('Precision : {0:0.4f}'.format(precision))
Precision : 0.9765
Recall
Recall can be defined as the percentage of correctly predicted positive outcomes out of all the actual
positive outcomes. It can be given as the ratio of true positives (TP) to the sum of true positives and
false negatives (TP + FN). Recall is also called Sensitivity.
Recall identifies the proportion of correctly predicted actual positives.
Mathematically, recall can be given as the ratio of TP to (TP + FN).
In [65]:
recall = TP / float(TP + FN)
print('Specificity : {0:0.4f}'.format(specificity))
Specificity : 0.9643
f1-score
f1-score is the weighted harmonic mean of precision and recall. The best possible f1-
score would be 1.0 and the worst would be 0.0. f1-score is the harmonic mean of precision and
recall. So, f1-score is always lower than accuracy measures as they embed precision and recall
into their computation. The weighted average of f1-score should be used to compare classifier
models, not global accuracy.
Support
Support is the actual number of occurrences of the class in our dataset.
Adjusting the classification threshold level
In [69]:
# print the first 10 predicted probabilities of two classes- 2 and 4
y_pred_prob = knn.predict_proba(X_test)[0:10]
y_pred_prob
Out[69]:
array([[1. , 0. ],
[1. , 0. ],
[0.33333333, 0.66666667],
[1. , 0. ],
[0. , 1. ],
[1. , 0. ],
[0. , 1. ],
[1. , 0. ],
[0. , 1. ],
[0.66666667, 0.33333333]])
Observations
In each row, the numbers sum to 1.
There are 2 columns which correspond to 2 classes - 2 and 4.
- Class 2 - predicted probability that there is benign cancer.
In [70]:
# store the probabilities in dataframe
y_pred_prob_df
Out[70]:
Prob of - benign Prob of - malignant
cancer (2) cancer (4)
0 1.000000 0.000000
1 1.000000 0.000000
2 0.333333 0.666667
3 1.000000 0.000000
4 0.000000 1.000000
5 1.000000 0.000000
6 0.000000 1.000000
7 1.000000 0.000000
8 0.000000 1.000000
9 0.666667 0.333333
In [71]:
# print the first 10 predicted probabilities for class 4 - Probability of malignant cancer
knn.predict_proba(X_test)[0:10, 1]
Out[71]:
array([0. , 0. , 0.66666667, 0. , 1. ,
0. , 1. , 0. , 1. , 0.33333333])
In [72]:
# store the predicted probabilities for class 4 - Probability of malignant cancer
y_pred_1 = knn.predict_proba(X_test)[:, 1]
In [73]:
# plot histogram of predicted probabilities
Observations
We can see that the above histogram is positively skewed.
The first column tell us that there are approximately 80 observations with 0 probability
of malignant cancer.
There are few observations with probability > 0.5.
So, these few observations predict that there will be malignant cancer.
Comments
In binary problems, the threshold of 0.5 is used by default to convert predicted
probabilities into class predictions.
Threshold can be adjusted to increase sensitivity or specificity.
Sensitivity and specificity have an inverse relationship. Increasing one would always
decrease the other and vice versa.
Adjusting the threshold level should be one of the last step you do in the model-building
process.
19. ROC-AUC
Table of Contents
ROC Curve
Another tool to measure the classification model performance visually is ROC Curve. ROC Curve
stands for Receiver Operating Characteristic Curve. An ROC Curve is a plot which
shows the performance of a classification model at various classification threshold levels.
The ROC Curve plots the True Positive Rate (TPR) against the False Positive Rate
(FPR) at various threshold levels.
True Positive Rate (TPR) is also called Recall. It is defined as the ratio of TP to (TP +
FN).
False Positive Rate (FPR) is defined as the ratio of FP to (FP + TN).
In the ROC Curve, we will focus on the TPR (True Positive Rate) and FPR (False Positive Rate) of a
single point. This will give us the general performance of the ROC curve which consists of the TPR
and FPR at various threshold levels. So, an ROC Curve plots TPR vs FPR at different classification
threshold levels. If we lower the threshold levels, it may result in more items being classified as
positve. It will increase both True Positives (TP) and False Positives (FP).
In [74]:
# plot ROC Curve
[Link](figsize=(6,4))
[Link]['[Link]'] = 12
[Link]()
ROC curve help us to choose a threshold level that balances sensitivity and specificity for a
particular context.
ROC AUC
ROC AUC stands for Receiver Operating Characteristic - Area Under Curve. It is a
technique to compare classifier performance. In this technique, we measure the area under the
curve (AUC). A perfect classifier will have a ROC AUC equal to 1, whereas a purely random
classifier will have a ROC AUC equal to 0.5.
So, ROC AUC is the percentage of the ROC plot that is underneath the curve.
In [75]:
# compute ROC AUC
ROC AUC of our model approaches towards 1. So, we can conclude that our classifier does a good
job in predicting whether it is benign or malignant cancer.
In [76]:
# calculate cross-validated ROC AUC
In [77]:
# Applying 10-Fold Cross Validation
print('Cross-validation scores:{}'.format(scores))
Cross-validation scores:[0.87719298 0.96491228 0.94736842 0.98214286
0.96428571 0.96428571
0.98181818 0.98181818 1. 0.98181818]
We can summarize the cross-validation accuracy by calculating its mean.
In [78]:
# compute Average cross-validation score
If we look at all the 10 scores produced by the 10-fold cross-validation, we can also conclude that
there is a relatively high variance in the accuracy between folds, ranging from 100% accuracy to
87.72% accuracy. So, we can conclude that the model is very dependent on the particular folds used
for training, but it also be the consequence of the small size of the dataset.
We can see that 10-fold cross-validation accuracy does not result in performance improvement for
this model.
linkcode
21. Results and Conclusion
Table of Contents
1. In this project, I build a kNN classifier model to classify the patients suffering from breast cancer. The
model yields very good performance as indicated by the model accuracy which was found to be
0.9786 with k=7.
2. With k=3, the training-set accuracy score is 0.9821 while the test-set accuracy to be 0.9714. These
two values are quite comparable. So, there is no question of overfitting.
3. I have compared the model accuracy score which is 0.9714 with null accuracy score which is
0.6071. So, we can conclude that our K Nearest Neighbors model is doing a very good job in
predicting the class labels.
4. Our original model accuracy score with k=3 is 0.9714. Now, we can see that we get same accuracy
score of 0.9714 with k=5. But, if we increase the value of k further, this would result in enhanced
accuracy. With k=6,7,8 we get accuracy score of 0.9786. So, it results in performance improvement.
If we increase k to 9, then accuracy decreases again to 0.9714. So, we can conclude that our
optimal value of k is 7.
5. kNN Classification model with k=7 shows more accurate predictions and less number of errors than
k=3 model. Hence, we got performance improvement with k=7.
6. ROC AUC of our model approaches towards 1. So, we can conclude that our classifier does a good
job in predicting whether it is benign or malignant cancer.
7. Using the mean cross-validation, we can conclude that we expect the model to be around 96.46 %
accurate on average.
8. If we look at all the 10 scores produced by the 10-fold cross-validation, we can also conclude that
there is a relatively high variance in the accuracy between folds, ranging from 100% accuracy to
87.72% accuracy. So, we can conclude that the model is very dependent on the particular folds used
for training, but it also be the consequence of the small size of the datase