Lesson 15 of 30

Chapter 15 — K-Nearest Neighbors Classification

Chapter overview

K-nearest neighbors (KNN) classifies a new observation by comparing it with labeled training observations. The algorithm locates the k closest examples under a chosen distance measure and combines their labels. This local, example-based logic is intuitive, but its performance depends strongly on feature scale, neighborhood size, irrelevant dimensions, and the way evaluation is organized.

This chapter moves from geometric intuition to a leakage-safe scikit-learn workflow. Students calculate distances manually, inspect neighbor votes and probabilities, compare uniform and distance weighting, study the bias–variance pattern across values of k, and complete a practical lab using the breast cancer dataset built into scikit-learn.

Learning objectives

  • Explain why nearby observations can provide evidence for a class label.
  • Describe KNN as an instance-based, non-parametric, and lazy learning method.
  • Calculate and interpret Euclidean, Manhattan, and Minkowski distances.
  • Explain how n_neighbors, weights, metric, and p change a prediction.
  • Standardize numerical features inside a leakage-safe pipeline.
  • Relate small and large values of k to bias, variance, noise, and underfitting.
  • Evaluate training and validation performance across candidate values of k.
  • Recognize computational and high-dimensional limitations of KNN.
CORE IDEA  KNN does not learn one global equation. It stores labeled examples and makes a local decision around each new observation.

 

Chapter map

Table 15.1. Questions answered in this chapter

Section

Central question

Practical focus

15.1

How does a local vote become a prediction?Distances, neighbors, voting

15.2

Which settings control the neighborhood?k, weights, metric, p

15.3

Why must features be comparable?StandardScaler and Pipeline

15.4

How does k control complexity?Training and validation curves

15.5

When is KNN useful or risky?Strengths, limits, decision guide

Lab

Which configuration generalizes best?Controlled comparison on one split

 

Prerequisites

Students should understand features X, target y, classification, training and validation sets, and basic accuracy. Familiarity with pandas, NumPy, and scikit-learn pipelines is helpful. The practical lab supplies complete code and can be run in Jupyter Notebook, JupyterLab, Google Colab, or a Python script.

Running example

Imagine classifying a cell sample as benign or malignant from numerical measurements. A new sample is compared with labeled historical samples. If most nearby samples are malignant, that local evidence supports a malignant prediction. The lab uses scikit-learn’s breast cancer dataset, but the same workflow applies to many classification problems with meaningful feature-space similarity.

EVALUATION DISCIPLINE  Choose k, the weighting rule, and the distance metric with validation data or cross-validation. Keep the final test set untouched until the modeling recipe is fixed.

 

15.1 Basic principle

KNN rests on a local smoothness assumption: observations that are close in a useful feature space often share a label. The method turns that assumption into a repeatable prediction procedure.

Similar observations should have similar labels

Similarity is not discovered automatically; it is defined by the selected features, their representation, their scale, and the distance measure. Two customers may be similar in recent purchasing behavior but very different in age. Two images may be visually similar after feature extraction even though their raw pixel values are not. KNN succeeds when geometric closeness corresponds reasonably well to the class structure of the problem.

This assumption is local rather than global. The model does not require one straight line or one equation to describe every class boundary. Different regions can follow different patterns because every prediction is based on a neighborhood around the query point.

Table 15.2. The vocabulary of a KNN prediction

Term

Meaning in KNN

Question to ask

Observation

One labeled training exampleWhat does one row represent?

Feature space

Coordinate system formed by the inputsDo coordinates express useful similarity?

Distance

Numerical dissimilarity between two pointsIs the metric suitable and scaled?

Neighborhood

The k closest training observationsHow local should the decision be?

Vote

Aggregation of neighbor labelsEqual or distance-based influence?

 

Figure 15.1. Prediction with k-nearest neighbors

1

2

3

4

5

Receive a new point

Measure distances

Select the k closest

Aggregate their labels

Return class and score

 

Distance between observations

For two observations a and b with p features, a distance function converts feature-by-feature differences into one nonnegative number. Smaller values indicate greater similarity under that definition. A distance of zero means the represented feature values are identical, though duplicated feature vectors can still have conflicting labels.

EUCLIDEAN DISTANCE (L2)
d(a, b) = √[Σⱼ (aⱼ − bⱼ)²]

Straight-line distance; large coordinate differences receive extra emphasis because they are squared.

 

MANHATTAN DISTANCE (L1)
d(a, b) = Σⱼ |aⱼ − bⱼ|

Axis-aligned distance; differences are accumulated without squaring.

 

MINKOWSKI DISTANCE
d(a, b) = [Σⱼ |aⱼ − bⱼ|ᵖ]¹⁄ᵖ

A family of distances: p = 1 gives Manhattan and p = 2 gives Euclidean.

 

A worked distance example

Suppose a = (2, 6) and b = (5, 2). Their coordinate differences are −3 and 4. The Euclidean distance is √(3² + 4²) = 5. The Manhattan distance is |−3| + |4| = 7. The ranking of candidate neighbors can change when the metric changes, especially in spaces with several dimensions or outliers.

