Predicting Heart Disease with k-Nearest Neighbors: Diving Deep into Instance-Based Learning
In the rapidly evolving landscape of artificial intelligence and machine learning, few algorithms have stood the test of time quite like k-Nearest Neighbors (kNN). This versatile workhorse has been a staple of data science for over half a century, and continues to find new applications across industries. In this post, we‘ll take an in-depth look at using kNN for a critical healthcare task: predicting heart disease from clinical data.
But first, some sobering statistics. According to the World Health Organization, cardiovascular diseases (CVDs) claim an estimated 17.9 million lives each year, making them the leading cause of death worldwide[^1]. Early detection and intervention can dramatically improve outcomes, but predicting CVD risk remains challenging due to the complex web of factors involved.
This is where machine learning comes in. By training models on large datasets of patient attributes and outcomes, we can uncover subtle patterns and make personalized risk assessments. And among the myriad ML algorithms available, kNN stands out for its simplicity, flexibility, and robustness.
Foundations of Instance-Based Learning
At its core, kNN belongs to a family of techniques called instance-based or memory-based learning. In contrast to model-based methods that try to distill the training data into a compact set of parameters, instance-based learners keep the raw examples around and use them directly to make predictions[^2].
The key idea is that similar inputs should have similar outputs. In other words, things that look alike tend to be alike. This notion of similarity is quantified by a distance metric, which measures how "close" two data points are in the feature space.
Some common distance metrics for continuous features include:
- Euclidean distance: $d(x,y) = \sqrt{\sum_{i=1}^n (x_i – y_i)^2}$
- Manhattan distance: $d(x,y) = \sum_{i=1}^n |x_i – y_i|$
- Minkowski distance: $d(x,y) = (\sum_{i=1}^n |x_i – y_i|^p)^{1/p}$ (generalizes Euclidean and Manhattan)
For categorical features, we can use metrics like Hamming distance, Jaccard similarity, or cosine similarity.
Once we‘ve defined our distance function, making predictions with kNN is straightforward:
- Calculate the distance between the query point and every example in the training set.
- Sort the examples by ascending distance and keep the top k.
- Return the majority class among these k nearest neighbors.
Typically, k is treated as a hyperparameter to be tuned on a validation set or via cross-validation. Larger values of k lead to smoother decision boundaries but risk obscuring local patterns, while smaller k‘s can overfit to noise in the training data.
One variant is weighted kNN, where each neighbor‘s vote is scaled by its distance to the query point. Common weighting functions include inverse distance (1/d) and Gaussian kernels (exp(-d^2)). This gives more influence to closer neighbors and can improve accuracy, at the cost of increased computation.
Other notable instance-based learners include locally weighted regression (LWR), which fits a linear model to the k nearest neighbors, and support vector machines (SVMs), which find the maximum-margin hyperplane separating the classes.
Strengths and Limitations of kNN
So what makes kNN such an enduring and widely-used algorithm? Here are some of its key strengths:
-
Non-parametric: kNN makes no assumptions about the shape of the decision boundaries or the underlying data distribution. This flexibility allows it to adapt to complex patterns that would be hard to capture with a parametric model.
-
Naturally multiclass: While some algorithms like logistic regression and SVMs are inherently binary, kNN trivially extends to multiclass problems by taking a plurality vote among the neighbors.
-
Robust to noisy training data: Because kNN looks at the local neighborhood rather than trying to fit a global model, it‘s less sensitive to random noise and outliers in the training set.
-
Trivial to update: Adding new examples is as simple as appending them to the training set, making kNN well-suited for online learning scenarios where data arrives incrementally.
However, kNN also has some significant limitations:
-
Expensive predictions: Since kNN compares each query point to the entire training set, prediction time scales linearly with the number of examples. For large datasets, this can be prohibitively slow.
-
Curse of dimensionality: As the number of features grows, the distance between any two points tends to become more uniform, making it harder for kNN to differentiate between neighbors[^3]. Dimensionality reduction techniques like PCA or feature selection can help mitigate this.
-
Sensitive to feature scaling: Because kNN relies on distance calculations, features with larger ranges can dominate the metric and wash out the influence of smaller-scaled features. Normalizing or standardizing the data is crucial for achieving good results.
-
Lack of interpretability: Unlike models that learn explicit parameters, kNN‘s "model" is just the training data itself. This makes it hard to interpret why the algorithm is making certain predictions or identify the most important features.
Despite these drawbacks, kNN remains a powerful tool in the data scientist‘s arsenal, and its versatility makes it applicable to a wide range of problems.
Diagnosing Heart Disease with kNN
Now let‘s see how we can apply kNN to the task of predicting heart disease from clinical attributes. We‘ll be using the Cleveland Heart Disease dataset, a classic benchmark in ML research[^4].
The dataset contains 303 patients, each described by 13 features:
- Age: in years
- Sex: 0 = female, 1 = male
- Chest pain type: 1 = typical angina, 2 = atypical angina, 3 = non-anginal pain, 4 = asymptomatic
- Resting blood pressure: in mmHg
- Serum cholesterol: in mg/dl
- Fasting blood sugar: 0 = <120 mg/dl, 1 = >120 mg/dl
- Resting electrocardiographic results: 0 = normal, 1 = ST-T wave abnormality, 2 = left ventricular hypertrophy
- Maximum heart rate achieved: in bpm
- Exercise induced angina: 0 = no, 1 = yes
- ST depression induced by exercise relative to rest: in mm
- Slope of the peak exercise ST segment: 1 = upsloping, 2 = flat, 3 = downsloping
- Number of major vessels colored by fluoroscopy: 0-3
- Thallium scan results: 3 = normal, 6 = fixed defect, 7 = reversible defect
The target variable is the presence of heart disease, indicated by a binary label (0 = no disease, 1 = disease).
Before training our kNN model, let‘s do some quick exploratory analysis:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
df = pd.read_csv(‘processed.cleveland.csv‘)
sns.countplot(x=‘target‘, data=df)
plt.show()

