SythraOpen app

K-Nearest Neighbors (KNN) in Python: Math, Distance Metrics, and Code

K-Nearest Neighbors (KNN) is an instance-based lazy learning algorithm that classifies new data points by measuring spatial distances (e.g. Euclidean) and taking a majority vote among the K closest training points.

Sythra

8 min read

XLinkedIn
K-Nearest Neighbors (KNN) in Python: Math, Distance Metrics, and Code — cover illustration

K-Nearest Neighbors (KNN) is a non-parametric, distance-based supervised learning algorithm. Rather than estimating mathematical parameter weights during a training phase, KNN stores the complete training dataset as a spatial coordinate index and predicts new observations at query time by tallying votes (for classification) or averaging values (for regression) among the KK closest neighboring data points.

Imagine moving to a new residential street and wanting to estimate your property's value. Rather than building a complex macroeconomic housing model, you look at the 3 or 5 houses physically closest to yours and take their average price. KNN operates on this exact spatial assumption: data points residing close together in multidimensional feature space share similar target labels.

1. Why KNN Is Called a 'Lazy Learner'

  • Zero Training Computation: The fit() step merely caches the training matrix (O(1)O(1) time complexity).
  • Inference-Heavy: The computational workload is deferred entirely to predict(), which computes distances to all NN training points across dd dimensions (O(Nd)O(N \cdot d) per query).
  • Non-Parametric Flexibility: Makes zero assumptions regarding linear boundaries or underlying Gaussian distributions, conforming naturally to complex, winding decision boundaries.

2. Distance Metric Taxonomy

Distance MetricMathematical FormulaProperties & Typical Usage
Euclidean Distance (L2L_2)d(a,b)=j=1n(ajbj)2d(a, b) = \sqrt{\sum_{j=1}^{n} (a_j - b_j)^2}Straight-line spatial distance. Standard default for isotropic continuous features.
Manhattan Distance (L1L_1)d(a,b)=j=1najbjd(a, b) = \sum_{j=1}^{n} |a_j - b_j|Grid-based 'city block' distance. Robust when features have differing scales or sparse values.
Minkowski Distance (LpL_p)D(a,b)=(j=1najbjp)1pD(a, b) = \left( \sum_{j=1}^{n} |a_j - b_j|^p \right)^{\frac{1}{p}}Generalization parameterized by pp: p=1    L1p=1 \implies L_1, p=2    L2p=2 \implies L_2.
Cosine Distance1abab1 - \frac{a \cdot b}{\|a\| \|b\|}Measures angular orientation rather than magnitude. Ideal for text and document embeddings.

3. The Bias-Variance Tradeoff in Choosing K

Choice of KKModel ComplexityDecision Boundary ShapePrimary Risk
K=1K = 1 (Small KK)Maximum ComplexityHighly sensitive, island-like contours around individual points.High Variance (Overfitting): Susceptible to noisy/mislabeled points.
Optimal KK (N\approx \sqrt{N} odd)BalancedSmooth, locally adaptive class contours.Optimal Generalization: Balances local sensitivity with noise rejection.
K=NK = N (Large KK)Minimum ComplexityFlat, global partition.High Bias (Underfitting): Predicts global dataset majority everywhere.

4. Worked Numerical Example (By Hand)

Consider 55 training points in 2D space. We classify query point xnew=(3,3)x_{\text{new}} = (3, 3) with K=3K = 3:

PointFeature 1Feature 2Class LabelEuclidean Distance to (3,3)(3, 3)
P112Red(31)2+(32)2=52.24\sqrt{(3-1)^2 + (3-2)^2} = \sqrt{5} \approx 2.24
P223Red(32)2+(33)2=1=1.00\sqrt{(3-2)^2 + (3-3)^2} = \sqrt{1} = 1.00
P366Blue(36)2+(36)2=184.24\sqrt{(3-6)^2 + (3-6)^2} = \sqrt{18} \approx 4.24
P431Red(33)2+(31)2=4=2.00\sqrt{(3-3)^2 + (3-1)^2} = \sqrt{4} = 2.00
P575Blue(37)2+(35)2=204.47\sqrt{(3-7)^2 + (3-5)^2} = \sqrt{20} \approx 4.47
  • Distance Ranking: P2 (1.001.00) < P4 (2.002.00) < P1 (2.242.24) < P3 (4.244.24) < P5 (4.474.47).
  • Top K=3K = 3 Neighbors: P2 (Red), P4 (Red), P1 (Red).
  • Majority Vote: 33 Red vs 00 Blue     \implies Predicted Class: Red.