PYTHON  •   Calculate distances from one query point

import numpy as np

 

query = np.array([2.0,  6.0])

points = np.array([

    [5.0,  2.0],

    [3.0,  5.0],

    [8.0,  7.0],

])

 

euclidean = np.sqrt(((points - query) ** 2).sum(axis=1))

manhattan = np.abs(points - query).sum(axis=1)

 

print("Euclidean:", euclidean.round(3))

print("Manhattan:", manhattan.round(3))

 

Nearest neighbors

After distances are calculated, the training observations are ordered from smallest to largest distance. The first k observations form the neighborhood. Only these neighbors contribute to a standard KNN prediction. A point just outside the neighborhood contributes nothing with uniform weighting, even if its distance is almost identical to that of the kth neighbor.

TIE DETAIL  If several training points are at identical distances near the neighborhood boundary, the selected set can depend on ordering and implementation details. Ties are a reason to inspect data duplication, increase k cautiously, or use distance weighting.

 

Majority voting

With uniform weights, every neighbor contributes one vote. The predicted class is the class with the largest vote count. For k = 5, neighbor labels [A, A, B, A, B] produce class A because A receives three votes. In multiclass problems, the winner needs only more votes than every competing class; it does not always need more than half of all votes.

Table 15.3. Equal votes can disagree with inverse-distance votes

Neighbor rank

Distance

Label

Uniform contribution

Distance-weighted contribution

1

0.20

A

1

5.00

2

0.40

B

1

2.50

3

0.50

B

1

2.00

Total A

1

5.00

Total B

2

4.50

 

Uniform voting predicts B because B has two of the three votes. An inverse-distance rule predicts A because the single A neighbor is much closer. This example shows that weights change the decision rule, not merely the reported probability.

Class probabilities in scikit-learn

KNeighborsClassifier can return class-probability estimates through predict_proba(). With uniform weights, the probability for a class is its fraction of the k neighbors. With distance weights, it is the class’s normalized total weight. These are local vote proportions, not automatically calibrated probabilities; their reliability should be evaluated when decisions depend on probability quality.

PYTHON  •   Fit a small classifier and inspect local probabilities

from sklearn.datasets import load_iris

from sklearn.model_selection import train_test_split

from sklearn.neighbors import KNeighborsClassifier

from sklearn.pipeline import make_pipeline

from sklearn.preprocessing import StandardScaler

 

X, y = load_iris(return_X_y=True)

X_train, X_test, y_train, y_test = train_test_split(

    X, y, test_size=0.25, random_state=42, stratify=y

)

 

model = make_pipeline(

    StandardScaler(),

    KNeighborsClassifier(n_neighbors=5),

)

model.fit(X_train, y_train)

print("Prediction:", model.predict(X_test[:1]))

print("Probabilities:", model.predict_proba(X_test[:1]).round(3))

 

Lazy learning and model fitting

KNN is often called a lazy learner because fitting does not estimate a compact predictive equation. The fit step mainly validates and stores the training data and prepares an internal neighbor-search structure when appropriate. Most computation occurs at prediction time, when distances or search queries are needed for new observations.

Table 15.4. Work performed during fitting and prediction

Stage

What KNN does

Practical implication

Fit

Stores training features and labelsUsually fast, but memory grows with training size

Predict

Finds neighbors and aggregates labelsCan be slow for many queries or high dimensions

Update

Typically refit with the expanded datasetNew reference cases can change local decisions

 

15.2 Main parameters

A KNN model is defined by the neighborhood size, the rule that weights neighbors, the distance metric, and search settings. These choices determine which examples influence a prediction and by how much.

Number of neighbors: n_neighbors

The parameter n_neighbors is k. A small k produces a highly local decision; a large k averages evidence over a broader region. The largest valid k cannot exceed the number of training observations available to the fitted model, and cross-validation folds may contain fewer training samples than the full dataset.

  • k = 1 assigns the label of the single closest training observation.
  • Increasing k usually smooths the decision boundary and reduces sensitivity to one noisy point.
  • Very large k can make the majority class dominate and erase meaningful local structure.
  • Odd values can reduce some two-class vote ties, but odd k is not a universal rule for multiclass or weighted voting.
SELECTION RULE  Treat k as a hyperparameter. Compare plausible values on validation data or through cross-validation; do not choose the value that performs best on the final test set.

 

Uniform versus distance weighting

Table 15.5. Neighbor weighting options

weights setting

Contribution of each neighbor

When it may help

Main caution

uniform

Every selected neighbor has equal influenceDense, reasonably homogeneous neighborhoodsA distant kth neighbor counts as much as the closest

distance

Closer neighbors receive larger inverse-distance weightsNeighborhoods with varying density or very close evidenceCan amplify duplicates, noise, or tiny distances

callable

User-defined function maps distances to weightsDomain-specific influence rulesRequires careful testing and documentation

 

