Implementing k Nearest Neighbours from scratch in Python

This post is a follow up from the previous post. I will work through an implementation from scratch in python.

The code is saved at my github.

The data

The data is a well known dataset related to features of Iris flowers, from the Iris Plants Database. It might be the best known dataset in classification literature, related to several classic papers. The data is here, and more info on the dataset is here.

The code

First, importing the dataset and having a look at the data.

# import required packages
from csv import reader
from math import sqrt

#create a simple function for loading csvs

def load_csv(filename):
    dataset = list()
    with open(filename, 'r') as file:
        csv_read = reader(file)
        for row in csv_read:
            if not row:
                continue
            dataset.append(row)
    return dataset

#identify the file name and load the data

filename = 'iris.csv'
dataset = load_csv(filename)

#view the data

#note it is all in string format and needs to be reformatted

dataset
First view of the data.

Then cleaning the data, converting strings to the correct formats.

# function to convert string column to float

#strip removed trailing and leading blanks
#float function converts to float

def str_to_float(dataset, col):
    for row in dataset:
        row[col] = float(row[col].strip())

#apply the formula to all but the last column

for i in range(len(dataset[0])-1):
    str_to_float(dataset, i)

#now need to clean up the categorical variable, convert it to an integer.

def str_to_int(dataset, col):
    #identify classes
    classes = [row[col] for row in dataset]
    #get distinct classes
    distinct = set(classes)
    #initialise dictionary of class values
    lookup = dict()
    #fill dict with integer for each class value
    for i, value in enumerate(distinct):
        lookup[value] = i
        print('[%s] = %d' % (value, i))
    #lookup into the dictionary, based on the string
    for row in dataset:
        row[col] = lookup[row[col]]
    return lookup 

 #apply formula to last column
    
str_to_int(dataset, len(dataset[0])-1)

Cleaner!

The algorithm

Functions to calculate euclidean distance, identify neighbours, and apply the algorithm to new data.


# Calculate the Euclidean distance
def euclid_dist(r1, r2):
    #initialise distance
    distance = 0.0
    #loop through rows
    for i in range(len(r1)-1):
        distance += (r1[i] - r2[i])**2
    return sqrt(distance)

# Locate the most similar neighbors
def get_neighbours(train, test_row, num_neighbours):
    #train is the training dataset
    #test_row is a single observation
    #num_neighbours is the predefined k value
    
    #initialise list
    distances = list()
    #loop through train rows
    for train_row in train:
        #measure distance betweeen test row and train row
        dist = euclid_dist(test_row, train_row)
        #add train row and distance to distances dataset
        distances.append((train_row, dist))
    #sort by distance
    distances.sort(key=lambda tup: tup[1])
    neighbours = list()
    #identify top k neighbours
    for i in range(num_neighbours):
        neighbours.append(distances[i][0])
    return neighbours
 

# Make a prediction

def predict_class(train, test_row, num_neighbours):
    #identify neighbours using neighbour function
    neighbours = get_neighbours(train, test_row, num_neighbours)
    #keep the class variable
    output = [row[-1] for row in neighbours]
    #assign the most common category as the prediction
    prediction = max(set(output), key=output.count)
    return prediction
 

and then applying it to a single new input!

# define k
num_neighbours = 5
# define a new record
row = [4.3,1.1,2.2,1.1]
# predict the class
label = predict_class(dataset, row, num_neighbours)
print('Data=%s, Predicted: %s' % (row, label))
It runs!

Testing the accuracy of the algorithm

import random

random.shuffle(dataset)

train_data = dataset[:120]
test_data = dataset[120:]

num_neighbours = 5

correct = 0
for i in range(len(test_data)):
    y = predict_class(train_data, test_data[i][0:4], num_neighbours)
    if y == test_data[i][4]:
        correct += 1
    print(y, test_data[i][4])

29 out of 30 correct classifications in the test dataset. Not bad!

Leave a Comment