📓Case Study

Dataset Walkthrough


Το dataset περιλαμβάνει κυτταρολογικά χαρακτηριστικά από 699 βιοψίες. Στόχος μας είναι η κατασκευή ενός μοντέλου που προβλέπει αν ένας όγκος είναι καλοήθης (benign) ή κακοήθης (malignant), λειτουργώντας ως εργαλείο υποστήριξης απόφασης (Decision Support Tool) για τους ιατρούς.

Data Preview

rmarkdown::paged_table(bc)


Note!

Για λόγους στατιστικής και προκειμένου να έχουμε καλή εικόνα της κατανομής των δειγμάτων (καλοήθης - κακοήθης) είναι καλό να ερευνήσουμε την συχνότητα εμφάνισης θετικών και αρνητικών τιμών σο δείγμα:

table(bc$Class) %>% 
  as.data.frame() %>% 
  kable(col.names = c("Κλάση", "Συχνότητα"), align = "c") %>% 
  kable_styling(bootstrap_options = "striped", full_width = F)
Κλάση Συχνότητα
benign 444
malignant 239


# 1. Υπολογισμός συσχέτισης
cor_matrix <- cor(bc[, 1:9])
cor_df <- as.data.frame(round(cor_matrix, 2))

for (i in 1:ncol(cor_df)) {
  cor_df[, i] <- ifelse(abs(cor_matrix[, i]) > 0.7 & abs(cor_matrix[, i]) < 1, 
                        paste0("**", cor_df[, i], "**"), 
                        as.character(cor_df[, i]))
}

# 3. Εμφάνιση πίνακα
knitr::kable(cor_df, 
             caption = "Πίνακας Συσχετίσεων", 
             align = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                latex_options = c("scale_down", "hold_position"), 
                font_size = 13,                                   
                full_width = FALSE)
Πίνακας Συσχετίσεων
Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size Bare.nuclei Bl.cromatin Normal.nucleoli Mitoses
Cl.thickness 1 0.64 0.65 0.49 0.52 0.59 0.55 0.53 0.35
Cell.size 0.64 1 0.91 0.71 0.75 0.69 0.76 0.72 0.46
Cell.shape 0.65 0.91 1 0.69 0.72 0.71 0.74 0.72 0.44
Marg.adhesion 0.49 0.71 0.69 1 0.59 0.67 0.67 0.6 0.42
Epith.c.size 0.52 0.75 0.72 0.59 1 0.59 0.62 0.63 0.48
Bare.nuclei 0.59 0.69 0.71 0.67 0.59 1 0.68 0.58 0.34
Bl.cromatin 0.55 0.76 0.74 0.67 0.62 0.68 1 0.67 0.35
Normal.nucleoli 0.53 0.72 0.72 0.6 0.63 0.58 0.67 1 0.43
Mitoses 0.35 0.46 0.44 0.42 0.48 0.34 0.35 0.43 1


Ο πίνακας συσχετίσεων αποκαλύπτει ισχυρές εξαρτήσεις μεταξύ των χαρακτηριστικών, με κυριότερη τη γραμμική σχέση μεταξύ του μεγέθους και του σχήματος των κυττάρων (0,91), η οποία υποδηλώνει σημαντική συνάφεια στην πληροφορία που φέρουν. Πολλές μεταβλητές, όπως η χρωματίνη και η επιθηλιακή δομή, παρουσιάζουν επίσης υψηλούς συντελεστές (άνω του 0,70) σε σχέση με τα μορφολογικά χαρακτηριστικά, καταδεικνύοντας ένα ιδιαίτερα συνεκτικό σύνολο δεδομένων. Στον αντίποδα, η μεταβλητή των μιτώσεων εμφανίζει σταθερά τις χαμηλότερες συσχετίσεις, υποδηλώνοντας μια πιο ανεξάρτητη συμπεριφορά μέσα στο μοντέλο. Η συνολική αυτή εικόνα εξηγεί τη δυνατότητα των αλγορίθμων να εντοπίζουν σαφή πρότυπα, γεγονός που δικαιολογεί την υψηλή προβλεπτική ακρίβεια που σημειώθηκε στην αξιολόγηση.