When weights='distance', scikit-learn gives nearer observations greater influence. If a query exactly matches one or more training points, zero-distance handling is special: the coincident points dominate rather than causing division by zero. Conflicting duplicates should still be investigated because they indicate label ambiguity or data-quality problems.

PYTHON  •   Compare equal and distance-weighted voting

from sklearn.neighbors import KNeighborsClassifier

 

uniform_knn = KNeighborsClassifier(

    n_neighbors=7,

    weights="uniform",

)

distance_knn = KNeighborsClassifier(

    n_neighbors=7,

    weights="distance",

)

 

uniform_knn.fit(X_train, y_train)

distance_knn.fit(X_train, y_train)

 

print("Uniform accuracy:", uniform_knn.score(X_test, y_test))

print("Distance accuracy:", distance_knn.score(X_test, y_test))

 

Distance metric and Minkowski power p

The default metric is Minkowski with p = 2, which corresponds to Euclidean distance. Setting p = 1 gives Manhattan distance. The metric controls the geometry of neighborhoods; it should reflect how feature differences combine in the application. The same k can produce different neighbors under different metrics.

Table 15.6. Common distance choices

Metric

scikit-learn setting

Geometry

Useful consideration

Euclidean

metric='minkowski', p=2Straight-line L2 distanceCommon for standardized continuous features

Manhattan

metric='minkowski', p=1Axis-aligned L1 distanceLess emphasis on a single large coordinate gap

Minkowski

metric='minkowski', p>0General Lp familyp changes feature-difference emphasis

Custom/callable

metric=function

Domain-defined

Flexible but slower and easier to misuse

 

PYTHON  •   Configure Manhattan and Euclidean KNN

from sklearn.pipeline import make_pipeline

from sklearn.preprocessing import StandardScaler

 

knn_l1 = make_pipeline(

    StandardScaler(),

    KNeighborsClassifier(

        n_neighbors=9, metric="minkowski", p=1

    ),

)

knn_l2 = make_pipeline(

    StandardScaler(),

    KNeighborsClassifier(

        n_neighbors=9, metric="minkowski", p=2

    ),

)

 

knn_l1.fit(X_train, y_train)

knn_l2.fit(X_train, y_train)

 

Search algorithm, leaf_size, and n_jobs

KNeighborsClassifier also exposes implementation settings. algorithm='auto' lets scikit-learn select a suitable strategy among brute force, KD tree, and ball tree. The fastest choice depends on sample size, dimensionality, metric, and data representation. leaf_size affects tree construction, query speed, and memory, while n_jobs controls parallel work for neighbor searches where supported.

Table 15.7. Secondary implementation parameters

Parameter

Main role

Default starting point

algorithm

Neighbor-search strategyUse 'auto'; benchmark only when latency matters

leaf_size

Tree construction/query trade-offKeep default until profiling shows a need

n_jobs

Parallelism for neighbor searchUse available resources responsibly

metric_params

Extra arguments for a selected metricOnly when the metric requires them

 

PRIORITY ORDER  Tune the statistical choices first: feature representation, scaling, k, weights, and metric. Optimize search settings only after predictive behavior is sound and prediction-time cost has been measured.

 

15.3 Importance of scaling

Distance compares numerical coordinates. If one feature spans thousands and another spans fractions, the large-range feature can dominate the neighborhood even when it is not more informative.

Why large ranges dominate

Consider two customer features: age in years and annual income in dollars. A difference of 10 years contributes 100 to squared Euclidean distance, while a difference of $20,000 contributes 400,000,000. Without scaling, income almost completely determines which customers are called close. The algorithm is responding to units, not necessarily importance.

Table 15.8. Scaling changes the meaning of a coordinate difference

Feature

Raw values

Raw difference

Approximate standardized difference

Age

30 vs 40 years

10

1.0 standard deviations

Income

$60,000 vs $80,000

20,000

1.0 standard deviations

 

STANDARD SCORE
z = (x − μ) / σ

StandardScaler learns the training mean and standard deviation separately for each feature.

 

Standardization before KNN

StandardScaler centers each numerical feature around its training mean and scales it by its training standard deviation. A difference of one transformed unit therefore represents approximately one training-set standard deviation. Standardization does not make a feature useful, remove outliers, or guarantee a normal distribution; it only places features on comparable statistical scales.

  • Fit the scaler on training data only.
  • Use the fitted scaler to transform validation, test, and future observations.
  • Keep scaling and KNN together in one Pipeline so cross-validation refits preprocessing inside each training fold.
  • Consider robust scaling or domain-specific transformations when extreme outliers distort means and standard deviations.

Leakage-safe pipeline

Applying fit_transform to the full dataset before splitting leaks information from the evaluation rows into the scaling statistics. A pipeline prevents this common error because fit is called separately within the appropriate training subset, while predict and score reuse the learned transformation.

PYTHON  •   Build a leakage-safe scaled KNN pipeline

from sklearn.neighbors import KNeighborsClassifier

from sklearn.pipeline import Pipeline

