k-Nearest Neighbors Classification
Implement k-nearest neighbors classification in Python for machine learning. Hands-on steps, edge cases, and what to study next.
Focus: implement k-nearest neighbors classification
You've trained models that learn from data, but sometimes the smartest move is to let the data speak for itself. Enter k-nearest neighbors (k-NN), a classification algorithm so intuitive that it's often the first stop for any ML practitioner. This lesson walks you through implementing k-nearest neighbors classification in Python — from the mental model to production-ready code — so you can classify new data with confidence, even when the boundary between classes is messy.
The Problem This Lesson Solves
Imagine you're building a system to detect whether a mushroom is edible or poisonous based on features like cap diameter and gill color. You have hundreds of labeled examples, but you can't write explicit if-else rules for every combination. Rules fail when data overlaps or when you encounter a new example that doesn't fit your predefined categories.
k-NN solves this by using the data itself as the decision rule. Instead of learning a function that maps features to labels, it classifies a new point by looking at the closest labeled points in your dataset. It's a lazy learner — it doesn't build a model during training; it just stores the training data. That simplicity is its superpower: no assumptions about data distribution, no complex training loop, and it works surprisingly well for many real-world problems.
By the end of this lesson, you'll understand the core idea behind k-NN, implement it from scratch with Python, and then use scikit-learn's production-ready KNeighborsClassifier to classify real data.
Core Concept / Mental Model
Think of voting by your neighbors. If you move to a new city and want to know if a neighborhood is safe, you ask the five people living closest to you. If four out of five say it's safe, you'd likely feel comfortable. That's k-NN in a nutshell.
Formally, k-NN works in feature space — a multi-dimensional space where each axis is a feature (e.g., height, weight, color intensity). Each labeled data point is a dot in this space. To classify a new, unlabeled point:
- Calculate the distance (often Euclidean) between the new point and every training point.
- Select the k training points with the smallest distances — these are your neighbors.
- Take a majority vote among the labels of those k neighbors. The new point gets the label that appears most often.
If k is too small (like 1), you risk overfitting to noise. If k is too large, you wash out local patterns. The sweet spot is found empirically, usually with odd values to avoid ties in binary classification.
Key Definitions
- Feature vector: An array of numbers representing a data point.
- Distance metric: A function that quantifies similarity; Euclidean distance is the default.
- Training set: Labeled examples used for classification.
- Query point: The unlabeled example you want to classify.
How It Works Step by Step
Here's the algorithmic flow — the core logic you'll implement:
- Store the training data: Keep all feature vectors and their labels.
- Compute distances: For a new query point, calculate the distance to every training point.
- Sort distances: Rank the training points by increasing distance.
- Select k neighbors: Take the first k entries from the sorted list.
- Vote: Count the labels among those k neighbors.
- Predict: Return the label with the highest count.
The Mathematics: Euclidean Distance
For two points p and q in n-dimensional space, the Euclidean distance is:
d(p, q) = sqrt( (p₁ - q₁)² + (p₂ - q₂)² + ... + (pₙ - qₙ)² )
In NumPy, this becomes: np.sqrt(np.sum((p - q)**2)).
Choosing k
- k = 1: Perfectly memorizes the training set — high variance, poor generalization.
- k too large: Decisions become global, ignoring local structure — high bias.
- Odd k avoids tie in binary classification.
You'll typically try k = 3, 5, 7 and evaluate with cross-validation.
Hands-On Walkthrough
Let's implement k-NN from scratch first to build intuition, then we'll use scikit-learn for the production version. We'll use the classic Iris dataset — 150 flowers with 3 species, 4 features, perfect for classification.
Step 1: Implement from Scratch
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
def euclidean_distance(p, q):
return np.sqrt(np.sum((p - q) ** 2))
def k_nearest_neighbors(X_train, y_train, query, k=5):
distances = [(i, euclidean_distance(query, x)) for i, x in enumerate(X_train)]
distances.sort(key=lambda t: t[1])
neighbors = [y_train[i] for i, _ in distances[:k]]
vote = Counter(neighbors)
return vote.most_common(1)[0][0]
# Load data and split
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.2, random_state=42
)
# Test on the first test sample
pred = k_nearest_neighbors(X_train, y_train, X_test[0], k=5)
print(f"Predicted: {iris.target_names[pred]}, Actual: {iris.target_names[y_test[0]]}")
# Expected output: Predicted: setosa, Actual: setosa
This simple code works because the Iris data has clear boundaries. Notice we used most_common(1) to get the majority vote.
Step 2: Evaluate Accuracy on the Test Set
def predict_all(X_train, y_train, X_test, k=5):
return [k_nearest_neighbors(X_train, y_train, x, k) for x in X_test]
predictions = predict_all(X_train, y_train, X_test, k=5)
accuracy = np.mean(predictions == y_test)
print(f"Accuracy: {accuracy * 100:.2f}%")
# Expected output: Accuracy: 100.00% (or 96.67% depending on split)
Step 3: Use scikit-learn's Optimized Implementation
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier(n_neighbors=5)
model.fit(X_train, y_train)
accuracy = model.score(X_test, y_test)
print(f"Scikit-learn accuracy: {accuracy * 100:.2f}%") # ▶ 100.0%
The scikit-learn version uses efficient data structures (KD-trees or ball trees) under the hood, making it much faster on large datasets.
Pro tip: Always use scikit-learn for real projects. The from-scratch code is for learning only — it's slow for large datasets because it computes all distances each time.
Compare Options / When to Choose What
| Approach | Pros | Cons | Best For |
|---|---|---|---|
| From scratch | Understands the algorithm fully | Slow, no optimizations | Learning, small toy datasets |
| scikit-learn KNeighborsClassifier | Fast, supports multiple distance metrics, cross-validation, parallel processing | Less control over internals | Production, medium to large datasets |
| KD-tree / Ball-tree | Faster neighbor search than brute force | Only for low-dimensional data | Large datasets with < 20 features |
Variations to explore: - Distance metrics: Manhattan (L1) vs Euclidean (L2) changes the decision boundary. - Weighted voting: Give closer neighbors more influence (useful for noisy data). - Radius-based neighbor search: Fixed radius instead of fixed k — good for uneven density.
Troubleshooting & Edge Cases
1. Feature Scaling is Critical
If one feature has a much larger range (e.g., salary in dollars vs. age), it dominates the distance calculation, making other features useless. Always standardize features to mean 0 and variance 1.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # fit only on training!
2. Ties in Voting
With even k, you can get a tie. Use odd k. Alternatively, use weighted voting to break ties by distance.
3. The Curse of Dimensionality
As the number of features increases, all points become equidistant, and k-NN loses meaning. If you have hundreds of features, reduce dimensionality (e.g., PCA) or switch to another algorithm.
4. Imbalanced Classes
If one class has 99% of the data, k-NN will always predict that class. Use stratified sampling or resampling techniques like SMOTE.
5. Wrong Accuracy from Data Leakage
Never fit the scaler on the entire dataset before splitting — you leak information from the test set. Always fit on training data only, as shown above.
What You Learned & What's Next
You now understand the core idea behind k-NN: classification by neighbor voting. You implemented it from scratch, used scikit-learn's optimized version, and learned how to choose k and handle edge cases. You can now confidently implement k-nearest neighbors classification for your own projects.
As next steps in your Python for machine learning path:
- Explore hyperparameter tuning with GridSearchCV to find the best k automatically.
- Learn about other lazy learners like RadiusNeighborsClassifier.
- Move on to model evaluation with cross-validation to get robust accuracy estimates.
The key takeaway? k-NN is a powerful baseline — simple to implement, hard to beat on small datasets, and a great first model to try before reaching for complex algorithms.
Now go ahead and practice by applying k-NN to a dataset from sklearn.datasets like the breast cancer dataset. Try different k values and distance metrics, and see how feature scaling affects your accuracy.
Practice recap
Try applying k-NN to the breast cancer dataset (available in sklearn.datasets). First split the data, scale it, and then compare accuracy with k=3, 5, 7. Use cross_val_score to get a more robust estimate. Observe how accuracy changes with k and whether scaling improves your results.
Common mistakes
- Forgetting to scale features — if one feature has a larger range, it can dominate the distance calculation and make other features irrelevant.
- Using even k in binary classification without handling ties — this can lead to unpredictable predictions instead of a majority vote.
- Fitting the scaler on the entire dataset before splitting — this leaks test information into the training process and inflates accuracy.
- Assuming k-NN works well with high-dimensional features — the curse of dimensionality degrades its performance dramatically.
Variations
- Use Manhattan distance instead of Euclidean for high-dimensional or grid-like data.
- Weight neighbors by inverse distance to give closer points more influence.
- Use RadiusNeighborsClassifier to classify based on a fixed radius instead of k — useful when data density varies.
Real-world use cases
- Recommendation systems: find similar users or products by feature vectors.
- Fraud detection: classify transactions as fraudulent or legitimate by comparing to known examples.
- Image recognition: identify handwritten digits by pixel similarity.
Key takeaways
- k-NN classifies by majority vote among the k closest training points.
- Distance metrics and feature scaling significantly affect performance.
- Choose odd k to avoid ties and tune k via cross-validation.
- Scikit-learn's KNeighborsClassifier is the production-ready choice.
- Always scale features before applying k-NN.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.