Μεταβλητές ανά Θεματική Περιοχή

🔬 Μορφολογία Κυττάρου

  • Cell.size: Ομοιομορφία μεγέθους των κυττάρων (1 έως 10).

  • Cell.shape: Ομοιομορφία σχήματος των κυττάρων (1 έως 10).

  • Epith.c.size: Μέγεθος των επιθηλιακών κυττάρων (1 έως 10).

🧬 Πυρηνικά Χαρακτηριστικά

  • Bare.nuclei: Παρουσία “γυμνών” πυρήνων χωρίς περιβάλλον κυτταρόπλασμα (1 έως 10).

  • Bl.chromatin: Η υφή και η κατανομή της χρωματίνης στον πυρήνα (1 έως 10).

  • Normal.nucleoli: Η εμφάνιση και το μέγεθος των πυρηνίσκων στο εσωτερικό του πυρήνα (1 έως 10).

🧫 Δομή Ιστού & Ανάπτυξη

  • Cl.thickness: Το πάχος των ομάδων (clumps) των κυττάρων (1 έως 10).

  • Marg.adhesion: Ο βαθμός προσκόλλησης των κυττάρων στην περιφέρεια (1 έως 10).

  • Mitoses: Ο ρυθμός μίτωσης, που υποδεικνύει την ταχύτητα αναπαραγωγής των κυττάρων (1 έως 10).

🎯 Διάγνωση & Στόχος

  • Class (Target Variable): Η μεταβλητή που θέλουμε να προβλέψουμε. Δείχνει αν ο όγκος είναι καλοήθης (benign) ή κακοήθης (malignant).
Baseline με Random Forest


Για λόγους καλής διαχείρησης των δεδομέων μας και για να έχουμε ένα αντικειμενικό, non-biased μοντέλο, αξίζει να προβούμε σε ορισμένες ενέργειες παρέμβασης στο δείγμα που θα τροφοδοτήσουμε το μοντέλο για να εκπαιδευτεί και στη συνέχεια να ελεγχθεί.


Αρχικά, διασφαλίζουμε ότι έχουμε σωστή αναλογία θετικών και αρνητικών διαγνώσεων στα Test και Train δείγματα. Όπως διαπιστώνουμε παρακάτω, η αναλογία είναι πολύ παρόμοια, εφόσον κυμαίνεται περί το 65 προς 35 (αρνητικά έναντι θετικών) και στα δύο sets:

train_index <- createDataPartition(bc$Class, p = 0.7, list = FALSE)

train_set <- bc[train_index, ]
test_set  <- bc[-train_index, ]

# Δημιουργία αναλυτικού πίνακα για τον έλεγχο της κατανομής
split_comparison <- data.frame(
  Κλάση = levels(bc$Class),
  Train_N = as.numeric(table(train_set$Class)),
  Test_N = as.numeric(table(test_set$Class)),
  Train_Perc = as.numeric(prop.table(table(train_set$Class))) * 100,
  Test_Perc = as.numeric(prop.table(table(test_set$Class))) * 100
)

knitr::kable(split_comparison, digits = 1, 
             col.names = c("Κλάση", "Train (N)", "Test (N)", "Train (%)", "Test (%)"),
             caption = "Έλεγχος Stratified Split: Κατανομή Καλοηθών και Κακοηθών Όγκων") %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Έλεγχος Stratified Split: Κατανομή Καλοηθών και Κακοηθών Όγκων
Κλάση Train (N) Test (N) Train (%) Test (%)
benign 311 133 64.9 65.2
malignant 168 71 35.1 34.8