from sklearn.preprocessing import StandardScaler

 

knn_pipeline = Pipeline([

    ("scale"StandardScaler()),

    ("knn"KNeighborsClassifier(

        n_neighbors=7,

        weights="uniform",

        metric="minkowski",

        p=2,

    )),

])

 

knn_pipeline.fit(X_train, y_train)

test_score = knn_pipeline.score(X_test, y_test)

print(f"Test accuracy: {test_score:.3f}")

 

A direct comparison: scaled versus unscaled

The following experiment uses the breast cancer dataset because its numerical features have very different ranges. Both models receive the same training and test rows and use the same k. Only the scaling step changes, so the comparison isolates the effect of feature scale.

PYTHON  •   Compare raw and standardized feature spaces

from sklearn.datasets import load_breast_cancer

from sklearn.model_selection import train_test_split

 

X, y = load_breast_cancer(return_X_y=True)

X_train, X_test, y_train, y_test = train_test_split(

    X, y, test_size=0.25, random_state=42, stratify=y

)

raw_knn = KNeighborsClassifier(n_neighbors=7)

scaled_knn = make_pipeline(

    StandardScaler(),  KNeighborsClassifier(n_neighbors=7)

)

raw_knn.fit(X_train, y_train)

scaled_knn.fit(X_train, y_train)

print("Raw:", raw_knn.score(X_test, y_test))

print("Scaled:", scaled_knn.score(X_test, y_test))

 

INTERPRETATION  A higher scaled score is evidence that raw units distorted neighbor selection in this dataset. It is not proof that StandardScaler is always the best transformation for every feature or application.

 

Categorical, binary, and sparse features

KNN is naturally expressed in a numerical feature space. Nominal categories usually require encoding, but ordinary one-hot encoding plus Euclidean distance can give a categorical mismatch a geometry that may not reflect domain similarity. Mixed numerical and categorical data may require carefully weighted transformations, a suitable metric, or another model family.

Binary indicators already have comparable numerical ranges, but their relative influence still depends on how many indicators exist and how they are weighted. For sparse matrices, centering can destroy sparsity; StandardScaler(with_mean=False) or MaxAbsScaler may be more appropriate. Always test the complete representation, not one preprocessing step in isolation.

Table 15.9. Representation decisions before KNN

Data characteristic

Potential treatment

KNN caution

Continuous numericStandardScaler or robust alternativeOutliers and units affect distance

Ordinal

Meaningful numeric encoding, then scaleSpacing between levels may not be equal
Nominal categoricalOne-hot or domain metricEuclidean geometry may be arbitrary
Sparse high-dimensionalMaxAbsScaler or no centeringDistance concentration and slow queries

Missing values

Impute inside a pipelineStandard KNN cannot compare NaN values directly

 

15.4 Bias–variance behavior

The value of k controls how much local detail the classifier follows. It therefore acts as a complexity parameter, even though KNN does not fit a traditional parametric equation.

Figure 15.2. The effect of k on model flexibility

SMALL k

MODERATE k

LARGE k

Detailed boundary
Low bias • High variance
Sensitive to noise

Balanced locality
Often strongest validation result
Problem-dependent

Smooth boundary
Higher bias • Lower variance
May erase local structure

 

Small k: flexible and potentially noisy

With k = 1, every training observation owns a local region and can determine predictions near itself. Training accuracy is often extremely high because each training point is its own nearest neighbor. This low-bias behavior can follow curved boundaries, but it also makes the classifier sensitive to mislabeled points, duplicates, sampling variation, and measurement noise.

Large k: smoother and potentially underfitted

As k increases, a prediction averages labels across a wider region. Local fluctuations matter less, which can reduce variance. If the neighborhood becomes too broad, distinct subgroups are blended together and the global majority class can dominate. Training and validation performance can then both decline, indicating underfitting.

Table 15.10. Interpreting training and validation curves

Pattern

Training score

Validation score

Likely diagnosis

Very small k

Very high

Noticeably lower

High variance or sensitivity to noise

Moderate k

High

Highest or stableUseful bias–variance balance

Very large k

Lower

Lower

High bias / oversmoothing
All k values weak

Weak

Weak

Representation, metric, or problem may be unsuitable

 

Generate a validation curve

A validation curve holds the evaluation method constant while varying one hyperparameter. The next example uses stratified five-fold cross-validation. Because k is nested inside a pipeline step named knn, the parameter path is knn__n_neighbors.

PYTHON  •   Training and validation scores across k

import numpy as np

from sklearn.model_selection import validation_curve

 

k_values = np.arange(1,  322)

train_scores, validation_scores = validation_curve(

    scaled_knn,

    X,

    y,

    param_name="kneighborsclassifier__n_neighbors",

    param_range=k_values,

 

PYTHON  •   Training and validation scores across k — continued

    cv=5,

    scoring="accuracy",

    n_jobs=-1,

)

 

train_mean = train_scores.mean(axis=1)

validation_mean = validation_scores.mean(axis=1)

