04 K-Nearest Neighbors

90-Minute Main Lesson: Distance → Neighbors → Out-of-Sample Choice

  • Objectives: Calculate and interpret distances; explain training-only scaling; choose \(k\) with time-based validation using Euclidean distance and uniform voting; diagnose distance concentration.

  • learning path:

    • prerequisites and distance 15 min → scaling and \(k\) 20 min → bias–variance and high-dimensional distance checks 20 min → Fuyao Glass main case 25 min → transfer and feedback 10 min.

    • Weighted KNN, regression, and KD-trees are extensions.

  • Answer first: What are the Euclidean and Manhattan distances from \((0,0)\) to \((3,4)\)?

  • Feedback: They are \(5\) and \(7\). Choose a metric from the task’s meaning of similarity, not by habit.

Today’s Agenda: Credit Risk Assessment

Imagine you are a credit manager, and a new customer applies for a loan.

Your core task is:

  • Assess Risk: How likely is this customer to default in the future?
  • Make a Decision: Should you approve or reject the loan?

This is a classic business classification problem.

Credit Risk Assessment Icon An icon symbolizing the process of evaluating a loan application for risk.

The Data-Driven Solution

You are not guessing blindly. Your bank possesses valuable historical data.

  • Existing Data: Complete records of thousands of past customers.
    • Features: Income, debt, age, occupation…
    • Outcome: Paid on time ✅ or Defaulted ❌
  • Our Approach: Use this data to profile the new customer’s risk. This is where the K-Nearest Neighbors (KNN) algorithm comes into play.

KNN’s Core Idea: ‘Birds of a feather flock together’

The philosophy of the KNN algorithm is very simple: To determine the class of a new sample, just look at which class its nearest ‘neighbors’ belong to.

It’s like trying to figure out a stranger’s hobbies; the easiest way is to see what kind of people their closest friends are.

K-Nearest Neighbors (KNN) Concept A new data point is classified based on the majority class of its three nearest neighbors. ? Class A Class B k=3 Neighborhood 2 Class A, 1 Class B ⇒ Classified as Class A

The Business Analogy for KNN

Machine Learning Concept Business Analogy (Credit Assessment)
New Sample (Unknown Class) A new loan applicant
Neighbors (Known Class) Historical customers in the database who are most similar to the new applicant
Class ‘Will Default’ or ‘Will Not Default’
Measure of ‘Closeness’ Similarity of customer features (e.g., income, age, debt)
The value ‘K’ How many similar customers do we consult to make a decision?

Our Learning Roadmap Today

  1. Core | Define the neighbor rule: how distance, \(k\), and voting jointly produce a prediction.
  2. Core | Scale before searching: fit preprocessing on training data only and explain why scale changes the neighbors.
  3. Core | Select the model chronologically: choose in advance Euclidean distance and uniform voting, then choose \(k\) with training-only ordered folds.
  4. Core | evaluate on the final test period: after choosing the model on validation data, report prevalence, ROC-AUC, AP, and a confusion matrix once and state its limits.
  5. Extension | Mechanisms and scale: weighted KNN, regression, KD/Ball Trees, and high-dimensional probability derivations; Core retains one normalized-distance intuition and its algorithmic implication.

4.1 Formal Definition of the k-NN Rule

Given a sample \(x_n\) to be classified, the KNN decision process involves two steps:

  1. Find Neighbors: In the entire dataset, identify the \(k\) samples that are closest in distance to \(x_n\).
  2. Majority Vote: Among these \(k\) neighbors, use a majority vote to assign the most frequent class as the predicted class for \(x_n\).

\[ \large{y_n = \underset{c \in \mathcal{Y}}{\arg\max} \sum_{i=1}^k I(y_i = c)} \tag{1}\]

Here, \(I(\cdot)\) is an indicator function. It is 1 if the class \(y_i\) of neighbor \(i\) is equal to class \(c\), and 0 otherwise. This formula is essentially counting votes.

Understanding the Three Key Elements of KNN

To successfully apply the KNN algorithm, we must clearly define three core components. They are the building blocks of any KNN model.

  • Distance Metric
    • How do we quantify the ‘similarity’ between samples?
  • The Choice of \(k\)
    • How many neighbors should we look at? One or ten?
  • Decision Rule
    • How do we make the final judgment based on the neighbors? (e.g., simple majority vote)

Key Element 1: The Distance Metric

How do we define ‘close’? This depends on the distance metric we choose.

Distance Metric Concept Two points are connected by a scaled line, representing the measurement of distance between them. Distance Metric Sample A Sample B d(A, B) = ?

Key Element 2: The Choice of k

How many neighbors should we consult? This is the critical dial for model complexity.

The Choice of k in KNN A central point is shown with two concentric circles representing k=3 and k=8 neighborhoods. The Choice of k k=3 k=8

Key Element 3: The Decision Rule

How do we aggregate the ‘opinions’ of the neighbors? The most common method is ‘majority rules’.

KNN Decision Rule: Majority Vote Five neighbors (3 circles, 2 squares) vote, and the majority class (circle) determines the final classification. Decision Rule: Majority Vote Classes of k=5 Neighbors Final Decision: Class A

Deep Dive: Distance Metrics

The similarity between samples is measured by a distance function. For two \(d\)-dimensional samples \(x_i\) and \(x_j\), common distance metrics include:

  • Euclidean Distance: The familiar straight-line distance, and the most commonly used in KNN.

    \[ \large{L_2(x_i, x_j) = \sqrt{\sum_{l=1}^d (x_{il} - x_{jl})^2}} \]

  • Manhattan Distance: Imagine driving in a city where you can only travel along a grid.

    \[ \large{L_1(x_i, x_j) = \sum_{l=1}^d |x_{il} - x_{jl}|} \]

Visualizing Distance Metrics

Euclidean distance is ‘as the crow flies’, while Manhattan distance is ‘walking the blocks’.

Euclidean vs. Manhattan Distance A diagram showing two points on a grid. The Manhattan distance is the sum of the horizontal and vertical paths. The Euclidean distance is the direct diagonal path. Euclidean vs. Manhattan Distance Point A (xA, yA) B (xB, yB) |xₓ - xₐ| |yₐ - yₓ| Manhattan distance (L₁) Walk along the axes L₁ = |Δx| + |Δy| Euclidean distance (L₂) Shortest straight path L₂ = √(Δx² + Δy²)

Critical Prerequisite: Data Standardization

Important Note: Distance metrics are highly sensitive to the scale of features.

  • The Problem: If you directly calculate distance using ‘Annual Income’ (in thousands) and ‘Age’ (in years), the large numerical values of income will completely dominate the calculation, making the effect of age negligible.
  • The Solution: Data Standardization. Transform all features to a similar scale (e.g., a mean of 0 and a standard deviation of 1).

