Intro

Pada kesempatan kali ini, saya akan mencoba melakukan prediksi terhadap pasien yang terkena kanker prostat, akan diprediksi apakah kankernya ganas atau jinak kategori dari beberapa variabel penunjangnya. Algoritma yang akan saya gunakan yaitu menggunakan logistik regression dan k-nearest neighbor yang termasuk dalam supervised learning.

Tujuan dalam analisis kali ini adalah akan memprediksi apakah kanker tersebut ganas atau jinak, dan kita ingin membuat model yang memfokuskan untuk mendeteksi kanker prostat yang ganas (Malignant).

library(dplyr)
library(GGally)
library(caret)
library(class)
library(rsample)

Dataset yang akan saya gunakan yaitu data mengenai pasien yang terkena kanker prostat berdasarkan beberapa karakteristik yang menyertai yang dapat Anda unduh langsung pada Kaggle https://www.kaggle.com/datasets/sajidsaifi/prostate-cancer.

Import Data

df <- read.csv("Prostate_Cancer.csv")
df

Dari data yang ada, terdapat 10 kolom dan 100 baris data. Untuk kebutuhan analisa kita, kita akan membuang kolom yang dirasa tidak perlu untuk masuk ke analisis kita.

Data Cleaning

df <- df %>% 
          select(-id) %>% 
          mutate(diagnosis_result = factor(diagnosis_result,
                 levels = c("B", "M"),
                 labels = c("Benign", "Malignant")))
df

Kita membuang kolom id, dan mengubah isi data di kolom diagnosis_result agar lebih informatif.

Exploratory Data Analysis

Pada tahap ini, kita akan mengeksplorasi data.

is.na(df) %>% colSums()
##  diagnosis_result            radius           texture         perimeter 
##                 0                 0                 0                 0 
##              area        smoothness       compactness          symmetry 
##                 0                 0                 0                 0 
## fractal_dimension 
##                 0
glimpse(df)
## Rows: 100
## Columns: 9
## $ diagnosis_result  <fct> Malignant, Benign, Malignant, Malignant, Malignant, …
## $ radius            <int> 23, 9, 21, 14, 9, 25, 16, 15, 19, 25, 24, 17, 14, 12…
## $ texture           <int> 12, 13, 27, 16, 19, 25, 26, 18, 24, 11, 21, 15, 15, …
## $ perimeter         <int> 151, 133, 130, 78, 135, 83, 120, 90, 88, 84, 103, 10…
## $ area              <int> 954, 1326, 1203, 386, 1297, 477, 1040, 578, 520, 476…
## $ smoothness        <dbl> 0.143, 0.143, 0.125, 0.070, 0.141, 0.128, 0.095, 0.1…
## $ compactness       <dbl> 0.278, 0.079, 0.160, 0.284, 0.133, 0.170, 0.109, 0.1…
## $ symmetry          <dbl> 0.242, 0.181, 0.207, 0.260, 0.181, 0.209, 0.179, 0.2…
## $ fractal_dimension <dbl> 0.079, 0.057, 0.060, 0.097, 0.059, 0.076, 0.057, 0.0…

Dapat dilihat, data yang kita gunakan sudah sesuai dengan tipe data yang seharusnya, dan tidak terdapat missing value.

Logistic Regression

Sebagai metode pertama, kita akan menggunakan metode Logistik Regression dalam memprediksi data kita.

Pre-Processing Data

Sebelum melakukan pemodelan, kita perlu melihat terlebih dahulu proporsi dari target variabel yang kita miliki pada kolom diagnosis_result.

prop.table(table(df$diagnosis_result))
## 
##    Benign Malignant 
##      0.38      0.62
table(df$diagnosis_result)
## 
##    Benign Malignant 
##        38        62

Kita melihat proporsi data yang ada, karena akan lebih baik jika data yang kita punya seimbang, dan data diatas bisa dibilang cukup seimbang untuk kita jadikan model.

Cross Validation

Selanjutnya yaitu melakukan splitting data menjadi data train dan data test. Tujuannya yaitu pada data train akan kita gunakan untuk modeling/pelatihan, sedangkan data test akan kita gunakan sebagai penguji model yang sudah kita buat jika dihadapkan dengan unseen data (data baru). Selain itu hal ini dapat digunakan untuk melihat kemampuan model yang kita buat dalam menghadapi unseen data.

