K-Nearest Neighbors

K-Nearest Neighbors (KNN) is a non-parametric, instance-based learning algorithm that classifies or regresses a new data point by finding the K most similar examples in the training set and aggregating their labels. It matters because it requires zero training time, is trivially interpretable, and serves as the conceptual foundation for many modern similarity-based systems. The counterintuitive fact: KNN has no explicit "training" phase — the entire dataset is the model, which means prediction gets slower as you add more data, the exact opposite of most algorithms.

The core insight

The fundamental assumption is beautifully simple: things that are similar to each other tend to belong to the same category. When you see a new, unknown point, find the K training points closest to it in feature space and let them "vote" on what the new point should be. There is no equation to fit, no weights to learn — the data speaks for itself at prediction time. The "aha moment" is realizing that you can build a surprisingly powerful classifier without ever explicitly learning anything, just by remembering everything.

How It Works

KNN operates in two conceptual phases: storage and lookup. During storage, you simply keep the entire training dataset in memory. During lookup (prediction), you measure the distance from the query point to every stored point, find the K closest, and aggregate their labels.

Distance Metric

The most common distance metric is Euclidean distance:

1
d(A,B)=∑i=1n(Ai−Bi)2\quad d(A, B) = \sqrt{\sum_{i=1}^{n}(A_i - B_i)^2}

Explanation: The straight-line distance between two points A and B across all n features — the familiar ruler distance extended into many dimensions.

Symbols:

  • d(A,B)d(A, B): distance between points A and B
  • AiA_i, BiB_i: the value of the i-th feature for points A and B respectively
  • nn: total number of features (dimensions)
  • ∑\sum: sum over all features

Another common choice is Manhattan distance, which adds absolute differences instead of squaring them:

2
d(A,B)=∑i=1n∣Ai−Bi∣\quad d(A, B) = \sum_{i=1}^{n}|A_i - B_i|

Explanation: The "city block" distance — how far you'd travel if you could only move along grid lines, never diagonally.

Classification Decision

Once K neighbors are found, the predicted class is determined by majority vote:

3
y^=arg⁡max⁡c∑i=1K1(yi=c)\quad \hat{y} = \arg\max_{c} \sum_{i=1}^{K} \mathbb{1}(y_i = c)

Explanation: Pick whichever class label c appears most often among the K neighbors — literally count the votes and choose the winner.

Symbols:

  • y^\hat{y}: the predicted class label
  • arg⁡max⁡c\arg\max_c: the class c that maximizes the expression
  • 1(yi=c)\mathbb{1}(y_i = c): an indicator that equals 1 if neighbor i has class c, else 0
  • K: the number of neighbors to consider

Python Implementation

Here's KNN from scratch, and then using scikit-learn: ```python import numpy as np from collections import Counter from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score

# ── KNN from scratch ───────────────────────────────────────── class SimpleKNN: def __init__(self, k=3): self.k = k def fit(self, X, y):

# "Training" is just storing the data — that's it! self.X_train = X self.y_train = y def predict(self, X): return np.array([self._predict_one(x) for x in X]) def _predict_one(self, x):

# Step 1: compute distances to ALL training points distances = np.sqrt(np.sum((self.X_train - x) ** 2, axis=1))

# Step 2: find the K nearest indices k_indices = np.argsort(distances)[: self.k]

# Step 3: majority vote k_labels = self.y_train[k_indices] most_common = Counter(k_labels).most_common(1) return most_common[0][0]

# ── Load and split data ─────────────────────────────────────── iris = load_iris() X, y = iris.data, iris.target X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 )

# ── IMPORTANT: scale features before KNN ───────────────────── scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)

# ── From-scratch model ──────────────────────────────────────── knn_scratch = SimpleKNN(k=3) knn_scratch.fit(X_train_scaled, y_train) scratch_preds = knn_scratch.predict(X_test_scaled) print(f"Scratch accuracy: {accuracy_score(y_test, scratch_preds):.3f}")

# ── Scikit-learn model ──────────────────────────────────────── knn_sklearn = KNeighborsClassifier(n_neighbors=3, metric="euclidean") knn_sklearn.fit(X_train_scaled, y_train) sklearn_preds = knn_sklearn.predict(X_test_scaled) print(f"Sklearn accuracy: {accuracy_score(y_test, sklearn_preds):.3f}")

# ── Tune K with cross-validation ───────────────────────────── from sklearn.model_selection import cross_val_score for k in [1, 3, 5, 7, 11, 15]: scores = cross_val_score( KNeighborsClassifier(n_neighbors=k), X_train_scaled, y_train, cv=5 ) print(f"K={k:2d} CV accuracy: {scores.mean():.3f} ± {scores.std():.3f}") ``` ``` Scratch accuracy: 1.000 Sklearn accuracy: 1.000 K= 1 CV accuracy: 0.942 ± 0.026 K= 3 CV accuracy: 0.958 ± 0.033 K= 5 CV accuracy: 0.958 ± 0.033 K= 7 CV accuracy: 0.967 ± 0.021 K=11 CV accuracy: 0.950 ± 0.038 K=15 CV accuracy: 0.942 ± 0.026 ``` Notice that K=7 wins on this dataset — neither the smallest nor the largest K.

Feature Scaling Is Mandatory

KNN uses raw distances, so a feature measured in kilometres will dominate a feature measured in metres even if the metres feature is more informative. Always scale: ```python

# Without scaling — feature with large range dominates distance

# With StandardScaler — each feature has mean=0, std=1

# With MinMaxScaler — each feature is in [0, 1] from sklearn.preprocessing import MinMaxScaler

# Demonstrate the danger of not scaling X_bad = np.column_stack([ np.random.randn(100) 1000, # feature 1: huge range np.random.randn(100) 0.01, # feature 2: tiny range ])

# Distance will be almost entirely determined by feature 1!

# Feature 2 is effectively invisible to KNN. print("Always scale your features before using KNN.") ```

