Prediktor mortalitas di rumah sakit untuk pasien gagal jantung yang dirawat di unit perawatan intensif (ICU) masih kurang diketahui. Tujuan dari pembuatan model ini adalah untuk mengembangkan dan memvalidasi model prediksi untuk mortalitas seluruh penyebab di rumah sakit pada pasien gagal jantung yang dirawat di ICU.
Data karakteristik demografis, tanda vital, dan laboratorium yang digunakan diekstraksi dari tabel-tabel berikut dalam dataset MIMIC III menggunakan Structured Query Language (SQL) queries (PostgreSQL, versi 9.6): ADMISSIONS, PATIENTS, ICUSTAYS, D_ICD DIAGNOSIS, DIAGNOSIS_ICD, LABEVENTS, D_LABIEVENTS, CHARTEVENTS, D_ITEMS, NOTEEVENTS, dan OUTPUTEVENTS. Dan sudah dimuat dalam situs Kaggle https://www.kaggle.com/datasets/saurabhshahane/in-hospital-mortality-prediction
Dataset mencakup data berikut:
Karakteristik demografis dan tanda vital dicatat selama 24 jam pertama dari setiap masuk rumah sakit dan variabel laboratorium diukur selama seluruh pasien tinggal di ICU. Komorbiditas diidentifikasi menggunakan kode ICD-9. Untuk data variabel dengan beberapa pengukuran, nilai yang digunakan merupakan rata-rata hitung dan dimasukkan untuk analisis. Hasil utama dari penelitian ini adalah mortalitas di rumah sakit, yang didefinisikan sebagai status vital pada saat keluar dari rumah sakit pada pasien yang selamat dan yang tidak selamat.
Dataset ini dapat digunakan untuk membangun model prediksi yang dapat membantu dokter dan tenaga medis dalam mengevaluasi risiko gagal jantung pada pasien dan memberikan perawatan yang lebih baik dan lebih personal.
Objektif:
Sebelum menganalisa dataset, kita akan memanggil dataset
“in-hospital-mortality-prediction” dari diamond.csv dan
disimpan kedalam object bernama dia, kemudian akan
dilakukan Exploratory Data Analyst terhadap dataset tersebut.
heart <- read.csv("data01.csv")Pertama kita akan memanggil library yang dibutuhkan,
karena kita akan melakukan sedikit visualisasi data dan membuat model
logistic regression, maka kita akan menggunkan library sebagai
berikut.
library(dplyr)
library(gtools)
library(car)
library(GGally)
library(caret)
library(class)
library(corrplot)
library(DMwR)
library(mice)
library(ROSE)
library(ggplot2)Selanjutnya kita akan melihat dataset yang sudah kita panggil,
gunakan head untuk melihat 6 baris pertama.
head(heart)Kita juga dapat melihat isi dataset dengan menggunakan
str
str(heart)#> 'data.frame': 1177 obs. of 51 variables:
#> $ group : int 1 1 1 1 1 1 1 1 1 1 ...
#> $ ID : int 125047 139812 109787 130587 138290 154653 194420 153461 113076 147252 ...
#> $ outcome : int 0 0 0 0 0 0 0 0 0 0 ...
#> $ age : int 72 75 83 43 75 76 72 83 61 67 ...
#> $ gendera : int 1 2 2 2 2 1 1 2 2 1 ...
#> $ BMI : num 37.6 NA 26.6 83.3 31.8 ...
#> $ hypertensive : int 0 0 0 0 1 1 1 1 1 1 ...
#> $ atrialfibrillation : int 0 0 0 0 0 1 0 1 1 0 ...
#> $ CHD.with.no.MI : int 0 0 0 0 0 0 0 0 0 0 ...
#> $ diabetes : int 1 0 0 0 0 0 0 1 1 1 ...
#> $ deficiencyanemias : int 1 1 1 0 1 1 0 1 0 0 ...
#> $ depression : int 0 0 0 0 0 0 0 0 0 0 ...
#> $ Hyperlipemia : int 1 0 0 0 0 1 1 0 0 0 ...
#> $ Renal.failure : int 1 0 1 0 1 1 1 0 1 0 ...
#> $ COPD : int 0 1 0 0 1 1 1 0 0 0 ...
#> $ heart.rate : num 68.8 101.4 72.3 94.5 67.9 ...
#> $ Systolic.blood.pressure : num 156 140 135 126 157 ...
#> $ Diastolic.blood.pressure: num 68.3 65 61.4 73.2 58.1 ...
#> $ Respiratory.rate : num 16.6 20.9 23.6 21.9 21.4 ...
#> $ temperature : num 36.7 36.7 36.5 36.3 36.8 ...
#> $ SP.O2 : num 98.4 96.9 95.3 93.8 99.3 ...
#> $ Urine.output : num 2155 1425 2425 8760 4455 ...
#> $ hematocrit : num 26.3 30.8 27.7 36.6 29.9 ...
#> $ RBC : num 2.96 3.14 2.62 4.28 3.29 ...
#> $ MCH : num 28.2 31.1 34.3 26.1 30.7 ...
#> $ MCHC : num 31.5 31.7 31.3 30.4 33.7 ...
#> $ MCV : num 89.9 98.2 109.8 85.6 91 ...
#> $ RDW : num 16.2 14.3 23.8 17 16.3 ...
#> $ Leucocyte : num 7.65 12.74 5.48 8.22 8.83 ...
#> $ Platelets : num 305 246 204 216 251 ...
#> $ Neutrophils : num 74.7 NA 68.1 81.8 NA ...
#> $ Basophils : num 0.4 NA 0.55 0.15 NA 0.3 0.2 NA 0.55 NA ...
#> $ Lymphocyte : num 13.3 NA 24.5 14.5 NA ...
#> $ PT : num 10.6 NA 11.3 27.1 NA ...
#> $ INR : num 1 NA 0.95 2.67 NA ...
#> $ NT.proBNP : num 1956 2384 4081 668 30802 ...
#> $ Creatine.kinase : num 148 60.6 16 85 111.7 ...
#> $ Creatinine : num 1.958 1.122 1.871 0.586 1.95 ...
#> $ Urea.nitrogen : num 50 20.3 33.9 15.3 43 ...
#> $ glucose : num 115 148 149 128 146 ...
#> $ Blood.potassium : num 4.82 4.45 5.83 4.39 4.78 ...
#> $ Blood.sodium : num 139 139 141 138 137 ...
#> $ Blood.calcium : num 7.46 8.16 8.27 9.48 8.73 ...
#> $ Chloride : num 109.2 98.4 105.9 92.1 104.5 ...
#> $ Anion.gap : num 13.2 11.4 10 12.4 15.2 ...
#> $ Magnesium.ion : num 2.62 1.89 2.16 1.94 1.65 ...
#> $ PH : num 7.23 7.22 7.27 7.37 7.25 ...
#> $ Bicarbonate : num 21.2 33.4 30.6 38.6 22 ...
#> $ Lactic.acid : num 0.5 0.5 0.5 0.6 0.6 ...
#> $ PCO2 : num 40 78 71.5 75 50 ...
#> $ EF : int 55 55 35 55 55 35 55 75 50 55 ...
Dari hasil tinjauan dataset di atas, ternyata dataset terdiri dari 1170 baris dan 51 kolom. Beberapa kolom ada yang bertipe integer dan numerik.
Dari inspeksi data yang dilakukan terdapat beberapa kolom yang memiliki deskripsi sebagai berikut:
group: grupID: id pasienoutcome: Meninggal akibat gagal jantung atau tidak. (0
= Hidup, 1 = Meninggal)age: Usia pasien pada saat masuk rumah sakitgendera: jenis kelamin pasien (1 = Female, 2 =
Male)BMI: Indeks Massa Tubuh adalah ukuran lemak tubuh
seseorang berdasarkan berat dan tinggi badannya. IMT dihitung dengan
membagi berat badan seseorang dalam kilogram dengan kuadrat tinggi
badannya dalam meter.hypertensive: Apakah pasien menderita tekanan darah
tinggi? (0 = tidak, 1 = iya)atrialfibrillation: Apakah pasien menderita atrial
fibrillation? (0 = tidak, 1 = iya)CHD.with.no.MI: Apakah pasien mengalami penyakit
jantung koroner tanpa serangan jantung? (0 = tidak, 1 = iya)diabetes: Apakah pasien menderita diabetes? (0 = tidak,
1 = iya)deficiencyanemias: Apakah pasien menderita Defisiensi
anemia? (0 = tidak, 1 = iya)depression: Apakah pasien menderita depresi? (0 =
tidak, 1 = iya)Hyperlipemia: Apakah pasien menderita Hyperlipemia? (0
= tidak, 1 = iya)Renal_failure: Apakah pasien menderita Gagal ginjal? (0
= tidak, 1 = iya)COPD: Apakah pasien menderita Chronic Obstructive
Pulmonary Disease? (0 = tidak, 1 = iya)Heart.rate: Heart rate adalah jumlah detak jantung
seseorang dalam satu menit. Ini adalah indikator kesehatan
kardiovaskular individu.Systolic.blood pressure: Tekanan darah sistolik adalah
tekanan di arteri saat jantung berkontraksi dan memompa darah
keluar.Diastolic.blood pressure: Tekanan darah diastolik
adalah tekanan di arteri saat jantung rileks antara detak.Respiratory.rate: Respiratory rate adalah jumlah napas
yang diambil seseorang dalam satu menit. Ini adalah indikator fungsi
paru-paru dan dapat digunakan untuk memantau pernapasan pasien.
-Temperature: Suhu adalah derajat kepanasan atau kedinginan
tubuh seseorang. Ini dapat diukur dalam derajat Fahrenheit atau
Celsius.SP.O2: SP.O2, atau pulse oximetry, adalah ukuran
saturasi oksigen dalam darah seseorang. Ini adalah indikator penting
fungsi pernapasan.Urine.output: Urine output adalah jumlah urine yang
diproduksi seseorang dalam periode waktu tertentu. Ini adalah indikator
fungsi ginjal.Hematocrit: Hematokrit adalah persentase volume sel
darah merah dalam darah. Ini adalah ukuran kemampuan darah untuk membawa
oksigen.RBC: Sel darah merah (RBC) adalah jenis sel darah yang
paling umum dan membawa oksigen dari paru-paru ke seluruh tubuh.MCH: Mean corpuscular hemoglobin (MCH) adalah rata-rata
jumlah hemoglobin dalam satu sel darah merah.MCHC: Mean corpuscular hemoglobin concentration (MCHC)
adalah rata-rata konsentrasi hemoglobin dalam volume sel darah merah
yang diberikan.MCV: Mean corpuscular volume (MCV) adalah rata-rata
volume satu sel darah merah.RDW: Red cell distribution width (RDW) adalah ukuran
variasi ukuran sel darah merah.Leucocyte: Leukosit, atau sel darah putih, adalah jenis
sel darah yang berperan dalam sistem kekebalan tubuh.Platelets: Platelet adalah fragmen sel kecil yang
membantu dalam pembekuan darah dan mencegah pendarahan berlebihan.Neutrophils: Neutrofil adalah jenis sel darah putih
yang membantu melawan infeksi.Basophils: Basofil adalah jenis sel darah putih yang
berperan dalam respons kekebalan tubuh.Lymphocyte: Limfosit adalah jenis sel darah putih yang
berperan dalam sistem kekebalan tubuh.PT: Prothrombin time (PT) adalah tes darah yang
mengukur waktu pembekuan darah.INR: International normalized ratio (INR) adalah
perhitungan yang digunakan untuk standarisasi hasil tes prothrombin time
(PT).NT.proBNP: N-terminal pro b-type natriuretic peptide
(NT.proBNP) adalah protein yang dilepaskan sebagai respons terhadap
gagal jantung.Creatine.kinase: Creatinekinase (CK) adalah enzim yang
terdapat dalam otot rangka dan jantung yang digunakan sebagai indikator
kerusakan otot.Creatinine: Creatinine adalah zat buangan yang
dihasilkan dari otot dan diekskresikan melalui ginjal. Kadar kreatinin
dalam darah dapat digunakan sebagai indikator fungsi ginjal.Urea.nitrogen: Urea nitrogen (BUN) adalah zat buangan
yang dihasilkan dari metabolisme protein dan diekskresikan melalui
ginjal. Kadar BUN dalam darah juga dapat digunakan sebagai indikator
fungsi ginjal.Glucose: Glukosa adalah jenis gula sederhana yang
digunakan sebagai sumber energi oleh sel-sel tubuh. Kadar glukosa dalam
darah dapat digunakan sebagai indikator diabetes dan masalah kesehatan
lainnya.Blood.potassium: Kadar kalium dalam darah dapat
mempengaruhi fungsi otot dan jantung.Blood.sodium: Kadar natrium dalam darah dapat
mempengaruhi keseimbangan cairan dalam tubuh.Blood.calcium: Kadar kalsium dalam darah penting untuk
fungsi saraf, otot, dan tulang.Chloride: Kadar klorida dalam darah dapat mempengaruhi
keseimbangan elektrolit dalam tubuh.Anion.gap: Anion gap adalah selisih antara kation dan
anion dalam plasma darah dan dapat digunakan sebagai indikator masalah
metabolik.Magnesium.ion: Kadar magnesium dalam darah penting
untuk fungsi saraf dan otot.PH: PH adalah ukuran keasaman atau kebasaan dalam
larutan. Kadar pH dalam darah dapat digunakan sebagai indikator masalah
kesehatan tertentu.Bicarbonate: Bicarbonate adalah zat yang membantu
mempertahankan keseimbangan asam-basa dalam tubuh.Lactic.acid: Asam laktat adalah produk sampingan dari
metabolisme anaerobik yang dapat menyebabkan kelelahan dan masalah
kesehatan lainnya.PCO2: PCO2, atau tekanan parsial karbon dioksida,
adalah ukuran jumlah karbon dioksida dalam darah.EF: Ejection fraction (EF) adalah persentase volume
darah yang dipompa oleh ventrikel kiri jantung selama satu kontraksi.
Ini adalah indikator kesehatan jantung individu.Mengecek nilai unik di setiap kolom untuk membantu dalam penentuan
tipe data terutama tipe data kategorik/factor. Karena ingin mengecek
semua kolomnya sekaligus maka digunakan perintah
lapply().
lengths(lapply(heart, unique))#> group ID outcome
#> 2 1177 3
#> age gendera BMI
#> 68 2 934
#> hypertensive atrialfibrillation CHD.with.no.MI
#> 2 2 2
#> diabetes deficiencyanemias depression
#> 2 2 2
#> Hyperlipemia Renal.failure COPD
#> 2 2 2
#> heart.rate Systolic.blood.pressure Diastolic.blood.pressure
#> 1095 1103 1078
#> Respiratory.rate temperature SP.O2
#> 1005 776 867
#> Urine.output hematocrit RBC
#> 811 1056 1045
#> MCH MCHC MCV
#> 926 803 601
#> RDW Leucocyte Platelets
#> 836 983 1085
#> Neutrophils Basophils Lymphocyte
#> 636 74 542
#> PT INR NT.proBNP
#> 784 426 1147
#> Creatine.kinase Creatinine Urea.nitrogen
#> 692 729 925
#> glucose Blood.potassium Blood.sodium
#> 803 645 600
#> Blood.calcium Chloride Anion.gap
#> 588 675 486
#> Magnesium.ion PH Bicarbonate
#> 447 414 657
#> Lactic.acid PCO2 EF
#> 302 465 14
Selanjutnya kita bisa mengubah tipe data di setiap kolom berdasarkan informasi diatas
Melakukan inspeksi tipe data untuk memastikan tipe data dari setiap kolomnya sudah sesuai.
glimpse(heart)#> Rows: 1,177
#> Columns: 51
#> $ group <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
#> $ ID <int> 125047, 139812, 109787, 130587, 138290, 15465…
#> $ outcome <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
#> $ age <int> 72, 75, 83, 43, 75, 76, 72, 83, 61, 67, 70, 8…
#> $ gendera <int> 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 1, 2, …
#> $ BMI <dbl> 37.58818, NA, 26.57263, 83.26463, 31.82484, 2…
#> $ hypertensive <int> 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
#> $ atrialfibrillation <int> 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, …
#> $ CHD.with.no.MI <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
#> $ diabetes <int> 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, …
#> $ deficiencyanemias <int> 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, …
#> $ depression <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
#> $ Hyperlipemia <int> 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, …
#> $ Renal.failure <int> 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, …
#> $ COPD <int> 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, …
#> $ heart.rate <dbl> 68.83784, 101.37037, 72.31818, 94.50000, 67.9…
#> $ Systolic.blood.pressure <dbl> 155.86667, 140.00000, 135.33333, 126.40000, 1…
#> $ Diastolic.blood.pressure <dbl> 68.33333, 65.00000, 61.37500, 73.20000, 58.12…
#> $ Respiratory.rate <dbl> 16.62162, 20.85185, 23.64000, 21.85714, 21.36…
#> $ temperature <dbl> 36.71429, 36.68254, 36.45370, 36.28704, 36.76…
#> $ SP.O2 <dbl> 98.39474, 96.92308, 95.29167, 93.84615, 99.28…
#> $ Urine.output <dbl> 2155, 1425, 2425, 8760, 4455, 1840, 2450, 303…
#> $ hematocrit <dbl> 26.27273, 30.78000, 27.70000, 36.63750, 29.93…
#> $ RBC <dbl> 2.960000, 3.138000, 2.620000, 4.277500, 3.286…
#> $ MCH <dbl> 28.25000, 31.06000, 34.32000, 26.06250, 30.66…
#> $ MCHC <dbl> 31.52000, 31.66000, 31.30000, 30.41250, 33.66…
#> $ MCV <dbl> 89.90000, 98.20000, 109.80000, 85.62500, 91.0…
#> $ RDW <dbl> 16.22000, 14.26000, 23.82000, 17.03750, 16.26…
#> $ Leucocyte <dbl> 7.6500000, 12.7400000, 5.4800000, 8.2250000, …
#> $ Platelets <dbl> 305.10000, 246.40000, 204.20000, 216.37500, 2…
#> $ Neutrophils <dbl> 74.65000, NA, 68.10000, 81.80000, NA, 85.4000…
#> $ Basophils <dbl> 0.40, NA, 0.55, 0.15, NA, 0.30, 0.20, NA, 0.5…
#> $ Lymphocyte <dbl> 13.300000, NA, 24.500000, 14.500000, NA, 9.30…
#> $ PT <dbl> 10.60000, NA, 11.27500, 27.06667, NA, 18.7833…
#> $ INR <dbl> 1.000000, NA, 0.950000, 2.666667, NA, 1.70000…
#> $ NT.proBNP <dbl> 1956.0, 2384.0, 4081.0, 668.0, 30802.0, 34183…
#> $ Creatine.kinase <dbl> 148.00000, 60.60000, 16.00000, 85.00000, 111.…
#> $ Creatinine <dbl> 1.9583333, 1.1222222, 1.8714286, 0.5857143, 1…
#> $ Urea.nitrogen <dbl> 50.00000, 20.33333, 33.85714, 15.28571, 43.00…
#> $ glucose <dbl> 114.63636, 147.50000, 149.00000, 128.25000, 1…
#> $ Blood.potassium <dbl> 4.816667, 4.450000, 5.825000, 4.386667, 4.783…
#> $ Blood.sodium <dbl> 138.7500, 138.8889, 140.7143, 138.5000, 136.6…
#> $ Blood.calcium <dbl> 7.463636, 8.162500, 8.266667, 9.476923, 8.733…
#> $ Chloride <dbl> 109.16667, 98.44444, 105.85714, 92.07143, 104…
#> $ Anion.gap <dbl> 13.166667, 11.444444, 10.000000, 12.357143, 1…
#> $ Magnesium.ion <dbl> 2.618182, 1.887500, 2.157143, 1.942857, 1.650…
#> $ PH <dbl> 7.230000, 7.225000, 7.268000, 7.370000, 7.250…
#> $ Bicarbonate <dbl> 21.16667, 33.44444, 30.57143, 38.57143, 22.00…
#> $ Lactic.acid <dbl> 0.5000000, 0.5000000, 0.5000000, 0.6000000, 0…
#> $ PCO2 <dbl> 40.00000, 78.00000, 71.50000, 75.00000, 50.00…
#> $ EF <int> 55, 55, 35, 55, 55, 35, 55, 75, 50, 55, 75, 5…
Kolom yang tipe datanya akan diubah: - group, outcome, gendera, hypertensive, atrialfibrillation, CHD.with.no.MI, diabetes, deficiencyanemias, depression, Hyperlipemia, Renal.failure, COPD -> Factor
Kolom yang dibuang: - group dan ID
Mengubah sekaligus beberapa tipe data dengan menggunakan perintah
mutate_at
heart1 <- heart %>%
select(-c(group,ID)) %>%
mutate_at(vars(outcome, gendera, hypertensive, atrialfibrillation, CHD.with.no.MI, diabetes, deficiencyanemias, depression, Hyperlipemia, Renal.failure, COPD), as.factor)Mengecek kembali tipe data:
# Cek kembali struktur data
glimpse(heart1)#> Rows: 1,177
#> Columns: 49
#> $ outcome <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
#> $ age <int> 72, 75, 83, 43, 75, 76, 72, 83, 61, 67, 70, 8…
#> $ gendera <fct> 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 1, 2, …
#> $ BMI <dbl> 37.58818, NA, 26.57263, 83.26463, 31.82484, 2…
#> $ hypertensive <fct> 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
#> $ atrialfibrillation <fct> 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, …
#> $ CHD.with.no.MI <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
#> $ diabetes <fct> 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, …
#> $ deficiencyanemias <fct> 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, …
#> $ depression <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
#> $ Hyperlipemia <fct> 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, …
#> $ Renal.failure <fct> 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, …
#> $ COPD <fct> 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, …
#> $ heart.rate <dbl> 68.83784, 101.37037, 72.31818, 94.50000, 67.9…
#> $ Systolic.blood.pressure <dbl> 155.86667, 140.00000, 135.33333, 126.40000, 1…
#> $ Diastolic.blood.pressure <dbl> 68.33333, 65.00000, 61.37500, 73.20000, 58.12…
#> $ Respiratory.rate <dbl> 16.62162, 20.85185, 23.64000, 21.85714, 21.36…
#> $ temperature <dbl> 36.71429, 36.68254, 36.45370, 36.28704, 36.76…
#> $ SP.O2 <dbl> 98.39474, 96.92308, 95.29167, 93.84615, 99.28…
#> $ Urine.output <dbl> 2155, 1425, 2425, 8760, 4455, 1840, 2450, 303…
#> $ hematocrit <dbl> 26.27273, 30.78000, 27.70000, 36.63750, 29.93…
#> $ RBC <dbl> 2.960000, 3.138000, 2.620000, 4.277500, 3.286…
#> $ MCH <dbl> 28.25000, 31.06000, 34.32000, 26.06250, 30.66…
#> $ MCHC <dbl> 31.52000, 31.66000, 31.30000, 30.41250, 33.66…
#> $ MCV <dbl> 89.90000, 98.20000, 109.80000, 85.62500, 91.0…
#> $ RDW <dbl> 16.22000, 14.26000, 23.82000, 17.03750, 16.26…
#> $ Leucocyte <dbl> 7.6500000, 12.7400000, 5.4800000, 8.2250000, …
#> $ Platelets <dbl> 305.10000, 246.40000, 204.20000, 216.37500, 2…
#> $ Neutrophils <dbl> 74.65000, NA, 68.10000, 81.80000, NA, 85.4000…
#> $ Basophils <dbl> 0.40, NA, 0.55, 0.15, NA, 0.30, 0.20, NA, 0.5…
#> $ Lymphocyte <dbl> 13.300000, NA, 24.500000, 14.500000, NA, 9.30…
#> $ PT <dbl> 10.60000, NA, 11.27500, 27.06667, NA, 18.7833…
#> $ INR <dbl> 1.000000, NA, 0.950000, 2.666667, NA, 1.70000…
#> $ NT.proBNP <dbl> 1956.0, 2384.0, 4081.0, 668.0, 30802.0, 34183…
#> $ Creatine.kinase <dbl> 148.00000, 60.60000, 16.00000, 85.00000, 111.…
#> $ Creatinine <dbl> 1.9583333, 1.1222222, 1.8714286, 0.5857143, 1…
#> $ Urea.nitrogen <dbl> 50.00000, 20.33333, 33.85714, 15.28571, 43.00…
#> $ glucose <dbl> 114.63636, 147.50000, 149.00000, 128.25000, 1…
#> $ Blood.potassium <dbl> 4.816667, 4.450000, 5.825000, 4.386667, 4.783…
#> $ Blood.sodium <dbl> 138.7500, 138.8889, 140.7143, 138.5000, 136.6…
#> $ Blood.calcium <dbl> 7.463636, 8.162500, 8.266667, 9.476923, 8.733…
#> $ Chloride <dbl> 109.16667, 98.44444, 105.85714, 92.07143, 104…
#> $ Anion.gap <dbl> 13.166667, 11.444444, 10.000000, 12.357143, 1…
#> $ Magnesium.ion <dbl> 2.618182, 1.887500, 2.157143, 1.942857, 1.650…
#> $ PH <dbl> 7.230000, 7.225000, 7.268000, 7.370000, 7.250…
#> $ Bicarbonate <dbl> 21.16667, 33.44444, 30.57143, 38.57143, 22.00…
#> $ Lactic.acid <dbl> 0.5000000, 0.5000000, 0.5000000, 0.6000000, 0…
#> $ PCO2 <dbl> 40.00000, 78.00000, 71.50000, 75.00000, 50.00…
#> $ EF <int> 55, 55, 35, 55, 55, 35, 55, 75, 50, 55, 75, 5…
Tampak tipe data setiap kolomnya sudah sesuai.
Mengecek apakah terdapat data yang memiliki kesamaan nilai atau duplikat.
#cek duplicate
sum(duplicated(heart1))#> [1] 0
Ternyata tidak ada data yang berisi duplikat value, selanjutnya cek apakah terdapat missing values.
colSums(is.na(heart1))#> outcome age gendera
#> 1 0 0
#> BMI hypertensive atrialfibrillation
#> 215 0 0
#> CHD.with.no.MI diabetes deficiencyanemias
#> 0 0 0
#> depression Hyperlipemia Renal.failure
#> 0 0 0
#> COPD heart.rate Systolic.blood.pressure
#> 0 13 16
#> Diastolic.blood.pressure Respiratory.rate temperature
#> 16 13 19
#> SP.O2 Urine.output hematocrit
#> 13 36 0
#> RBC MCH MCHC
#> 0 0 0
#> MCV RDW Leucocyte
#> 0 0 0
#> Platelets Neutrophils Basophils
#> 0 144 259
#> Lymphocyte PT INR
#> 145 20 20
#> NT.proBNP Creatine.kinase Creatinine
#> 0 165 0
#> Urea.nitrogen glucose Blood.potassium
#> 0 18 0
#> Blood.sodium Blood.calcium Chloride
#> 0 1 0
#> Anion.gap Magnesium.ion PH
#> 0 0 292
#> Bicarbonate Lactic.acid PCO2
#> 0 229 294
#> EF
#> 0
Ternyata terdapat banyak kolom yang mengandung missing values. Kita akan menangani kolom yang mengandung missing values dengan cara berikut.
Pertama kita akan melihat ada berapa baris yang tidak mengandung missing values
sum(!complete.cases(heart))/nrow(heart1)#> [1] 0.6363636
Ternyata sekitar 63,6% data tidak mengandung missing values
Kita bisa melihat distribusi dari missing values dari dataframe kita
library(VIM)
aggr_plot <- aggr(heart1, col=c('navyblue','red'), numbers=TRUE, sortVars=TRUE, labels=names(heart), cex.axis=.7, gap=3, ylab=c("Histogram of missing data","Pattern"))#>
#> Variables sorted by number of missings:
#> Variable Count
#> Bicarbonate 0.2497875956
#> Anion.gap 0.2480883602
#> Platelets 0.2200509771
#> PH 0.1945624469
#> age 0.1826677995
#> INR 0.1401869159
#> Neutrophils 0.1231945624
#> Leucocyte 0.1223449448
#> temperature 0.0305862362
#> Basophils 0.0169923534
#> Lymphocyte 0.0169923534
#> Diastolic.blood.pressure 0.0161427358
#> Creatinine 0.0152931181
#> COPD 0.0135938828
#> heart.rate 0.0135938828
#> Renal.failure 0.0110450297
#> Systolic.blood.pressure 0.0110450297
#> Respiratory.rate 0.0110450297
#> group 0.0008496177
#> Blood.potassium 0.0008496177
#> ID 0.0000000000
#> outcome 0.0000000000
#> gendera 0.0000000000
#> BMI 0.0000000000
#> hypertensive 0.0000000000
#> atrialfibrillation 0.0000000000
#> CHD.with.no.MI 0.0000000000
#> diabetes 0.0000000000
#> deficiencyanemias 0.0000000000
#> depression 0.0000000000
#> Hyperlipemia 0.0000000000
#> SP.O2 0.0000000000
#> Urine.output 0.0000000000
#> hematocrit 0.0000000000
#> RBC 0.0000000000
#> MCH 0.0000000000
#> MCHC 0.0000000000
#> MCV 0.0000000000
#> RDW 0.0000000000
#> PT 0.0000000000
#> NT.proBNP 0.0000000000
#> Creatine.kinase 0.0000000000
#> Urea.nitrogen 0.0000000000
#> glucose 0.0000000000
#> Blood.sodium 0.0000000000
#> Blood.calcium 0.0000000000
#> Chloride 0.0000000000
#> Magnesium.ion 0.0000000000
#> Lactic.acid 0.0000000000
Berdasarkan dari hasil dataframe, PCO2 dan PH mendominasi missing values yakni hampir 25% dari total data. Kita asumsikan data missing values disini karena datanya tidak diinput oleh petugas atau tidak melakukan uji lab di variabel tersebut.
Untuk menangani missing values, kita akan menggunakan salah satu metode yaitu dengan MICE (Multivariate Imputation via Chained Equations). Untuk menggunakan MICE kita membutuhkan missing values yang tersebar acak atau MCAR dan MAR, metode MICE tidak cocok untuk variabel yang memiliki korelasi kuat dengan variabel lain. Untuk memahaminya bisa akses https://dept.stat.lsa.umich.edu/~jerrick/courses/stat701/notes/mi.html
Berdasarkan beberapa penelitian, kebanyakan missing values di kasus epidemiologi klinik itu bertipe MAR dan MCAR seperti data tidak diinput, kerusakan alat tes lab dan adanya kesalahan dalam pengambilan sampel, maka kita asumsikan missing values bertipe MCAR atau MAR, maka kita bisa menggunakan MICE.
Selanjutnya kita akan mengatasi missing valuesnya dengan menggunakan metode MICE.
# Menggunakan MICE untuk mengatasi missing values
set.seed(123)
imp <- mice(heart1, m=5, maxit=20, seed=500)
# Menyimpan hasil imputasi dalam bentuk DataFrame
heart_clean <- complete(imp)
# Cek kembali missing values pada hasil imputasi
colSums(is.na(heart_clean))Disarankan tidak menggunakan banyak m
danmaxit karena akan berpengaruuh ke durasi
komputasinya.
Kita akan melihat metode apa yang dipakai dalam mengisi missing
values di masing-masing kolom dengan summary
summary(imp)#> Class: mids
#> Number of multiple imputations: 5
#> Imputation methods:
#> outcome age gendera
#> "logreg" "" ""
#> BMI hypertensive atrialfibrillation
#> "pmm" "" ""
#> CHD.with.no.MI diabetes deficiencyanemias
#> "" "" ""
#> depression Hyperlipemia Renal.failure
#> "" "" ""
#> COPD heart.rate Systolic.blood.pressure
#> "" "pmm" "pmm"
#> Diastolic.blood.pressure Respiratory.rate temperature
#> "pmm" "pmm" "pmm"
#> SP.O2 Urine.output hematocrit
#> "pmm" "pmm" ""
#> RBC MCH MCHC
#> "" "" ""
#> MCV RDW Leucocyte
#> "" "" ""
#> Platelets Neutrophils Basophils
#> "" "pmm" "pmm"
#> Lymphocyte PT INR
#> "pmm" "pmm" "pmm"
#> NT.proBNP Creatine.kinase Creatinine
#> "" "pmm" ""
#> Urea.nitrogen glucose Blood.potassium
#> "" "pmm" ""
#> Blood.sodium Blood.calcium Chloride
#> "" "pmm" ""
#> Anion.gap Magnesium.ion PH
#> "" "" "pmm"
#> Bicarbonate Lactic.acid PCO2
#> "" "pmm" "pmm"
#> EF
#> ""
#> PredictorMatrix:
#> outcome age gendera BMI hypertensive atrialfibrillation
#> outcome 0 1 1 1 1 1
#> age 1 0 1 1 1 1
#> gendera 1 1 0 1 1 1
#> BMI 1 1 1 0 1 1
#> hypertensive 1 1 1 1 0 1
#> atrialfibrillation 1 1 1 1 1 0
#> CHD.with.no.MI diabetes deficiencyanemias depression
#> outcome 1 1 1 1
#> age 1 1 1 1
#> gendera 1 1 1 1
#> BMI 1 1 1 1
#> hypertensive 1 1 1 1
#> atrialfibrillation 1 1 1 1
#> Hyperlipemia Renal.failure COPD heart.rate
#> outcome 1 1 1 1
#> age 1 1 1 1
#> gendera 1 1 1 1
#> BMI 1 1 1 1
#> hypertensive 1 1 1 1
#> atrialfibrillation 1 1 1 1
#> Systolic.blood.pressure Diastolic.blood.pressure
#> outcome 1 1
#> age 1 1
#> gendera 1 1
#> BMI 1 1
#> hypertensive 1 1
#> atrialfibrillation 1 1
#> Respiratory.rate temperature SP.O2 Urine.output hematocrit
#> outcome 1 1 1 1 1
#> age 1 1 1 1 1
#> gendera 1 1 1 1 1
#> BMI 1 1 1 1 1
#> hypertensive 1 1 1 1 1
#> atrialfibrillation 1 1 1 1 1
#> RBC MCH MCHC MCV RDW Leucocyte Platelets Neutrophils
#> outcome 1 1 1 1 1 1 1 1
#> age 1 1 1 1 1 1 1 1
#> gendera 1 1 1 1 1 1 1 1
#> BMI 1 1 1 1 1 1 1 1
#> hypertensive 1 1 1 1 1 1 1 1
#> atrialfibrillation 1 1 1 1 1 1 1 1
#> Basophils Lymphocyte PT INR NT.proBNP Creatine.kinase
#> outcome 1 1 1 1 1 1
#> age 1 1 1 1 1 1
#> gendera 1 1 1 1 1 1
#> BMI 1 1 1 1 1 1
#> hypertensive 1 1 1 1 1 1
#> atrialfibrillation 1 1 1 1 1 1
#> Creatinine Urea.nitrogen glucose Blood.potassium
#> outcome 1 1 1 1
#> age 1 1 1 1
#> gendera 1 1 1 1
#> BMI 1 1 1 1
#> hypertensive 1 1 1 1
#> atrialfibrillation 1 1 1 1
#> Blood.sodium Blood.calcium Chloride Anion.gap Magnesium.ion
#> outcome 1 1 1 1 1
#> age 1 1 1 1 1
#> gendera 1 1 1 1 1
#> BMI 1 1 1 1 1
#> hypertensive 1 1 1 1 1
#> atrialfibrillation 1 1 1 1 1
#> PH Bicarbonate Lactic.acid PCO2 EF
#> outcome 1 1 1 1 1
#> age 1 1 1 1 1
#> gendera 1 1 1 1 1
#> BMI 1 1 1 1 1
#> hypertensive 1 1 1 1 1
#> atrialfibrillation 1 1 1 1 1
Ternyata dengan menggunakan MICE, missing values diisi dengan method “pmm” untuk kolom numerik dan method “logreg” untuk kolom kategorik secara otomatis.
Untuk melihat apa saja method yang bisa digunakan bisa akses link berikut https://search.r-project.org/CRAN/refmans/mice/html/mice.html
Selanjutnya, mari kita cek ulang kolom missing valuesnya
colSums(is.na(heart_clean))#> outcome age gendera
#> 0 0 0
#> BMI hypertensive atrialfibrillation
#> 0 0 0
#> CHD.with.no.MI diabetes deficiencyanemias
#> 0 0 0
#> depression Hyperlipemia Renal.failure
#> 0 0 0
#> COPD heart.rate Systolic.blood.pressure
#> 0 0 0
#> Diastolic.blood.pressure Respiratory.rate temperature
#> 0 0 0
#> SP.O2 Urine.output hematocrit
#> 0 0 0
#> RBC MCH MCHC
#> 0 0 0
#> MCV RDW Leucocyte
#> 0 0 0
#> Platelets Neutrophils Basophils
#> 0 0 0
#> Lymphocyte PT INR
#> 0 0 0
#> NT.proBNP Creatine.kinase Creatinine
#> 0 0 0
#> Urea.nitrogen glucose Blood.potassium
#> 0 0 0
#> Blood.sodium Blood.calcium Chloride
#> 0 0 0
#> Anion.gap Magnesium.ion PH
#> 0 0 0
#> Bicarbonate Lactic.acid PCO2
#> 0 0 0
#> EF
#> 0
Sekarang kita dapat memeriksa distribusi data asli dan data yang diisi oleh MICE:
Sebagai contoh kita menggunakan Bicarbonate dan PCO2 tadi
marginplot(heart1[c("PCO2","Bicarbonate")])xyplot(imp,Bicarbonate ~ PCO2,pch=18,cex=1)
Tampak beberapa data yang berwarna biru muda yang tersebar acak telah
masuk ke dataframe kita.
Karena data kita sudah bersih, kita bisa lannjut ke proses EDA.
Dalam kasus kali ini, kita akan memprediksi kolom target
outcome berdasarkan beberapa variabel prediktor yang
dipilih. Kita akan menentukan:
outcome.outcome.Berdasarkan informasi dari basic knowledge dari seorang expert di bidang yang terkait dengan dataset ini, variabel-variabel penting yang menyebabkan pengaruh terhadap mortalitas gagal jantung yaitu: Age, gendera, BMI, hypertensive, atrialfibrillation, CHD.with.no.MI, diabetes, Hyperlipemia, Renal.failure, heart.rate, Systolic.blood.pressure, Diastolic.blood.pressure, Respiratory.rate, temperature, SP.O2, Urine.output, RBC, Platelets, PT, NT.proBNP, Creatine.kinase, Creatinine, glucose, Blood.potassium, EF
Maka dari itu kita akan memilih kolom-kolom berdasarkan informasi tersebut.
heart_new <- heart_clean %>%
select(c(outcome,age,gendera,BMI,hypertensive,atrialfibrillation,CHD.with.no.MI,diabetes,Hyperlipemia,Renal.failure,heart.rate,Systolic.blood.pressure,Diastolic.blood.pressure,Respiratory.rate,temperature,SP.O2,Urine.output,RBC,Platelets,PT,NT.proBNP,Creatine.kinase,Creatinine,glucose,Blood.potassium,EF))Selanjutnya kita akan cek apakah ada ketidaknormalan pada setiap
kolom dari dataframe heart_new. Kita bisa mengeceknya
dengan menggunakan summary.
summary(heart_new)#> outcome age gendera BMI hypertensive
#> 0:1017 Min. :19.00 1:559 Min. : 13.35 0:332
#> 1: 160 1st Qu.:65.00 2:618 1st Qu.: 24.30 1:845
#> Median :77.00 Median : 28.23
#> Mean :74.06 Mean : 29.98
#> 3rd Qu.:85.00 3rd Qu.: 33.34
#> Max. :99.00 Max. :104.97
#> atrialfibrillation CHD.with.no.MI diabetes Hyperlipemia Renal.failure
#> 0:646 0:1076 0:681 0:730 0:747
#> 1:531 1: 101 1:496 1:447 1:430
#>
#>
#>
#>
#> heart.rate Systolic.blood.pressure Diastolic.blood.pressure
#> Min. : 36.00 Min. : 75.0 Min. : 24.74
#> 1st Qu.: 72.38 1st Qu.:105.4 1st Qu.: 52.05
#> Median : 83.61 Median :116.0 Median : 58.46
#> Mean : 84.57 Mean :117.9 Mean : 59.48
#> 3rd Qu.: 95.95 3rd Qu.:128.5 3rd Qu.: 65.43
#> Max. :135.71 Max. :203.0 Max. :107.00
#> Respiratory.rate temperature SP.O2 Urine.output
#> Min. :11.14 Min. :33.25 Min. : 75.92 Min. : 0
#> 1st Qu.:17.92 1st Qu.:36.29 1st Qu.: 95.00 1st Qu.: 958
#> Median :20.36 Median :36.65 Median : 96.45 Median :1660
#> Mean :20.80 Mean :36.67 Mean : 96.27 Mean :1888
#> 3rd Qu.:23.40 3rd Qu.:37.02 3rd Qu.: 97.92 3rd Qu.:2495
#> Max. :40.90 Max. :39.13 Max. :100.00 Max. :8820
#> RBC Platelets PT NT.proBNP
#> Min. :2.030 Min. : 9.571 Min. :10.10 Min. : 50
#> 1st Qu.:3.120 1st Qu.: 168.909 1st Qu.:13.16 1st Qu.: 2251
#> Median :3.490 Median : 222.667 Median :14.61 Median : 5840
#> Mean :3.575 Mean : 241.504 Mean :17.48 Mean : 11014
#> 3rd Qu.:3.900 3rd Qu.: 304.250 3rd Qu.:18.80 3rd Qu.: 14968
#> Max. :6.575 Max. :1028.200 Max. :71.27 Max. :118928
#> Creatine.kinase Creatinine glucose Blood.potassium
#> Min. : 8.0 Min. : 0.2667 Min. : 66.67 Min. :3.000
#> 1st Qu.: 46.0 1st Qu.: 0.9400 1st Qu.:113.50 1st Qu.:3.900
#> Median : 90.0 Median : 1.2875 Median :136.55 Median :4.115
#> Mean : 246.2 Mean : 1.6428 Mean :148.66 Mean :4.177
#> 3rd Qu.: 188.0 3rd Qu.: 1.9000 3rd Qu.:169.33 3rd Qu.:4.400
#> Max. :42987.5 Max. :15.5273 Max. :414.10 Max. :6.567
#> EF
#> Min. :15.00
#> 1st Qu.:40.00
#> Median :55.00
#> Mean :48.72
#> 3rd Qu.:55.00
#> Max. :75.00
Tampak ada keanehan pada nilai maksimum pada variabel Creatine.kinase yakni mencapai nilai 42987.5 dan ini bisa dikategorikan outlier. Akan tetapi sebagai info, CK adalah enzim yang ditemukan di jantung, otak, dan otot rangka, dan kisaran normalnya adalah 20 hingga 380. Sebagian besar CK dalam darah berasal dari otot, dan levelnya naik karena olahraga atau cedera, dan dalam beberapa kasus naik menjadi 10K hingga 200K dalam kasus rhabdomyolysis, kelainan ginjal atau lisis diafragma (Laporan kasus). Oleh karena itu, outlier ini tidak dihapus.
Untuk variabel lain yang memiliki nilai maksimum yang tinggi kita mengasumsikan kalau nilainya adalah kasus khusus.
Kita akan mengecek keseimbangan data dari variabel target
prop.table(table(heart_new$outcome))#>
#> 0 1
#> 0.8640612 0.1359388
Kita akan menangani ketidak-seimbangan data target ini ketika sudah membagi data menjadi train dan test agar tidak terjadi leakage pada saat prediksi berjalan.
RNGkind(sample.kind = "Rounding")
set.seed(29) #mengunci kerendoman
# index sampling
index <- sample(x = nrow(heart_new), size = nrow(heart_new)*0.8)
# splitting
heart_train <- heart_new[index,]
heart_test <- heart_new[-index,]Kita akan mengecek hasil pembagian datanya
nrow(heart_train)#> [1] 941
nrow(heart_test)#> [1] 236
Ada sebanyak 941 data yang akan digunkana sebagai train dan 236 data yang akan ditest
table(heart_train$outcome)#>
#> 0 1
#> 813 128
Ternyata data target sangat tidak seimbang.
Untuk menyeimbangkan data targetnya kita bisa menggunakan salah satu
metode dalam mengatasi kelas target yang tidak seimbang yakni menambah
data dengan metode SMOTE, SMOTE dapat
digunakan ketika kelas minoritas terlalu sedikit dibandingkan kelas
mayoritas tanpa kehilangan informasi penting pada data.
Proporsi kelas yang balance penting untuk data train karena kita akan melatih model menggunakan data train.
#untuk menginstal DMwR karena DMwR sudah tidak tersedia.
#remotes::install_github("cran/DMwR")
set.seed(123)
heart_train <- SMOTE(outcome ~ ., heart_train, perc.over = 500, k=5, perc.under=120)
prop.table(table(heart_train$outcome))#>
#> 0 1
#> 0.5 0.5
model_all <- glm(outcome ~., heart_train, family = "binomial", control = list(trace = F))
summary(model_all)#>
#> Call:
#> glm(formula = outcome ~ ., family = "binomial", data = heart_train,
#> control = list(trace = F))
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -2.4710 -0.8720 -0.0637 0.8739 2.3598
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 13.085144000 5.535488427 2.364 0.018085 *
#> age 0.018617985 0.006445415 2.889 0.003870 **
#> gendera2 -0.324763322 0.127635124 -2.544 0.010944 *
#> BMI 0.013454980 0.008934424 1.506 0.132075
#> hypertensive1 -0.513788110 0.135342674 -3.796 0.000147 ***
#> atrialfibrillation1 0.277867742 0.128488741 2.163 0.030573 *
#> CHD.with.no.MI1 0.605189099 0.199346767 3.036 0.002398 **
#> diabetes1 0.218265016 0.131921224 1.655 0.098024 .
#> Hyperlipemia1 -0.076552028 0.129586746 -0.591 0.554695
#> Renal.failure1 -0.501319107 0.145198093 -3.453 0.000555 ***
#> heart.rate 0.030336583 0.005225444 5.806 0.00000000642 ***
#> Systolic.blood.pressure -0.003896122 0.004799798 -0.812 0.416949
#> Diastolic.blood.pressure -0.041188168 0.009139221 -4.507 0.00000658287 ***
#> Respiratory.rate 0.081940666 0.017499855 4.682 0.00000283587 ***
#> temperature -0.266773327 0.123875870 -2.154 0.031275 *
#> SP.O2 -0.091057622 0.027220386 -3.345 0.000822 ***
#> Urine.output -0.000359301 0.000068774 -5.224 0.00000017473 ***
#> RBC -0.234666416 0.118009639 -1.989 0.046752 *
#> Platelets -0.002941575 0.000626689 -4.694 0.00000268133 ***
#> PT 0.019908183 0.009441425 2.109 0.034979 *
#> NT.proBNP 0.000027686 0.000005432 5.097 0.00000034570 ***
#> Creatine.kinase 0.000570280 0.000160831 3.546 0.000391 ***
#> Creatinine 0.244632106 0.083320411 2.936 0.003324 **
#> glucose 0.005364854 0.001336696 4.014 0.00005982058 ***
#> Blood.potassium 0.623495454 0.177845891 3.506 0.000455 ***
#> EF -0.000753860 0.005624311 -0.134 0.893374
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 2129.3 on 1535 degrees of freedom
#> Residual deviance: 1613.2 on 1510 degrees of freedom
#> AIC: 1665.2
#>
#> Number of Fisher Scoring iterations: 6
Kemudian kita akan melakukan feature selection / tuning menggunakan stepwise tipe backward (menghilangkan predictor satu persatu sampai didapat nilai AIC paling rendah)
model_step <- step(object = model_all,
direction = "backward",
trace = FALSE)
summary(model_step)#>
#> Call:
#> glm(formula = outcome ~ age + gendera + BMI + hypertensive +
#> atrialfibrillation + CHD.with.no.MI + diabetes + Renal.failure +
#> heart.rate + Diastolic.blood.pressure + Respiratory.rate +
#> temperature + SP.O2 + Urine.output + RBC + Platelets + PT +
#> NT.proBNP + Creatine.kinase + Creatinine + glucose + Blood.potassium,
#> family = "binomial", data = heart_train, control = list(trace = F))
#>
#> Deviance Residuals:
#> Min 1Q Median 3Q Max
#> -2.47125 -0.87372 -0.06518 0.86987 2.39123
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 12.930591246 5.514762912 2.345 0.019041 *
#> age 0.017657101 0.006383979 2.766 0.005678 **
#> gendera2 -0.329503816 0.125544096 -2.625 0.008675 **
#> BMI 0.013162394 0.008918464 1.476 0.139982
#> hypertensive1 -0.530092765 0.134119651 -3.952 0.000077375471 ***
#> atrialfibrillation1 0.284237830 0.128071712 2.219 0.026462 *
#> CHD.with.no.MI1 0.618627111 0.197796810 3.128 0.001762 **
#> diabetes1 0.209176348 0.131568572 1.590 0.111865
#> Renal.failure1 -0.498149911 0.142745532 -3.490 0.000483 ***
#> heart.rate 0.031438092 0.005034520 6.245 0.000000000425 ***
#> Diastolic.blood.pressure -0.044426855 0.008077267 -5.500 0.000000037929 ***
#> Respiratory.rate 0.083550777 0.017362937 4.812 0.000001494134 ***
#> temperature -0.274062325 0.123112652 -2.226 0.026007 *
#> SP.O2 -0.091203412 0.027071155 -3.369 0.000754 ***
#> Urine.output -0.000368658 0.000067488 -5.463 0.000000046922 ***
#> RBC -0.216630361 0.115041704 -1.883 0.059692 .
#> Platelets -0.003020391 0.000612601 -4.930 0.000000820464 ***
#> PT 0.021292242 0.009289158 2.292 0.021896 *
#> NT.proBNP 0.000028031 0.000005226 5.364 0.000000081413 ***
#> Creatine.kinase 0.000585548 0.000158519 3.694 0.000221 ***
#> Creatinine 0.236647779 0.081193704 2.915 0.003561 **
#> glucose 0.005294937 0.001316758 4.021 0.000057904483 ***
#> Blood.potassium 0.631122990 0.176565355 3.574 0.000351 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 2129.3 on 1535 degrees of freedom
#> Residual deviance: 1614.3 on 1513 degrees of freedom
#> AIC: 1660.3
#>
#> Number of Fisher Scoring iterations: 6
Karena AIC nya lebih kecil yaitu 1641.8 diabnding sebelumnya 1647.1,
maka kita gunakan model_step
Interpretasi Koefisien: Karena hasil dari model menampilkan koefisien dalam bentuk log of odds maka kita akan mengubah log of odds ke nilai odds
exp(model_step$coefficient)#> (Intercept) age gendera2
#> 412747.4758638 1.0178139 0.7192805
#> BMI hypertensive1 atrialfibrillation1
#> 1.0132494 0.5885504 1.3287489
#> CHD.with.no.MI1 diabetes1 Renal.failure1
#> 1.8563777 1.2326624 0.6076538
#> heart.rate Diastolic.blood.pressure Respiratory.rate
#> 1.0319375 0.9565456 1.0871404
#> temperature SP.O2 Urine.output
#> 0.7602847 0.9128320 0.9996314
#> RBC Platelets PT
#> 0.8052276 0.9969842 1.0215205
#> NT.proBNP Creatine.kinase Creatinine
#> 1.0000280 1.0005857 1.2669948
#> glucose Blood.potassium
#> 1.0053090 1.8797203
Berikut hasil interpretasi untuk beberapa variabel prediktor,
Age: 1.0144513 Odds ratio untuk usia adalah 1.0144513. Ini menunjukkan bahwa setiap peningkatan 1 tahun usia pasien, maka kemungkinan meninggal akibat gagal jantung akan meningkat sebesar 1.01 kali, ketika variabel prediktor lainnya diatur pada nilai yang sama.
Gendera2: 0.6294582 Odds ratio untuk gendera2 adalah 0.6294582. Ini menunjukkan bahwa pasien dengan gender male memiliki kemungkinan 0.63 kali lebih rendah untuk meninggal akibat gagal jantung dibandingkan dengan pasien dengan gender female, ketika variabel prediktor lainnya diatur pada nilai yang sama.
Atrialfibrillation1: 1.3320582 Odds ratio untuk atrialfibrillation1 adalah 1.3320582. Ini menunjukkan bahwa pasien dengan fibrilasi atrium memiliki kemungkinan 1.3 kali lebih tinggi untuk meninggal akibat gagal jantung dibandingkan dengan pasien tanpa fibrilasi atrium, ketika variabel prediktor lainnya diatur pada nilai yang sama.
Heart.rate: 1.0247106 Odds ratio untuk heart.rate adalah 1.0247106. Ini menunjukkan bahwa setiap peningkatan 1 denyut jantung dalam satu menit, maka kemungkinan meninggal akibat gagal jantung akan meningkat sebesar 1.02 kali, ketika variabel prediktor lainnya diatur pada nilai yang sama.
Diastolic.blood.pressure: 0.9510547 Odds ratio untuk Diastolic.blood.pressure adalah 0.9510547. Ini menunjukkan bahwa setiap peningkatan 1 mmHg tekanan darah diastolik, maka kemungkinan meninggal akibat gagal jantung akan menurun sebesar 0.95 kali, ketika variabel prediktor
Temperature: 0.8203311 Odds ratio untuk temperature adalah 0.8203311. Ini menunjukkan bahwa setiap peningkatan 1 derajat Celsius suhu tubuh, maka kemungkinan meninggal akibat gagal jantung akan menurun sebesar 0.82 kali, ketika variabel prediktor lainnya diatur pada nilai yang sama.
SP.O2: 0.8945223 Odds ratio untuk SP.O2 adalah 0.8945223. Ini menunjukkan bahwa setiap peningkatan 1 persen saturasi oksigen dalam darah, maka kemungkinan meninggal akibat gagal jantung akan menurun sebesar 0.89 kali, ketika variabel prediktor lainnya diatur pada nilai yang sama.
Urine.output: 0.9996536 Odds ratio untuk urine.output adalah 0.9996536. Ini menunjukkan bahwa setiap peningkatan 1 ml urine output, maka kemungkinan meninggal akibat gagal jantung akan menurun sangat sedikit (kurang dari 1%), ketika variabel prediktor lainnya diatur pada nilai yang sama.
Platelets: 0.9969780 Odds ratio untuk platelets adalah 0.9969780. Ini menunjukkan bahwa setiap peningkatan 1 (1000, dalam unit ribuan) sel trombosit per mikroliter darah, maka kemungkinan meninggal akibat gagal jantung akan menurun sedikit (kurang dari 1%), ketika variabel prediktor lainnya diatur pada nilai yang sama.
NT.proBNP: 1.0000303 Odds ratio untuk NT.proBNP adalah 1.0000303. Ini menunjukkan bahwa setiap peningkatan 1 pg/mL kadar NT.proBNP dalam darah, maka kemungkinan meninggal akibat gagal jantung akan meningkat sebesar 1.0 kali, ketika variabel prediktor lainnya diatur pada nilai yang sama.
Blood.potassium: 2.5134246 Odds ratio untuk blood.potassium adalah 2.5134246. Ini menunjukkan bahwa pasien dengan kadar potassium darah yang tinggi memiliki kemungkinan 2.51 kali lebih tinggi untuk meninggal akibat gagal jantung dibandingkan dengan pasien dengan kadar potassium darah normal, ketika variabel prediktor lainnya diatur pada nilai yang sama.
Selanjutnya, karena dalam model regresi logistik kita tidak menginginkan adanya multikolinearitas, sehingga kita akan mengecek apakah ada indikasi multikolinearitas pada variabel di model yang telah dibuat.
vif(model_step)#> age gendera BMI
#> 1.520861 1.064437 1.424701
#> hypertensive atrialfibrillation CHD.with.no.MI
#> 1.084321 1.108062 1.019068
#> diabetes Renal.failure heart.rate
#> 1.136273 1.287954 1.337829
#> Diastolic.blood.pressure Respiratory.rate temperature
#> 1.357476 1.239541 1.165543
#> SP.O2 Urine.output RBC
#> 1.107445 1.328313 1.222002
#> Platelets PT NT.proBNP
#> 1.135694 1.077485 1.300593
#> Creatine.kinase Creatinine glucose
#> 1.119810 1.674034 1.140015
#> Blood.potassium
#> 1.292129
Tampak tidak adanya nilai vif > 10, artinya tidak ada multikolinearitas pada model ini.
Selanjutnya kita akan memprediksi probability outcome
untuk data heart_test dan disimpan pada kolom baru
bernama pred_out.
heart_test$pred_out <- predict(object = model_step,
newdata = heart_test,
type = "response")
heart_testKemudian akan kita klasifikasikan data heart_test berdasarkan
pred_out dan selanjutnya hasilnya di simpan pada kolom baru
bernama pred_Label, kita akan gunakan threshold 0.5.
heart_test$pred_Label <- ifelse(heart_test$pred_out < 0.5, 0, 1) %>%
as.factor()
heart_testKita akan mengecek hasil prediksi dan data aktualnya
heart_test %>%
select(pred_out, pred_Label, outcome)confusionMatrix(
data = heart_test$pred_Label, #hasil prediksi kita
reference = heart_test$outcome, #data aslinya/aktual
positive = "1"
)#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 147 8
#> 1 57 24
#>
#> Accuracy : 0.7246
#> 95% CI : (0.6629, 0.7805)
#> No Information Rate : 0.8644
#> P-Value [Acc > NIR] : 1
#>
#> Kappa : 0.286
#>
#> Mcnemar's Test P-Value : 0.000000002622
#>
#> Sensitivity : 0.7500
#> Specificity : 0.7206
#> Pos Pred Value : 0.2963
#> Neg Pred Value : 0.9484
#> Prevalence : 0.1356
#> Detection Rate : 0.1017
#> Detection Prevalence : 0.3432
#> Balanced Accuracy : 0.7353
#>
#> 'Positive' Class : 1
#>
Dari hasil di atas diperoleh informasi:
Selanjutnya kita akan mengevaluasi model sesuai dengan business case-nya
Karena kita ingin mengurangi kesalahan prediksi pasien hidup ternyata meninggal, maka metrik yang digunakan yakni * Metrics = Recall/FN
Setelah menentukan metrik yang digunakan, kita akan melakukan tuning model dengan salah satu cara tuning yaitu mengubah treshold prediction (note: cara ini tidak terlalu dianjurkan karena sangat tergantung pada konteks bisnis)
# treshold diganti jadi 0.4 (direndahkan untuk meningkatkan recall)
heart_test$pred_Label_tune <- ifelse(heart_test$pred_out < 0.4,
yes = 0,
no = 1)
# confusion matrix
confusionMatrix(
data = as.factor(heart_test$pred_Label_tune),
reference = heart_test$outcome,
positive = "1"
)#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 134 5
#> 1 70 27
#>
#> Accuracy : 0.6822
#> 95% CI : (0.6187, 0.7411)
#> No Information Rate : 0.8644
#> P-Value [Acc > NIR] : 1
#>
#> Kappa : 0.2697
#>
#> Mcnemar's Test P-Value : 0.0000000000001467
#>
#> Sensitivity : 0.8438
#> Specificity : 0.6569
#> Pos Pred Value : 0.2784
#> Neg Pred Value : 0.9640
#> Prevalence : 0.1356
#> Detection Rate : 0.1144
#> Detection Prevalence : 0.4110
#> Balanced Accuracy : 0.7503
#>
#> 'Positive' Class : 1
#>
Perbandingan nilai yang diperoleh
Ternyata model sebelum tuning menghasilkan accuracy lebih tinggi daripada model setelah tuning, akan tetapi sensitivity justru tinggi di model setelah tuning.
heart_train$pred_out <- predict(object = model_step,
newdata = heart_train,
type = "response")
heart_train$pred_Label <- ifelse(heart_train$pred_out < 0.5, 0, 1) %>%
as.factor()
confusionMatrix(
data = heart_train$pred_Label, #hasil prediksi kita
reference = heart_train$outcome, #data aslinya/aktual
positive = "1"
)#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 574 201
#> 1 194 567
#>
#> Accuracy : 0.7428
#> 95% CI : (0.7202, 0.7645)
#> No Information Rate : 0.5
#> P-Value [Acc > NIR] : <0.0000000000000002
#>
#> Kappa : 0.4857
#>
#> Mcnemar's Test P-Value : 0.7627
#>
#> Sensitivity : 0.7383
#> Specificity : 0.7474
#> Pos Pred Value : 0.7451
#> Neg Pred Value : 0.7406
#> Prevalence : 0.5000
#> Detection Rate : 0.3691
#> Detection Prevalence : 0.4954
#> Balanced Accuracy : 0.7428
#>
#> 'Positive' Class : 1
#>
Tampak nilai accuracy di train lebih besar dibandingkan di test, hal ini mengindikasikan model overfitting, tapi selisihnya hanya 1.66%, bisa dikatakan model masih bisa diterima sebagai model bukan overfitting.
Model selanjutnya yang akan dibuat adalah k-NN atau K-nearest neighboor yang mana cara kerja model ini akan mengklasifikasi data baru dengan membandingkan karakteristik data baru (data test) dengan data yang ada (data train). Kedekatan karakteristik tersebut diukur dengan Euclidean Distance hingga didapatkan jarak. Kemudian akan dipilih k tetangga terdekat dari data baru tersebut, kemudian ditentukan kelasnya menggunakan majority voting.
Hal yang perlu diperhatikan ketika membuat model k-NN yakni range di setiap variabelnya harus sama, karena k-NN mengklasifikasikan data dengan cara mengukur jarak, hal ini akan mempengaruhi hasil dari prediksi ketika range dari setiap variabel yang digunakan tidak di scaling terlebih dahulu.
Untuk k-NN, dipisahkan antara prediktor dan targetnya. Sebelum melakukan pemisahan, kita akan melakukan scaling pada data kita. Disini kita akan tetap memasukkan variabel kategorik karena dari data hanya terdiri dari 2 angka saja.
# mengubah prediktor kategorik menjadi numerik karena hanya terdapat dua nilai yaitu 0 dan 1
heart_train1 <- heart_train %>%
mutate_if(is.factor, ~ as.numeric(as.character(.x))) %>%
mutate(gendera = ifelse(gendera == 1, 0, 1))
heart_test1 <- heart_test %>%
mutate_if(is.factor, ~ as.numeric(as.character(.x))) %>%
mutate(gendera = ifelse(gendera == 1, 0, 1))
# prediktor
heart_train_x1 <- select(heart_train1, -c(outcome, pred_out, pred_Label))
heart_test_x1 <- select(heart_test1, -c(outcome, pred_out, pred_Label, pred_Label_tune))
# target
heart_train_y1 <- heart_train1[, "outcome"]
heart_test_y1 <- heart_test1[, "outcome"]Data prediktor akan discaling menggunakan z-score standarization. Data test juga harus discaling menggunakan parameter dari data train (karena menganggap data test adalah unseen data).
heart_train_x1_scale <- scale(x = heart_train_x1)
heart_test_x1_scale <- scale(x = heart_test_x1,
center = attr(heart_train_x1_scale,"scaled:center"),
scale = attr(heart_train_x1_scale,"scaled:scale"))karena kita menscaling data test yang mana harus mengandung informasi mean dan std dari train agar di model test nantinya bisa mengenal bahwa data train itu bekerja di mean dan std tersebut.
Selanjutnya kita akan mencari nilia k optimum dengan cara mencari
akar dari jumlah data kita: sqrt(nrow(data))
# find optimum k
sqrt(nrow(heart_train1))#> [1] 39.19184
karena target bernilai genap jadi k-nya ganjil
library(class) # package untuk fungsi `knn()`
heart_pred <- knn(train = heart_train_x1_scale, #data train yang sudah discale
test = heart_test_x1_scale, #data test yang sudah discale
cl = heart_train_y1, #target dari data train latihan
k = 39)# confusion matrix
confusionMatrix(
data = as.factor(heart_pred),
reference = as.factor(heart_test_y1),
positive = "1"
)#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 136 8
#> 1 68 24
#>
#> Accuracy : 0.678
#> 95% CI : (0.6143, 0.7371)
#> No Information Rate : 0.8644
#> P-Value [Acc > NIR] : 1
#>
#> Kappa : 0.2327
#>
#> Mcnemar's Test P-Value : 0.00000000001308
#>
#> Sensitivity : 0.7500
#> Specificity : 0.6667
#> Pos Pred Value : 0.2609
#> Neg Pred Value : 0.9444
#> Prevalence : 0.1356
#> Detection Rate : 0.1017
#> Detection Prevalence : 0.3898
#> Balanced Accuracy : 0.7083
#>
#> 'Positive' Class : 1
#>
Dari hasil di atas diperoleh informasi:
Accuracy : 0.6695, artinya ada 66.95% data yang diklasifikasikan dengan benar.
Sensitivity : 0.71875, artinya 71.87% kelas positif diklasifikasikan dengan benar dari data aktualnya.
Pos Pred Value : 0.2500, artinya tingkat presisi model memprediksi kelas positif sebesar 25.00%
Target variabel = meninggal atau hidup pada pasien gagal jantung
Kelas positif = meninggal FN - diprediksi hidup ternyata meninggal FP - diprediksi meninggal ternyata hidup
Metrics = Recall/FN
Berdasarkan hasil model regresi logistik, ternyata ada beberapa faktor yang meningkatkan kemungkinan seorang akan meninggal ketika terkena gagal jantung seperti kandungan Blood.potassium, PT, NT.proBNP, Creatine.kinase, dan glucose yang tinggi pada pasien, tanda vital berupa Respiratory.rate dan heart rate yang tinggi dan juga beberapa penyakit bawaan yang diderita pasien seperti atrial fibrillation dan diabetes, dan jantung koroner tanpa serangan jantung.
Berdasarkan hasil dari ketiga model yang dibuat, diperoleh:
Model regresi logistik sebelum tuning:
Model regresi logistik seteleh tuning:
Model k-NN
Dari hasil 2 jenis model yang dilakukan, model regresi logistik memiliki nilai accuracy yang lebih tinggi dibanding dengan model k-NN. Untuk kasus ini, kita fokus untuk melihat nilai sensitivity karena kita ingin menurunkan salah tebak untuk kasus diprediksi hidup tapi ternyata meninggal. Maka berdasarkan nilai Sensitivity, untuk dataset model regresi logistik lebih cocok digunakan untuk melakukan prediksi pada dataset kali ini.
Dari perbandingan nilai akurasi di setiap model di atas, untuk kasus epidemiologi klinik seperti di dataset, nilai akurasinya belum bisa dikatakan baik, karena ketika kita membuat model untuk memprediksi nyawa seseorang, tingkat akurasi dan sensitivitas yang digunakan harus tinggi. Sehingga bisa disimpulkan model tidak bisa digunakan. Agar bisa digunakan perlu proses tuning lanjutan atau menambahkan data pada penelitian ini.
Missing data and multiple imputation in clinical epidemiological research https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5358992/
Principled Approaches to Missing Data in Epidemiologic Studies https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5860376/
Heckman imputation models for binary or continuous MNAR outcomes and MAR predictors https://bmcmedresmethodol.biomedcentral.com/articles/10.1186/s12874-018-0547-1
Exceptionally High Creatine Kinase (CK) Levels in Multicausal and Complicated Rhabdomyolysis: A Case Report https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5507674/