set.seed(100) # merujuk pada key untuk proses CV knn

index <- initial_split(data=df,  # data awal sebelum split
                       prop = 0.7, #proporsi split 80:20
                       strata = diagnosis_result) #label kelas agar pembagian train dan test antara kelas positif dan negatif sama

df_train <- training(index)
df_test <- testing(index)
prop.table(table(df_train$diagnosis_result))
## 
##    Benign Malignant 
## 0.3768116 0.6231884
prop.table(table(df_test$diagnosis_result))
## 
##    Benign Malignant 
## 0.3870968 0.6129032

Modelling

Untuk tahap ini, kita melakukan pemodelan dengan menggunakan regresi logistik. Pemodelan menggunakan fungsi glm() dalam memodelkan menggunakan regresi logistik. Variabel yang digunakan adalah beberapa variabel yang kita anggap mempengaruhi target variabel, dimana variabel target menjadi variabel responnya.

ggcorr(data = df,  hjust = 1, layout.exp = 3, label = T)
## Warning in ggcorr(data = df, hjust = 1, layout.exp = 3, label = T): data in
## column(s) 'diagnosis_result' are not numeric and were ignored

# model_base <- glm(formula = diagnosis_result ~ perimeter+compactness+symmetry+fractal_dimension, family = "binomial", 
#              data = df_train)
# summary(model_base)

All Predictor

model <- glm(formula = diagnosis_result ~., family = "binomial", 
             data = df_train)
summary(model)
## 
## Call:
## glm(formula = diagnosis_result ~ ., family = "binomial", data = df_train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -3.2556  -0.4329   0.1513   0.5666   1.5915  
## 
## Coefficients:
##                     Estimate Std. Error z value Pr(>|z|)
## (Intercept)        5.045e+00  1.155e+01   0.437    0.662
## radius            -9.467e-03  7.935e-02  -0.119    0.905
## texture            8.604e-02  7.817e-02   1.101    0.271
## perimeter         -7.358e-02  1.425e-01  -0.516    0.606
## area               1.042e-02  1.119e-02   0.931    0.352
## smoothness        -1.436e+00  2.869e+01  -0.050    0.960
## compactness        3.872e+01  2.444e+01   1.585    0.113
## symmetry          -9.697e+00  2.309e+01  -0.420    0.675
## fractal_dimension -1.276e+02  1.286e+02  -0.993    0.321
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 91.422  on 68  degrees of freedom
## Residual deviance: 48.940  on 60  degrees of freedom
## AIC: 66.94
## 
## Number of Fisher Scoring iterations: 6

Jika dilihat dari model yang sudah dibuat, terlihat bahwa prediktornya tidak ada yang signifikan terhadap target. Maka dari itu kita akan mencoba menggunakan Step-Wise Regression.

Step-wise Method

step(model, direction = "backward", trace = FALSE)
## 
## Call:  glm(formula = diagnosis_result ~ area + compactness, family = "binomial", 
##     data = df_train)
## 
## Coefficients:
## (Intercept)         area  compactness  
##   -6.325084     0.008187    15.659154  
## 
## Degrees of Freedom: 68 Total (i.e. Null);  66 Residual
## Null Deviance:       91.42 
## Residual Deviance: 51.6  AIC: 57.6
model2 <- glm(formula = diagnosis_result ~ area + compactness, family = "binomial", 
    data = df_train)

summary(model2)
## 
## Call:
## glm(formula = diagnosis_result ~ area + compactness, family = "binomial", 
##     data = df_train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -3.6668  -0.5203   0.1332   0.5924   1.7901  
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -6.325084   1.659989  -3.810 0.000139 ***
## area         0.008187   0.002393   3.421 0.000625 ***
## compactness 15.659154   7.034049   2.226 0.026001 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 91.422  on 68  degrees of freedom
## Residual deviance: 51.597  on 66  degrees of freedom
## AIC: 57.597
## 
## Number of Fisher Scoring iterations: 6

Dengan menggunakan step “backward”, kita mendapatkan model yang lebih baik dari sebelumnya. Terlihat bahwa terdapat 2 prediktor yang signifikan, yaitu area dan compactness.

Predict

