1 Introduction

1.1 Tujuan Pelatihan

Sebelum mengenal tentang classification, mari sedikit membahas tentang machine learning itu sendiri. Machine Learning adalah suatu bidang ilmiah yang mempelajari tentang algoritma dan model statistik yang digunakan oleh sistem komputer dengan mengandalkan pola. Machine learning atau dalam Bahasa Indonesia Pembelajaran Mesin juga memberikan kemampuan pada sistem untuk secara otomatis belajar dan meningkat dari pengalaman tanpa diprogram secara eksplisit. Pembelajaran mesin berfokus pada pengembangan program komputer yang dapat mengakses data dan menggunakannya untuk belajar sendiri.

Lalu kita masuk kedalam pembahasan apa itu classification? Classification dapat didefinisikan sebagai proses memprediksi kelas atau kategori dari nilai yang diamati atau titik data yang diberikan. Output yang dikategorikan dapat memiliki bentuk seperti “Black” atau “White” atau “spam” atau “no spam”. Secara matematis, classification adalah tugas mendekati fungsi pemetaan (f) dari variabel input (X) ke variabel output (Y). Ini pada dasarnya milik pembelajaran mesin yang diawasi di mana target juga disediakan bersama dengan set data input.

BERBAGAI ALGORITMA CLASSIFICATION

Berikut ini adalah beberapa algoritma classification :

  • Logistic Regression

  • Support Vector Machine (SVM)

  • Decision Tree

  • Naïve Bayes

  • Random Forest

Tujuan utama dari kursus ini adalah untuk memberikan pengenalan yang komprehensif untuk classification in machine learning dengan menggunakan data yang ada.

Mempelajari algoritma klasifikasi dari dasar, menyelidiki dasar matematika yang mendukung algoritma Naïve Bayes, algoritma Decision Tree, dan algoritma Random Forest.

  1. Naive Bayes

merupakan algoritma klasifikasi yang didasari oleh Bayes’ Theorem of Probability.

Memiliki karakter:

  • Hubungan antara prediktor dengan target variabel dianggap saling dependen.
  • Dikatakan “Naive” karena tiap prediktor diasumsikan saling independent (tidak berhubungan satu sama lain) dan memiliki bobot yang sama (memiliki kepentingan atau pengaruh yang sama) dalam melakukan prediksi. Hal ini untuk memudahkan kalkulasi (rumus menjadi lebih simpel) dan mengurangi beban komputasi.

Kekurangan dari Naive Bayes:

  • Skewness due to data scarcity yaitu timbul bias ketika ada kejadian yang jarang muncul atau bahkan tidak muncul sama sekali.
  • Laplace Smoothing merupakan solusi dari skewness due to data scarcity, digunakan untuk tuning model karena ketika kita menggunakan nilai laplace yang berbeda akan menghasilkan probabilitas yang berbeda pula.
  1. Decision Tree

merupakan tree-based model yang cukup sederhana dengan performa yang robust/powerful untuk prediksi. Decision Tree menghasilkan visualisasi berupa pohon keputusan yang dapat diinterpretasi dengan mudah.

Memiliki karakter:

  • Variable predictor diasumsikan saling dependent, sehingga dapat mengatasi multicollinearity.
  • Dapat mengatasi nilai predictor numerik yang berupa outlier.
  • Decision Tree tidak hanya terbatas pada kasus Classification, namun dapat digunakan pada kasus Regression.
  • Robust (performa model stabil)

Kekurangan dari Decision Tree:

  • Kecenderungannya untuk overfitting.
  • Untuk mengatasinya, decision tree perlu tahu kapan ia berhenti membuat cabang sehingga pohon yang dihasilkan tidak terlalu kompleks. Pemotongan/pencegahan cabang pohon itu dinamakan Pruning, dimana kita mencegah Decision Tree untuk membuat cabang berdasarkan kriteria tertentu.
  1. Random Forest

merupakan salah satu jenis Ensemble Method yang terdiri dari banyak Decision Tree. Masing-masing Decision Tree memiliki karakteristik masing-masing dan tidak saling berhubungan. Random Forest memanfaatkan konsep Bagging (Bootstrap and Aggregation) dalam pembuatannya.

Memiliki karakter:

  • Menekan bias dan variance dari Decision Tree, sehingga performa lebih baik dalam memprediksi.
  • Automatic feature selection: Prediktor dipilih secara random pada pembuatan Decision Tree.
  • Terdapat out-of-bag error sebagai pengganti evaluasi model.