Before applying KNN, data standardization is almost always a mandatory step.

Why Standardization Is Crucial

The Importance of Data Standardization for KNN A before-and-after comparison showing how feature scaling prevents one feature (Income) from dominating distance calculations over another (Age). 1. Before Standardization Income (Range: 10k-100k) Age (Range: 20-60) A B Distance is dominated by income. ΔIncome overwhelms the smaller contribution from ΔAge. 2. After Standardization Income Z-score Age Z-score A' B' Both features share one scale. ΔZ-Income and ΔZ-Age contribute comparably.

Deep Dive: The Choice of k

The choice of \(k\) directly impacts the model’s performance and represents a Bias-Variance Tradeoff.

  • Small \(k\) values:
    • The model is more complex, with a more irregular decision boundary.
    • It is susceptible to noise, leading to Overfitting.
    • Low Bias, High Variance.
  • Large \(k\) values:
    • The model is simpler, with a smoother decision boundary.
    • It may ignore local, subtle structures in the data, leading to Underfitting.
    • High Bias, Low Variance.

Small k: High Flexibility, Prone to Overfitting

When k is small (e.g., k=1), the model’s decision boundary becomes highly convoluted, trying to perfectly match every training point, including noise.

KNN Overfitting with k=1 A new point is shown being misclassified because its single nearest neighbor is a noise point from a different class. KNN Algorithm: How k=1 Leads to Overfitting New Point Nearest neighbor (k=1) Noise / outlier point 1. Find nearest neighbor 2. It is the pink square (a noise / outlier point) 3. Predict pink Noise drives the error

Large k: High Stability, Prone to Underfitting

When k is large (e.g., k=N), the model ignores local data structures, and the decision boundary becomes overly simple, failing to capture class differences.

KNN Underfitting with a Large k The left panel shows a large neighborhood containing seven blue and four red samples, so a new point near the local red cluster is predicted blue by the global majority. Three separate cards on the right explain global voting, majority dominance, and high-bias underfitting. Large k: Global Voting Erases Local Structure Blue region Red region Global k=N neighborhood Query 1. Global reach Local signal fades 2. Majority wins Blue dominates 3. Red cluster lost High bias / underfit Too smooth

Finding the Optimal k Value

So, how do we find that ‘just right’ value of \(k\)?

  • It’s not a guessing game: We can’t choose based on intuition.
  • A systematic approach:
    • We typically use Cross-Validation.

    • We split the data into several folds, iteratively use one part as a ‘mini test set’ to evaluate the performance of different \(k\) values, and finally select the \(k\) that performs the best on average.

We will demonstrate this process in the upcoming Python practical session.

Let’s Visualize KNN with an Example

Suppose we have a dataset of tumors, classified as ‘Benign’ or ‘Malignant’. A new patient arrives (represented by ?), and we need to determine if their tumor is benign or malignant.

Code
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_blobs
input_feature_matrix, target_values = make_blobs(n_samples=50, centers=2, random_state=4, cluster_std=1.5)
new_point = np.array([[-3, 8]])
plt.style.use('seaborn-v0_8-whitegrid')  # 展示当前步骤的结果。
plt.figure(figsize=(10, 6))  # 展示当前步骤的结果。
sns.scatterplot(x=input_feature_matrix[target_values==0, 0], y=input_feature_matrix[target_values==0, 1], s=120, label='Benign', marker='o', color='royalblue', ec='black')  # 展示当前步骤的结果。
sns.scatterplot(x=input_feature_matrix[target_values==1, 0], y=input_feature_matrix[target_values==1, 1], s=120, label='Malignant', marker='X', color='darkorange', ec='black')  # 展示当前步骤的结果。
plt.scatter(new_point[:, 0], new_point[:, 1], s=300, c='red', marker='P', label='Query', ec='black', linewidth=1.5)  # 展示当前步骤的结果。
plt.title('A New Sample to be Classified', fontsize=20, pad=15)  # 展示当前步骤的结果。
plt.xlabel('Feature 1 (Texture)', fontsize=20)  # 保证投影时坐标轴标题可读。
plt.ylabel('Feature 2 (Radius)', fontsize=20)  # 保证投影时坐标轴标题可读。
plt.legend(loc='upper right', fontsize=20)  # 保证投影时图例可读。
plt.tick_params(axis='both', labelsize=20)  # 放大刻度文字以满足投影阅读距离。
plt.gca().set_aspect('equal', adjustable='box')  # 展示当前步骤的结果。
plt.tight_layout()  # 展示当前步骤的结果。
plt.show()  # 展示当前步骤的结果。
A new sample with labeled tumor data
Figure 1: A new sample with labeled tumor data

When k=1: The Closest Neighbor Decides Everything

1-NN is the simplest form of KNN. We only look at the single closest neighbor.

In this example, the sample closest to the new patient belongs to the ‘Malignant’ class, so 1-NN would classify it as Malignant. This could be a flawed judgment influenced by noise.

Code
input_feature_matrix, target_values = make_blobs(n_samples=50, centers=2, random_state=4, cluster_std=1.5)
new_point = np.array([[-3, 8]])
distances_k1 = np.sqrt(np.sum((input_feature_matrix - new_point)**2, axis=1))
nearest_neighbor_idx = np.argmin(distances_k1)
plt.style.use('seaborn-v0_8-whitegrid')  # 展示当前步骤的结果。
fig, ax = plt.subplots(figsize=(14, 6))  # 为主图与右侧独立图例预留各自的投影空间。
sns.scatterplot(x=input_feature_matrix[target_values==0, 0], y=input_feature_matrix[target_values==0, 1], s=100, label='Benign', marker='o', color='royalblue', alpha=0.4)  # 展示当前步骤的结果。
sns.scatterplot(x=input_feature_matrix[target_values==1, 0], y=input_feature_matrix[target_values==1, 1], s=100, label='Malignant', marker='X', color='darkorange', alpha=0.4)  # 展示当前步骤的结果。
plt.scatter(new_point[:, 0], new_point[:, 1], s=300, c='red', marker='P', label='New Patient', ec='black', linewidth=1.5)  # 展示当前步骤的结果。
plt.scatter(input_feature_matrix[nearest_neighbor_idx, 0], input_feature_matrix[nearest_neighbor_idx, 1], s=350,
            facecolors='none', edgecolors='green', linewidth=3, label='Nearest (k=1)')