best_k = k_values[validation_mean.argmax()]

print("Best cross-validated k:", best_k)

 

PYTHON  •   Plot the bias–variance pattern

import matplotlib.pyplot as plt

 

plt.figure(figsize=(85))

plt.plot(k_values, train_mean, marker="o", label="Training")

plt.plot(k_values, validation_mean, marker="o", label="Validation")

plt.xlabel("Number of neighbors (k)")

plt.ylabel("Accuracy")

plt.title("KNN validation curve")

plt.xticks(k_values)

plt.grid(alpha=0.25)

plt.legend()

plt.tight_layout()

plt.show()

 

How to choose a useful k range

  • Start with positive integer values that are smaller than every cross-validation training fold.
  • Use a broad coarse range first; then examine a narrower range around promising values.
  • Include both odd and even values when the task is multiclass or distance-weighted.
  • Compare more than the maximum mean score: inspect variability, class-specific metrics, latency, and stability.
  • If several neighboring values perform similarly, prefer a stable region rather than a fragile single peak.
DO NOT OVERREAD ONE CURVE  A validation curve is an estimate. A tiny difference between adjacent k values may be sampling noise. Report cross-validation variability and confirm that the chosen model meets the problem’s actual error costs.

 

15.5 Advantages and limitations

KNN is valuable because its logic is transparent and flexible. The same properties that make it simple also create memory, latency, dimensionality, and representation challenges.

Advantages

  • Simple and intuitive: the prediction can be explained through nearby examples.
  • No explicit training equation: there is no assumption of one global linear decision boundary.
  • Nonlinear decision boundaries: local neighborhoods can follow complex class shapes.
  • Natural multiclass support: the vote can include any number of class labels.
  • Few central hyperparameters: k, weights, distance metric, and preprocessing provide a clear experimental starting point.
  • Useful benchmark: a scaled KNN model can reveal whether local similarity contains predictive signal.

Limitations

  • Sensitive to irrelevant features: every included coordinate can distort distance.
  • Sensitive to scale and units: preprocessing is part of the model definition.
  • Slow prediction on large datasets: neighbor search is performed for each query.
  • Memory intensive: the reference training observations must be retained.
  • Affected by high dimensionality: distances become less discriminative as dimensions grow.
  • Limited extrapolation: KNN predicts from observed local labels rather than a learned structural law.
  • Vulnerable to class imbalance and uneven density: local votes can favor common classes.
  • Awkward with mixed or structured inputs: meaningful similarity may require specialized representations.

The curse of dimensionality

Adding features increases the volume of the feature space rapidly. Training observations become sparse, and the nearest point may no longer be meaningfully close. Distances can concentrate so that the closest and farthest observations have similar values. More data, careful feature selection, dimensionality reduction, or a different model family may be necessary.

Table 15.11. Failure signals and responses

Problem signal

Why it harms KNN

Possible response

Many irrelevant featuresNoise contributes to every distanceFeature selection or domain review
Thousands of sparse featuresNeighbors become less distinctDimensionality reduction or linear model
Millions of training rowsStorage and query cost growApproximate search or another algorithm
Highly imbalanced targetMajority neighbors dominateClass-aware metrics, resampling in training, or weights
Rapidly changing processOld neighbors stop being representativeTime-aware validation and retraining

 

When KNN is a sensible candidate

KNN is most attractive when the dataset is small or medium sized, features can be placed in a meaningful numerical space, prediction latency is not extremely constrained, and local similarity is plausible. It is less attractive when the data are enormous, very sparse, very high dimensional, strongly mixed-type, or when a compact equation and fast scoring are essential.

Table 15.12. A quick KNN suitability guide

Condition

Favors KNN

Suggests caution

Dataset size

Small to medium

Very large training set

Feature geometry

Scaled, meaningful numeric coordinatesArbitrary encodings or mixed units

Dimensionality

Low to moderate

Very high or sparse

Deployment

Moderate query volumeStrict low-latency scoring

Pattern

Local neighborhoods are informativeSimilarity is poorly defined

 

MODEL-SELECTION PERSPECTIVE  KNN should compete under the same split, preprocessing discipline, and metric as other candidate models. Its intuitive mechanism is not a substitute for validation.

 

Practical lab — Study the effect of k

In this lab, students build a scaled KNN classifier for the breast cancer dataset, compare training and validation performance across values of k, investigate distance and weighting choices, select one configuration using validation data, and evaluate it once on an untouched test set.

Lab objectives

  • Create reproducible training, validation, and test subsets with stratification.
  • Build a StandardScaler–KNN pipeline that prevents preprocessing leakage.
  • Record training and validation accuracy for a controlled range of k values.
  • Plot the two curves and interpret underfitting and overfitting behavior.
  • Compare uniform versus distance weighting and Manhattan versus Euclidean distance.
  • Freeze the selected configuration before the final test evaluation.

Dataset and experimental rules

Table 15.13. Practical lab design

Element

Choice

Reason

Dataset