Kekurangan dari Random Forest:

  • Pembuatan model yang membutuhkan waktu yang cukup lama.
  • Practice yang baik saat selesai melakukan training adalah menyimpan model tersebut ke dalam bentuk file RDS dengan function saveRDS() agar model dapat langsung digunakan tanpa harus training dari awal.

2 Explanatory Data Analysis

2.1 Import Library & Data

Load Library

library(dplyr) # for data wrangling
library(ggplot2) # to visualize data
library(gridExtra) # to display multiple graph
library(inspectdf) # for EDA
library(tidymodels) # to build tidy models
library(caret) # to pre-process data
library(rsample) # to splitting data random
library(e1071) # to naive bayes model
library(ROCR) # to ROC AUC
library(plotly)  # to visualize data
library(rpart) # to decision tree
library(rattle) # to decision tree
library(rpart.plot) # to decision tree

Read Data

Dataset yang digunakan merupakan hasil tes diagnosis dan riwayat penyakit diabetes dari 768 pasien.

# read data
diab <- read.csv("diabetes.csv")

# cek data
head(diab)

Deskripsi data:

  • pregnant: Number of times pregnant

  • glucose: Plasma glucose concentration (glucose tolerance test)

  • pressure: Diastolic blood pressure (mm Hg)

  • triceps: Triceps skin fold thickness (mm)

  • insulin: 2-Hour serum insulin (mu U/ml)

  • mass: Body mass index (weight in kg/(height in m)^2)

  • pedigree: Diabetes pedigree function

  • age: Age (years)

  • diabetes: Test for Diabetes

  • kelas positif: diabetes (pos)

  • kelas negatif: sehat (neg)

2.2 Data Wrangling

Cek tipe data pada dataset diab:

glimpse(diab)
#> Rows: 768
#> Columns: 9
#> $ pregnant <int> 6, 1, 8, 1, 0, 5, 3, 10, 2, 8, 4, 10, 10, 1, 5, 7, 0, 7, 1, 1…
#> $ glucose  <int> 148, 85, 183, 89, 137, 116, 78, 115, 197, 125, 110, 168, 139,…
#> $ pressure <int> 72, 66, 64, 66, 40, 74, 50, 0, 70, 96, 92, 74, 80, 60, 72, 0,…
#> $ triceps  <int> 35, 29, 0, 23, 35, 0, 32, 0, 45, 0, 0, 0, 0, 23, 19, 0, 47, 0…
#> $ insulin  <int> 0, 0, 0, 94, 168, 0, 88, 0, 543, 0, 0, 0, 0, 846, 175, 0, 230…
#> $ mass     <dbl> 33.6, 26.6, 23.3, 28.1, 43.1, 25.6, 31.0, 35.3, 30.5, 0.0, 37…
#> $ pedigree <dbl> 0.627, 0.351, 0.672, 0.167, 2.288, 0.201, 0.248, 0.134, 0.158…
#> $ age      <int> 50, 31, 32, 21, 33, 30, 26, 29, 53, 54, 30, 34, 57, 59, 51, 3…
#> $ diabetes <chr> "pos", "neg", "pos", "neg", "pos", "neg", "pos", "neg", "pos"…

Sesuaikan tipe data pada dataset diab:

# your code
diab <- diab %>% 
  mutate(diabetes = as.factor(diabetes))
# inspect missing value
round(prop.table(table(is.na(diab))),3)
#> 
#> FALSE 
#>     1
data.frame(cbind(missing = colSums(is.na(diab)), 
      prop = round(prop.table(colSums(is.na(diab))),2)))

Data set sudah baik, karena tidak terdapat missing value di setiap variable / kolom nya.

3 Exploratory Data Analysis

Target Variable Proportion

x <- inspectdf::inspect_cat(diab)
show_plot(x)

Berdasarkan plot, ada proporsi yang tidak seimbang antara level dalam variabel target kami. Untuk melakukan penyeimbangan data tersebut dapat dilakukan upSampling / downSampling, untuk saat ini saya akan menggunakan downsampling (bukan upsampling) untuk menyeimbangkan proporsi.

# inspect corelation between predictors
GGally::ggcorr(diab[,-9], hjust = 1, layout.exp = 2, label = T, label_size = 2.9)