Pada tahap ini kita akan melakukan prediksi pada data df_test/unseen data, menggunakan model yang sudah kita training sebelumnya.

df_test$pred_diagnosis <- predict(object = model2, newdata = df_test , type = "response")
df_test
df_test$pred_label <- ifelse(df_test$pred_diagnosis>0.5 , "Malignant" , "Benign") %>% as.factor()
df_test
df_test %>% select(diagnosis_result,pred_diagnosis,pred_label)

Model Evaluation

Setelah membuat model, kita tidak akan langsung menggunakannya, akan tetapi kita evaluasi model tersebut terlebih dahulu, untuk mengukur apakah model tersebut layak kita gunakan atau tidak.

Confusion Matrix

table(predicted = df_test$pred_label, actual = df_test$diagnosis_result)
##            actual
## predicted   Benign Malignant
##   Benign        11         3
##   Malignant      1        16
confusionMatrix(data = df_test$pred_label, reference = df_test$diagnosis_result, positive="Malignant")
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  Benign Malignant
##   Benign        11         3
##   Malignant      1        16
##                                           
##                Accuracy : 0.871           
##                  95% CI : (0.7017, 0.9637)
##     No Information Rate : 0.6129          
##     P-Value [Acc > NIR] : 0.001628        
##                                           
##                   Kappa : 0.7362          
##                                           
##  Mcnemar's Test P-Value : 0.617075        
##                                           
##             Sensitivity : 0.8421          
##             Specificity : 0.9167          
##          Pos Pred Value : 0.9412          
##          Neg Pred Value : 0.7857          
##              Prevalence : 0.6129          
##          Detection Rate : 0.5161          
##    Detection Prevalence : 0.5484          
##       Balanced Accuracy : 0.8794          
##                                           
##        'Positive' Class : Malignant       
## 

K-NN

Selanjutnya, kita akan menggunakan metode K-NN (K-Nearest Neighbour) dalam memprediksi data kita.

Cross Validation

Dalam tahap ini, kita langsung saja membagi data menjadi data train dan data testing, karena Data Cleaning sudah dilakukan diatas.

set.seed(100) # merujuk pada key untuk proses CV knn

index <- initial_split(data=df,  # data awal sebelum split
                       prop = 0.7, #proporsi split 80:20
                       strata = diagnosis_result) #label kelas agar pembagian train dan test antara kelas positif dan negatif sama

train <- training(index)
test <- testing(index)
prop.table(table(train$diagnosis_result))
## 
##    Benign Malignant 
## 0.3768116 0.6231884
prop.table(table(test$diagnosis_result))
## 
##    Benign Malignant 
## 0.3870968 0.6129032

Proporsi data terlihat cukup seimbang, dan dapat kita gunakan untuk modelling.

# prediktor data train
train_x <- train %>% select_if(is.numeric) # dipilih semua kolom yang numerik karena akan discaling

# target data train
train_y <- train %>% select(diagnosis_result) # dipisahkan khusus untuk kelas target

# prediktor data test
test_x <- test %>% select_if(is.numeric)

# target data test
test_y <-  test %>% select(diagnosis_result)
# code ini hanya boleh dirun 1 kali
train_x <- scale(train_x)

test_x <- scale(test_x, 
                center=attr(train_x, "scaled:center"), #nilai rata-rata train
                scale=attr(train_x, "scaled:scale")) # nilai sd train

Sebagai tambahan, dalam tahap diatas, kita melakukan scaling terhadap data kita, atau bisa dibilang, kita menyamakan ukuran dari data kita, sehingga model yang dihasilkan oleh K-NN bisa maksimal.

Modelling

Untuk memilih K-nya, kita melihat dari akar dari jumlah baris dari data kita.

sqrt(nrow(train_x))
## [1] 8.306624

Angka yang didapatkan kemudian kita bulatkan jadi 9, karena data kita genap (100 baris), maka K-nya harus ganjil, agar model K-NN dapat menitik beratkan di salah satu data kita (Malignant atau Benign) sehingga data baru / unseen data, dapat diklasifikasikan ke data yang ada di titik beratnya. (tidak boleh seimbang)

df_pred <- knn(train = train_x, #prediktor data train
    test = test_x, #prediktor data test
    cl = train_y$diagnosis_result, #target data train
    k=9) # jumlah k yang digunakan untuk klasifikasi

