Dalam proyek ini, saya bertujuan untuk melakukan prediksi terhadap penumpang kapal Titanic yang selamat berdasarkan dataset yang berisi biodata mereka. Saya akan menggunakan algoritma logistik regresi dan k-nearest neighbor dalam supervised learning untuk menghasilkan prediksi apakah seorang penumpang akan selamat atau tidak berdasarkan informasi pada biodatanya.
Dataset Titanic yang digunakan berasal dari Kaggle. Data
tersebut digunakan untuk menganalisis dan melakukan prediksi terkait
kejadian di kapal Titanic.
#> Rows: 891
#> Columns: 12
#> $ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,…
#> $ Survived <int> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1…
#> $ Pclass <int> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3…
#> $ Name <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (Fl…
#> $ Sex <chr> "male", "female", "female", "female", "male", "male", "mal…
#> $ Age <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, 39, 14, …
#> $ SibSp <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0…
#> $ Parch <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0…
#> $ Ticket <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "37…
#> $ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625,…
#> $ Cabin <chr> "", "C85", "", "C123", "", "", "E46", "", "", "", "G6", "C…
#> $ Embarked <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S"…
Survived (Survival): Kolom ini menunjukkan apakah
penumpang tersebut selamat atau tidak. Nilai 0 berarti tidak selamat,
sedangkan nilai 1 berarti selamat.
pclass (Ticket class): Kolom ini menunjukkan kelas
tiket yang dibeli oleh penumpang. Nilai 1 menandakan kelas tiket
pertama, nilai 2 menandakan kelas tiket kedua, dan nilai 3 menandakan
kelas tiket ketiga.
sex (Jenis kelamin): Kolom ini menunjukkan jenis
kelamin penumpang. Nilai “male” menandakan laki-laki, sedangkan nilai
“female” menandakan perempuan.
Age (Usia): Kolom ini menunjukkan usia penumpang
dalam tahun.
Sibsp (# of siblings/spouses aboard the Titanic):
Kolom ini menunjukkan jumlah saudara kandung atau pasangan yang juga
berada di kapal Titanic bersama penumpang.
Parch (# of parents/children aboard the Titanic):
Kolom ini menunjukkan jumlah orang tua atau anak yang juga berada di
kapal Titanic bersama penumpang.
Ticket (Nomor tiket): Kolom ini menunjukkan nomor
tiket penumpang.
Fare (Tarif penumpang): Kolom ini menunjukkan tarif
yang dibayarkan oleh penumpang.
Cabin (Nomor kabin): Kolom ini menunjukkan nomor
kabin tempat penumpang menginap di kapal Titanic.
Embarked (Pelabuhan Keberangkatan): Kolom ini
menunjukkan pelabuhan keberangkatan penumpang. Nilai “C” menandakan
Cherbourg, “Q” menandakan Queenstown, dan “S” menandakan
Southampton.
PassengerId : Id Penumpang
Name : Nama penumpang
Terdapat beberapa variabel yang tidak perlu digunakan seperti Name, PassengerId, Ticket dan Cabin. Dan terdapat beberapa variabel yang belum sesuai untuk tipe datanya , sehingga perlu diperbaiki:
titanic <-titanic %>%
select(-Name, -PassengerId, -Ticket, -Cabin) %>%
mutate(Survived = as.factor(Survived),
Pclass = as.factor(Pclass),
Sex = as.factor(Sex),
SibSp = as.factor(SibSp),
Parch = as.factor(Parch),
Embarked = as.factor(Embarked))
glimpse(titanic)#> Rows: 891
#> Columns: 8
#> $ Survived <fct> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0…
#> $ Pclass <fct> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3, 2…
#> $ Sex <fct> male, female, female, female, male, male, male, male, female,…
#> $ Age <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, 39, 14, 55,…
#> $ SibSp <fct> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0…
#> $ Parch <fct> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0…
#> $ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21…
#> $ Embarked <fct> S, C, S, S, S, Q, S, S, S, C, S, S, S, S, S, S, Q, S, S, C, S…
#> Survived Pclass Sex Age SibSp Parch Fare Embarked
#> 0 0 0 177 0 0 0 0
Terdapat outlier pada dataset Titanic yaitu pada kolom
Age sehingga harus diatasi. Pada kolom Age
akan diatasi missing valuenya mengisi dengan nilai mean nya.
# Mengatasi missing value pada titanic_train kolom `Age`
mean_age <- mean(titanic$Age, na.rm = TRUE)
titanic$Age[is.na(titanic$Age)] <- mean_age#> Survived Pclass Sex Age SibSp Parch Fare Embarked
#> 0 0 0 0 0 0 0 0
Sudah tidak terdapat missing value pada dataset titanic
Sebelum melakukan pemodelan, perlu memperhatikan proporsi dari
variabel target yaitu Survived.
#>
#> 0 1
#> 0.6161616 0.3838384
#>
#> 0 1
#> 549 342
Jika dilihat dari proporsi kedua kelas masih balance, karena masih 62:38 (Imbalance itu ketika lebih besar dari 70:30), sehingga kita tidak terlalu membutuhkan pre-processing tambahan untuk menyeimbangkan proporsi antar dua kelas target variabel.
Dalam pemodelan menggunakan regresi logistik, fungsi glm() digunakan untuk memodelkan regresi logistik. Variabel yang dipilih sebagai faktor-faktor yang mempengaruhi variabel target akan menjadi variabel independen dalam model ini, sementara variabel target menjadi variabel dependen atau respons.
Dalam pemodelan ini, kita menggunakan regresi logistik dengan fungsi glm() untuk mengidentifikasi hubungan antara variabel independen dan variabel dependen (target).
model_all <- glm(formula = Survived ~ ., family = "binomial",
data = titanic_train)
summary(model_all)#>
#> Call:
#> glm(formula = Survived ~ ., family = "binomial", data = titanic_train)
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 18.277320 1660.846066 0.011 0.99122
#> Pclass2 -0.888496 0.337798 -2.630 0.00853 **
#> Pclass3 -1.800991 0.335224 -5.373 0.0000000776 ***
#> Sexmale -2.584227 0.219669 -11.764 < 0.0000000000000002 ***
#> Age -0.038668 0.009525 -4.060 0.0000491121 ***
#> SibSp1 0.036827 0.245722 0.150 0.88087
#> SibSp2 0.017798 0.671983 0.026 0.97887
#> SibSp3 -2.049221 0.893662 -2.293 0.02184 *
#> SibSp4 -1.473292 0.798387 -1.845 0.06499 .
#> SibSp5 -15.093263 1695.101810 -0.009 0.99290
#> SibSp8 -15.947470 760.443520 -0.021 0.98327
#> Parch1 0.426951 0.324744 1.315 0.18860
#> Parch2 -0.036503 0.434585 -0.084 0.93306
#> Parch3 0.379117 1.288802 0.294 0.76863
#> Parch4 -15.845567 1196.999516 -0.013 0.98944
#> Parch5 -0.794839 1.236537 -0.643 0.52036
#> Fare 0.003543 0.002932 1.208 0.22697
#> EmbarkedC -14.637412 1660.846005 -0.009 0.99297
#> EmbarkedQ -14.654923 1660.846024 -0.009 0.99296
#> EmbarkedS -15.052603 1660.845997 -0.009 0.99277
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 953.47 on 711 degrees of freedom
#> Residual deviance: 628.01 on 692 degrees of freedom
#> AIC: 668.01
#>
#> Number of Fisher Scoring iterations: 15
Pada pemodelan yang pertama, masih banyak variabel prediktor yang tidak signifikan terhadap target variabel, oleh karena itu kita akan coba melakukan model fitting menggunakan metode stepwise.
#> Start: AIC=668.01
#> Survived ~ Pclass + Sex + Age + SibSp + Parch + Fare + Embarked
#>
#> Df Deviance AIC
#> - Parch 5 633.20 663.20
#> - Embarked 3 631.83 665.83
#> - Fare 1 629.75 667.75
#> <none> 628.01 668.01
#> - SibSp 6 642.10 670.10
#> - Age 1 645.74 683.74
#> - Pclass 2 658.36 694.36
#> - Sex 1 795.44 833.44
#>
#> Step: AIC=663.2
#> Survived ~ Pclass + Sex + Age + SibSp + Fare + Embarked
#>
#> Df Deviance AIC
#> - Embarked 3 637.97 661.97
#> - Fare 1 634.83 662.83
#> <none> 633.20 663.20
#> - SibSp 6 648.76 666.76
#> - Age 1 654.44 682.44
#> - Pclass 2 668.04 694.04
#> - Sex 1 808.60 836.60
#>
#> Step: AIC=661.97
#> Survived ~ Pclass + Sex + Age + SibSp + Fare
#>
#> Df Deviance AIC
#> <none> 637.97 661.97
#> - Fare 1 640.52 662.52
#> - SibSp 6 656.67 668.67
#> - Age 1 660.02 682.02
#> - Pclass 2 673.41 693.41
#> - Sex 1 823.74 845.74
#>
#> Call:
#> glm(formula = Survived ~ Pclass + Sex + Age + SibSp + Fare, family = "binomial",
#> data = titanic_train)
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) 3.494496 0.499009 7.003 0.00000000000251 ***
#> Pclass2 -0.991092 0.327083 -3.030 0.00244 **
#> Pclass3 -1.851036 0.318355 -5.814 0.00000000608586 ***
#> Sexmale -2.613427 0.211765 -12.341 < 0.0000000000000002 ***
#> Age -0.040987 0.009083 -4.513 0.00000640013649 ***
#> SibSp1 0.099751 0.231524 0.431 0.66658
#> SibSp2 0.102354 0.646342 0.158 0.87417
#> SibSp3 -2.127799 0.898051 -2.369 0.01782 *
#> SibSp4 -1.521881 0.785470 -1.938 0.05268 .
#> SibSp5 -15.308295 1694.892859 -0.009 0.99279
#> SibSp8 -16.143079 756.507897 -0.021 0.98298
#> Fare 0.003848 0.002603 1.478 0.13929
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for binomial family taken to be 1)
#>
#> Null deviance: 953.47 on 711 degrees of freedom
#> Residual deviance: 637.97 on 700 degrees of freedom
#> AIC: 661.97
#>
#> Number of Fisher Scoring iterations: 15
#> [1] 668.013
#> [1] 661.9738
AIC menggambarkan banyaknya informasi yang hilang dari suatu model.
Semakin kecil AIC, semakin sedikit informasi yang hilang. Dari hasi
diatas, diketahui bahwa nilai AIC model_bw lebih kecil
dibandingkan dengan model_all.
Dengan menggunakan model_bw hasil dari stepwise, kita akan coba prediksi menggunakan data test yang sudah kita miliki.
titanic_test$pred_titanic <- factor(ifelse(titanic_test$prob_bw > 0.5, 1,0))
titanic_test[1:10, c("pred_titanic", "Survived")]Dalam syntax diatas, ketika probabilitas data test lebih dari 0.5, artinya dia selamat atau tidak selamat.
Setelah dilakukan prediksi menggunakan model, masih ada saja prediksi yang salah. Pada klasifikasi, kita mengevaluasi model berdasarkan confusion matrix:
library(caret)
log_conf <- confusionMatrix(titanic_test$pred_titanic, titanic_test$Survived, positive = "1")
log_conf#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 101 18
#> 1 15 45
#>
#> Accuracy : 0.8156
#> 95% CI : (0.751, 0.8696)
#> No Information Rate : 0.648
#> P-Value [Acc > NIR] : 0.0000006191
#>
#> Kappa : 0.5914
#>
#> Mcnemar's Test P-Value : 0.7277
#>
#> Sensitivity : 0.7143
#> Specificity : 0.8707
#> Pos Pred Value : 0.7500
#> Neg Pred Value : 0.8487
#> Prevalence : 0.3520
#> Detection Rate : 0.2514
#> Detection Prevalence : 0.3352
#> Balanced Accuracy : 0.7925
#>
#> 'Positive' Class : 1
#>
Interpretasi Berdasarkan hasil confusion matrix di atas, dapat kita interpretasikan sebagai berikut:
Dari keseluruhan data yang diklasifikasikan oleh model, sebesar 81,56% memiliki prediksi yang benar (akurasi). Artinya, sekitar 81,56% dari total observasi yang ada di dataset dapat diprediksi dengan benar oleh model.
Ketika memprediksi kelas “Survived” (klasifikasi positif), model memiliki recall sebesar 71,43%. Hal ini berarti bahwa dari semua observasi yang sebenarnya termasuk dalam kelas “Survived”, model mampu mengklasifikasikan dengan benar sekitar 71,43% dari totalnya.
Ketika memprediksi kelas “Not Survived” (klasifikasi negatif), model memiliki spesifisitas sebesar 87,07%. Ini berarti bahwa dari semua observasi yang sebenarnya termasuk dalam kelas “Not Survived”, model mampu mengklasifikasikan dengan benar sekitar 87,07% dari totalnya.
Dataset titanic sdah diperbaiki sehingga tidak perlu dilakukan lagi manipulasi data, dan lainnya.
#> Rows: 891
#> Columns: 8
#> $ Survived <fct> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0…
#> $ Pclass <fct> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3, 2…
#> $ Sex <fct> male, female, female, female, male, male, male, male, female,…
#> $ Age <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 29.69912, 5…
#> $ SibSp <fct> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0…
#> $ Parch <fct> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0…
#> $ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21…
#> $ Embarked <fct> S, C, S, S, S, Q, S, S, S, C, S, S, S, S, S, S, Q, S, S, C, S…
Dalam menggunakan KNN, prediktor nya harus numeric sehingga kita hanya akan menggunakan prediktor yang numerik saja.
#>
#> 0 1
#> 0.6161616 0.3838384
Insight: Masih balance, karena masih 62:38. (Imbalance itu ketika lebih besar dari 70:30)
Kita perlu memeriksa range nilai variable prediktor. Jika ada nilai yang tinggi sendiri dibanding yang lain, maka variable tersebut akan sangat mempengaruhi hasil klasifikasi dan mengabaikan variable yang lain.
#> Survived Age Fare
#> 0:549 Min. : 0.42 Min. : 0.00
#> 1:342 1st Qu.:22.00 1st Qu.: 7.91
#> Median :29.70 Median : 14.45
#> Mean :29.70 Mean : 32.20
#> 3rd Qu.:35.00 3rd Qu.: 31.00
#> Max. :80.00 Max. :512.33
Range tiap variabel berbeda sehingga perlu dilakukan feature scaling di tahap data pre-processing.
RNGkind(sample.kind = "Rounding")
set.seed(123)
# index sampling
index <- sample(x = nrow(titanic_num),
size = nrow(titanic_num)*0.8) # mau ambil 0.8 (80%) utk data train, sisanya utk data test
# splitting
titanic_num_train <- titanic_num[index, ]
titanic_num_test <- titanic_num[-index, ] #>
#> 0 1
#> 0.6161616 0.3838384
Perlu diperhatikan bahwa 1 data harus menggunakan 1 tipe scaling yang sama. Untuk k-NN, dipisahkan antara prediktor dan label (target variabelnya).
library(dplyr)
# prediktor
titanic_num_train_x <- titanic_num_train %>% select_if(is.numeric)
titanic_num_test_x <- titanic_num_test %>% select_if(is.numeric)
# target
titanic_num_train_y <- titanic_num_train[,"Survived"]
titanic_num_test_y <- titanic_num_test[,"Survived"]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).
k optimum adalah akar dari jumlah data:
sqrt(nrow(data))
#> [1] 26.68333
Target 2 kelas: 1 -> Survived 0 -> Not Survived
Sehingga nilai K optimum: 27
library(class) # package untuk fungsi `knn()`
titanic_num_pred <- knn(train = titanic_num_train_xs,
test = titanic_num_test_xs,
cl = titanic_num_train_y,
k = 27)
head(titanic_num_pred, 10)#> [1] 1 0 0 1 0 1 0 0 0 0
#> Levels: 0 1
# confusion matrix
library(caret)
log_KNN <- confusionMatrix(data = titanic_num_pred,
reference = titanic_num_test_y,
positive = "1")
log_KNN#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 95 34
#> 1 21 29
#>
#> Accuracy : 0.6927
#> 95% CI : (0.6196, 0.7594)
#> No Information Rate : 0.648
#> P-Value [Acc > NIR] : 0.1196
#>
#> Kappa : 0.2931
#>
#> Mcnemar's Test P-Value : 0.1056
#>
#> Sensitivity : 0.4603
#> Specificity : 0.8190
#> Pos Pred Value : 0.5800
#> Neg Pred Value : 0.7364
#> Prevalence : 0.3520
#> Detection Rate : 0.1620
#> Detection Prevalence : 0.2793
#> Balanced Accuracy : 0.6396
#>
#> 'Positive' Class : 1
#>
Interpretasi
Berdasarkan hasil confusion matrix di atas, dapat kita interpretasikan sebagai berikut:
Dari keseluruhan data yang diklasifikasikan oleh model, sebesar 69,27% memiliki prediksi yang benar (akurasi). Artinya, sekitar 69,27% dari total observasi yang ada di dataset dapat diprediksi dengan benar oleh model.
Ketika memprediksi kelas “Survived” (klasifikasi positif), model memiliki recall sebesar 46,03%. Hal ini berarti bahwa dari semua observasi yang sebenarnya termasuk dalam kelas “Survived”, model mampu mengklasifikasikan dengan benar sekitar 46,03% dari totalnya.
Ketika memprediksi kelas “Not Survived” (klasifikasi negatif), model memiliki spesifisitas sebesar 81,90%. Ini berarti bahwa dari semua observasi yang sebenarnya termasuk dalam kelas “Not Survived”, model mampu mengklasifikasikan dengan benar sekitar 81,90% dari totalnya.
eval_logit <- data_frame(Accuracy = log_conf$overall[1],
Recall = log_conf$byClass[1],
Specificity = log_conf$byClass[2],
Precision = log_conf$byClass[3])
eval_knn <- data_frame(Accuracy = log_KNN$overall[1],
Recall = log_KNN$byClass[1],
Specificity = log_KNN$byClass[2],
Precision = log_KNN$byClass[3])
eval_logit🏥 Resiko
Kesimpulan: Metrics yang perlu diprioritaskan atau dievaluasi adalah Recall.
Interpretasi
Jika dilihat dari kedua metode tersebut, yaitu dengan menggunakan Regresi Logistik dan K-NN, kemampuan model dalam memprediksi penumpang yang sebenarnya selamat sebagai tidak selamat lebih baik dengan menggunakan metode Regresi Logistik karena memiliki nilai recall= 71,4% lebih besar dari pada menggunakan metode K-NN.
Kesimpulan
Jika kita mengibaratkan diri kita sebagai seorang ahli keselamatan kapal Titanic, di mana tindakan yang akan diambil terhadap penumpang yang selamat dan yang tidak selamat akan berbeda signifikan. Kesalahan dalam memprediksi penumpang yang sebenarnya selamat sebagai tidak selamat (False Negative) memiliki risiko yang lebih besar, karena dapat mengakibatkan penundaan atau kelalaian dalam menyelamatkan nyawa penumpang yang sebenarnya harus diselamatkan. Oleh karena itu, dalam hal ini, fokus pada metrik recall (kemampuan untuk mengidentifikasi penumpang yang sebenarnya selamat) menjadi sangat penting untuk meminimalkan risiko tersebut.
Saran
Dalam meningkatkan prediksi penumpang yang selamat dalam kasus Titanic, penting untuk memperhatikan metrik recall. Mengidentifikasi dengan akurat penumpang yang sebenarnya selamat akan membantu dalam mengambil tindakan yang tepat dan memastikan keselamatan mereka. Selain itu, perlu dilakukan evaluasi lebih lanjut terhadap model dan fitur yang digunakan untuk memperbaiki kinerja prediksi.