Berdasarkan plot di atas, ada beberapa variabel prediktor yang memiliki korelasi tinggi satu sama lain. Variabel-variabel tersebut adalah pregnant. Ini memberi kami peringatan dini bahwa data ini mungkin tidak sesuai untuk beberapa model seperti Naive Bayes.

4 Cross Validation

Split data diab menjadi diab_train dan diab_test dengan proporsi 80:20

RNGkind(sample.kind = "Rounding")
set.seed(1013)

# your code here
index_diab <- sample(x = nrow(diab), 
                     size= nrow(diab)*0.8)

diab_train <- diab[index_diab, ]
diab_test <- diab[-index_diab, ]
#upsampling
RNGkind(sample.kind = "Rounding")
set.seed(1013)

diab_train <- downSample(x = diab_train %>% select(-diabetes),
                       y = diab_train$diabetes,
                       yname = "diabetes") #nama kolom target

Cek Proposinya lagi

prop.table(table(diab_train$diabetes))
#> 
#> neg pos 
#> 0.5 0.5

Data sudah balance, sehingga bisa dilakukan modeling.

5 Modeling: Naive Bayes

Berdasarkan karakteristiknya, data diab kami sebenarnya tidak terlalu sesuai untuk Naive Bayes. Dari deskripsi data, beberapa prediktor kami memiliki korelasi yang tinggi satu sama lain. Prediktor juga variabel kontinu. Meski begitu, kami masih akan mencoba menggunakan Naive Bayes dan hasilnya akan dibandingkan dengan model lainnya. Saat membangun model Naive Bayes, kita juga harus menerapkan estimator Laplace.

naive <- naiveBayes(formula = diabetes ~ . , 
                    data = diab_train,
                    laplace = 1)
# model fitting
naive_pred <- predict(naive, diab_test, type = "class") # for the class prediction
naive_prob <- predict(naive, diab_test, type = "raw") # for the probability

# result
naive_table <- select(diab_test, diabetes) %>%
  bind_cols(diabetes_pred = naive_pred) %>% 
  bind_cols(diabetes_eprob = round(naive_prob[,1],4)) %>% 
  bind_cols(diabetes_pprob = round(naive_prob[,2],4))

# performance evaluation - confusion matrix
naive_table %>% 
  conf_mat(diabetes, diabetes_pred) %>% 
  autoplot(type = "heatmap")

naive_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, diabetes_pred),
    sensitivity = sens_vec(diabetes, diabetes_pred),
    specificity = spec_vec(diabetes, diabetes_pred),
    precision = precision_vec(diabetes, diabetes_pred)
  )
# ROC
naive_roc <- data.frame(prediction=round(naive_prob[,1],4),
                        trueclass=as.numeric(naive_table$diabetes=="pos"))
head(naive_roc)
naive_roc <- ROCR::prediction(naive_roc$prediction, naive_roc$trueclass) 

# ROC curve
plot(performance(naive_roc, "tpr", "fpr"),
     main = "ROC")
abline(a = 0, b = 1, lty= 2)

- TPR: dari seluruh pasien yang actualnya diabetes, persentase model kita mengklasifikasi dengan benar (tetap diprediksi sebagai diabetes). - FPR: dari seluruh pasien yang actualnya sehat, persentase model kita salah mengklasifikasi (diprediksi jadi diabetes).

# AUC
auc_ROCR_n <- performance(naive_roc, measure = "auc")
auc_ROCR_n <- auc_ROCR_n@y.values[[1]]
auc_ROCR_n
#> [1] 0.2141667
# metrics lama
model_n <- naive_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, diabetes_pred),
    sensitivity = sens_vec(diabetes, diabetes_pred),
    specificity = spec_vec(diabetes, diabetes_pred),
    precision = precision_vec(diabetes, diabetes_pred)
  ) %>% 
  cbind(AUC = auc_ROCR_n)

Untuk informasi Anda, ini adalah metrik yang akan kami evaluasi dari model:

  • Accuracy: kemampuan untuk memprediksi dengan benar kedua kelas dari total pengamatan.
  • Precision: kemampuan untuk memprediksi dengan benar kelas positif dari total kelas yang diprediksi-positif (positif palsu rendah).
  • Recall: kemampuan untuk memprediksi dengan benar kelas positif dari total kelas positif aktual (negatif palsu rendah).
  • Specificity: kemampuan untuk memprediksi dengan benar kelas negatif dari total kelas negatif aktual.

