Ex2

Author

enrico vompa

Published

October 3, 2026

library(MASS)
library(caret)
Loading required package: ggplot2
Loading required package: lattice
library(e1071)

Attaching package: 'e1071'
The following object is masked from 'package:ggplot2':

    element
library(randomForest)
randomForest 4.7-1.2
Type rfNews() to see new features/changes/bug fixes.

Attaching package: 'randomForest'
The following object is masked from 'package:ggplot2':

    margin
library(ggplot2)
library(gridExtra)

Attaching package: 'gridExtra'
The following object is masked from 'package:randomForest':

    combine
# took functions from practice 3

fisher_score <- function(X, y) {
  scores <- numeric(ncol(X))
  classes <- unique(y)
  for(i in 1:ncol(X)) {
    overall_mean <- mean(X[, i])
    numerator <- 0
    denominator <- 0
    for(c in classes) {
      X_c <- X[y == c, i]
      n_c <- length(X_c)
      mean_c <- mean(X_c)
      var_c <- var(X_c)
      numerator <- numerator + n_c * (mean_c - overall_mean)^2
      denominator <- denominator + n_c * var_c
    }
    scores[i] <- numerator / denominator
  }
  return(scores)
}

calc_metrics <- function(conf_matrix) {
  accuracy <- sum(diag(conf_matrix)) / sum(conf_matrix)

  diag_n <- diag(conf_matrix)
  col_sum <- colSums(conf_matrix)
  row_sum <- rowSums(conf_matrix)
  precision <- mean(diag_n / ifelse(col_sum == 0, 1, col_sum), na.rm = TRUE)
  recall    <- mean(diag_n / ifelse(row_sum == 0, 1, row_sum), na.rm = TRUE)
  f1_score  <- 2 * ((precision * recall) / (precision + recall))
  return(data.frame(accuracy = accuracy, precision = precision, recall = recall, f1_score = f1_score))
}

