library(dplyr)
library(knitr)
library(caret)
library(pROC)
set.seed(123)
df <- read.csv("wdbc.data", header = FALSE)
names <- c(
'ID', 'Diagnosis', 'radius1', 'texture1', 'perimeter1', 'area1',
'smoothness1', 'compactness1', 'concavity1', 'concave_points1',
'symmetry1', 'fractal_dimension1', 'radius2', 'texture2', 'perimeter2',
'area2', 'smoothness2', 'compactness2', 'concavity2', 'concave_points2',
'symmetry2', 'fractal_dimension2', 'radius3', 'texture3', 'perimeter3',
'area3', 'smoothness3', 'compactness3', 'concavity3', 'concave_points3',
'symmetry3', 'fractal_dimension3'
)
colnames(df) <- names
head(df)
missing<-data.frame(apply(df, 2, function(x) sum(is.na(x))))
colnames(missing) <- "Missing Number"
missing
prop.table(table(df$Diagnosis))
##
## B M
## 0.6274165 0.3725835
names(df)[!sapply(df, is.numeric)]
## [1] "Diagnosis"
par(mfrow = c(5, 6), mar = c(3,3,2,1))
invisible(mapply(function(x, nm) hist(x, main = nm, xlab = nm), df[3:32], names(df[3:32])))
df$Diagnosis <- factor(df$Diagnosis, levels = c("M", "B"))
normalized <- subset(df, select = -c(ID, Diagnosis))
t <- data.frame(Column=character(), Outliers=integer(), Percentage=numeric(),
stringsAsFactors = FALSE)
for (i in colnames(normalized)) {
m <- mean(df[[i]], na.rm = TRUE)
s <- sd(df[[i]], na.rm = TRUE)
z <- abs((df[[i]] - m) / s)
rows.outliers <- which(z > 3)
percent <- round((length(rows.outliers)/nrow(df))*100, 1)
t <- rbind(t, data.frame(Column = i,
Outliers = length(rows.outliers),
Percentage = percent))
}
kable(t, caption = "Outliers", align = 'c')
| Column | Outliers | Percentage |
|---|---|---|
| radius1 | 5 | 0.9 |
| texture1 | 4 | 0.7 |
| perimeter1 | 7 | 1.2 |
| area1 | 8 | 1.4 |
| smoothness1 | 5 | 0.9 |
| compactness1 | 9 | 1.6 |
| concavity1 | 9 | 1.6 |
| concave_points1 | 6 | 1.1 |
| symmetry1 | 5 | 0.9 |
| fractal_dimension1 | 7 | 1.2 |
| radius2 | 7 | 1.2 |
| texture2 | 9 | 1.6 |
| perimeter2 | 8 | 1.4 |
| area2 | 6 | 1.1 |
| smoothness2 | 7 | 1.2 |
| compactness2 | 12 | 2.1 |
| concavity2 | 6 | 1.1 |
| concave_points2 | 6 | 1.1 |
| symmetry2 | 11 | 1.9 |
| fractal_dimension2 | 10 | 1.8 |
| radius3 | 6 | 1.1 |
| texture3 | 4 | 0.7 |
| perimeter3 | 6 | 1.1 |
| area3 | 10 | 1.8 |
| smoothness3 | 3 | 0.5 |
| compactness3 | 10 | 1.8 |
| concavity3 | 7 | 1.2 |
| concave_points3 | 0 | 0.0 |
| symmetry3 | 9 | 1.6 |
| fractal_dimension3 | 9 | 1.6 |
normalized$Diagnosis <- df$Diagnosis
sample_size <- floor(0.8 * nrow(normalized))
set.seed(123)
index <- sample(seq_len(nrow(normalized)), size = sample_size)
train <- normalized[index, ]
test <- normalized[-index, ]
train.x <- subset(train, select = -Diagnosis)
train.y <- train$Diagnosis
test.x <- subset(test, select = -Diagnosis)
test.y <- test$Diagnosis
m <- sapply(train.x, mean)
s <- sapply(train.x, sd)
scale_with <- function(X, m, s) as.data.frame(sweep(sweep(X, 2, m, "-"), 2, s, "/"))
train.x_s<-scale_with(train.x, m, s)
test.x_s<-scale_with(test.x, m, s)
knn_mode <- function(x) {
ux <- unique(x)
index <- match(x, ux)
counts <- tabulate(index, length(ux))
ux[which.max(counts)]
}
knn_distance <- function(p, q) {
sqrt(sum((p - q)^2))
}
knn_all_dists <- function(train, u) {
n <- nrow(train)
v <- numeric(n)
for (i in 1:n) {
p <- train[i, ]
v[i] <- knn_distance(p, u)
}
return(v)
}
knn_predict <- function(train_x, train_y, u, k) {
if (is.data.frame(train_y)) train_y <- train_y[[1]]
d <- knn_all_dists(as.matrix(train_x), as.numeric(u))
ds <- order(d)[1:k]
pred <- knn_mode(train_y[ds])
factor(pred, levels = levels(train_y))
}
knn_predict_all <- function(train_x, train_y, test_x, k = 5) {
n <- nrow(test_x)
pred <- factor(rep(NA, n), levels = levels(train_y))
for (i in 1:n) {
u <- test_x[i, ]
pred[i] <- knn_predict(train_x, train_y, u, k)
}
return(pred)
}
knn_score <- function(train_x, train_y, test_x, k) {
if (is.data.frame(train_y)) train_y <- train_y[[1]]
n <- nrow(test_x)
scores <- numeric(n)
pos = 'M'
for (i in 1:n) {
u <- test_x[i, ]
d <- knn_all_dists(as.matrix(train_x), as.numeric(u))
n <- order(d)[1:k]
scores[i] <- mean(train_y[n] == pos)
}
scores
}
knn_acc <- function(test_y, pred, positive = 'M') {
TP <- sum(test_y == positive & pred == positive)
TN <- sum(test_y != positive & pred != positive)
FP <- sum(test_y != positive & pred == positive)
FN <- sum(test_y == positive & pred != positive)
accuracy <- ((TP + TN) / length(pred)) * 100
return(list(
accuracy = accuracy,
True_Positive = TP,
True_Negative = TN,
False_Positive = FP,
False_Negative = FN
))
}
knn_mode: finds the most frequent label among neighbors.
knn_distance: calculates Euclidean distance between two points.
knn_all_dists: uses knn_distance to compute the distance between one sample and all points in the training data.
knn_predict: uses those distances to predict one label.
knn_predict_all: loops through all test samples to predict their labels.
knn_score: calculates the fraction of “M” neighbors (used later for ROC/AUC).
knn_acc: computes accuracy and returns confusion matrix metrics — TP, TN, FP, and FN.
k <- seq(1, 51, by = 2)
acc <- numeric(length(k))
for (i in seq_along(k)) {
x <- k[i]
prediction <- knn_predict_all(train_x = train.x_s, train_y = train.y, test_x = test.x_s, k = x)
result <- knn_acc(test_y = test.y, pred = prediction)
acc[i] <- result$accuracy
}
acc_k <- data.frame(k = k, accuracy = acc)
plot(acc_k$k, acc_k$accuracy, type = "b",
xlab = "k", ylab = "Accuracy", main = "kNN Accuracy vs k")
which.max(acc_k$accuracy)
## [1] 1
runs<-list()
for (i in 1:5) {
folds <- createFolds(train.y, k = 10)
cv_res <- data.frame(k=integer(), fold=integer(), Accuracy=double())
for (f in seq_along(folds)) {
idx <- folds[[f]]
cv_train.x <- train.x[-idx, , drop=FALSE]
cv_train.y <- train.y[-idx]
cv_test.x <- train.x[idx, , drop=FALSE]
cv_test.y <- factor(train.y[idx], levels = levels(train.y))
m <- sapply(cv_train.x, mean)
s <- sapply(cv_train.x, sd)
cv_train.x <- scale_with(cv_train.x, m, s)
cv_test.x <- scale_with(cv_test.x, m, s)
for (K in k) {
pred <- knn_predict_all(cv_train.x, cv_train.y, cv_test.x, K)
acc <- knn_acc(cv_test.y, pred)
cv_res <- rbind(cv_res, data.frame(k = K, fold = f, Accuracy = acc$accuracy))
}
}
summary <- aggregate(Accuracy ~ k, data = cv_res, mean)
runs[[i]]<-summary
}
runs <- do.call(rbind, runs)
result <- runs %>%
group_by(k) %>%
summarise(
Accuracy = mean(Accuracy, na.rm = TRUE),
.groups = "drop"
)
result
opt_k <-result$k[which.max(result$Accuracy)]
opt_k
## [1] 3
score <- knn_score(train.x_s, train.y, test.x_s, k=opt_k)
roc <- roc(test.y, score)
## Setting levels: control = M, case = B
## Setting direction: controls > cases
roc
##
## Call:
## roc.default(response = test.y, predictor = score)
##
## Data: score in 53 controls (test.y M) > 61 cases (test.y B).
## Area under the curve: 0.979
plot(roc)
auc(roc)
## Area under the curve: 0.979
final_pred <- knn_predict_all(train.x_s, train.y, test.x_s, k=opt_k)
knn_acc(test.y, final_pred)
## $accuracy
## [1] 95.61404
##
## $True_Positive
## [1] 49
##
## $True_Negative
## [1] 60
##
## $False_Positive
## [1] 1
##
## $False_Negative
## [1] 4