Selain itu, ada juga kurva Receiver Operating Characteristics (ROC) dan Area Under Curve (AUC) untuk mengevaluasi model kami. ROC memplot proporsi true positive rate (TPR atau Sensitivitas) dengan proporsi false negative rate (FNR atau 1-Spesifikasi). ROC adalah kurva probabilitas dan AUC mewakili derajat atau ukuran keterpisahan. Ini memberitahu seberapa banyak model mampu membedakan antara kelas. Semakin dekat kurva mencapai kiri atas plot (True positive tinggi sementara False negative rendah), semakin baik model kita. Semakin tinggi skor AUC, semakin baik model kami memisahkan kelas target kami.

Berdasarkan hasil, accuracy, sensitivity, specificity, dan precision model kami dapat diterima. Sayangnya, Kurva ROC belum menunjukkan pemisahan yang cukup baik dengan skor AUC 0.2141667 dan dapat ditingkatkan lebih lanjut.

Dalam kasus kami, diabetes diklasifikasikan untuk mengetahui tindakan lanjutan untuk pasien. Kesalahan klasifikasi kedua kelas sama-sama akan berdampak ke pasien. Hal ini karena jika pasien yang mengalami diabetes namun didiagnosa sehat, maka kemungkinan pasien akan terlambat mendapatkan penanganan medisnya. Sedangkan jika pasien yang sehat namun didiagnosa diabetes, maka pasien tersebut mungkin akan mengeluarkan biaya lebih yang tidak seharusnya.

Di bawah ini, saya akan mendemonstrasikan beberapa praktik penyetelan model dengan mengubah threshold untuk klasifikasi.

# model tuning - metrics function
metrics <- function(cutoff, prob, ref, postarget, negtarget) 
{
  predict <- factor(ifelse(prob >= cutoff, postarget, negtarget))
  conf <- caret::confusionMatrix(predict , ref, positive = postarget)
  acc <- conf$overall[1]
  rec <- conf$byClass[1]
  prec <- conf$byClass[3]
  spec <- conf$byClass[2]
  mat <- t(as.matrix(c(rec , acc , prec, spec))) 
  colnames(mat) <- c("recall", "accuracy", "precicion", "specificity")
  return(mat)
}
co <- seq(0.01,0.99,length=100)
result <- matrix(0,100,4)

# apply function metrics
for(i in 1:100){
  result[i,] = metrics(cutoff = co[i], 
                     prob = naive_table$diabetes_eprob, 
                     ref = as.factor(ifelse(naive_table$diabetes == "pos", 1, 0)), 
                     postarget = "1", 
                     negtarget = "0")
}

# visualize
ggplotly(tibble("Recall" = result[,1],
           "Accuracy" = result[,2],
           "Precision" = result[,3],
           "Specificity" = result[,4],
                   "Cutoff" = co) %>% 
  gather(key = "Metrics", value = "value", 1:4) %>% 
  ggplot(aes(x = Cutoff, y = value, col = Metrics)) +
  geom_line(lwd = 1.5) +
  scale_color_manual(values = c("darkred","darkgreen","orange", "blue")) +
  scale_y_continuous(breaks = seq(0,1,0.1), limits = c(0,1)) +
  scale_x_continuous(breaks = seq(0,1,0.1)) +
  labs(title = "Tradeoff Model Perfomance") +
  theme_minimal() +
  theme(legend.position = "top",
        panel.grid.minor.y = element_blank(),
        panel.grid.minor.x = element_blank()))

Dari plot di atas, kita tahu bahwa break-even point dari recall-specificity adalah pada 0,26. Tetapi jika kita menetapkan threshold 0,66, kita mendapatkan recall 10% , specificity 61% sekaligus memiliki accuracy 27%.

# tuning final model
naive_table <- naive_table %>% 
  mutate(tuning_pred = as.factor(ifelse(diabetes_eprob >= 0.26, "pos", "neg")))

# metrics result
final_n <- naive_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, tuning_pred),
    sensitivity = sens_vec(diabetes, tuning_pred),
    specificity = spec_vec(diabetes, tuning_pred),
    precision = precision_vec(diabetes, tuning_pred)
  ) %>% 
  cbind(AUC = auc_ROCR_n)
final_n