Breast cancer Wisconsin (diagnostic)Built into scikit-learn; numerical binary classification

Split

60% train / 20% validation / 20% testSeparate model selection from final evaluation

Stratification

Use y in both split operationsPreserve benign/malignant proportions approximately

Preprocessing

StandardScaler inside PipelineComparable feature scales without leakage

Primary metric

Accuracy for the k curveSimple controlled study; supplement with recall and F1

Random state

42

Repeatable lab results

 

IMPORTANT  This dataset is educational. A classroom score does not establish clinical validity, safety, or suitability for medical decisions.

 

Step 1 — Load and inspect the data

PYTHON  •   Load the dataset and inspect the target

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

 

from sklearn.datasets import load_breast_cancer

from sklearn.metrics import classification_report, confusion_matrix

from sklearn.model_selection import train_test_split

from sklearn.neighbors import KNeighborsClassifier

from sklearn.pipeline import make_pipeline

from sklearn.preprocessing import StandardScaler

 

data = load_breast_cancer(as_frame=True)

X = data.data

y = data.target

 

print("Shape:", X.shape)

print("Class names:"list(data.target_names))

print(y.value_counts().sort_index())

 

The target labels are 0 for malignant and 1 for benign in this dataset. Write the label meaning next to every class-specific metric so that a high recall value cannot be misinterpreted.

Step 2 — Create protected subsets

PYTHON  •   Create 60/20/20 stratified subsets

X_train, X_temp, y_train, y_temp = train_test_split(

    X,

    y,

    test_size=0.40,

    random_state=42,

    stratify=y,

)

 

X_valid, X_test, y_valid, y_test = train_test_split(

    X_temp,

 

PYTHON  •   Create 60/20/20 stratified subsets — continued

    y_temp,

    test_size=0.50,

    random_state=42,

    stratify=y_temp,

)

 

print("Train:", X_train.shape)

print("Validation:", X_valid.shape)

print("Test:", X_test.shape)

 

Step 3 — Audit the split

PYTHON  •   Compare class proportions

def class_share(labels):

    return labels.value_counts(normalize=True).sort_index()

 

shares = pd.DataFrame({

    "train"class_share(y_train),

    "validation"class_share(y_valid),

    "test":  class_share(y_test),

})

 

shares.index = ["malignant""benign"]

print(shares.round(3))

 

assert set(X_train.index).isdisjoint(X_valid.index)

assert set(X_train.index).isdisjoint(X_test.index)

assert set(X_valid.index).isdisjoint(X_test.index)

 

Step 4 — Sweep candidate values of k

Every candidate uses exactly the same split, scaler, metric, and weighting rule. Only k changes. This controlled design makes the curve interpretable.

PYTHON  •   Record training and validation performance

k_values = list(range(1322))

records = []

 

forin k_values:

    model = make_pipeline(

        StandardScaler(),

        KNeighborsClassifier(

            n_neighbors=k,

            weights="uniform",

            metric="minkowski",

            p=2,

 

PYTHON  •   Record training and validation performance — continued

        ),

    )

    model.fit(X_train, y_train)

    records.append({

        "k": k,

        "train_accuracy": model.score(X_train, y_train),

        "validation_accuracy": model.score(X_valid, y_valid),

    })

 

results = pd.DataFrame(records)

print(results.round(3))

 

Step 5 — Plot the training and validation curves

PYTHON  •   Visualize model complexity

plt.figure(figsize=(85))

plt.plot(

    results["k"], results["train_accuracy"],

    marker="o", label="Training accuracy",

)

plt.plot(

    results["k"], results["validation_accuracy"],

    marker="o", label="Validation accuracy",

)

plt.xlabel("Number of neighbors (k)")

plt.ylabel("Accuracy")

plt.title("KNN: training versus validation performance")

plt.xticks(k_values)

plt.grid(alpha=0.25)

plt.legend()

plt.tight_layout()

plt.show()

 

Questions for the curve

  • At which k is training accuracy highest? Why is this expected?
  • Which k values produce the strongest validation accuracy?
  • Where is the training–validation gap largest?
  • At what point do both curves begin to fall, if they do?
  • Is the best result a broad plateau or a fragile isolated peak?

Step 6 — Select k without touching the test set

PYTHON  •   Identify the strongest validation candidate

best_row = results.loc[

    results["validation_accuracy"].idxmax()

]

selected_k = int(best_row["k"])

 

print("Selected k:", selected_k)

print(

    "Validation accuracy:",

    round(best_row["validation_accuracy"], 3),

)

print(

    "Training accuracy:",

    round(best_row["train_accuracy"], 3),

)

 

TIE POLICY  If several k values have the same validation score, prefer a value within a stable neighborhood of good results. Record the tie rule before examining the test score.

 

Step 7 — Compare weights and metrics

Use the selected k and vary one additional choice at a time. The test set remains protected. The following four configurations compare equal versus distance weighting and Manhattan versus Euclidean distance.

PYTHON  •   Run a controlled configuration comparison