researcher · Evelyn Fix | 1904 – 1965 | Invented K-Nearest Neighbors

Evelyn Fix was an American statistician at UC Berkeley whose work focused on nonparametric methods and statistical theory. In 1951, she co-authored an unpublished technical report with Joseph Hodges introducing the discriminatory analysis method that became KNN — one of the most-cited unpublished works in all of statistics. Remarkably, because the report was never formally published in a journal, Fix never received the academic recognition commensurate with the enormous impact of her idea during her lifetime.

Figure
KNN Decision Boundary — K=1 vs K=5
KNN: Finding K Nearest Neighbors Feature Space query? K=3 search radius 1 2 3 Voting (K=3) Neighbor 1 → Class A ✓ Neighbor 2 → Class A ✓ Neighbor 3 → Class B ✗ Prediction: Class A (2 votes vs 1 vote) Class A Class B Query point
Figure
Curse of Dimensionality — Why KNN Struggles in High Dimensions
The Curse of Dimensionality 2D space neighbors nearby ✓ add dims 100D space must search far ✗ Curse of Dimensionality • All points become equidistant • "Near" loses meaning → use PCA first! Same number of points — but space expands exponentially with each new dimension

Intuition

The Mental Model

Imagine you've just moved to a new city and want to know if a neighbourhood is safe. You don't have a formula — instead, you ask the five nearest residents about their experience. If four of them say "it's great", you conclude it's probably fine. That's exactly KNN: the neighbourhood of a point tells you what that point probably is. Another analogy: a doctor diagnosing a rare condition might think "this patient's symptoms most closely resemble three previous patients who all had Disease X — so probably Disease X." The doctor isn't running equations; she's doing nearest-neighbour lookup in her memory of past cases. The key insight is that this works because the world has structure — similar inputs tend to produce similar outputs, and that structure is preserved in the geometric arrangement of your data points.

Historical Development

Origins · 1 minhow this was discovered
History
1951
Evelyn Fix and Joseph Hodges publish "Discriminatory Analysis, Nonparametric Discrimination: Consistency Properties" as a USAF School of Aviation Medicine technical report — the original, unpublished KNN paper.
1967
Thomas Cover and Peter Hart publish "Nearest Neighbor Pattern Classification" in IEEE Transactions on Information Theory, giving the first rigorous theoretical analysis and proving that KNN's error rate is at most twice the Bayes optimal error rate.
1977
The term "k-nearest neighbor" becomes standardized in the statistical pattern recognition literature following its widespread adoption in textbooks of the era.
1985
Jerome Friedman, Jon Bentley, and Raphael Finkel introduce the KD-tree data structure, enabling sub-linear time nearest neighbor search and making KNN practical on larger datasets.
1991
The "curse of dimensionality" (originally coined by Bellman in 1957) is formally connected to KNN's failure modes — researchers demonstrate that in high dimensions all pairwise distances converge, destroying the neighborhood concept.
1997
Ball trees are proposed as an alternative to KD-trees for high-dimensional data, improving KNN scalability somewhat.
2001
Cover trees are introduced, providing theoretically optimal nearest neighbor search for intrinsically low-dimensional data embedded in high dimensions.
2004
Locality Sensitive Hashing (LSH) enables approximate KNN at scale, making the algorithm viable for million-point datasets in production systems.
2010
Scikit-learn 0.1 ships with a high-quality KNeighborsClassifier implementation, cementing KNN as a standard baseline in the modern ML toolkit.
2020
Approximate nearest neighbor libraries (FAISS, HNSW, Annoy) revive KNN ideas at billion-scale for embedding-based retrieval in modern deep learning systems.

Why It Matters

Real-World Impact
Recommendation Systems (early Netflix, Amazon)Early collaborative filtering used KNN on user-item rating vectors — "find the K users most similar to you and recommend what they liked." This was the dominant recommendation approach before matrix factorisation and deep learning took over.
Medical Diagnosis SystemsKNN is widely used in medical imaging and clinical decision support where interpretability is critical — a doctor can literally be shown the K most similar historical patient cases, making the prediction auditable and explainable in a way neural networks cannot match.
Anomaly Detection in CybersecurityNetwork intrusion detection systems (NIDS) use KNN to flag unusual traffic patterns — if a new packet's K nearest neighbors in feature space are all normal, it's likely normal; if they're all known attacks, raise an alert.
Image Recognition (CIFAR-10 baseline)Before deep learning, pixel-space KNN on CIFAR-10 achieved ~40% accuracy. Researchers still use KNN as a sanity-check baseline; if your model can't beat KNN, something is wrong.
Geospatial ApplicationsTools like Google Maps use KNN concepts to answer "find the 5 nearest coffee shops to this GPS coordinate" — spatial indexing via KD-trees makes this real-time.
Drug DiscoveryIn cheminformatics, molecular fingerprints (binary feature vectors) are compared using KNN with Tanimoto distance to find compounds with similar biological activity to a query molecule.
Embedding-Based Retrieval (Modern AI)Systems like semantic search (Pinecone, Weaviate, FAISS) use approximate KNN on high-dimensional embeddings from transformer models — KNN is at the heart of every vector database powering RAG systems today.

Prerequisites

What This Leads To

See Also

Decision Trees · Naive Bayes · Logistic Regression · Principal Component Analysis · DBSCAN Clustering

Before this

Where this leads