CSCI 1109 — Practical Data Science
By the end of this module, you should be able to:
Imagine an emergency department that wants to flag patients at high risk of needing ICU care within the next 24 hours.
For each patient, we might have features like:
heart_rateoxygen_saturationrespiratory_ratetemperature_clactate_levelLabel:
needs_icu (0 = no, 1 = yes)k-NN idea:
“Find previous patients who look most similar in this feature space.
See what happened to them. Use that as our prediction.”
This is a high-impact decision: getting it wrong affects resource use and patient outcomes.
Feature vector
Feature space
Nearest neighbours
The plot shows:
Use the slider under the plot to:
Two common distance measures between vectors \(\mathbf{x}\) and \(\mathbf{z}\):
🔑 Key idea
distance is defined in feature space, not physical space.
We choose the metric that makes sense for our data and task.
Definition:
\[ d_{L2}(\mathbf{x}, \mathbf{z}) = \sqrt{ \sum_{j=1}^d \left(x_j - z_j\right)^2 } \]
Example (2 features):
\[ \begin{aligned} d_{L2}(\mathbf{x}, \mathbf{z}) &= \sqrt{(90 - 100)^2 + (92 - 89)^2} \\ &= \sqrt{(-10)^2 + (3)^2} \\ &= \sqrt{100 + 9} = \sqrt{109} \approx 10.44 \end{aligned} \]
Definition:
\[ d_{L1}(\mathbf{x}, \mathbf{z}) = \sum_{j=1}^d \left|x_j - z_j\right| \]
Same patients:
\[ d_{L1}(\mathbf{x}, \mathbf{z}) = |90 - 100| + |92 - 89| = 10 + 3 = 13 \]
Both metrics say “these patients aren’t too far apart”—just slightly different ways of counting.
Given:
Algorithm (classification)
Warning
No training step! k-NN is an instance-based / lazy learner.
Suppose we use features:
oxygen_saturation (70–100)lab_marker_crp (0–10,000, mg/L)Three patients:
Which is closer to Patient 1?
Clinically, a big drop in oxygen (say from 92 to 80) is serious.
In raw Euclidean distance:
The distance is dominated by
lab_marker_crp.
Even if oxygen levels are very different, CRP completely swamps them.
Warning
Raw distances on features with wildly different scales can be misleading, especially in medicine.
Take 2-feature vectors:
\[ \mathbf{x} = [x_{ \text{oxygen}}, x_{ \text{crp}}], \quad \mathbf{z} = [z_{ \text{oxygen}}, z_{ \text{crp}}] \]
Squared L2 distance:
\[ \begin{aligned} d_{L2}^2(\mathbf{x}, \mathbf{z}) &= (x_{ \text{oxygen}} - z_{ \text{oxygen}})^2 + (x_{ \text{crp}} - z_{ \text{crp}})^2 \end{aligned} \]
If CRP ranges from 0 to 10,000, then even a small relative difference in CRP (say 500) gives:
\[ (500)^2 = 250{,}000 \]
Compare that to oxygen: a difference of 5 percentage points gives
\[ (5)^2 = 25 \]
So CRP contributes 10,000× more to the distance.
Note
Without scaling, k-NN might act like “distance is in CRP only,” ignoring oxygen almost completely.
For the full math + code details on scaling, see the earlier module:
For this module, remember:
Suppose we scale all features by the same constant \(c > 0\):
\[ \mathbf{x}' = c \mathbf{x}, \quad \mathbf{z}' = c \mathbf{z} \]
Then:
\[ \begin{aligned} d_{L2}(\mathbf{x}', \mathbf{z}') &= \sqrt{\sum_j (c x_j - c z_j)^2} \\ &= \sqrt{\sum_j c^2 (x_j - z_j)^2} \\ &= c \sqrt{\sum_j (x_j - z_j)^2} \\ &= c \, d_{L2}(\mathbf{x}, \mathbf{z}) \end{aligned} \]
So all distances are multiplied by the same factor \(c\).
Takeaway
We’ll build a toy dataset in 2D:
| heart_rate | oxygen_sat | needs_icu | |
|---|---|---|---|
| 0 | 70 | 98 | 0 |
| 1 | 85 | 95 | 0 |
| 2 | 90 | 93 | 0 |
| 3 | 110 | 88 | 1 |
| 4 | 120 | 85 | 1 |
New patient: heart_rate=100, oxygen_sat=90. What does k-NN predict?
| heart_rate | oxygen_sat | needs_icu | distance | |
|---|---|---|---|---|
| 3 | 110 | 88 | 1 | 10.198039 |
| 2 | 90 | 93 | 0 | 10.440307 |
| 1 | 85 | 95 | 0 | 15.811388 |
| 4 | 120 | 85 | 1 | 20.615528 |
| 0 | 70 | 98 | 0 | 31.048349 |
For k = 3, the neighbours are indices 3, 2, 1 with labels [1, 0, 0] → majority 0.
k-NN predicts: this patient does not need ICU-level care.
Warning
This is just a toy example; real clinical models would be carefully validated!
We can visualize the points and our new patient.