6 Modeling: Decision Tree

Model pohon keputusan adalah salah satu model berbasis pohon yang memiliki manfaat utama yang dapat diinterpretasikan. Pohon keputusan adalah algoritma yang akan membuat sekumpulan aturan yang divisualisasikan dalam diagram yang menyerupai pohon.

Untuk benar-benar memahami tampilannya, mari kita buat model pohon keputusan berdasarkan data diab_train kita. Kita dapat menggunakan paket rpart untuk membangun model pohon keputusan dan menggunakan paket rattle dan rpart.plot untuk memvisualisasikannya.

# model building
dtree <- rpart(formula = diabetes ~ ., 
               data = diab_train, 
               method = "class")

fancyRpartPlot(dtree, sub = NULL)

Sebuah decision tree terdiri dari beberapa bagian. Kotak pertama di bagian atas plot adalah root node. Akar akan membelah dan membuat cabang dengan aturan tertentu. Setiap cabang diakhiri dengan node. Beberapa node dipecah lagi menjadi node lain dan disebut internal node. Node yang tidak membelah lagi atau muncul di ujung pohon disebut terminal node atau leaf node, seperti halnya daun pada pohon.

Setiap node menunjukkan:

  • the predicted class (pos/neg).
  • the probability of pos/neg class.
  • the percentage of observations in the node.
  • the root and internal nodes juga menunjukkan aturan (variabel dengan threshold/nilai) yang akan mempartisi setiap observasi.
# model fitting
dtree_pred <- predict(dtree, diab_test, type = "class")
dtree_prob <- predict(dtree, diab_test, type = "prob")

# result
dtree_table <- select(diab_test, diabetes) %>%
  bind_cols(diabetes_pred = dtree_pred) %>% 
  bind_cols(diabetes_eprob = round(dtree_prob[,1],4)) %>% 
  bind_cols(diabetes_pprob = round(dtree_prob[,2],4))

# performance evaluation - confusion matrix
dtree_table %>% 
  conf_mat(diabetes, diabetes_pred) %>% 
  autoplot(type = "heatmap")

dtree_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, diabetes_pred),
    sensitivity = sens_vec(diabetes, diabetes_pred),
    specificity = spec_vec(diabetes, diabetes_pred),
    precision = precision_vec(diabetes, diabetes_pred)
  )
# ROC
dtree_roc <- data.frame(prediction=round(dtree_prob[,1],4),
                        trueclass=as.numeric(dtree_table$diabetes=="pos"))
head(dtree_roc)
dtree_roc <- ROCR::prediction(dtree_roc$prediction, dtree_roc$trueclass) 

# ROC curve
plot(performance(dtree_roc, "tpr", "fpr"),
     main = "ROC")
abline(a = 0, b = 1, lty = 2)

- TPR: dari seluruh pasien yang actualnya diabetes, persentase model kita mengklasifikasi dengan benar (tetap diprediksi sebagai diabetes). - FPR: dari seluruh pasien yang actualnya sehat, persentase model kita salah mengklasifikasi (diprediksi jadi diabetes).

# AUC
auc_ROCR_d <- performance(dtree_roc, measure = "auc")
auc_ROCR_d <- auc_ROCR_d@y.values[[1]]
auc_ROCR_d
#> [1] 0.250463
# metrics lama
model_d <- dtree_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, diabetes_pred),
    sensitivity = sens_vec(diabetes, diabetes_pred),
    specificity = spec_vec(diabetes, diabetes_pred),
    precision = precision_vec(diabetes, diabetes_pred)
  ) %>% 
  cbind(AUC = auc_ROCR_d)

Dari hasil model decision tree, saya tidak akan menyetel model dari parameter karena dengan menggunakan parameter default, kita sudah mendapatkan pohon berukuran normal (tidak perlu melakukan perform tree pruning). Sebagai gantinya, saya akan mencoba menyetel model dengan mengubah threshold untuk klasifikasi.

for(i in 1:100){
  result[i,] = metrics(cutoff = co[i], 
                     prob = dtree_table$diabetes_eprob, 
                     ref = as.factor(ifelse(dtree_table$diabetes == "pos", 1, 0)), 
                     postarget = "1", 
                     negtarget = "0")
}