configurations = []

 

for weights in ["uniform""distance"]:

    forin [12]:

        model = make_pipeline(

            StandardScaler(),

            KNeighborsClassifier(

                n_neighbors=selected_k,

                weights=weights,

                metric="minkowski",

                p=p,

 

PYTHON  •   Run a controlled configuration comparison — continued

            ),

        )

        model.fit(X_train, y_train)

        configurations.append({

            "weights": weights,

            "p": p,

            "validation_accuracy": model.score(X_valid, y_valid),

        })

 

comparison = pd.DataFrame(configurations)

print(comparison.sort_values("validation_accuracy", ascending=False))

 

Step 8 — Freeze and evaluate the final model

Choose the final weights and p from the validation comparison. Then combine training and validation data, refit the complete pipeline, and evaluate once on the test set. The example below assumes the first row of the sorted comparison is selected.

PYTHON  •   Refit on development data and test once

best_config = comparison.sort_values(

    "validation_accuracy", ascending=False

).iloc[0]

 

X_development = pd.concat([X_train, X_valid])

y_development = pd.concat([y_train, y_valid])

 

final_model = make_pipeline(

    StandardScaler(),

    KNeighborsClassifier(

 

PYTHON  •   Refit on development data and test once — continued

        n_neighbors=selected_k,

        weights=best_config["weights"],

        metric="minkowski",

        p=int(best_config["p"]),

    ),

)

 

final_model.fit(X_development, y_development)

test_predictions = final_model.predict(X_test)

 

PYTHON  •   Report final classification results

print("Confusion matrix:")

print(confusion_matrix(y_test, test_predictions))

 

print()

print("Classification report:")

print(classification_report(

    y_test,

    test_predictions,

    target_names=data.target_names,

    digits=3,

))

 

print(

    "Final test accuracy:",

    round(final_model.score(X_test, y_test), 3),

)

 

Step 9 — Inspect the actual neighbors

A pipeline transforms the query before the KNN step can measure distance. The following code retrieves the five nearest development observations for one test case and displays their labels. This local evidence can support debugging, but it does not prove causality or clinical relevance.

PYTHON  •   Inspect one prediction’s nearest examples

query = X_test.iloc[[0]]

scaler = final_model.named_steps["standardscaler"]

knn = final_model.named_steps["kneighborsclassifier"]

 

query_scaled = scaler.transform(query)

distances, indices = knn.kneighbors(

    query_scaled,

    n_neighbors=5,

)

 

neighbor_rows = X_development.iloc[indices[0]].copy()

neighbor_rows["target"] = y_development.iloc[indices[0]].to_numpy()

neighbor_rows["distance"] = distances[0]

 

print("Predicted class:", final_model.predict(query)[0])

print(neighbor_rows[["target""distance"]])

 

Expected observations

  • Training accuracy is usually greatest for the smallest k values.
  • Validation accuracy often improves after moving away from k = 1, then stabilizes or falls.
  • Scaling materially changes neighborhoods because the raw feature ranges differ.
  • Uniform and distance weighting may rank configurations differently.
  • Manhattan and Euclidean distance can produce different local neighborhoods.
  • The final test score may be lower than the best validation score because selection favored validation noise.

Lab deliverables

  • A reproducible notebook containing all code and outputs.
  • A table of k, training accuracy, and validation accuracy.
  • One labeled training-versus-validation curve.
  • A comparison table for weights and p.
  • A final confusion matrix and classification report.
  • A 150–250 word interpretation explaining the selected k and the observed bias–variance behavior.

Extension challenges

  • Replace the single validation set with StratifiedKFold cross-validation and report mean ± standard deviation.
  • Compare the scaled model with an unscaled model using exactly the same data splits.
  • Evaluate balanced accuracy, malignant-class recall, and F1 in addition to overall accuracy.
  • Add deliberately irrelevant random features and measure how KNN performance changes.
  • Time prediction for batches of increasing size and discuss deployment implications.
RESPONSIBLE INTERPRETATION  For a health-related dataset, false negatives and false positives have different consequences. Accuracy alone is not sufficient for a real clinical decision, and this lab is not a deployment study.

 

Common mistakes and corrections

Table 15.14. Frequent KNN errors

Mistake

Why it causes trouble

Correction

Use raw mixed-scale featuresUnits determine the neighborhoodScale numeric features inside a pipeline
Scale before splittingEvaluation rows influence preprocessingFit transformations only on training data
Choose k on the test setTest data becomes part of model selectionUse validation or cross-validation
Assume odd k prevents all tiesMulticlass and weighted votes can still tieInspect tie behavior and stability
Add every available featureIrrelevant dimensions distort distanceUse domain review and validation
Interpret vote share as calibrated riskLocal proportions may be miscalibratedEvaluate calibration separately
Ignore prediction costLazy learning shifts work to scoringBenchmark latency and memory
Use one metric for imbalanceMajority performance can hide minority errorsReport class-aware metrics

 

Knowledge check