Model Comparison

# K-NN
confusionMatrix(data=df_pred, reference=test_y$diagnosis_result, positive="Malignant")
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  Benign Malignant
##   Benign        10         1
##   Malignant      2        18
##                                           
##                Accuracy : 0.9032          
##                  95% CI : (0.7425, 0.9796)
##     No Information Rate : 0.6129          
##     P-Value [Acc > NIR] : 0.0003434       
##                                           
##                   Kappa : 0.7929          
##                                           
##  Mcnemar's Test P-Value : 1.0000000       
##                                           
##             Sensitivity : 0.9474          
##             Specificity : 0.8333          
##          Pos Pred Value : 0.9000          
##          Neg Pred Value : 0.9091          
##              Prevalence : 0.6129          
##          Detection Rate : 0.5806          
##    Detection Prevalence : 0.6452          
##       Balanced Accuracy : 0.8904          
##                                           
##        'Positive' Class : Malignant       
## 
# LOGISTIC REGRESSION

confusionMatrix(data = df_test$pred_label, reference = df_test$diagnosis_result, positive="Malignant")
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  Benign Malignant
##   Benign        11         3
##   Malignant      1        16
##                                           
##                Accuracy : 0.871           
##                  95% CI : (0.7017, 0.9637)
##     No Information Rate : 0.6129          
##     P-Value [Acc > NIR] : 0.001628        
##                                           
##                   Kappa : 0.7362          
##                                           
##  Mcnemar's Test P-Value : 0.617075        
##                                           
##             Sensitivity : 0.8421          
##             Specificity : 0.9167          
##          Pos Pred Value : 0.9412          
##          Neg Pred Value : 0.7857          
##              Prevalence : 0.6129          
##          Detection Rate : 0.5161          
##    Detection Prevalence : 0.5484          
##       Balanced Accuracy : 0.8794          
##                                           
##        'Positive' Class : Malignant       
## 

Conclusion

Berdasarkan 2 model yang sudah kita buat, kurang lebih model hampir sama baiknya. Akan tetapi dalam pemilihan model machine learning, kita juga harus menyesuaikan dengan kebutuhan/tujuan kita di awal.

Tujuan kita yaitu ingin memfokuskan untuk mendeteksi kanker prostat yang ganas (Malignant).

Setelah dilakukan prediksi menggunakan model, masih ada saja prediksi yang salah. Pada klasifikasi, kita mengevaluasi model berdasarkan confusion matrix:

knitr::include_graphics("img/tnfp.PNG")

  • TP (True Positive) = Ketika kita memprediksi kelas positive, dan itu benar
  • TN (True Negative) = Ketika kita memprediksi kelas negative, dan itu benar
  • FP (False Positive) = Ketika kita memprediksi kelas positive, dan itu salah
  • FN (False Negative) = Ketika kita memprediksi kelas negative, dan itu salah

  • Accuracy: seberapa tepat model kita memprediksi kelas target (secara global)
  • Sensitivity/ Recall: ukuran kebaikan model terhadap kelas positif
  • Specificity: ukuran kebaikan model terhadap kelas negatif
  • Pos Pred Value/Precision: seberapa presisi model memprediksi kelas positif

Kita ingin mengurangi prediksi seseorang terkena kanker prostat Jinak (prediksi kelas Benign), namun aktualnya orang tersebut terkena kanker prostat yang Ganas (aktual kelas Malignant).

FN : ketika kita prediksi seseorang kankernya jinak, namun aslinya kankernya ganas

FP : ketika kita prediksi seseorang kankernya ganas, namun aslinya kankernya jinak

recall: ketika kita ingin meminimalisir FN Precision : ketika kita ingin meminimalisir FP

Untuk kasus ini kita akan mengurangi FN atau FP, agar pengembangan model kedepannya lebih baik juga. Kita ingin mengurangi kesalahan model dalam memprediksi kanker ganas, maka dari itu, kita akan mengurangi FN, agar dapat dilakukan pemeriksaan lanjutan, dan juga guna untuk mengurangi resiko orang tersebut kankernya semakin parah.

Maka dari itu, kita dapat memilih model dengan metode K-NN, karena Sensitivity/ Recall lebih baik dibandingkan dengan metode Logistic Regression.