Στη συνέχεια, ορίζουμε στο μοντέλο να δημιουργήσει 500 δέντρα απόφασης (ntree = 500), ενώ ενεργοποιούμε την επιλογή importance = TRUE ώστε αργότερα να δούμε ποια χαρακτηριστικά (όπως π.χ. το μέγεθος του κυττάρου) έπαιξαν τον σημαντικότερο ρόλο στην τελική απόφαση.

# Εκπαίδευση του μοντέλου Random Forest
rf_model <- randomForest(Class ~ ., 
                         data = train_set, 
                         ntree = 500, 
                         importance = TRUE)

Παρακάτω ακολουθεί ο κώδικας που ελέγχει την απόδοση του μοντέλου μας, ενώ επίσης θα υπολογίσουμε τις βασικές μετρικές που μας ενδιαφέρουν προκειμένου να το αξιολογήσουμε.

# 1. Προβλέψεις (Κλάσεις και Πιθανότητες)
rf_preds <- predict(rf_model, test_set)
rf_probs <- predict(rf_model, test_set, type = "prob")[, "malignant"]

# 2. Confusion Matrix
rf_cm <- confusionMatrix(rf_preds, test_set$Class, positive = "malignant")

# 3. Υπολογισμός AUC
rf_roc <- roc(test_set$Class, rf_probs)
rf_auc <- auc(rf_roc)

# 4. Δημιουργία Συνοπτικού Πίνακα
rf_metrics_df <- data.frame(
  Μετρική = c("Accuracy (Ακρίβεια)", 
               "Sensitivity (Ευαισθησία)", 
               "Specificity (Εξειδίκευση)", 
               "AUC (Area Under Curve)"),
  Τιμή = c(as.numeric(rf_cm$overall["Accuracy"]), 
          as.numeric(rf_cm$byClass["Sensitivity"]), 
          as.numeric(rf_cm$byClass["Specificity"]), 
          as.numeric(rf_auc))
)

