k Nearest Neighbors (Regression): Principles, Examples, and Python Implementation

k Plus Proches Voisins (Régression) : Guide Complet — Principes, Exemples et Implémentation Python

k Nearest Neighbors Regression: Complete Guide

Summary — k nearest neighbors regression is a supervised learning method that predicts a numerical value by averaging the targets of the k observations closest to the point to be predicted. Unlike k-NN classification, where neighbors vote by majority for a class, regression computes a mean (possibly distance-weighted) of the target values. This simple yet powerful approach can model complex relationships without assuming a predefined functional form.


Mathematical Principle

The core idea of k nearest neighbors regression is remarkably straightforward. Given a new example x and a training set D = {(x₁, y₁), …, (xₙ, yₙ)}, where each yᵢ is a continuous value, we identify the k closest observations in D according to a distance metric (typically Euclidean distance).

Uniform Predictions

The prediction ŷ(x) is the arithmetic mean of the targets of the k nearest neighbors:

ŷ(x) = (1/k) × Σ yᵢ

where the sum is over the indices i corresponding to the k nearest neighbors of x.

Weighted Predictions

To give more influence to the closest neighbors, inverse-distance weighting is often used:

ŷ(x) = Σ (wᵢ × yᵢ) / Σ wᵢ

where wᵢ = 1 / d(x, xᵢ) and d(·, ·) is the chosen distance. A very close neighbor will have a very high weight, while a distant neighbor will have a low weight. This variant is particularly useful when local data exhibits abrupt variations.

Distance Metrics

The choice of distance is crucial because it determines which points are considered “similar”:

  • Euclidean distance (L2): d(x, x′) = √(Σ (xⱼ − x′ⱼ)²) — the most common, corresponds to natural geometric intuition.
  • Manhattan distance (L1): d(x, x′) = Σ |xⱼ − x′ⱼ| — more robust to outliers, useful in high dimensions.
  • Minkowski distance (Lp): d(x, x′) = (Σ |xⱼ − x′ⱼ|ᵖ)^(1/p) — parametric generalization; p=2 gives Euclidean, p=1 gives Manhattan.
  • Mahalanobis distance: takes correlations between variables into account, but requires inverting the covariance matrix.

It is essential to normalize or standardize variables before applying k-NN. Indeed, a variable with a scale ten times larger will dominate the distance calculation to the detriment of others.


Intuition

Imagine you want to estimate the price of an apartment in a neighborhood you don’t know well. You don’t have a sophisticated economic model, but you know recent sale prices in the area. The k nearest neighbors regression approach consists of finding the k apartments most similar to yours (same size, same number of bedrooms, same neighborhood) and averaging their sale prices. If two apartments are nearly identical to yours and a third is somewhat different, the first two “count” more in your estimation — this is the idea of distance weighting.

What distinguishes k nearest neighbors regression from parametric methods like linear regression is that it assumes no functional form a priori. The relationship between features and the target emerges directly from the data. If the true relationship is sine-wave shaped, k-NN will discover it on its own, provided k is well chosen and there is sufficient training data.

This property makes k-NN a nonparametric and lazy learning method: no learning step is performed in advance. The training data is simply memorized, and all the “work” is deferred to prediction time.


See Also


Python Implementation

Installing Dependencies

pip install scikit-learn numpy matplotlib

Example 1: Noisy Sinusoidal Data

Let’s start with a visual example using noisy sinusoidal data:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# Generate noisy sinusoidal data
np.random.seed(42)
X = np.sort(5 * np.random.rand(200, 1), axis=0)
y = np.sin(X).ravel() + np.random.normal(0, 0.15, X.shape[0])

# Train/test split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# k-NN with k=5
knn = KNeighborsRegressor(n_neighbors=5, weights="uniform")
knn.fit(X_train, y_train)

# Predictions
y_pred = knn.predict(X_test)
print(f"R² on test: {r2_score(y_test, y_pred):.4f}")
print(f"RMSE on test: {np.sqrt(mean_squared_error(y_test, y_pred)):.4f}")

# Visualization
X_plot = np.linspace(0, 5, 500).reshape(-1, 1)
y_true_plot = np.sin(X_plot)
y_pred_plot = knn.predict(X_plot)

plt.figure(figsize=(10, 6))
plt.scatter(X_train, y_train, c="steelblue", alpha=0.5, s=20, label="Training")
plt.scatter(X_test, y_test, c="orange", alpha=0.5, s=20, label="Test")
plt.plot(X_plot, y_true_plot, c="red", linewidth=2, label="True function (sin)")
plt.plot(X_plot, y_pred_plot, c="green", linewidth=2, label="k-NN prediction (k=5)")
plt.xlabel("x")
plt.ylabel("y")
plt.title("k Nearest Neighbors Regression on Sinusoidal Data")
plt.legend()
plt.tight_layout()
plt.show()

The k-NN model captures the sinusoidal shape without any prior knowledge of the sine function — it simply averages local neighbors.

Example 2: Comparison with Linear Regression

The strength of k-NN regression becomes clear when comparing it to a linear model on nonlinear data:

from sklearn.linear_model import LinearRegression