ggplotly(data_frame("Recall" = result[,1],
                    "Accuracy" = result[,2],
                    "Precision" = result[,3],
                    "Specificity" = result[,4],
                    "Cutoff" = co) %>% 
  gather(key = "Metrics", value = "value", 1:4) %>% 
  ggplot(aes(x = Cutoff, y = value, col = Metrics)) +
  geom_line(lwd = 1.5) +
  scale_color_manual(values = c("darkred","darkgreen","orange", "blue")) +
  scale_y_continuous(breaks = seq(0,1,0.1), limits = c(0,1)) +
  scale_x_continuous(breaks = seq(0,1,0.1)) +
  labs(title = "Tradeoff Model Perfomance") +
  theme_minimal() +
  theme(legend.position = "top",
        panel.grid.minor.y = element_blank(),
        panel.grid.minor.x = element_blank()))
# tuning final model
dtree_table <- dtree_table %>% 
  mutate(tuning_pred = as.factor(ifelse(diabetes_eprob >= 0.69, "pos", "neg")))

# metrics result
final_d <- dtree_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, tuning_pred),
    sensitivity = sens_vec(diabetes, tuning_pred),
    specificity = spec_vec(diabetes, tuning_pred),
    precision = precision_vec(diabetes, tuning_pred)
  ) %>% 
  cbind(AUC = auc_ROCR_d)
final_d

7 Modeling: Random Forest

Untuk benar-benar memahami cara kerja random forest , mari terapkan algoritme random forest ke data diabetes kita.

# model building
set.seed(1013)
ctrl <- trainControl(method="repeatedcv", 
                     number=5, 
                     repeats=5) # k-fold cross validation

forest <- train(diabetes ~ ., 
                data=diab_train, 
                method="rf", 
                trControl = ctrl)
forest
#> Random Forest 
#> 
#> 428 samples
#>   8 predictor
#>   2 classes: 'neg', 'pos' 
#> 
#> No pre-processing
#> Resampling: Cross-Validated (5 fold, repeated 5 times) 
#> Summary of sample sizes: 342, 342, 342, 344, 342, 342, ... 
#> Resampling results across tuning parameters:
#> 
#>   mtry  Accuracy   Kappa    
#>   2     0.7224268  0.4449867
#>   5     0.7341042  0.4682384
#>   8     0.7332179  0.4665231
#> 
#> Accuracy was used to select the optimal model using the largest value.
#> The final value used for the model was mtry = 5.

Dari ringkasan model, kita mengetahui bahwa jumlah optimal variabel yang dipertimbangkan untuk dipecah pada setiap node adalah 8. Kita juga dapat memeriksa pentingnya setiap variabel yang digunakan di random forest kita menggunakan varImp().

varImp(forest)
#> rf variable importance
#> 
#>           Overall
#> glucose  100.0000
#> mass      47.1589
#> age       36.7729
#> pedigree  29.3881
#> pressure  15.7786
#> pregnant  10.8465
#> triceps    0.2399
#> insulin    0.0000

Saat menggunakan random forest - kami tidak diharuskan untuk membagi dataset kami menjadi data train dan test karena random forest sudah memiliki estimates out-of-bag (OOB) yang bertindak sebagai perkiraan accuracy yang andal pada unseen examples. Meskipun, juga dimungkinkan untuk mengadakan regular train-test cross-validation. Misalnya, OOB yang kami capai (dalam summary di bawah) dihasilkan dari dataset diab_train kami.

plot(forest$finalModel)
legend("topright", colnames(forest$finalModel$err.rate),col=1:6,cex=0.8,fill=1:6)

forest$finalModel
#> 
#> Call:
#>  randomForest(x = x, y = y, mtry = min(param$mtry, ncol(x))) 
#>                Type of random forest: classification
#>                      Number of trees: 500
#> No. of variables tried at each split: 5
#> 
#>         OOB estimate of  error rate: 26.4%
#> Confusion matrix:
#>     neg pos class.error
#> neg 156  58   0.2710280
#> pos  55 159   0.2570093

Mari kita uji model random forest kita ke dataset diab_test kita:

# model fitting
forest_pred <- predict(forest, diab_test, type = "raw")
forest_prob <- predict(forest, diab_test, type = "prob")

# result
forest_table <- select(diab_test, diabetes) %>%
  bind_cols(diabetes_pred = forest_pred) %>% 
  bind_cols(diabetes_eprob = round(forest_prob[,1],4)) %>% 
  bind_cols(diabetes_pprob = round(forest_prob[,2],4))