Choose one answer for each question. Complete the questions before consulting the answer key.

1. What does KNN primarily use to classify a new observation?

  • A. A fitted linear equation
  • B. Labels of nearby training observations
  • C. The global target mean
  • D. Random class assignment

2. With uniform weights and k = 5, how is the class selected?

  • A. The farthest label wins
  • B. The class with the most neighbor votes wins
  • C. All classes receive equal probability
  • D. The smallest numeric label wins

3. Minkowski distance with p = 2 is equivalent to:

  • A. Manhattan distance
  • B. Cosine similarity
  • C. Euclidean distance
  • D. Hamming distance

4. Why is scaling especially important for KNN?

  • A. KNN requires normal targets
  • B. Large-range features can dominate distance
  • C. Scaling removes every outlier
  • D. It creates more observations

5. What is the safest place for StandardScaler?

  • A. Fit on all rows before splitting
  • B. Inside a pipeline fitted on training data
  • C. After final prediction
  • D. Only on the target

6. A very small k usually produces:

  • A. Higher bias and lower variance
  • B. A more flexible, noise-sensitive boundary
  • C. A global constant prediction
  • D. Faster fitting of a linear equation

7. A very large k can underfit because:

  • A. It ignores too many neighbors
  • B. It averages over overly broad regions
  • C. It always uses Manhattan distance
  • D. It removes scaling

8. weights='distance' means:

  • A. Farther neighbors count more
  • B. Only one neighbor is used
  • C. Closer neighbors have greater influence
  • D. Features are standardized automatically

9. Which is a key high-dimensional limitation?

  • A. Distances can become less discriminative
  • B. KNN becomes a linear model
  • C. The target becomes continuous
  • D. Training labels disappear

10. When should the final test set be used?

  • A. To select k repeatedly
  • B. To choose scaling after inspecting results
  • C. Once after the recipe is frozen
  • D. Before training data are created

Answer key and explanations

Table 15.15. Knowledge-check solutions

Answer

Explanation

1 — B

KNN bases a query’s prediction on labeled training examples that are closest under the selected distance.

2 — B

Uniform weighting gives every selected neighbor one vote, and the largest vote total wins.

3 — C

Minkowski p = 2 is Euclidean distance; p = 1 is Manhattan distance.

4 — B

Without scaling, features with large numerical ranges can dominate the distance calculation.

5 — B

A pipeline fits the scaler on each training subset and reuses it for held-out observations.

6 — B

Small neighborhoods create detailed boundaries with lower bias and higher sensitivity to sampling noise.

7 — B

Large neighborhoods blend labels across wide regions and can erase useful local structure.

8 — C

Inverse-distance weighting gives closer selected neighbors greater influence.

9 — A

In high dimensions, observations become sparse and near/far distances can be less distinguishable.

10 — C

The test set is reserved for the final evaluation after preprocessing and hyperparameters are fixed.

 

Score interpretation

  • 9–10 correct: ready to build and evaluate a leakage-safe KNN classifier.
  • 7–8 correct: solid understanding; revisit scaling or bias–variance behavior where needed.
  • 5–6 correct: repeat the manual distance example and the k-curve lab.
  • 0–4 correct: review the prediction sequence and parameter table before continuing.

Chapter summary

  • KNN predicts from the labels of nearby training observations in a defined feature space.
  • Distance, representation, and scale determine which observations are considered similar.
  • Uniform weights give equal votes; distance weights give closer neighbors more influence.
  • Minkowski p = 1 gives Manhattan distance and p = 2 gives Euclidean distance.
  • Standardization should be fitted on training data inside a pipeline to prevent leakage.
  • Small k creates flexible, high-variance behavior; large k creates smoother, higher-bias behavior.
  • Validation curves reveal the relationship between k, training fit, and generalization.
  • KNN is intuitive and nonlinear but can be slow, memory intensive, scale sensitive, and weak in high dimensions.
  • The final model must be selected without using the protected test set.
ONE SENTENCE TO REMEMBER  KNN is only as meaningful as the geometry you create: choose useful features, scale them correctly, and validate the neighborhood size on unseen data.

 

Key vocabulary

Table 15.16. Essential terminology

Term

Meaning

Instance-based learningPrediction based directly on stored training examples

Neighbor

A training observation close to a query under the chosen metric

k

Number of neighbors used for a standard prediction
Minkowski distanceLp distance family controlled by the power p
Uniform weightingEqual contribution from every selected neighbor
Distance weightingGreater contribution from closer selected neighbors

Feature scaling

Transformation that makes numerical feature ranges comparable
Bias–variance trade-offBalance between oversimplification and sensitivity to data variation
Curse of dimensionalityLoss of meaningful locality as feature-space dimensions increase

 

Further reading

What’s next?

The next chapter studies decision tree classification. Unlike KNN, a tree learns an explicit hierarchy of feature-based rules during training and usually predicts quickly without scaling. Comparing the two models reveals how algorithm assumptions, preprocessing needs, interpretability, and computational cost shape model selection.