LBB Naive Bayes, Decision Tree & Random Forest : Heart Disease Classification

Objective

Pada Learning By Building kali ini, saya akan melakukan prediksi pada Heart Disease Data Set dimana data tersebut berisikan informasi pasien yang memiliki penyakit jantung dan tidak beserta dengan parameter-parameter yang sudah dilakukan pengecekan atau pengujian. Dimana saya sudah pernah melakukan prediksi menggunakan Heart Disease Data pada LBB C1 dengan hasil model logistic regression mendapatkan nilai recall sebesar 82.14%. Sehingga objective dari LBB ini adalah melakukan prediksi kembali menggunakan algoritma Naive Bayes, Decision Tree, dan Random Forest untuk melakukan klasifikasi pasien mana yang memiliki penyakit jantung atau tidak.

Import Library

library(dplyr)
library(e1071) # Naive-Bayes
library(caret) # Evaluasi model
library(partykit) # Decission Tree
library(randomForest) # Random Forest

Import Data

Heart Disease Data Set memiliki 4 database, tetapi yang digunakan pada LBB ini merupakan database kota Cleveland. Kita akan memasukan dataset tersebut menggunakan fungsi read.table() dikarenakan format dari data Heart adalah .data. Tidak lupa juga menggungkan parameter fileEncoding = 'UTF-8', sep = ',' dan na.strings = '?'. Lalu kita assign kedalam variabel heart.

heart <- read.table('Data Input/processed.cleveland.data', fileEncoding="UTF-8", sep = ",", na.strings = "?")
head(heart)

Data Wrangling & EDA

Rename Column

Jika kita lihat dari hasil fungsi glimpse() nama kolom masih belum sesuai, maka dari itu kita akan melakukan penamaan ulang dari kolom-kolom data heart menggunakan fungsi names().

names(heart) = c('age', 'sex', 'cp', 'trestbps', 'chol', 'fbs', 'restecg', 'thalach', 'exang', 'oldpeak', 'slope', 'ca', 'thal', 'class')

Untuk melihat apakah sudah berhasil dilakukan rename dapat menggunakan fungsi head().

head(heart)

Berikut adalah deskripsi kolom pada data house:

  • age : Age in years
  • sex : Sex (1 = male; 0 = female)
  • cp : Chest paintype: - 1 = typical angina - 2 = atypical angina - 3 = non-angina pain - 4 = asymptom
  • trestbps : Resting blood pressure (mm Hg)
  • chol : Serum cholesterol (mg/dl)
  • fbs : Fasting blood sugar > 120 mg/dl (1 = true; 0 = false)
  • restecg : Resting electrocardiographic results: - 0 = normal - 1 = having ST-T wave abnormality (T wave inversions and/or ST elevation or depression of > 0.05 mV) - 2 = showing probable or definite left ventricular hypertrophy by Estes’ criteria
  • thalach : Maximum heart rate achieved
  • exang : Exercise induced angina (1 = yes; 0 = no)
  • oldpeak : ST depression induced by exercise relative to rest
  • slope : The slope of the peak exercise ST segment: - 1 = upsloping - 2 = flat - 3 = downsloping
  • ca : Number of major vessels (0-3) colored by flourosopy
  • thal : 3 = normal; 6 = fixed defect; 7 = reversable defect
  • class : Diagnosis of heart disease (angiographic disease status) - 0 = no presence - 1 = least likely to have heart disease - 2 = >1 - 3 = >2 - 4 = more likely have heart disease

Missing Value

Selanjutnya kita akan melakukan pengecekan missing value pada data heart menggunakan kombinasi fungsi colSums() dan is.na().

colSums(is.na(heart))
#>      age      sex       cp trestbps     chol      fbs  restecg  thalach 
#>        0        0        0        0        0        0        0        0 
#>    exang  oldpeak    slope       ca     thal    class 
#>        0        0        0        4        2        0

Dapat dilihat bahwa data heart memiliki 4 missing value pada kolom ca dan 2 missing value pada kolom thal. Dikarenakan jumlah dari missing values masih dibawah 5% maka kita akan drop missing value tersebut menggunakan fungsi complete.case() dan di assign ke heart_clean

heart_clean <- heart[complete.cases(heart),]

Untuk memastikan apakah sudah berhasil dilakukan drop nya missing value tersebut dapat dilakukan cek missing value kembali.