run_experiment <- function(data) {
  train_index <- createDataPartition(data$label, p = 0.7, list = FALSE)
  train_data <- data[train_index, ]
  test_data  <- data[-train_index, ]
  
  X_train <- train_data[, -which(names(train_data) == "label")]
  y_train <- train_data$label
  X_test  <- test_data[, -which(names(test_data) == "label")]
  y_test  <- test_data$label
  
  scores <- fisher_score(X_train, y_train)
  selected_features <- order(scores, decreasing = TRUE)[1:2]
  
  X_train_sel <- X_train[, selected_features]
  X_test_sel  <- X_test[, selected_features]
  cat("Selected features:", colnames(X_train_sel), "\n\n")
  
  train_control <- trainControl(method = "cv", number = 5)
  grid_dt <- data.frame(cp = c(0.000001, 0.0001, 0.1))
  model_dt <- train(x = X_train_sel, y = y_train, 
                    method = "rpart", tuneGrid = grid_dt, trControl = train_control)
  
  grid_svm <- expand.grid(sigma = c(0.1, 1), C = c(1, 10))
  model_svm <- train(x = X_train_sel, y = y_train, 
                     method = "svmRadial", tuneGrid = grid_svm, trControl = train_control)

  grid_rf <- expand.grid(mtry = c(1, 2), splitrule = "gini", min.node.size = c(1, 5))
  model_rf <- train(x = X_train_sel, y = y_train, 
                    method = "ranger", 
                    tuneGrid = grid_rf, 
                    trControl = train_control)
  
  pred_dt  <- predict(model_dt, X_test_sel)
  pred_svm <- predict(model_svm, X_test_sel)
  pred_rf  <- predict(model_rf, X_test_sel)
  
  conf_dt  <- table(predicted = pred_dt, Actual = y_test)
  conf_svm <- table(predicted = pred_svm, Actual = y_test)
  conf_rf  <- table(predicted = pred_rf, Actual = y_test)
  
  metrics_dt  <- calc_metrics(conf_dt)
  metrics_svm <- calc_metrics(conf_svm)
  metrics_rf  <- calc_metrics(conf_rf)
  
  x_min <- min(X_test_sel[, 1]) - 1
  x_max <- max(X_test_sel[, 1]) + 1
  y_min <- min(X_test_sel[, 2]) - 1
  y_max <- max(X_test_sel[, 2]) + 1
  
  grid <- expand.grid(
    X1 = seq(x_min, x_max, length.out = 150),
    X2 = seq(y_min, y_max, length.out = 150)
  )
  colnames(grid) <- colnames(X_train_sel) 
  
  pred_grid_dt  <- predict(model_dt, newdata = grid)
  pred_grid_svm <- predict(model_svm, newdata = grid)
  pred_grid_rf  <- predict(model_rf, newdata = grid)
  
  grid$Bound_DT  <- pred_grid_dt
  grid$Bound_SVM <- pred_grid_svm
  grid$Bound_RF  <- pred_grid_rf
  grid_long <- data.frame(
    X1 = rep(grid[[1]], 3),
    X2 = rep(grid[[2]], 3),
    Bound = c(grid$Bound_DT, grid$Bound_SVM, grid$Bound_RF),
    Model = factor(rep(c("Decision Tree", "SVM", "Random Forest"), each = nrow(grid)),
                   levels = c("Decision Tree", "SVM", "Random Forest"))
  )

  df_plot_long <- data.frame(
    X1 = rep(X_test_sel[, 1], 3),
    X2 = rep(X_test_sel[, 2], 3),
    true_class = rep(y_test, 3), 
    predicted = c(pred_dt, pred_svm, pred_rf),
    Model = factor(rep(c("Decision Tree", "SVM", "Random Forest"), each = length(y_test)), levels = c("Decision Tree", "SVM", "Random Forest"))
  )

  p_combined <- ggplot() +
    geom_tile(data = grid_long, aes(x = X1, y = X2, fill = Bound), alpha = 0.2) +
    geom_point(data = df_plot_long, aes(x = X1, y = X2, fill = true_class, color = predicted), shape = 21, size = 2, stroke = 1) +
    facet_wrap(~ Model, ncol = 3) +
    labs(x = colnames(X_train_sel)[1], y = colnames(X_train_sel)[2],
      fill = "true class", color = "predicted class") + 
    theme_minimal(base_size = 14) +
    theme(legend.position = "bottom", legend.text = element_blank())
  
  print(p_combined)
  results <- rbind(DecisionTree = metrics_dt, SVM = metrics_svm, RandomForest = metrics_rf)
  return(results)
}

all_metrics <- list()
for (file in c("blobs.csv", "halfmoons.csv", "happy_face.csv")) {
  cat("\nprocessing dataset:", file, "\n")
  dataset <- read.csv(file)
  label_col_index <- ncol(dataset)
  dataset[, label_col_index] <- as.factor(dataset[, label_col_index])
  levels(dataset[, label_col_index]) <- paste0("Class", 1:nlevels(dataset[, label_col_index]))
  colnames(dataset)[label_col_index] <- "label"
  metrics <- run_experiment(dataset)
  all_metrics[[file]] <- metrics
  print(knitr::kable(metrics, digits = 3, caption = paste("Classification metrics:", file)))
}

processing dataset: blobs.csv 
Selected features: x y 



Table: Classification metrics: blobs.csv

|             | accuracy| precision| recall| f1_score|
|:------------|--------:|---------:|------:|--------:|
|DecisionTree |    0.989|     0.989|  0.989|    0.989|
|SVM          |    0.991|     0.991|  0.991|    0.991|
|RandomForest |    0.988|     0.988|  0.988|    0.988|

processing dataset: halfmoons.csv 
Selected features: x y 



Table: Classification metrics: halfmoons.csv