ax.set_title('k=1: closest neighbor is malignant', fontsize=34, pad=12)  # 保持投影可读,并避免标题压缩主图。
ax.set_xlabel('Feature 1 (Texture)', fontsize=34)  # 保证投影时坐标轴标题可读。
ax.set_ylabel('Feature 2 (Radius)', fontsize=34)  # 保证投影时坐标轴标题可读。
ax.legend(loc='center left', bbox_to_anchor=(1.03, 0.5), fontsize=34, frameon=True)  # 将图例移入专用右侧走廊,避免遮挡数据。
ax.tick_params(axis='both', labelsize=34)  # 放大刻度文字以满足投影阅读距离。
ax.set_aspect('equal', adjustable='box')  # 展示当前步骤的结果。
fig.tight_layout()  # 展示当前步骤的结果。
plt.show()  # 展示当前步骤的结果。
The decision process for 1-NN
Figure 2: The decision process for 1-NN

When k=5: Majority Vote Reverses the Outcome

Now, we increase \(k\) to 5 and examine the 5 nearest neighbors.

  • Among these 5 neighbors, 4 are ‘Benign’ and 1 is ‘Malignant’.
  • According to the majority vote rule (4 > 1), 5-NN will classify the new sample as Benign.

This example clearly shows that the choice of \(k\) has a decisive impact on the final result.

Code
input_feature_matrix, target_values = make_blobs(n_samples=50, centers=2, random_state=4, cluster_std=1.5)
new_point = np.array([[-3, 8]])
distances_k5 = np.sqrt(np.sum((input_feature_matrix - new_point)**2, axis=1))
neighbor_count = 5
nearest_k_indices = np.argsort(distances_k5)[:neighbor_count]
plt.style.use('seaborn-v0_8-whitegrid')  # 展示当前步骤的结果。
fig, ax = plt.subplots(figsize=(14, 6))  # 为主图与右侧独立图例预留各自的投影空间。
sns.scatterplot(x=input_feature_matrix[target_values==0, 0], y=input_feature_matrix[target_values==0, 1], s=100, label='Benign', marker='o', color='royalblue', alpha=0.3)  # 展示当前步骤的结果。
sns.scatterplot(x=input_feature_matrix[target_values==1, 0], y=input_feature_matrix[target_values==1, 1], s=100, label='Malignant', marker='X', color='darkorange', alpha=0.3)  # 展示当前步骤的结果。
plt.scatter(new_point[:, 0], new_point[:, 1], s=300, c='red', marker='P', label='Query', ec='black', linewidth=1.5)  # 展示当前步骤的结果。
plt.scatter(input_feature_matrix[nearest_k_indices, 0], input_feature_matrix[nearest_k_indices, 1], s=350,
            facecolors='none', edgecolors='green', linewidth=3, label='5 nearest')
center = new_point.flatten()
radius = distances_k5[nearest_k_indices[-1]]
circle = plt.Circle(center, radius, color='green', fill=False, linestyle='--', linewidth=2)
plt.gca().add_patch(circle)  # 展示当前步骤的结果。
ax.set_title('k=5: majority vote is benign', fontsize=34, pad=12)  # 保持投影可读,并避免标题压缩主图。
ax.set_xlabel('Feature 1 (Texture)', fontsize=34)  # 放大横轴标题以满足投影阅读距离。
ax.set_ylabel('Feature 2 (Radius)', fontsize=34)  # 放大纵轴标题以满足投影阅读距离。
ax.legend(loc='center left', bbox_to_anchor=(1.03, 0.5), fontsize=34, frameon=True)  # 将图例移入专用右侧走廊,避免遮挡数据。
ax.tick_params(axis='both', labelsize=34)  # 放大刻度文字以满足投影阅读距离。
ax.set_aspect('equal', adjustable='box')  # 展示当前步骤的结果。
fig.tight_layout()  # 展示当前步骤的结果。
plt.show()  # 展示当前步骤的结果。
The decision process for 5-NN
Figure 3: The decision process for 5-NN

4.2 Weighted k-Nearest Neighbors (Weighted k-NN)

Extension: Main lessons to the high-dimensional diagnosis and main case.

Weighted k-NN lets closer neighbors speak more loudly by assigning inverse-distance weights:

\[ \begin{aligned} y_n &= \underset{c \in \mathcal{Y}}{\arg\max}\sum_{i=1}^k w_i I(y_i=c),\\ w_i &= \frac{1}{\operatorname{distance}(x_n,x_i)}. \end{aligned} \]

The Intuition Behind Weighted KNN

Closer neighbors have a ‘louder voice’, while distant neighbors have a ‘quieter voice’.

Weighted KNN Intuition A new point is classified based on distance-weighted votes from its neighbors. A single, very close neighbor outweighs two more distant neighbors. Weighted KNN Closer means more weight Point to Classify w = 0.8 w = 0.3 w = 0.2 Weights decide Standard vote 1 vs. 2 → pink Weighted vote Purple 0.8 Pink 0.5 Result: purple

Implementing Weighted KNN in scikit-learn

In scikit-learn, weights='distance' gives closer neighbors more voting power. This slide shows an executable Extension configuration; the Core Practice chooses in advance uniform voting and never uses test results to choose weighting.

Code
from sklearn.neighbors import KNeighborsClassifier  # Importing a Weighted Nearest Neighbor Classifier
weighted_knn_example = KNeighborsClassifier(n_neighbors=5, weights='distance')  # Set up the weighted-neighbor model before fitting it
weighted_knn_example.get_params()["weights"]  # Check the after-class Extension configuration
'distance'

Any after-class weighting comparison requires a new training-only validation and a separate untouched test period; the current main lesson does not compare that rule.

4.3 KNN for More Than Just Classification: Regression

The core idea of KNN also applies to regression problems, where the goal is to predict a continuous value (like a stock price or house value).

Classification

  • Goal: Predict a discrete class
  • Method: Neighbors vote
  • Example: Is an email ‘Spam’ or ‘Not Spam’?

Regression

  • Goal: Predict a continuous value
  • Method: Neighbors are averaged
  • Example: Predict the sale price of a house

The Mathematics of KNN Regression

For a new sample, the predicted value from a KNN regression model is the average (or weighted average) of the target values of its \(k\) nearest neighbors.

\[ \large{\hat{y}_n = \frac{1}{k} \sum_{i=1}^k y_i} \]

Here, \(y_i\) is the actual numerical value of the \(i\)-th neighbor.

4.4 Real-World Challenges and Solutions

So far, we’ve dealt with idealized numerical data. But in the business world, data is often more complex.

  • Challenge 1: How do we handle non-numerical features (like ‘City’ or ‘Product Category’)?
  • Challenge 2: What if the dataset is massive (millions of records)? KNN predictions become extremely slow.