Using the same toy dataset:
In real systems:
We’ll explore hyperparameters more in other modules.
| temp_c | crp | needs_antibiotics | distance | |
|---|---|---|---|---|
| 1 | 38.5 | 200 | 1 | 100.001250 |
| 2 | 39.2 | 10 | 0 | 290.002483 |
| 0 | 37.0 | 5 | 0 | 295.001695 |
| 3 | 36.8 | 1500 | 1 | 1200.000600 |
Rough distance intuition
So whichever patient has CRP closest to 300 will be nearest, regardless of temperature.
scikit-learnUse standardization for both features and use 🔗 KNeighborsClassifier:
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
X_train = patients2[["temp_c", "crp"]]
y_train = patients2["needs_antibiotics"]
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
knn = KNeighborsClassifier(n_neighbors=3, metric="euclidean")
knn.fit(X_train_scaled, y_train)
# scale the new patient using *training* stats
new_scaled = scaler.transform([[38.0, 300]])
pred = knn.predict(new_scaled)
pred_proba = knn.predict_proba(new_scaled)
pred, pred_proba(array([0]), array([[0.66666667, 0.33333333]]))
pred_proba gives probabilities for the two classes.🔑 Key idea
After scaling, both temperature and CRP matter to the notion of “closeness”.
For k-NN (like any model):
Common metrics (classification):
Questions to ask:
Even simple models like k-NN can cause harm if used carelessly:
race, disability_status, or postal_code, “closeness” may encode social biases.Although \(k\)-NN is often a “toy” model in courses, its ideas show up in many systems:
\(k\)-NN is a good mental model for “similarity-based retrieval”, even when the actual model is more complex.
Some “explainable AI” methods borrow ideas from k-NN:
Note
k-NN is not like those other models; however, it can be a way of thinking about local behaviour of complex systems.
Conceptual (MCQ)
Suppose we run k-NN on two features: oxygen_saturation (70–100) and crp (0–200,000). We don’t scale either feature. Which is most likely?
A. Oxygen and CRP contribute equally to distance.
B. CRP will dominate distance calculations.
C. Oxygen will dominate distance calculations.
D. It depends only on k, not on feature scales.
Computation (short)
Compute the L2 distance between \(\mathbf{x} = [37.0, 5]\) and \(\mathbf{z} = [39.0, 15]\).
Scaling (MCQ)
We standardize each feature using training data (subtract mean, divide by std). How does this affect k-NN?
A. It leaves distances unchanged for every pair of points.
B. It can change which points are nearest neighbours.
C. It always makes the model worse.
D. It only matters for regression, not classification.
Conceptual (short)
Explain in one sentence why we must fit the scaler on the training data only, then apply it to validation/test data.
