K-Nearest Neighbors (KNN)
Review when to use this method, its data requirements, implementation patterns, and interpretation guidance.
Description
A simple yet effective non-parametric method that classifies or predicts based on the majority class or average value of the k nearest data points. KNN makes no assumptions about data distribution and adapts to complex decision boundaries through local approximations, making it useful for classification, regression, recommendation systems, and as a baseline for more complex models when prior knowledge about data structure is limited. The algorithm's performance heavily depends on distance metric selection and feature scaling.
Use Cases
- classification
- regression
- recommendation systems
- missing value imputation
- anomaly detection
- pattern recognition
Requirements
- Sample Size: small, medium
- Missing Data: none, random
- Data Distribution: any
- Relationship Type: distance-based, non-linear, local
Variable Types
Dependent Variables
- continuous
- categorical
- binary
- ordinal
Independent Variables
- continuous
- categorical
Implementation
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
# Create preprocessing and modeling pipeline
pipe = Pipeline([
('scaler', StandardScaler()),
('knn', KNeighborsClassifier())
])
# Parameter grid for tuning
param_grid = {
'knn__n_neighbors': [3, 5, 7, 9],
'knn__weights': ['uniform', 'distance'],
'knn__p': [1, 2] # 1: Manhattan, 2: Euclidean
}
# Classification with tuned parameters
grid = GridSearchCV(pipe, param_grid, cv=5)
grid.fit(X_train, y_train)
# Regression model
knn_reg = Pipeline([
('scaler', StandardScaler()),
('knn', KNeighborsRegressor(n_neighbors=5))
])
knn_reg.fit(X_train, y_train)
# Best parameters
print(grid.best_params_)
# Visualize decision boundaries (for 2D data)
plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train)
plt.title('KNN Decision Boundary')
plt.show()
Documentation
library(caret)
library(FNN)
# Scale features
preProc <- preProcess(train_data, method=c("center", "scale"))
train_scaled <- predict(preProc, train_data)
test_scaled <- predict(preProc, test_data)
# Classification with tuned parameters
ctrl <- trainControl(method="cv", number=5)
knn_fit <- train(
y ~ .,
data = train_scaled,
method = "knn",
trControl = ctrl,
tuneGrid = expand.grid(k = 1:10),
metric = "Accuracy"
)
# Regression
knn_reg <- knn.reg(
train = train_scaled[, -which(names(train_scaled) == "y")],
test = test_scaled[, -which(names(test_scaled) == "y")],
y = train_scaled$y,
k = 5
)
# Plot results
plot(knn_fit)
plot(test_scaled$y, knn_reg$pred, main="Actual vs Predicted")
Documentation
KNN
/TARGET target_var
/FEATURES var1 var2 var3
/NEIGHBORS 5
/DISTANCE MINKOWSKI(2)
/WEIGHTS DISTANCE
/PRINT CONFUSION
/SAVE PREDICTED(pred_knn) NEIGHBORS(neighbors).
* For regression:
KNN
/TARGET target_var
/FEATURES var1 var2 var3
/NEIGHBORS 5
/TYPE REGRESSION
/PRINT SUMMARY.
Documentation
/* Classification */
PROC KNN DATA=train
TESTDATA=test
K=5
METHOD=EUCLIDEAN
STANDARDIZE=STD;
INPUT var1-var8 / LEVEL=INTERVAL;
TARGET target_var / LEVEL=NOMINAL;
SCORE OUT=scored;
RUN;
/* Regression */
PROC KNN DATA=train
TESTDATA=test
K=5
METHOD=EUCLIDEAN
STANDARDIZE=STD
TYPE=REGRESSION;
INPUT var1-var8 / LEVEL=INTERVAL;
TARGET target_var / LEVEL=INTERVAL;
RUN;
Documentation
* Classification
knn y x1 x2 x3, k(5) distance(euclidean) weight(inverse)
predict yhat
* Regression
knnreg y x1 x2 x3, k(5) distance(minkowski)
* Find optimal k
knn tune y x1-x3, krange(1 10) cv(5)
Documentation
Synthetic Data Example
Comprehensive synthetic datasets demonstrating KNN's ability to handle different distance metrics and neighborhood sizes
R Code for Data Generation and Analysis
# Generate synthetic data with different patterns
set.seed(123)
library(ggplot2)
# Cluster pattern
n <- 200
cluster1 <- data.frame(
x1 = rnorm(n/2, mean=-1, sd=0.5),
x2 = rnorm(n/2, mean=-1, sd=0.5),
y = "Class1"
)
cluster2 <- data.frame(
x1 = rnorm(n/2, mean=1, sd=0.5),
x2 = rnorm(n/2, mean=1, sd=0.5),
y = "Class2"
)
df_cluster <- rbind(cluster1, cluster2)
# Checkerboard pattern
x1 <- runif(n, -2, 2)
x2 <- runif(n, -2, 2)
df_checker <- data.frame(
x1,
x2,
y = factor(ifelse((x1 > 0 & x2 > 0) | (x1 < 0 & x2 < 0), "Class1", "Class2"))
)
# Visualize datasets
ggplot(df_cluster, aes(x1, x2, color=y)) + geom_point() + ggtitle("Cluster Pattern")
ggplot(df_checker, aes(x1, x2, color=y)) + geom_point() + ggtitle("Checkerboard Pattern")
# Train-test split
set.seed(456)
train_idx <- sample(1:n, 0.7*n)
train_cluster <- df_cluster[train_idx, ]
test_cluster <- df_cluster[-train_idx, ]
train_checker <- df_checker[train_idx, ]
test_checker <- df_checker[-train_idx, ]
# Scale features
scale_fn <- function(train, test) {
means <- apply(train[,1:2], 2, mean)
sds <- apply(train[,1:2], 2, sd)
train_scaled <- as.data.frame(scale(train[,1:2], center=means, scale=sds))
test_scaled <- as.data.frame(scale(test[,1:2], center=means, scale=sds))
train_scaled$y <- train$y
test_scaled$y <- test$y
list(train=train_scaled, test=test_scaled)
}
cluster_scaled <- scale_fn(train_cluster, test_cluster)
checker_scaled <- scale_fn(train_checker, test_checker)
# Fit KNN models with different k
library(class)
k_values <- c(1, 3, 5, 10, 20)
results <- data.frame()
for (k in k_values) {
# Cluster pattern
pred_cluster <- knn(
train = cluster_scaled$train[,1:2],
test = cluster_scaled$test[,1:2],
cl = cluster_scaled$train$y,
k = k
)
acc_cluster <- mean(pred_cluster == cluster_scaled$test$y)
# Checkerboard pattern
pred_checker <- knn(
train = checker_scaled$train[,1:2],
test = checker_scaled$test[,1:2],
cl = checker_scaled$train$y,
k = k
)
acc_checker <- mean(pred_checker == checker_scaled$test$y)
results <- rbind(results, data.frame(
k = k,
accuracy_cluster = acc_cluster,
accuracy_checker = acc_checker
))
}
# Plot accuracy vs k
ggplot(results, aes(k)) +
geom_line(aes(y=accuracy_cluster, color="Cluster Pattern")) +
geom_line(aes(y=accuracy_checker, color="Checkerboard Pattern")) +
labs(title="KNN Performance by k Value", y="Accuracy", color="Pattern") +
scale_x_continuous(breaks=k_values)
# Create decision boundary plots
library(gridExtra)
generate_boundary_plot <- function(df, k, title) {
# Create grid
rng <- apply(df[,1:2], 2, range)
x1_seq <- seq(rng[1,1], rng[2,1], length=100)
x2_seq <- seq(rng[1,2], rng[2,2], length=100)
grid <- expand.grid(x1=x1_seq, x2=x2_seq)
# Predict on grid
grid$pred <- knn(
train = df[,1:2],
test = grid[,1:2],
cl = df$y,
k = k
)
# Plot
ggplot(grid, aes(x1, x2, fill=pred)) +
geom_tile(alpha=0.3) +
geom_point(data=df, aes(color=y), size=2) +
ggtitle(paste(title, "(k =", k, ")")) +
theme_minimal()
}
p1 <- generate_boundary_plot(cluster_scaled$train, 3, "Cluster Pattern")
p2 <- generate_boundary_plot(checker_scaled$train, 10, "Checkerboard Pattern")
grid.arrange(p1, p2, ncol=2)
# Print results
cat("Accuracy by k value:\n")
print(results)
Expected Analysis Results
Console Output
Accuracy by k value:
k accuracy_cluster accuracy_checker
1 1 0.950 0.783
2 3 0.967 0.850
3 5 0.967 0.883
4 10 0.950 0.917
5 20 0.933 0.900
Optimal k for Cluster Pattern: 3 or 5
Optimal k for Checkerboard Pattern: 10
Visualizations
Interpretation Guide
Need help interpreting the results of your K-Nearest Neighbors (KNN) analysis? Our comprehensive interpretation guide explains:
- How to read and understand model outputs
- Interpreting coefficients and effect sizes correctly
- Understanding diagnostic plots and visualizations
- Common pitfalls and how to avoid them
- Making valid conclusions from your analysis