Challenge 1: Handling Categorical Features

The Problem: How do you calculate the ‘distance’ between samples that include text features like ‘Beijing’ and ‘Shanghai’?

Solutions:

  1. One-Hot Encoding: Convert the categorical feature into multiple binary (0/1) features. This is the most common approach.
  2. Use a specific distance metric: For example, the Hamming Distance, which counts the number of positions at which two strings of equal length are different.

Visualizing One-Hot Encoding

One-Hot Encoding Diagram A table shows how a 'City' column is transformed into three separate binary columns, with the conversion process highlighted. City Beijing Shanghai Guangzhou Transform City_BJ City_SH City_GZ 1 0 0 0 1 0 0 0 1

Challenge 2: Data Scale and Prediction Speed

A major drawback of KNN is its high computational cost.

  • Training Phase: Extremely fast. There’s almost no computation, just storing the data.
  • Prediction Phase: Extremely slow. For every new sample to be predicted, it must calculate its distance to every single training sample. If the training set has millions of records, this is unacceptable.

The Question: How can we speed up the process of finding nearest neighbors without sacrificing too much accuracy?

Acceleration Strategy 1: KD-Tree (k-dimensional tree)

A KD-Tree is a classic data structure for partitioning space. It recursively divides the k-dimensional space into a series of hyperrectangles.

  • Construction Process:
    1. Select a coordinate axis (e.g., the one with the largest variance).
    2. Find the median of all data points along that axis.
    3. Use a hyperplane perpendicular to that axis to split the space in two.
    4. Repeat this process recursively for the two subspaces until each region contains only a few data points.

How a KD-Tree Partitions 2D Space

