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 ForestImport 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] <- 1Berikut 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