5. Code: Python Implementation From Scratch & Scikit-Learn

1. NumPy KNN Classifier Engine From Scratch

import numpy as np
from collections import Counter

X_train = np.array([[1, 2], [2, 3], [6, 6], [3, 1], [7, 5]], dtype=float)
y_train = np.array(["Red", "Red", "Blue", "Red", "Blue"])

def euclidean_distance(a, b):
    return np.sqrt(np.sum((a - b) ** 2))

class SimpleKNN:
    def __init__(self, k=3, distance_weighted=False):
        self.k = k
        self.distance_weighted = distance_weighted
        self.X_train = None
        self.y_train = None

    def fit(self, X, y):
        # Lazy training step: store references
        self.X_train = np.array(X)
        self.y_train = np.array(y)

    def predict(self, x_new):
        distances = []
        for idx, x_point in enumerate(self.X_train):
            d = euclidean_distance(x_point, x_new)
            distances.append((d, self.y_train[idx]))

        # Sort by distance
        distances.sort(key=lambda item: item[0])
        neighbors = distances[:self.k]

        if not self.distance_weighted:
            labels = [label for _, label in neighbors]
            return Counter(labels).most_common(1)[0][0]
        else:
            # Inverse distance weighting: closer points carry higher voting weight
            weights = {}
            for d, label in neighbors:
                w = 1.0 / (d + 1e-6)
                weights[label] = weights.get(label, 0.0) + w
            return max(weights, key=weights.get)

knn = SimpleKNN(k=3)
knn.fit(X_train, y_train)

query = np.array([3, 3])
print(f"Predicted class for {query.tolist()}: {knn.predict(query)}")

2. Production Scikit-Learn Pipeline with Preprocessing

from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

# Distance-based models REQUIRE feature standardization
knn_pipe = make_pipeline(
    StandardScaler(),
    KNeighborsClassifier(n_neighbors=3, metric="euclidean", weights="uniform")
)
knn_pipe.fit(X_train, y_train)

print(f"Scikit-Learn Prediction: {knn_pipe.predict([[3, 3]])[0]}")
print(f"Class Probabilities: {knn_pipe.predict_proba([[3, 3]])[0]}")

6. Critical Pitfalls: The Curse of Dimensionality & Scaling

  • Mandatory Feature Standardization: If feature 1 ranges 010–1 and feature 2 ranges 050,0000–50,000, feature 2 completely dominates the distance metric. Always scale with StandardScaler.
  • The Curse of Dimensionality: As feature dimension dd grows, points become equidistant from one another in high-dimensional hypercubes, eroding relative distance metrics. Apply PCA or feature selection first.
  • Ties in Binary Classification: Always select odd values of KK (e.g. K=3,5,7K=3, 5, 7) for binary tasks to prevent 50/5050/50 vote deadlocks.

Summary

  • KNN is an instance-based lazy learner that makes predictions based on the majority vote of the KK nearest neighbors.
  • Distance Metrics: Euclidean (L2L_2) and Manhattan (L1L_1) measure spatial proximity across normalized continuous features.
  • Choosing KK balances the bias-variance tradeoff: small KK overfits to local noise; large KK underfits toward the global majority.
  • Feature scaling is strictly mandatory to prevent high-magnitude features from skewing distance measurements.

Common questions

Why is KNN called a 'lazy learner'?

KNN does not learn explicit parameters during training; it simply stores the dataset (O(1) complexity). All computations occur at query time when distances to every training sample are calculated (O(N*d) complexity).

Why is feature scaling strictly required for KNN?

Because KNN measures Euclidean or Manhattan distances, features with larger numerical magnitudes (e.g. income in thousands) would completely overwhelm features on smaller scales (e.g. age in years) without standardization.

How do you choose the optimal value for K in KNN?

K is chosen via cross-validation (typically starting around the square root of the sample size). Odd numbers are preferred for binary classification to avoid 50/50 tie votes.

What is the Curse of Dimensionality in KNN?

As the number of feature dimensions grows large, the volume of the space increases exponentially, making all data points appear roughly equidistant from each other and rendering distance metrics uninformative.