|             | accuracy| precision| recall| f1_score|
|:------------|--------:|---------:|------:|--------:|
|DecisionTree |    0.989|     0.989|  0.989|    0.989|
|SVM          |    0.989|     0.989|  0.989|    0.989|
|RandomForest |    0.994|     0.994|  0.995|    0.994|

processing dataset: happy_face.csv 
Selected features: y x 



Table: Classification metrics: happy_face.csv

|             | accuracy| precision| recall| f1_score|
|:------------|--------:|---------:|------:|--------:|
|DecisionTree |    0.975|     0.972|  0.973|    0.973|
|SVM          |    0.998|     0.999|  0.998|    0.998|
|RandomForest |    0.995|     0.997|  0.994|    0.995|
dataset_hm <- read.csv("halfmoons.csv")
label_col <- ncol(dataset_hm)
dataset_hm[, label_col] <- as.factor(dataset_hm[, label_col])
levels(dataset_hm[, label_col]) <- paste0("Class", 1:nlevels(dataset_hm[, label_col]))
colnames(dataset_hm)[label_col] <- "label"

n_rows <- nrow(dataset_hm)
dataset_hm$noise1 <- rnorm(n_rows)
dataset_hm$noise2 <- rnorm(n_rows)
dataset_hm$noise3 <- rnorm(n_rows)
current_data <- dataset_hm
results_iterative <- data.frame()

cat("\nIterative feature selection (SVM)\n")

Iterative feature selection (SVM)
while(ncol(current_data) > 1) {
  n_features <- ncol(current_data) - 1
  
  train_idx <- createDataPartition(current_data$label, p = 0.7, list = FALSE)
  train_data <- current_data[train_idx, ]
  test_data  <- current_data[-train_idx, ]
  
  X_train <- train_data[, -which(names(train_data) == "label"), drop = FALSE]
  y_train <- train_data$label
  X_test  <- test_data[, -which(names(test_data) == "label"), drop = FALSE]
  y_test  <- test_data$label
  
  train_control <- trainControl(method = "cv", number = 5)
  grid_svm <- expand.grid(sigma = 1, C = 1) 
  model_svm <- train(x = X_train, y = y_train, method = "svmRadial", 
                     tuneGrid = grid_svm, trControl = train_control)
  
  pred_svm <- predict(model_svm, X_test)
  conf_svm <- table(predicted = pred_svm, actual = y_test)
  mets <- calc_metrics(conf_svm)
  results_iterative <- rbind(results_iterative, data.frame(
    num_features = n_features,
    accuracy = mets$accuracy,
    Precision = mets$precision,
    Recall = mets$recall,
    F1_Score = mets$f1_score
  ))
  
  scores <- fisher_score(X_train, y_train)
  lowest_idx <- which.min(scores)
  lowest_name <- colnames(X_train)[lowest_idx]
  cat("Features:", n_features, "| Dropping:", lowest_name, "\n")
  current_data <- current_data[, !(names(current_data) %in% lowest_name), drop = FALSE]
}
Features: 5 | Dropping: noise2 
Features: 4 | Dropping: noise3 
Features: 3 | Dropping: noise1 
Features: 2 | Dropping: y 
Features: 1 | Dropping: x 
print(knitr::kable(results_iterative, digits = 3))


| num_features| accuracy| Precision| Recall| F1_Score|
|------------:|--------:|---------:|------:|--------:|
|            5|    0.967|     0.967|  0.967|    0.967|
|            4|    0.972|     0.972|  0.973|    0.973|
|            3|    0.983|     0.983|  0.984|    0.984|
|            2|    0.981|     0.981|  0.981|    0.981|
|            1|    0.914|     0.914|  0.918|    0.916|
ggplot(results_iterative, aes(x = num_features, y = accuracy)) +
  geom_line(color = "blue", size = 1.2) +
  geom_point(color = "red", size = 3) +
  scale_x_reverse(breaks = max(results_iterative$num_features):1) + 
  labs(x = "number of features", y = "accuracy") +
  theme_minimal(base_size = 14)
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.