# performance evaluation - confusion matrix 
forest_table %>% 
  conf_mat(diabetes, diabetes_pred) %>% 
  autoplot(type = "heatmap")

forest_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, diabetes_pred),
    sensitivity = sens_vec(diabetes, diabetes_pred),
    specificity = spec_vec(diabetes, diabetes_pred),
    precision = precision_vec(diabetes, diabetes_pred)
  )
# ROC
forest_roc <- data.frame(prediction=round(forest_prob[,1],4),
                      trueclass=as.numeric(forest_table$diabetes=="pos"))
head(forest_roc)
forest_roc <- ROCR::prediction(forest_roc$prediction, forest_roc$trueclass) 

# ROC curve
plot(performance(forest_roc, "tpr", "fpr"),
     main = "ROC")
abline(a = 0, b = 1, lty = 2)

# AUC
auc_ROCR_f <- performance(forest_roc, measure = "auc")
auc_ROCR_f <- auc_ROCR_f@y.values[[1]]
auc_ROCR_f
#> [1] 0.2111111
# metrics lama
model_f <- forest_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, diabetes_pred),
    sensitivity = sens_vec(diabetes, diabetes_pred),
    specificity = spec_vec(diabetes, diabetes_pred),
    precision = precision_vec(diabetes, diabetes_pred)
  ) %>% 
  cbind(AUC = auc_ROCR_f)

Berdasarkan analisis dan hasil di atas, model random forest memberi kami hasil accuracy, recall, specificity, dan precision yang cukup baik (all metrics > 62%). Namun nilai AUC modelnya kurang baik yaitu 22%, saya akan coba mengatur threshold untuk bisa mencapai score yang lebih tinggi pada setiap numeriknya.

for(i in 1:100){
  result[i,] = metrics(cutoff = co[i], 
                     prob = forest_table$diabetes_eprob, 
                     ref = as.factor(ifelse(forest_table$diabetes == "pos", 1, 0)), 
                     postarget = "1", 
                     negtarget = "0")
}

ggplotly(data_frame("Recall" = result[,1],
                    "Accuracy" = result[,2],
                    "Precision" = result[,3],
                    "Specificity" = result[,4],
                    "Cutoff" = co) %>% 
  gather(key = "Metrics", value = "value", 1:4) %>% 
  ggplot(aes(x = Cutoff, y = value, col = Metrics)) +
  geom_line(lwd = 1.5) +
  scale_color_manual(values = c("darkred","darkgreen","orange", "blue")) +
  scale_y_continuous(breaks = seq(0,1,0.1), limits = c(0,1)) +
  scale_x_continuous(breaks = seq(0,1,0.1)) +
  labs(title = "Tradeoff Model Perfomance") +
  theme_minimal() +
  theme(legend.position = "top",
        panel.grid.minor.y = element_blank(),
        panel.grid.minor.x = element_blank()))
# tuning final model
forest_table <- forest_table %>% 
  mutate(tuning_pred = as.factor(ifelse(diabetes_eprob >= 0.59, "pos", "neg")))

# metrics result
final_f <- forest_table %>%
  summarise(
    accuracy = accuracy_vec(diabetes, tuning_pred),
    sensitivity = sens_vec(diabetes, tuning_pred),
    specificity = spec_vec(diabetes, tuning_pred),
    precision = precision_vec(diabetes, tuning_pred)
  ) %>% 
  cbind(AUC = auc_ROCR_f)
final_f

8 Conclusion

rbind("Naive Bayes (lama)" = model_n, 
      "Decision Tree (lama)" = model_d, 
      "Random Forest (lama)" = model_f)
rbind( "Naive Bayes (baru)" = final_n, 
      "Decision Tree (baru)" = final_d, 
      "Random Forest (baru)" = final_f)

Berdasarkan tabel metrik di atas, model prediksi yang dibangun menggunakan model lama dikarenakan hasilnya lebih baik sebelum dilakukan pengaturan threshold.

Algoritma Random Forest (lama) memberikan hasil terbaik. Model memberikan akurasi tertinggi 74% sekaligus mempertahankan sensitivitas dengan nilai 80%, spesifisitas nya 64%, dan presisi di atas 70%. Oleh karena itu model terbaik untuk memprediksi diagnosa diabetes atau tidak berdasarkan prediktor yang ada adalah model Random Forest.