colSums(is.na(heart_clean))
#>      age      sex       cp trestbps     chol      fbs  restecg  thalach 
#>        0        0        0        0        0        0        0        0 
#>    exang  oldpeak    slope       ca     thal    class 
#>        0        0        0        0        0        0

Data Types

Jika dilihat dari deskripsi kolom terdapat beberapa kolo yang belum sesuai tipe data nya, sebelum dilakukan perubahan tipe data kita akan merubah value pada kolom class dimana tujuan dari pembuatan model adalah melakukan klasifikasi pasien mana yang memilik penyakit jantung dan tidak. Sehingga kita akan merubah nilai yang lebih besar dari 0 menjadi 1.

heart_clean$class[heart_clean$class > 0] <- 1

Berikut adalah kolom-kolom yang perlu dilakukan perubahan tipe datanya :

  • sex : dbl -> fct
  • cp : dbl -> fct
  • fbs : dbl -> fct
  • exang : dbl -> fct
  • slope : dbl -> fct
  • thal : dbl -> fct
  • class : int -> fct

Untuk melakuan perubahan tipe data kita akan menggunakan fungsi mutate() pada kolom-kolom tersebut:

heart_clean <- heart_clean %>% 
  mutate_at(vars(sex, cp, fbs, exang, slope, thal, class), as.factor)

head(heart_clean)

Class Imbalance Check

Sebelum data heart_clean digunakan untuk pembuat model, kita perlu melakukan pengecekan dari target variabel nya yaitu kolom class

prop.table(table(heart_clean$class))
#> 
#>         0         1 
#> 0.5387205 0.4612795

Jika dilihat bahwa untuk kategori 0 atau ‘no presence’ sebesar 53.87% sedangkan untuk kategori 1 atau ‘presence’ sebesar 46.12%. Menurut saya perbandingan class tersebut masih cukup balance untuk digunakan dalam pemodelan.

Cross Validation

Sebelum memasuki tahap modelling, dataframe heart_clean akan melalui tahap cross-validation. Tujuan dilakukannya cross-validation adalah membagi dataset kita menjadi dua bagian, yaitu data train dan test yang nanti kedepannya kita akan menggunakan data train untuk pembuatan model, sedangkan untuk data test akan digunakan untuk evaluasi model. Mengapa perlu dilakukan pembagian tersebut? dikarenakan agar kita melakukan evaluasi tidak menggunakan informasi yang sama saat melakukan pemodelan, sehingga hasil evaluasi dapat dibilang akan lebih ‘nyata’ karena data yang dilakukan evaluasi belum pernah dimasukan ke dalam model.

Kita akan menggunakan fungsi RNGkind() dan set.seed() untuk menghasilkan kombinasi angka random yang akan mengontrol saat dilakukannya splitting data.

RNGkind(sample.kind = 'Rounding')
set.seed(121)

Selanjutnya kita akan membagi data kita dengan ratio 80:20, fungsi yang digunakan adalah sample() yang akan di assign ke variabel index. Nantinya variabel index akan digunakan untuk subsetting pada data heart_clean.

index <- sample(x = nrow(heart_clean), size = nrow(heart_clean)*0.8)
heart_train <- heart_clean[index,]
heart_test <- heart_clean[-index,]

Setelah dilakukan pemisahan data menjadi heart_train dan heart_test, maka kita dapat melakukan tahap selanjutnya yaitu modelling menggunakan data heart_train.

Naive Bayes

Build Model

Dalam pembuatan model naive bayes kita akan menggunakn fungsi naiveBayes() dengan target variabel class terhadap semua prediktor, data yang digunakan adalah heart_train. Pada parameter laplace akan kita isi dengan 1, dan modelnya disimpan ke dalam variabel model_naive.

model_naive <- naiveBayes(formula = class ~ .,
                          data = heart_train,
                          laplace = 1)

Predict

Untuk melakukan prediksi, kita akan membuat data frame yang baru bernama heart_pred yang berisi kolom class. Kedepannya hasil dari prediksi setiap model akan di simpan pada data frame ini.

heart_pred <- select(heart_test, class)

Kita akan menggunakan fungsi predict() terhadap data heart_test menggunakan model_naive. Parameter type akan diisi dengan ‘class’ agar menghasilkan label prediksi dan disimpan pada data frame heart_test.