We see that the classes are slightly imbalanced, with about 54% of patients having heart disease. This is important to keep in mind when evaluating our model later on.
Next, let‘s check the pairwise correlations between features:
plt.figure(figsize=(12,10))
sns.heatmap(df.corr(), annot=True, cmap=‘coolwarm‘, linewidths=0.5)
plt.show()

There are a few moderately correlated pairs like age/trestbps and chol/trestbps, but nothing strong enough to warrant removing features at this stage.
Now we can build our kNN pipeline:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import confusion_matrix, classification_report
# Split into features and target
X = df.drop(‘target‘, axis=1)
y = df[‘target‘]
# 80/20 train-test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Standardize the features
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# Train kNN with different k values
k_values = [1, 3, 5, 7, 9, 11, 13, 15]
best_k = None
best_score = 0
for k in k_values:
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train, y_train)
score = knn.score(X_test, y_test)
if score > best_score:
best_k = k
best_score = score
print(f‘Best k: {best_k}‘)
print(f‘Best accuracy: {best_score:.3f}‘)
# Evaluate model on test set
knn = KNeighborsClassifier(n_neighbors=best_k)
knn.fit(X_train, y_train)
y_pred = knn.predict(X_test)
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))
Best k: 5
Best accuracy: 0.885
[[22 3]
[ 4 32]]
precision recall f1-score support
0 0.85 0.88 0.86 25
1 0.91 0.89 0.90 36
accuracy 0.89 61
macro avg 0.88 0.89 0.88 61
weighted avg 0.89 0.89 0.89 61
After trying k values from 1 to 15, we find that k=5 gives the best results on the 20% held-out test set with an accuracy of 88.5%. The confusion matrix shows that the model correctly identifies 22/25 patients without heart disease (specificity) and 32/36 patients with heart disease (sensitivity).
The classification report breaks this down further, showing that the model achieves high precision (0.91) and recall (0.89) for the positive class, and only slightly lower scores for the negative class. The F1 score, which is the harmonic mean of precision and recall, is 0.90 for the diseased group and 0.86 for the non-diseased group.
Overall, these results demonstrate the potential of kNN for accurately diagnosing heart disease from routine clinical data. Of course, there are many avenues for further improvement:
- More sophisticated feature selection or engineering, e.g. using domain knowledge to create interaction terms
- Ensembling kNN with other classifiers like decision trees or naive Bayes
- Experimenting with different distance metrics or weighting schemes
- Using techniques like locality-sensitive hashing to speed up nearest neighbor searches[^5]
- Exploring deep learning architectures that can learn useful representations from raw clinical data[^6]
Conclusion
In this post, we took a deep dive into using k-nearest neighbors for heart disease prediction. We covered the theoretical underpinnings of instance-based learning, the nuts and bolts of the kNN algorithm, and a step-by-step Python implementation on real patient data.
We saw that kNN, despite its simplicity and age, can be a powerful tool for uncovering patterns in complex medical datasets. By making localized predictions based on the most similar training examples, kNN is able to capture subtle relationships that global models might miss.
However, we also highlighted some of the limitations of kNN, including its computational inefficiency, sensitivity to feature scaling, and vulnerability to the curse of dimensionality. Understanding these weaknesses is critical for applying the algorithm responsibly and interpreting its results.
Ultimately, the promise of ML in healthcare is not to replace human doctors, but to augment their expertise with data-driven insights. By combining the intuition and experience of clinicians with the pattern recognition abilities of algorithms like kNN, we can work towards more accurate, personalized, and timely diagnoses of heart disease and other life-threatening conditions.
[^1]: World Health Organization. (2017). Cardiovascular diseases (CVDs). [^2]: Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning: data mining, inference, and prediction. Springer Science & Business Media. [^3]: Beyer, K., Goldstein, J., Ramakrishnan, R., & Shaft, U. (1999). When is "nearest neighbor" meaningful?. In International conference on database theory (pp. 217-235). Springer, Berlin, Heidelberg. [^4]: Detrano, R., Janosi, A., Steinbrunn, W., Pfisterer, M., Schmid, J. J., Sandhu, S., … & Froelicher, V. (1989). International application of a new probability algorithm for the diagnosis of coronary artery disease. The American journal of cardiology, 64(5), 304-310. [^5]: Andoni, A., & Indyk, P. (2008). Near-optimal hashing algorithms for approximate nearest neighbor in high dimensions. Foundations of Computer Science, 2008. FOCS‘08. IEEE 49th Annual IEEE Symposium on (pp. 459-468). IEEE. [^6]: Miotto, R., Wang, F., Wang, S., Jiang, X., & Dudley, J. T. (2017). Deep learning for healthcare: review, opportunities and challenges. Briefings in bioinformatics, 19(6), 1236-1246.