Code
import numpy as np  # Generate Reproducible 2D Teaching Point Sets
import matplotlib.pyplot as plt  # Draw KD Tree Spatial Partition
np.random.seed(42)  # Fixed Teaching Indicative Random Seeds
data_kd = np.random.rand(25, 2) * 10  # Generate 2D Points for Partitioning Demo
data_sorted_x = data_kd[data_kd[:, 0].argsort()]  # Sort by Horizontal Axis to Determine First Split
median_idx_x = len(data_sorted_x) // 2  # Locate Midpoints on Horizontal Axis
median_val_x = data_sorted_x[median_idx_x, 0]  # Extract First Vertical Split Location
left_data = data_sorted_x[:median_idx_x]  # Obtain First Split Left Side Point
left_data_sorted_y = left_data[left_data[:, 1].argsort()]  # Sort by Vertical Axis Left Point
median_val_ly = left_data_sorted_y[len(left_data_sorted_y) // 2, 1]  # Calculate Left Horizontal Split Position
right_data = data_sorted_x[median_idx_x + 1:]  # Obtain First Split Right Side Point
right_data_sorted_y = right_data[right_data[:, 1].argsort()]  # Sort by Vertical Axis Right Point
median_val_ry = right_data_sorted_y[len(right_data_sorted_y) // 2, 1]  # Calculate Right Horizontal Split Position
Code
plt.style.use('seaborn-v0_8-whitegrid')  # 展示当前步骤的结果。
fig, ax = plt.subplots(figsize=(8, 6.5))
ax.scatter(data_kd[:, 0], data_kd[:, 1], c='navy', s=50, ec='black', alpha=0.8)
ax.plot([median_val_x, median_val_x], [-1, 11], color='red', linestyle='--', linewidth=2, label='1st Split (x-axis)')
ax.plot([-1, median_val_x], [median_val_ly, median_val_ly], color='green', linestyle='--', linewidth=2, label='2nd Split (y-axis)')
ax.plot([median_val_x, 11], [median_val_ry, median_val_ry], color='green', linestyle='--')
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 11)
ax.set_title('KD-Tree Spatial Partitioning Process', fontsize=20, pad=15)
ax.set_xlabel('Feature 1', fontsize=20)
ax.set_ylabel('Feature 2', fontsize=20)
ax.legend(fontsize=20)  # 保证投影时图例可读。
ax.set_aspect('equal', adjustable='box')
plt.tight_layout()  # 展示当前步骤的结果。
plt.show()  # 展示当前步骤的结果。
How a KD-Tree recursively partitions a 2D space
Figure 4: How a KD-Tree recursively partitions a 2D space

Core Diagnosis: Vanishing Neighbor Contrast

Core idea: as \(d\) grows, distances concentrate; neighbors blur and KD-Tree pruning approaches brute force.

Check: nearest/farthest distance \(\to 1\) signals less stable voting or weighting—a risk, not a universal KNN failure theorem.

learning path: Core \(\to\) Practice; the optional probability After the optional topics to that same Practice.

Curse of Dimensionality Illustration A comparison between a dense low-dimensional space and a sparse high-dimensional space where all points are far from the center and each other. Low d: d = 2 Clear distance contrast High d: d ≫ 20 Neighbor distances become similar

Extension: The Curse of Dimensionality

After the optional topic: after this branch, continue to the main case without replaying the completed Core diagnosis.

The Core Question: What is ‘Distance’ in High-Dimensional Space?

Optional topic: before entering this branch, the 90-minute Core has completed the minimal relative-distance diagnosis. The boundary-layer, fourth-moment, and sub-gaussian derivations below are after-class work and return only forward to the still-pending main case; probability theory is not assumed in Core.

In high-dimensional spaces, the geometric intuition we’ve built from our low-dimensional (2D or 3D) experience often fails.

  • Our Intuition: Points are either near or far; you can always find a ‘nearest neighbor’.
  • High-Dimensional Reality: As dimensions increase, the Euclidean distances between pairs of points tend to become very similar to each other.

This lecture aims to reveal this counter-intuitive phenomenon—the failure of distance metrics—through intuitive examples and mathematical derivation.

A Simplified Model: A Corner Cell and a Boundary Layer

Let’s begin with a simple thought experiment: slicing a unit hypercube \([0, 1]^d\) at the midpoint \(0.5\) along each dimension.

This partitions the entire space into \(2^d\) identical ‘small hypercubes’.

The cell \([0,1/2]^d\) is one corner cell, not the entire interior of the cube. This partition illustrates shrinking corner volume; a genuine boundary layer needs a separate definition.

Low-Dimensional Intuition: Corner Partitions for d=1 and d=2

In low dimensions we can see the origin corner cell and its complement directly. This compares one corner with the rest of the cube, not an interior core with a boundary shell.

Low-Dimensional Space Partitioning The left panel divides a unit interval into two equal halves. The right panel divides a unit square into four equal corner cells; the lower-left cell contains the origin and the other three form its complement. Dimension d=1 Origin [0, .5] Other (.5, 1] 0 0.5 1 Two equal halves Dimension d=2 Origin 1/4 Other 3/4 0 1 1 Origin: lower left
Figure 5: In \(d=2\), out of the \(2^2=4\) small squares, only one, \([0, 1/2]\times[0, 1/2]\), is near the origin.

High Dimensions: Corner Cells and Boundary Layers

As \(d\) increases, the origin corner cell \([0,1/2]^d\) has volume \(2^{-d}\to0\); its complement is not the same object as a boundary layer.

\(d\) Cells (\(2^d\)) One corner (\(2^{-d}\)) Complement (\(1-2^{-d}\))
1 2 50% 50%
3 8 12.5% 87.5%
10 1,024 ~0.1% 99.9%
100 ~\(1.27 \times 10^{30}\) ~\(7.9 \times 10^{-31}\) ~100%

The width-\(\varepsilon\) boundary layer is

\[ B_\varepsilon=\{x\in[0,1]^d:\min_j\min(x_j,1-x_j)\le\varepsilon\}. \]

For fixed \(0<\varepsilon<1/2\), \(P(X\in B_\varepsilon)=1-(1-2\varepsilon)^d\to1\): uniform mass lies near at least one face.

From Discrete to Continuous: Distance Concentration Under a Uniform Distribution

The slicing model was a simplification. Now consider a more general case: \(n\) data points \(X_1, \ldots, X_n\) are independently and identically distributed in the unit hypercube \([0, 1]^d\).

Conditional finding

  • if sample size \(n\) is fixed and coordinates are i.i.d.

  • \(U(0,1)\), the finite set of Euclidean pair distances shares the same first-order scale, so its max–min relative gap converges to zero in probability.

\[ \begin{aligned} R_d &= \frac{dist_{max}-dist_{min}}{dist_{min}},\\ R_d &\xrightarrow{p}0 \qquad (n\text{ fixed}). \end{aligned} \]

If \(n\) grows with \(d\), coordinates are dependent, tails are heavy, or the metric changes, the extreme-distance claim needs additional conditions.

Mathematical Derivation 1: Setting up the Random Variable for Distance

Let’s analyze the distance between any two points \(X_i\) and \(X_j\).

  1. Point Representation: Each point \(X_j\) is a \(d\)-dimensional vector \((X_{j1}, \ldots, X_{jd})^T\).
  2. Component Distribution: Each component \(X_{jk}\) independently follows a \(U(0, 1)\) distribution.
    • Expectation: \(E(X_{jk}) = \frac{1}{2}\)
    • Variance: \(\operatorname{Var}(X_{jk}) = \frac{1}{12}\)
  3. Squared Distance: We analyze the squared Euclidean distance \(S_{ij} = \lVert X_i - X_j\rVert_2^2\) for mathematical convenience.

\[ \large{ S_{ij} = \sum_{k=1}^{d} (X_{ik} - X_{jk})^2 } \]

Mathematical Derivation 2: Calculating the Expected Squared Distance

Using the linearity of expectation, we can compute the expected value of \(S_{ij}\).

First, consider the expected squared difference in a single dimension: \(E[(X_{ik} - X_{jk})^2]\).

Using the variance definition \(\operatorname{Var}(Y) = E(Y^2) - [E(Y)]^2\), we have:

\[ \large{E[(X_{ik} - X_{jk})^2] = \operatorname{Var}(X_{ik} - X_{jk}) + [E(X_{ik} - X_{jk})]^2} \]

Since \(X_{ik}\) and \(X_{jk}\) are i.i.d.:

  • \(E(X_{ik} - X_{jk}) = E(X_{ik}) - E(X_{jk}) = \frac{1}{2} - \frac{1}{2} = 0\)
  • \(\operatorname{Var}(X_{ik} - X_{jk}) = \operatorname{Var}(X_{ik}) + \operatorname{Var}(X_{jk}) = \frac{1}{12} + \frac{1}{12} = \frac{1}{6}\)

So, \(E[(X_{ik} - X_{jk})^2] = \frac{1}{6} + 0^2 = \frac{1}{6}\).

Mathematical Derivation 3: Summing Across Dimensions

Finally, summing over all dimensions:

\[ \large{ E(S_{ij}) = E[\sum_{k=1}^{d} (X_{ik} - X_{jk})^2] = \sum_{k=1}^{d} E[(X_{ik} - X_{jk})^2] = \frac{d}{6} } \]

LLN: Relative Distance Concentration

For a fixed pair, \(S_{ij}\) sums \(d\) i.i.d. terms. The Law of Large Numbers gives

\[ \large{ \frac{S_{ij}}{d} = \frac{1}{d} \sum_{k=1}^{d} (X_{ik} - X_{jk})^2 \xrightarrow{p} E[(X_{ik} - X_{jk})^2] = \frac{1}{6} } \]

Because \(E[(X_{ik}-X_{jk})^4]=1/15\),

\[ \operatorname{Var}(S_{ij})=\frac{7d}{180},\qquad \frac{\operatorname{sd}(S_{ij})}{E(S_{ij})}=\sqrt{\frac{7}{5d}}. \]

  • Thus relative, not absolute, variation shrinks, and \(\lVert X_i-X_j\rVert_2/\sqrt d\xrightarrow{p}1/\sqrt6\).

  • Absolute variance does not vanish; fixed \(n\) extends the result to the finite set of pairs.

Conceptual Visualization: Concentration of the Distance Distribution

This phenomenon can be understood visually through the changing distribution of distances.

Phenomenon of Distance Concentration A graph showing that as dimension d increases, the probability distribution of pairwise distances narrows around a single mean value. A separate legend to the upper right identifies the low-, medium-, and high-dimensional distributions without covering the plotted curves. Distance Probability Density Dimension Low (d=2) Medium (d=10) High (d → ∞) E[Distance]
Figure 6: Interpret the sketch as the normalized distribution of \(D/\sqrt d\) or \(S/d\): its relative width shrinks. It does not claim that the unnormalized distance has vanishing absolute width.

Generalization: Concentration of the Norm

Related concentration results extend beyond the uniform cube, but they require explicit distribution and dependence assumptions.

  • Intuitive theorem: a centered isotropic sub-gaussian vector \(X\in\mathbb R^d\) has \(\lVert X\rVert_2\) concentrated on the \(\sqrt d\) scale.

  • Required qualification: the constants and tail bounds are controlled by the explicitly stated sub-gaussian parameters.

  • A stronger result (Vershynin, 2018) shows that for sub-gaussian vectors, the probability of deviating from \(\sqrt{d}\) decays exponentially with the dimension \(d\).

  • This means that in high-dimensional space, the length of a random vector is almost a deterministic, not random, quantity.

Practical Implications: Why ‘Nearest Neighbor’ Fails

The failure of distance metrics poses a severe challenge to many algorithms that rely on distance calculations, especially k-Nearest Neighbors (k-NN).

  • Goal of k-NN: To find the k neighbors in the feature space that are ‘most similar’ to a query point.
  • High-Dimensional Dilemma: If the distances from a query point to all its neighbors are nearly equal, there’s no meaningful difference between the ‘nearest’ and ‘farthest’ neighbors.
  • Result: Without low-dimensional structure or useful signal, neighbor ranks can lose contrast and predictions can become unstable; this is not a theorem that k-NN is meaningless in every high-dimensional task.

Summary

  1. High-Dimensional Geometry is Counter-intuitive: We cannot directly extend our low-dimensional intuitions to high-dimensional spaces.
  2. Define the Boundary Layer: In a uniform cube, a fixed-width boundary layer has volume share approaching one; the complement of one corner is not that layer.
  3. Relative Distance Concentration: Under stated distributions and fixed sample size, normalized distances have shrinking relative variation.
  4. Diagnose Algorithms: Concentration can weaken contrast for k-NN or clustering, but the effect depends on scaling, structure, metric, and sample growth.

After the optional topic

continue to the main case; do not skip training-only selection and the held-out test check.

Objective Retrieval Before the Main Case

Retrieve the opening objectives: calculate Euclidean/Manhattan distance, explain why scaling changes neighbors, choose \(k\) with time-respecting validation, and diagnose distance concentration.

Retrieval prompt: What are the Euclidean and Manhattan distances from \((0,0)\) to \((3,4)\)?

Answer

Euclidean is \(\sqrt{3^2+4^2}=5\); Manhattan is \(|3|+|4|=7\). The task’s meaning of similarity determines the metric.

Formative Check 1: Scale Before or After Splitting?

Should the scaler be fit on all observations or the training period only?

Answer

Fit on training only, then apply unchanged to validation/test. Full-sample moments contain future information and leak through preprocessing.

Main Case: Fuyao Glass KNN Decline Classification

  • Data used in this example: data/stock/stock_price_pre_adjusted.h5; key=data; Fuyao Glass 600660.XSHG; 2018–2024; predict next-day decline from day return, five-day return, and five-day volatility; chronological 80/20 split.

  • Nearby method source: Cover & Hart (1967), nearest-neighbor classification; scaling is fit on training observations and reused for test distances.

Code
from pathlib import Path  # Locate the downloaded data file

import pandas as pd  # Read local quotes into a table
from sklearn.preprocessing import StandardScaler  # Define Comparable Distance with Training Period Scale
from sklearn.neighbors import KNeighborsClassifier  # Use nearest neighbor majority voting to complete classification
from sklearn.metrics import average_precision_score, balanced_accuracy_score, confusion_matrix, roc_auc_score  # Prepare ranking and operating-point evidence for the fixed check
# Public download: https://assets.qiufei.site/data/stock/stock_price_pre_adjusted.h5
# After downloading, change the next line to the file's actual location on your device.
# Course-relative option: Path("data/stock/stock_price_pre_adjusted.h5")
# Windows: Path(r"C:\qiufei\data\stock\stock_price_pre_adjusted.h5")
# macOS: Path("/Users/your_name/data/stock/stock_price_pre_adjusted.h5")
# Linux: Path("/home/your_name/data/stock/stock_price_pre_adjusted.h5")
price_path = Path("/home/ubuntu/r2_data_mount/data/stock/stock_price_pre_adjusted.h5")
price_rows = pd.read_hdf(price_path, key='data', where=['order_book_id=="600660.XSHG"', 'date>=Timestamp("2018-01-01")', 'date<=Timestamp("2024-12-31")'], columns=['close'])  # Only Load Target Companies and Closing Prices
knn_frame = price_rows.reset_index().sort_values('date')  # Build Forecast Order by Trading Day
knn_frame['return_t'] = knn_frame['close'].pct_change()  # Compute one-day returns
knn_frame['return_5d_t'] = knn_frame['close'].pct_change(5)  # Compute cumulative five-day returns
knn_frame['volatility_5d_t'] = knn_frame['return_t'].rolling(5).std()  # Compute rolling five-day volatility
knn_frame['future_return_t1'] = knn_frame['return_t'].shift(-1)  # Retain continuous future returns before label construction to identify an unknown terminal label
knn_frame['target_date_t1'] = knn_frame['date'].shift(-1)  # Preserve the label-realization date for boundary purging
knn_frame = knn_frame.dropna()  # Drop rows with rolling-window missing values or unknown future returns before label construction
Code
knn_frame['down_t1'] = (knn_frame['future_return_t1'] < 0).astype(int)  # Create classes only for observed future returns
assert knn_frame['future_return_t1'].notna().all() and knn_frame['date'].max() < price_rows.reset_index()['date'].max()  # Verify that the last feature date is earlier than the last label realization date
split_row = int(len(knn_frame) * 0.8)  # Fixed First Eighty Percent as Training Period
test_start_date = knn_frame.iloc[split_row]['date']
train_rows = knn_frame[(knn_frame['date'] < test_start_date) & (knn_frame['target_date_t1'] < test_start_date)]  # Purge training labels realized in test
test_rows = knn_frame[knn_frame['date'] >= test_start_date]
assert train_rows['target_date_t1'].max() < test_rows['date'].min()  # Verify the label-realization boundary
feature_names = ['return_t', 'return_5d_t', 'volatility_5d_t']  # Define the three semantic dimensions of the distance space
pd.Series({'train_end': train_rows['date'].max(), 'test_start': test_rows['date'].min()})  # Verify only the time boundary before selection; do not read test-label summaries
train_end    2023-08-04
test_start   2023-08-08
dtype: datetime64[ns]

Formative Check 2: Bias–Variance and \(k\)

As \(k\) grows from 1 to 51, what usually happens to the boundary, training error, and variance?

Answer

The boundary smooths, training error usually rises, variance falls, and bias rises. Select \(k\) inside training validation, never from final-test results.

Step-by-Step Exercise: Choose \(k\)

  • Task:
    • Choose in advance Euclidean distance and uniform voting.

    • With five expanding training windows, compare \(k\in\{5,15,31\}\) by balanced accuracy.

    • After choosing \(k\), report test prevalence, ROC-AUC, AP, and the confusion matrix once.

  • Complete solution:
    • Fit a fresh scaler and uniform-vote Euclidean KNN inside each window, record validation scores, and select the highest mean.

    • Refit on the full training period and open the fixed test once.

    • Distance weighting and alternative metrics are Extension; this exercise selects neither them nor a separate probability threshold.

Code
from sklearn.model_selection import TimeSeriesSplit  # Keep training earlier than validation with an extended window
validation_rows = []  # Collect Equilibrium Accuracy Rate for Each Window and Candidate K
for fold_id, (fit_index, validation_index) in enumerate(TimeSeriesSplit(n_splits=5).split(train_rows), start=1):  # Generate Five Time Ordered Windows
    fold_train = train_rows.iloc[fit_index]  # Obtain Historical Training Segments for Current Window
    fold_validation = train_rows.iloc[validation_index]  # Obtain Validation Segments Immediately Following
    fold_train = fold_train[fold_train['target_date_t1'] < fold_validation['date'].min()]  # Purge labels realized in the validation fold
    assert fold_train['target_date_t1'].max() < fold_validation['date'].min()  # Verify each fold's label boundary
    fold_scaler = StandardScaler().fit(fold_train[feature_names])  # estimate scale only on current training segment
    for neighbor_count in [5, 15, 31]:  # Compare three pre-declared candidate values
        fold_model = KNeighborsClassifier(n_neighbors=neighbor_count, weights='uniform', metric='euclidean').fit(fold_scaler.transform(fold_train[feature_names]), fold_train['down_t1'])  # Compare candidate k values with the same Euclidean distance and uniform voting
        fold_prediction = fold_model.predict(fold_scaler.transform(fold_validation[feature_names]))  # Generate Forecast in Future Validation Segment
        validation_rows.append({'fold': fold_id, 'k': neighbor_count, 'balanced_accuracy': balanced_accuracy_score(fold_validation['down_t1'], fold_prediction)})  # Save Reviewable Breakdown Results
validation_table = pd.DataFrame(validation_rows)  # Organize step-by-step validation records
validation_summary = validation_table.groupby('k', as_index=False)['balanced_accuracy'].mean().sort_values('balanced_accuracy', ascending=False)  # Summarize average validation evidence
selected_k = int(validation_summary.iloc[0]['k'])  # Select Number of Neighbors by Training Period Only
final_scaler = StandardScaler().fit(train_rows[feature_names])  # Re-estimate the scale with the full training period after selection
final_knn = KNeighborsClassifier(n_neighbors=selected_k, weights='uniform', metric='euclidean').fit(final_scaler.transform(train_rows[feature_names]), train_rows['down_t1'])  # Refit the fixed specification on the full training period
final_test_prediction = final_knn.predict(final_scaler.transform(test_rows[feature_names]))
final_test_probability = final_knn.predict_proba(final_scaler.transform(test_rows[feature_names]))[:, 1]  # Generate ranking scores from the same fixed model
final_test_confusion = confusion_matrix(test_rows['down_t1'], final_test_prediction)  # Record majority-vote operating-point counts
test_summary = pd.Series({'selected_k': selected_k, 'prevalence': test_rows['down_t1'].mean(), 'roc_auc': roc_auc_score(test_rows['down_t1'], final_test_probability), 'AP': average_precision_score(test_rows['down_t1'], final_test_probability), 'balanced_accuracy': balanced_accuracy_score(test_rows['down_t1'], final_test_prediction), 'TN': final_test_confusion[0, 0], 'FP': final_test_confusion[0, 1], 'FN': final_test_confusion[1, 0], 'TP': final_test_confusion[1, 1]})  # Summarize the one held-out test check
display(validation_summary.round(6), test_summary.to_frame().T.round(6))  # Compact training selection and one-row held-out test check
Table 1
k balanced_accuracy
0 5 0.530118
1 15 0.509807
2 31 0.508497
selected_k prevalence roc_auc AP balanced_accuracy TN FP FN TP
0 5.0 0.469027 0.4645 0.452156 0.474161 79.0 101.0 78.0 81.0

Apply It to a New Case

  • Switch to Hengrui Pharmaceuticals 600276.XSHG;

  • choose in advance distance weighting, compare Euclidean/Manhattan distance and \(k\) on the same training-only chronological folds, choose one combination, and only then report test class balance, balanced accuracy, and query time once.

Complete-answer reminder

  • keep the chronological analysis clear, estimate scaling from training data only, compare distance measures on the same validation period, report both speed and predictive performance, and state the limitations.

  • Cite actual output and do not interpret predictive proximity causally.

Complete Solution for the New Case: Hengrui Distance Comparison

Table 2
Code
from time import perf_counter  # Measuring the query time of the fixed test set
from pathlib import Path  # Locate the downloaded data file
from sklearn.model_selection import GridSearchCV, TimeSeriesSplit  # Extending the window only during the training period
from sklearn.pipeline import Pipeline  # Fit scaling and KNN on the same training rows
# Public download: https://assets.qiufei.site/data/stock/stock_price_pre_adjusted.h5
# After downloading, change the next line to the file's actual location on your device.
# Course-relative option: Path("data/stock/stock_price_pre_adjusted.h5")
# Windows: Path(r"C:\qiufei\data\stock\stock_price_pre_adjusted.h5")
# macOS: Path("/Users/your_name/data/stock/stock_price_pre_adjusted.h5")
# Linux: Path("/home/your_name/data/stock/stock_price_pre_adjusted.h5")
price_path = Path("/home/ubuntu/r2_data_mount/data/stock/stock_price_pre_adjusted.h5")
transfer_rows = pd.read_hdf(price_path, key='data', where=['order_book_id=="600276.XSHG"', 'date>=Timestamp("2018-01-01")', 'date<=Timestamp("2024-12-31")'], columns=['close'])  # Select Hengrui Medicine Close Price
transfer_frame = transfer_rows.reset_index().sort_values('date')  # Sort by trading days in order of forecast availability
transfer_frame['return_t'] = transfer_frame['close'].pct_change()  # Compute one-day returns
transfer_frame['return_5d_t'] = transfer_frame['close'].pct_change(5)  # Compute five-day returns
transfer_frame['volatility_5d_t'] = transfer_frame['return_t'].rolling(5).std()  # Compute rolling five-day volatility
transfer_frame['future_return_t1'] = transfer_frame['return_t'].shift(-1)  # Retain continuous future returns before label construction
transfer_frame['target_date_t1'] = transfer_frame['date'].shift(-1)  # Preserve the label-realization date
transfer_frame = transfer_frame.dropna()  # Drop rows with unknown future outcomes or rolling-window missing values before label construction
transfer_frame['down_t1'] = (transfer_frame['future_return_t1'] < 0).astype(int)  # Create labels only for observed future returns
transfer_train = transfer_frame[(transfer_frame['date'] <= '2022-12-31') & (transfer_frame['target_date_t1'] < '2023-01-01')]  # Purge training labels realized in test
transfer_test = transfer_frame[transfer_frame['date'] >= '2023-01-01']
assert transfer_train['target_date_t1'].max() < transfer_test['date'].min()  # Verify the label-realization boundary
transfer_features = ['return_t', 'return_5d_t', 'volatility_5d_t']  # Three Dimensions of Fixed Distance Space
Table 3
Code
transfer_pipeline = Pipeline([('scale', StandardScaler()), ('knn', KNeighborsClassifier(weights='distance'))])  # Keep scaling inside each training fold
transfer_cv = TimeSeriesSplit(5, gap=1)  # Purge one row for the one-day forecast horizon in every fold
assert all(transfer_train.iloc[fit_idx]['target_date_t1'].max() < transfer_train.iloc[val_idx]['date'].min() for fit_idx, val_idx in transfer_cv.split(transfer_train))  # Verify every fold's label boundary
transfer_grid = {'knn__metric': ['euclidean', 'manhattan'], 'knn__n_neighbors': [5, 15, 31]}  # Declare the complete candidate set on common validation evidence
transfer_search = GridSearchCV(transfer_pipeline, transfer_grid, cv=transfer_cv, scoring='balanced_accuracy', return_train_score=False).fit(transfer_train[transfer_features], transfer_train['down_t1'])  # Select one metric and k using training-period folds only
validation_comparison = pd.DataFrame(transfer_search.cv_results_)[['param_knn__metric', 'param_knn__n_neighbors', 'mean_test_score']].sort_values('mean_test_score', ascending=False)  # Publish comparable validation summaries
selected_metric = transfer_search.best_params_['knn__metric']
selected_k = int(transfer_search.best_params_['knn__n_neighbors'])
query_start = perf_counter()  # Record a monotone clock before the unique test prediction
transfer_prediction = transfer_search.predict(transfer_test[transfer_features])  # Open the test once with the fixed combination
query_ms = (perf_counter() - query_start) * 1000  # Convert the unique test query to milliseconds
transfer_test_summary = pd.Series({'selected_metric': selected_metric, 'selected_k': selected_k, 'test_prevalence': transfer_test['down_t1'].mean(), 'balanced_accuracy': balanced_accuracy_score(transfer_test['down_t1'], transfer_prediction), 'query_ms': query_ms})  # Record one held-out test check for one specification

Transfer Validation and held-out-Test check

Code
display(validation_comparison.round(6), transfer_test_summary.to_frame().T.round(6))  # Separate validation selection from test evidence
param_knn__metric param_knn__n_neighbors mean_test_score
0 euclidean 5 0.519945
4 manhattan 15 0.517000
5 manhattan 31 0.512933
1 euclidean 15 0.511542
2 euclidean 31 0.510255
3 manhattan 5 0.508568
selected_metric selected_k test_prevalence balanced_accuracy query_ms
0 euclidean 5 0.52588 0.541321 2.9185
  • Expanding-window validation locks Euclidean distance with \(k=5\) from the six candidates; only then is the test opened once.

  • The test has 483 observations, decline prevalence 0.525880, and fixed-specification balanced accuracy 0.541321.

  • The last feature date is 2024-12-30, and unknown future labels are removed before integer conversion.

  • Timing varies by hardware; predictive proximity is not causal similarity.

Formative Check 3: The Curse of Dimensionality

If nearest and farthest distances become almost equal in high dimensions, what happens to KNN?

Answer

Relative neighbor contrast collapses, making local voting and distance weighting unstable. Use feature selection/reduction, a domain metric, or a model more robust to high dimension.

Sources and Further Reading

  • Cover & Hart (1967), “Nearest Neighbor Pattern Classification.”
  • Hastie, Tibshirani & Friedman, The Elements of Statistical Learning, Chapter 13.
  • scikit-learn User Guide: nearest neighbors and preprocessing.
  • Data: local pre-adjusted A-share data; file, key, fields, period, and split are stated above.

4.5 Summary: The Pros and Cons of KNN

Optional topic: after Core, optionally enter the weighting/regression/KD-tree branch, then continue to the final summary without replaying completed Core material.

Advantages (Pros) Disadvantages (Cons)
Simple principle, easy to implement High computational cost, slow predictions
No training required, highly adaptive (non-parametric) Large memory requirement, needs all training data
✅ Makes no assumptions about data distribution ❌ Sensitive to imbalanced data (majority class can dominate)
✅ Can be used for classification and regression ❌ Suffers badly from the Curse of Dimensionality
✅ Decision boundary can be very flexible ❌ Requires feature standardization

KNN’s Applications in Business Decision-Making

Thanks to its simplicity and flexibility, KNN is widely used in various business domains:

  • Finance: Customer credit scoring, fraudulent transaction detection.
  • Marketing: Customer segmentation, identifying high-value customer groups.
  • Recommendation Systems: ‘Customers who bought product X also bought product Y’ is a classic ‘find the nearest neighbor’ problem.
  • Healthcare: Disease diagnosis, genetic pattern recognition.
  • Retail: Predicting customer purchasing behavior, inventory optimization.

Chapter Review: Core and Extension Concepts

Concept Core Idea Key Parameters/Decisions
KNN Classification Majority rules k, distance metric
KNN Regression Average of neighbors k, distance metric
Weighted KNN Proximity matters weights='distance'
KD-Tree Space-for-time tradeoff Suitable for low-dimensional data
Curse of Dimensionality Distance loses meaning in high dimensions Feature selection/reduction is key
Standardization Make units comparable, not predictive importance equal Fit on training data before computing distance

Final Thoughts: The Keys to Success with KNN

To apply KNN successfully, economists and data scientists need to focus on:

  1. Feature Engineering: Selecting the most relevant features for the problem is crucial. Garbage in, garbage out.
  2. Distance Metric: Choose a distance metric appropriate for your specific problem. Euclidean distance is not always the best choice.
  3. Parameter Tuning: Choose in advance uniform Euclidean majority voting and select only \(k\) in training-only chronological folds, refitting preprocessing inside every fold; finish that choice before evaluating the test period once.
  4. Efficiency Considerations: For large datasets, it’s essential to consider acceleration algorithms like KD-Trees or Ball Trees, or to use approximate nearest neighbor search methods.

KNN is an excellent gateway into the world of machine learning—it’s simple, intuitive, and powerful.

Thank You!

Q & A