heart_pred$model_naive <- predict(model_naive, newdata = heart_test, type = 'class')

Model Evaluation

Tahap berikutnya adalah membuat confussion matrix menggunakan fungsi confusionMatrix() dari library caret. Tujuannya dilakukan ini adalah untuk melakukan evaluasi terhadap hasil prediksi dari model kita.

confusionMatrix(data = heart_pred$model_naive,
                reference = heart_pred$class)
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction  0  1
#>          0 29  7
#>          1  3 21
#>                                           
#>                Accuracy : 0.8333          
#>                  95% CI : (0.7148, 0.9171)
#>     No Information Rate : 0.5333          
#>     P-Value [Acc > NIR] : 1.056e-06       
#>                                           
#>                   Kappa : 0.6622          
#>                                           
#>  Mcnemar's Test P-Value : 0.3428          
#>                                           
#>             Sensitivity : 0.9062          
#>             Specificity : 0.7500          
#>          Pos Pred Value : 0.8056          
#>          Neg Pred Value : 0.8750          
#>              Prevalence : 0.5333          
#>          Detection Rate : 0.4833          
#>    Detection Prevalence : 0.6000          
#>       Balanced Accuracy : 0.8281          
#>                                           
#>        'Positive' Class : 0               
#> 

Kita akan melihat metric recall/sensitivity dikarenakan kita ingin mengurangi resiko orang yang diprediksi sehat tetapi memiliki penyakit jantung. Nilai recall/sensitivity pada model_naive sebesar 90.62%.

Decision Tree

Build Model

Fungsi yang digunakan untuk membuat decision tree adalah ctree() dari library partykit. Kita akan menggunakan semua variabel prediktor dengan target variabel class dari data heart_train agar nantinya kita dapat membandingkan performa dari tiap model.

model_dt <- ctree(formula = class ~ ., data = heart_train)

model_dt
#> 
#> Model formula:
#> class ~ age + sex + cp + trestbps + chol + fbs + restecg + thalach + 
#>     exang + oldpeak + slope + ca + thal
#> 
#> Fitted party:
#> [1] root
#> |   [2] thal in 3
#> |   |   [3] ca <= 0
#> |   |   |   [4] trestbps <= 138
#> |   |   |   |   [5] age <= 57: 0 (n = 52, err = 0.0%)
#> |   |   |   |   [6] age > 57: 0 (n = 13, err = 30.8%)
#> |   |   |   [7] trestbps > 138: 0 (n = 24, err = 29.2%)
#> |   |   [8] ca > 0
#> |   |   |   [9] cp in 1, 2, 3: 0 (n = 23, err = 21.7%)
#> |   |   |   [10] cp in 4: 1 (n = 16, err = 12.5%)
#> |   [11] thal in 6, 7
#> |   |   [12] cp in 1, 2, 3: 0 (n = 41, err = 46.3%)
#> |   |   [13] cp in 4: 1 (n = 68, err = 11.8%)
#> 
#> Number of inner nodes:    6
#> Number of terminal nodes: 7

Kita dapat melihat dari grafik decision tree yang kita buat, menggunakan fungsi plot()

plot(model_dt, type = 'simple')

Dari hasil plottingan tersebut decision tree yang kita buat memiliki 1 root node yaitu variabel thal, dan 5 internal node pada variabel cp, ca, trestbps dan age. Dimana dari internal node tersebut akan menghasilkan 7 leaf node yang dapat menghasilkan sebuah klasifikasi pada data heart_clean.

Predict

Tahap selanjutnya adalah melakukan prediksi menggunakan model_dt dan disimpan pada variabel heart_pred. Fungsi yang digunakan adalah predict() menggunakan data heart_test dan parameter type = ‘response’.

heart_pred$model_dt <- predict(model_dt, newdata = heart_test, type = 'response')

Model Evaluation

Dari hasil prediksi tersebut kita akan membuat confusion matrix menggunakan fungsi confusionMatrix() agar dapat melihat performa dari model kita.