# Same problem
X_lin = np.sort(5 * np.random.rand(200, 1), axis=0)
y_lin = np.sin(X_lin).ravel() + np.random.normal(0, 0.1, X_lin.shape[0])

# k-NN
knn_comp = KNeighborsRegressor(n_neighbors=5, weights="distance")
knn_comp.fit(X_lin, y_lin)

# Linear Regression
lr = LinearRegression()
lr.fit(X_lin, y_lin)

X_plot = np.linspace(0, 5, 500).reshape(-1, 1)

plt.figure(figsize=(10, 6))
plt.scatter(X_lin, y_lin, c="lightgray", s=15, label="Data")
plt.plot(X_plot, np.sin(X_plot), c="red", linewidth=2, label="True curve")
plt.plot(X_plot, lr.predict(X_plot), c="blue", linewidth=2, label="Linear regression")
plt.plot(X_plot, knn_comp.predict(X_plot), c="green", linewidth=2, label="k-NN (k=5, distance)")
plt.xlabel("x")
plt.ylabel("y")
plt.title("k-NN vs Linear Regression on nonlinear data")
plt.legend()
plt.tight_layout()
plt.show()

Linear regression can only draw a straight line: it completely fails to capture the sine wave. k-NN, on the other hand, follows the curve thanks to its nonparametric nature.

Example 3: Impact of Choosing k — Underfitting and Overfitting

The parameter k is the main lever of the bias-variance tradeoff:

k_values = [2, 5, 15, 50]

fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes = axes.ravel()

for i, k in enumerate(k_values):
    knn_k = KNeighborsRegressor(n_neighbors=k, weights="distance")
    knn_k.fit(X, y)
    y_plot = knn_k.predict(X_plot)

    axes[i].scatter(X, y, c="lightgray", s=10, label="Data")
    axes[i].plot(X_plot, np.sin(X_plot), c="red", linewidth=2, label="True curve", linestyle="--")
    axes[i].plot(X_plot, y_plot, c="navy", linewidth=2, label=f"k={k}")
    axes[i].set_title(f"k = {k}")
    axes[i].legend()
    axes[i].set_xlabel("x")
    axes[i].set_ylabel("y")

fig.suptitle("Impact of k on fit quality — k nearest neighbors regression")
plt.tight_layout()
plt.show()
  • k too small (k=2): the curve follows every noise fluctuation — this is overfitting. The model memorizes instead of generalizing.
  • k optimal (k=5 to k=15): the prediction follows the sine wave while smoothing the noise — this is the ideal zone.
  • k too large (k=50): the curve flattens and tends toward the global mean — this is underfitting. All local structure is lost.

To automate the choice of k, cross-validation is typically used:

from sklearn.model_selection import cross_val_score

k_range = range(1, 51)
cv_scores = []
for k in k_range:
    knn_cv = KNeighborsRegressor(n_neighbors=k, weights="distance")
    scores = cross_val_score(knn_cv, X, y, cv=5, scoring="neg_mean_squared_error")
    cv_scores.append(-scores.mean())

best_k = int(k_range[np.argmin(cv_scores)])
print(f"Best k according to cross-validation: {best_k}")

plt.figure(figsize=(8, 5))
plt.plot(list(k_range), cv_scores, marker="o")
plt.axvline(best_k, c="red", linestyle="--", label=f"Best k={best_k}")
plt.xlabel("k (number of neighbors)")
plt.ylabel("RMSE (cross-validation)")
plt.title("Optimal k selection by cross-validation")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Example 4: Multi-dimensional Synthetic Data

k nearest neighbors regression also works with several explanatory variables:

from sklearn.datasets import make_regression
from sklearn.preprocessing import StandardScaler

# Create a multi-dimensional dataset
X_multi, y_multi = make_regression(
    n_samples=1000, n_features=10, n_informative=5,
    noise=25, random_state=42
)

# Standardization — essential for k-NN
scaler = StandardScaler()
X_multi_scaled = scaler.fit_transform(X_multi)

X_tr, X_te, y_tr, y_te = train_test_split(
    X_multi_scaled, y_multi, test_size=0.25, random_state=42
)

# Comparison of uniform vs distance weighting
knn_uniform = KNeighborsRegressor(n_neighbors=7, weights="uniform")
knn_distance = KNeighborsRegressor(n_neighbors=7, weights="distance")

knn_uniform.fit(X_tr, y_tr)
knn_distance.fit(X_tr, y_tr)

r2_u = r2_score(y_te, knn_uniform.predict(X_te))
r2_d = r2_score(y_te, knn_distance.predict(X_te))

print(f"R² uniform:    {r2_u:.4f}")
print(f"R² distance:   {r2_d:.4f}")

Distance weighting often offers a slight gain in predictive quality, as it gives more confidence to the most similar neighbors.

About Salah YAHIAOUI

Salah YAHIAOUI publie sur The Pingouin des tutoriels Python et des décryptages sur l’intelligence artificielle. Les cas pratiques récents précisent leurs données, leurs tests et leurs limites. À propos, méthode éditoriale et contact.

View all posts by Salah YAHIAOUI →