knitr::kable(rf_metrics_df, digits = 4, align = "lc", 
             caption = "Αξιολόγηση Απόδοσης Random Forest στο Test Set",
             row.names = FALSE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Αξιολόγηση Απόδοσης Random Forest στο Test Set
Μετρική Τιμή
Accuracy (Ακρίβεια) 0.9755
Sensitivity (Ευαισθησία) 0.9718
Specificity (Εξειδίκευση) 0.9774
AUC (Area Under Curve) 0.9943


Ο παραπάνω πίνκας αναδεικνύει μια εξαιρετικά ισχυρή και αξιόπιστη προβλεπτική ικανότητα για το Random Forest μοντέλο μας. Αρχικά, η συνολική ακρίβεια που ανέρχεται στο 97,55% επιβεβαιώνει την ικανότητα του αλγορίθμου να ταξινομεί ορθά τη συντριπτική πλειονότητα των περιπτώσεων, ενώ η ισορροπία μεταξύ Sensitivity (97,18%) και Specificity (97,74%) αποδεικνύει ότι το μοντέλο διαχειρίζεται με την ίδια αποτελεσματικότητα τόσο τις θετικές όσο και τις αρνητικές κλάσεις. Παράλληλα, η τιμή του AUC (0,9943), αποτελεί την πιο ισχυρή ένδειξη της ποιότητας του μοντέλου, καθώς καταδεικνύει μια σχεδόν τέλεια ικανότητα διαχωρισμού μεταξύ των ομάδων.

Ακολουθεί ένα Variable Importance Plot, από όπου μπορούμε να αντλήσουμε πληροφορίες σχετικά με το κατά πόσο ήταν αποφασιστική η κάθε μεταβλητή στον καθορισμό του αποτελέσματος.

# Δημιουργία γραφήματος σημαντικότητας μεταβλητών
varImpPlot(rf_model, 
           main = "Σημαντικότητα Χαρακτηριστικών - Random Forest",
           col = "darkblue", 
           pch = 16)

Βάσει της ανάλυσης σημαντικότητας (Variable Importance), τα τρία χαρακτηριστικά που επηρεάζουν περισσότερο την πρόβλεψη του μοντέλου είναι:

  1. Cell.size: Η πιο κρίσιμη μεταβλητή για τη διατήρηση της ακρίβειας του μοντέλου.

  2. Cell.shape: Εξαιρετικά σημαντική κυρίως για ακρίβεια αλλά και για τον διαχωρισμό των κλάσεων.

  3. Bare.nuclei: Η τρίτη μεταβλητή που συμβάλλει καθοριστικά στη μείωση του σφάλματος πρόβλεψης. Υστερεί λίγο σε ακρίβεια σε σχέση με τη δεύτερη μεταβλητή αλλά υπερτερεί στον διαχωρισμό.

Boosting & Tuning


Το XGBoost δεν δέχεται τα δεδομένα σε μορφή πίνακα (Data Frame) όπως τα έχουμε μέχρι τώρα. Απαιτεί πίνακες (matrices), όπου οι ανεξάρτητες μεταβλητές πρέπει να μετατραπούν σε αριθμητικούς πίνακες, αλλά και αριθμητικό target, δηλαδή η μεταβλητή Class δεν μπορεί να είναι κείμενο (“benign”/“malignant”). Πρέπει να γίνει 0 για τους καλοήθεις και 1 για τους κακοήθεις όγκους.

# 1. Μετατροπή των χαρακτηριστικών σε matrix (μόνο οι στήλες 1 έως 9)
train_x <- as.matrix(train_set[, 1:9])
test_x  <- as.matrix(test_set[, 1:9])

# 2. Μετατροπή του target σε 0 και 1
# malignant = 1, benign = 0
train_y <- ifelse(train_set$Class == "malignant", 1, 0)
test_y  <- ifelse(test_set$Class == "malignant", 1, 0)

# 3. Δημιουργία των DMatrix (η ειδική δομή δεδομένων που προτιμά το XGBoost)
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest  <- xgb.DMatrix(data = test_x, label = test_y)


Προχωράμε στην εκπαίδευση του XGBoost. Αυτό το μοντέλο λειτουργεί προσθέτοντας σταδιακά νέα δέντρα, όπου κάθε επόμενο δέντρο προσπαθεί να διορθώσει τα λάθη των προηγούμενων. Στον παρακάτω κώδικα, βλέπουμε ως αποτέλεσμα και τις τιμές AUC που αντιστοιχούν σε κάθε νέο βελτιωμένο μοντέλο.

set.seed(44)

params <- list(
  objective   = "binary:logistic",
  eval_metric = "auc",
  max_depth   = 4,
  eta         = 0.1,        
  subsample   = 0.8,
  colsample_bytree = 0.8
)

xgb_model <- xgb.train(
  params              = params,
  data                = dtrain,
  nrounds             = 500,
  watchlist           = list(train = dtrain, test = dtest),
  early_stopping_rounds = 20,
  print_every_n       = 25,
  verbose             = 1
)
## Multiple eval metrics are present. Will use test_auc for early stopping.
## Will train until test_auc hasn't improved in 20 rounds.
## 
## [1]  train-auc:0.977368  test-auc:0.981785 
## [26] train-auc:0.998565  test-auc:0.990734 
## [51] train-auc:0.999426  test-auc:0.992799 
## [76] train-auc:0.999885  test-auc:0.993858 
## [101]    train-auc:0.999981  test-auc:0.994176 
## Stopping. Best iteration:
## [115]    train-auc:1.000000  test-auc:0.993540
## 
## [115]    train-auc:1.000000  test-auc:0.993540


Ας δούμε στη συνέχεια την επίδοση των δύο μοντέλων μας (Random Forest - Gradiend Boosting) συγκεντρωτικά, καθώς και τα ROC curves που αντιστοιχούν στο καθένα σε ένα διάγραμμα:

# 1. Προβλέψεις πιθανοτήτων για το XGBoost
xgb_probs <- predict(xgb_model, test_x)

# 2. Μετατροπή σε κλάσεις (Malignant αν > 0.5)
xgb_preds <- factor(ifelse(xgb_probs > 0.5, "malignant", "benign"), 
                    levels = c("benign", "malignant"))

# 3. Υπολογισμός Confusion Matrix και AUC για το XGBoost
xgb_cm  <- confusionMatrix(xgb_preds, test_set$Class, positive = "malignant")
xgb_roc <- roc(test_y, xgb_probs)

# 4. Δημιουργία του συγκριτικού πίνακα
comparison_df <- data.frame(
  Μετρική = c("Accuracy", "Sensitivity (Recall)", "Specificity", "AUC"),
  Random_Forest = as.numeric(c(rf_cm$overall["Accuracy"], 
                               rf_cm$byClass["Sensitivity"], 
                               rf_cm$byClass["Specificity"], 
                               rf_auc)),
  XGBoost = as.numeric(c(xgb_cm$overall["Accuracy"], 
                         xgb_cm$byClass["Sensitivity"], 
                         xgb_cm$byClass["Specificity"], 
                         auc(xgb_roc)))
)

# Εμφάνιση του πίνακα
knitr::kable(comparison_df, digits = 4, align = "lcc", row.names = FALSE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Μετρική Random_Forest XGBoost
Accuracy 0.9755 0.9657
Sensitivity (Recall) 0.9718 0.9437
Specificity 0.9774 0.9774
AUC 0.9943 0.9944
#Random Forest (Μπλε χρώμα)
plot(rf_roc, col = "#0B5CAD", lwd = 3, main = "Σύγκριση Καμπυλών ROC: RF vs XGBoost")

#XGBoost (Πορτοκαλί χρώμα)
plot(xgb_roc, col = "#e67e22", lwd = 3, add = TRUE)

# Προσθήκη υπομνήματος
legend("bottomright", 
       legend = c(paste("Random Forest (AUC:", round(rf_auc, 3), ")"), 
                  paste("XGBoost (AUC:", round(auc(xgb_roc), 3), ")")), 
       col = c("#0B5CAD", "#e67e22"), 
       lwd = 3, 
       bty = "n") 


📝 Συνοπτικό Συμπέρασμα & Αξιολόγηση Μοντέλων

  • Από τη σύγκριση των δύο αλγορίθμων (Random Forest και XGBoost) για τη διάγνωση του καρκίνου του μαστού, προκύπτουν τα εξής συμπεράσματα:

  • Το μοντέλο XGBoost πέτυχε κατά 0.0001 υψηλότερο AUC (0.9944 έναντι του 0.9943 που πέτυχε το Random Forest) . Αυτό υποδηλώνει ότι οι αλγόριθμοι είναι εξίσου ικανοί να διαχωρίζουν τις δύο κλάσεις (καλοήθεις vs κακοήθεις) σε όλα τα πιθανά επίπεδα πιθανότητας.

  • Εντούτοις, το Random Forest εμφάνισε υψηλότερη Ευαισθησία (Sensitivity/Recall: 0.9577) στο προκαθορισμένο κατώφλι του 0.5. Σε μια ογκολογική μελέτη, η Ευαισθησία (Sensitivity) είναι η πιο κρίσιμη μετρική, καθώς στόχος είναι η ελαχιστοποίηση των False Negatives.


Τελικό Συμπέρασμα

Με βάση τα παραπάνω, το Random Forest κρίνεται ως η ασφαλέστερη επιλογή για άμεση κλινική υποστήριξη στο τρέχον configuration. Το XGBoost, παρότι είναι εξίσου στατιστικά ποιοτικό μοντέλο, θα απαιτούσε περαιτέρω παραμετροποίηση του κατωφλίου απόφασης (threshold tuning) για να φτάσει ή να ξεπεράσει την ευαισθησία του Random Forest.