confusionMatrix(data = heart_pred$model_dt, heart_pred$class)
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction  0  1
#>          0 29  6
#>          1  3 22
#>                                          
#>                Accuracy : 0.85           
#>                  95% CI : (0.7343, 0.929)
#>     No Information Rate : 0.5333         
#>     P-Value [Acc > NIR] : 2.293e-07      
#>                                          
#>                   Kappa : 0.6966         
#>                                          
#>  Mcnemar's Test P-Value : 0.505          
#>                                          
#>             Sensitivity : 0.9062         
#>             Specificity : 0.7857         
#>          Pos Pred Value : 0.8286         
#>          Neg Pred Value : 0.8800         
#>              Prevalence : 0.5333         
#>          Detection Rate : 0.4833         
#>    Detection Prevalence : 0.5833         
#>       Balanced Accuracy : 0.8460         
#>                                          
#>        'Positive' Class : 0              
#> 

Kita akan menggunakan metric recall/sensitivity dikarenakan kita ingin mengurangi resiko orang yang diprediksi sehat tetapi memiliki penyakit jantung. Nilai recall/sensitivity pada model_dt sebesar 90.62%.

Random Forest

Data Pre-Processing

Untuk melakukan pembuatan modell random forest kita perlu melakukan tahap pre-processing kembali, yaitu menghilangkan kolom prediktor yang tidak memiliki variansi. Fungsi yang digunakan adalah nearZeroVar() akan menghasilkan kolom-kolom yang tidak memiliki variansi.

zero_var <- nearZeroVar(heart_clean)
heart_clean_zv <- select(heart_clean,-zero_var)
dim(heart_clean_zv)
#> [1] 297  14

Kita akan membandingkan kembali terhadap dimensi dari heart_clean menggunakan fungsi dim().

dim(heart_clean)
#> [1] 297  14

Dikarenakan dimensinya dari data nya sama, makan data heart_clean semua prediktornya memiliki variansi yang tinggi.

Build Model

Dalam pembuatan model random forest kita akan menggunakan k-fold cross-validation menggunakan fungsi trainControl() dengan nilai k=5 dan pembuatan set sebanyak 3 kali. Selanjutnya akan menggunakan fungsi train() untuk membuat modelnya.

# pembuatan control
set.seed(222)
control <- trainControl(method = "repeatedcv", number = 5, repeats = 3)

# pembuatan model random forest
model_rf <- train(class~ ., data = heart_train, method = "rf", trControl = control)

Predict

Untuk melakukan prediksi pada model_rf masih menggunakan fungsi predict() dengan data heart_test.

heart_pred$model_rf <- predict(model_rf, newdata = heart_test)

Model Evaluation

Tahap terakhir adalah melakukan evaluasi model menggunakan confussion matrix dari hasil prediksi dengan target variabel pada data heart_test menggunakan fungsi confusionMatrix().

confusionMatrix(heart_pred$model_rf, reference = heart_pred$class)
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction  0  1
#>          0 25  4
#>          1  7 24
#>                                           
#>                Accuracy : 0.8167          
#>                  95% CI : (0.6956, 0.9048)
#>     No Information Rate : 0.5333          
#>     P-Value [Acc > NIR] : 4.344e-06       
#>                                           
#>                   Kappa : 0.6341          
#>                                           
#>  Mcnemar's Test P-Value : 0.5465          
#>                                           
#>             Sensitivity : 0.7812          
#>             Specificity : 0.8571          
#>          Pos Pred Value : 0.8621          
#>          Neg Pred Value : 0.7742          
#>              Prevalence : 0.5333          
#>          Detection Rate : 0.4167          
#>    Detection Prevalence : 0.4833          
#>       Balanced Accuracy : 0.8192          
#>                                           
#>        'Positive' Class : 0               
#> 

Nilai recall yang dihasilkan oleh model_rf sebesar 78.12%.

Conclusion

Jika dilhat dari ketiga model yang sudah dibuat berdasarkan nilai recall dapat dilihat pada tabel berikut:

Model Recall
Naive Bayes 90.62%
Decision Tree 90.62%
Random Forest 78.12%

Penulis mempertimbangkan metric recall dikarenakan ingin mengurangi jumlah dari False Negative dari hasil prediksi, yang berarti mengurangi resiko orang yang diprediksi sehat tetapi memiliki penyakit jantung. Sehingga model yang dianggap cocok untuk melakukan prediksi terhadap penyakit jantung adalah model Naive Bayes dan Decision Tree dengan nilai recall sebesar 90.62%.

 

A work by Muhammad Yusuf Ibrahim

myusufibrahim@outlook.com