Industri perbankan menghadapi risiko kerugian akibat nasabah yang gagal melakukan pembayaran kewajiban kartu kredit. Kondisi ini dapat meningkatkan risiko kredit dan mempengaruhi stabilitas keuangan perusahaan. Oleh karena itu, diperlukan suatu model prediksi yang mampu mengidentifikasi nasabah yang berpotensi mengalami gagal bayar (default) pada periode berikutnya.
Penelitian ini bertujuan untuk membangun model klasifikasi menggunakan metode Decision Tree untuk memprediksi status default pembayaran kartu kredit pada bulan berikutnya berdasarkan karakteristik demografi, riwayat pembayaran, tagihan, dan jumlah pembayaran nasabah.
Pertanyaan yang ingin dijawab dalam analisis ini adalah:
Faktor-faktor apa yang berhubungan dengan risiko default pembayaran? Seberapa baik model Decision Tree dalam mengklasifikasikan nasabah yang berpotensi mengalami default? Bagaimana model dapat digunakan untuk membantu pengambilan keputusan kredit?
Dataset yang digunakan adalah Default of Credit Card Clients Dataset yang diperoleh dari UCI Machine Learning Repository. Dataset ini berisi informasi mengenai pelanggan kartu kredit di Taiwan yang digunakan untuk memprediksi kemungkinan terjadinya gagal bayar pada bulan berikutnya.
Dataset terdiri atas:
30.000 observasi 23 variabel prediktor 1 variabel target (DEFAULT)
Variabel target terdiri atas:
0 = Tidak Default 1 = Default
library(readxl)
library(dplyr)
library(ggplot2)
library(rpart)
library(rpart.plot)
library(caret)
library(pROC)
data_raw <- read_excel("default of credit card clients.xls", skip = 1)
# Melihat struktur awal
str(data_raw)
## tibble [30,000 × 25] (S3: tbl_df/tbl/data.frame)
## $ ID : num [1:30000] 1 2 3 4 5 6 7 8 9 10 ...
## $ LIMIT_BAL : num [1:30000] 20000 120000 90000 50000 50000 50000 500000 100000 140000 20000 ...
## $ SEX : num [1:30000] 2 2 2 2 1 1 1 2 2 1 ...
## $ EDUCATION : num [1:30000] 2 2 2 2 2 1 1 2 3 3 ...
## $ MARRIAGE : num [1:30000] 1 2 2 1 1 2 2 2 1 2 ...
## $ AGE : num [1:30000] 24 26 34 37 57 37 29 23 28 35 ...
## $ PAY_0 : num [1:30000] 2 -1 0 0 -1 0 0 0 0 -2 ...
## $ PAY_2 : num [1:30000] 2 2 0 0 0 0 0 -1 0 -2 ...
## $ PAY_3 : num [1:30000] -1 0 0 0 -1 0 0 -1 2 -2 ...
## $ PAY_4 : num [1:30000] -1 0 0 0 0 0 0 0 0 -2 ...
## $ PAY_5 : num [1:30000] -2 0 0 0 0 0 0 0 0 -1 ...
## $ PAY_6 : num [1:30000] -2 2 0 0 0 0 0 -1 0 -1 ...
## $ BILL_AMT1 : num [1:30000] 3913 2682 29239 46990 8617 ...
## $ BILL_AMT2 : num [1:30000] 3102 1725 14027 48233 5670 ...
## $ BILL_AMT3 : num [1:30000] 689 2682 13559 49291 35835 ...
## $ BILL_AMT4 : num [1:30000] 0 3272 14331 28314 20940 ...
## $ BILL_AMT5 : num [1:30000] 0 3455 14948 28959 19146 ...
## $ BILL_AMT6 : num [1:30000] 0 3261 15549 29547 19131 ...
## $ PAY_AMT1 : num [1:30000] 0 0 1518 2000 2000 ...
## $ PAY_AMT2 : num [1:30000] 689 1000 1500 2019 36681 ...
## $ PAY_AMT3 : num [1:30000] 0 1000 1000 1200 10000 657 38000 0 432 0 ...
## $ PAY_AMT4 : num [1:30000] 0 1000 1000 1100 9000 ...
## $ PAY_AMT5 : num [1:30000] 0 0 1000 1069 689 ...
## $ PAY_AMT6 : num [1:30000] 0 2000 5000 1000 679 ...
## $ default payment next month: num [1:30000] 1 1 0 0 0 0 0 0 0 0 ...
head(data_raw)
## # A tibble: 6 × 25
## ID LIMIT_BAL SEX EDUCATION MARRIAGE AGE PAY_0 PAY_2 PAY_3 PAY_4 PAY_5
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 20000 2 2 1 24 2 2 -1 -1 -2
## 2 2 120000 2 2 2 26 -1 2 0 0 0
## 3 3 90000 2 2 2 34 0 0 0 0 0
## 4 4 50000 2 2 1 37 0 0 0 0 0
## 5 5 50000 1 2 1 57 -1 0 -1 0 0
## 6 6 50000 1 1 2 37 0 0 0 0 0
## # ℹ 14 more variables: PAY_6 <dbl>, BILL_AMT1 <dbl>, BILL_AMT2 <dbl>,
## # BILL_AMT3 <dbl>, BILL_AMT4 <dbl>, BILL_AMT5 <dbl>, BILL_AMT6 <dbl>,
## # PAY_AMT1 <dbl>, PAY_AMT2 <dbl>, PAY_AMT3 <dbl>, PAY_AMT4 <dbl>,
## # PAY_AMT5 <dbl>, PAY_AMT6 <dbl>, `default payment next month` <dbl>
summary(data_raw)
## ID LIMIT_BAL SEX EDUCATION
## Min. : 1 Min. : 10000 Min. :1.000 Min. :0.000
## 1st Qu.: 7501 1st Qu.: 50000 1st Qu.:1.000 1st Qu.:1.000
## Median :15000 Median : 140000 Median :2.000 Median :2.000
## Mean :15000 Mean : 167484 Mean :1.604 Mean :1.853
## 3rd Qu.:22500 3rd Qu.: 240000 3rd Qu.:2.000 3rd Qu.:2.000
## Max. :30000 Max. :1000000 Max. :2.000 Max. :6.000
## MARRIAGE AGE PAY_0 PAY_2
## Min. :0.000 Min. :21.00 Min. :-2.0000 Min. :-2.0000
## 1st Qu.:1.000 1st Qu.:28.00 1st Qu.:-1.0000 1st Qu.:-1.0000
## Median :2.000 Median :34.00 Median : 0.0000 Median : 0.0000
## Mean :1.552 Mean :35.49 Mean :-0.0167 Mean :-0.1338
## 3rd Qu.:2.000 3rd Qu.:41.00 3rd Qu.: 0.0000 3rd Qu.: 0.0000
## Max. :3.000 Max. :79.00 Max. : 8.0000 Max. : 8.0000
## PAY_3 PAY_4 PAY_5 PAY_6
## Min. :-2.0000 Min. :-2.0000 Min. :-2.0000 Min. :-2.0000
## 1st Qu.:-1.0000 1st Qu.:-1.0000 1st Qu.:-1.0000 1st Qu.:-1.0000
## Median : 0.0000 Median : 0.0000 Median : 0.0000 Median : 0.0000
## Mean :-0.1662 Mean :-0.2207 Mean :-0.2662 Mean :-0.2911
## 3rd Qu.: 0.0000 3rd Qu.: 0.0000 3rd Qu.: 0.0000 3rd Qu.: 0.0000
## Max. : 8.0000 Max. : 8.0000 Max. : 8.0000 Max. : 8.0000
## BILL_AMT1 BILL_AMT2 BILL_AMT3 BILL_AMT4
## Min. :-165580 Min. :-69777 Min. :-157264 Min. :-170000
## 1st Qu.: 3559 1st Qu.: 2985 1st Qu.: 2666 1st Qu.: 2327
## Median : 22382 Median : 21200 Median : 20089 Median : 19052
## Mean : 51223 Mean : 49179 Mean : 47013 Mean : 43263
## 3rd Qu.: 67091 3rd Qu.: 64006 3rd Qu.: 60165 3rd Qu.: 54506
## Max. : 964511 Max. :983931 Max. :1664089 Max. : 891586
## BILL_AMT5 BILL_AMT6 PAY_AMT1 PAY_AMT2
## Min. :-81334 Min. :-339603 Min. : 0 Min. : 0
## 1st Qu.: 1763 1st Qu.: 1256 1st Qu.: 1000 1st Qu.: 833
## Median : 18105 Median : 17071 Median : 2100 Median : 2009
## Mean : 40311 Mean : 38872 Mean : 5664 Mean : 5921
## 3rd Qu.: 50191 3rd Qu.: 49198 3rd Qu.: 5006 3rd Qu.: 5000
## Max. :927171 Max. : 961664 Max. :873552 Max. :1684259
## PAY_AMT3 PAY_AMT4 PAY_AMT5 PAY_AMT6
## Min. : 0 Min. : 0 Min. : 0.0 Min. : 0.0
## 1st Qu.: 390 1st Qu.: 296 1st Qu.: 252.5 1st Qu.: 117.8
## Median : 1800 Median : 1500 Median : 1500.0 Median : 1500.0
## Mean : 5226 Mean : 4826 Mean : 4799.4 Mean : 5215.5
## 3rd Qu.: 4505 3rd Qu.: 4013 3rd Qu.: 4031.5 3rd Qu.: 4000.0
## Max. :896040 Max. :621000 Max. :426529.0 Max. :528666.0
## default payment next month
## Min. :0.0000
## 1st Qu.:0.0000
## Median :0.0000
## Mean :0.2212
## 3rd Qu.:0.0000
## Max. :1.0000
Variabel target adalah:
default payment next month1 = default0 = tidak default# Mengubah nama kolom agar lebih mudah dipanggil
names(data_raw) <- c(
"ID", "LIMIT_BAL", "SEX", "EDUCATION", "MARRIAGE", "AGE",
"PAY_0", "PAY_2", "PAY_3", "PAY_4", "PAY_5", "PAY_6",
"BILL_AMT1", "BILL_AMT2", "BILL_AMT3", "BILL_AMT4", "BILL_AMT5", "BILL_AMT6",
"PAY_AMT1", "PAY_AMT2", "PAY_AMT3", "PAY_AMT4", "PAY_AMT5", "PAY_AMT6",
"DEFAULT"
)
# Ubah target menjadi factor
data_raw$DEFAULT <- as.factor(data_raw$DEFAULT)
# Cek proporsi kelas
table(data_raw$DEFAULT)
##
## 0 1
## 23364 6636
prop.table(table(data_raw$DEFAULT))
##
## 0 1
## 0.7788 0.2212
Hasil distribusi kelas menunjukkan bahwa 77,88% nasabah tidak mengalami default pembayaran, sedangkan 22,12% nasabah mengalami default. Dengan demikian, kelas tidak default merupakan kelas mayoritas dalam dataset.
# Distribusi target
ggplot(data_raw, aes(x = DEFAULT)) +
geom_bar() +
labs(
title = "Distribusi Default Pembayaran",
x = "Default",
y = "Jumlah"
)
Perbedaan jumlah antara kelas default dan tidak default menunjukkan bahwa data didominasi oleh kelas tidak default. Informasi ini penting untuk diperhatikan dalam proses pembangunan dan evaluasi model klasifikasi.
Pada tahap ini dilakukan beberapa proses preprocessing untuk meningkatkan kualitas data sebelum digunakan dalam pemodelan.
Langkah-langkah yang dilakukan meliputi:
Menghapus variabel ID karena hanya berfungsi sebagai identitas unik dan tidak memiliki informasi prediktif. Mengubah variabel kategorik menjadi tipe factor agar dapat dikenali oleh algoritma klasifikasi. Membagi data menjadi data latih (training data) dan data uji (testing data).
Pembagian data dilakukan dengan proporsi 80:20 untuk memastikan model dapat dievaluasi pada data yang belum pernah dilihat sebelumnya.
data <- data_raw %>%
select(-ID) %>%
mutate(
SEX = as.factor(SEX),
EDUCATION = as.factor(EDUCATION),
MARRIAGE = as.factor(MARRIAGE),
DEFAULT = as.factor(DEFAULT)
)
# Split data
set.seed(123)
index <- createDataPartition(data$DEFAULT, p = 0.8, list = FALSE)
train_data <- data[index, ]
test_data <- data[-index, ]
dim(train_data)
## [1] 24001 24
dim(test_data)
## [1] 5999 24
Pada penelitian ini digunakan algoritma Decision Tree karena memiliki beberapa keunggulan, yaitu:
mudah dipahami dan diinterpretasikan, mampu menangani hubungan non-linear, dapat mengidentifikasi variabel yang paling berpengaruh terhadap target.
Model dibangun menggunakan paket rpart dengan metode klasifikasi.
dt_model <- rpart(
DEFAULT ~ .,
data = train_data,
method = "class",
control = rpart.control(cp = 0.01)
)
dt_model
## n= 24001
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 24001 5309 0 (0.7788009 0.2211991)
## 2) PAY_0< 1.5 21499 3572 0 (0.8338527 0.1661473) *
## 3) PAY_0>=1.5 2502 765 1 (0.3057554 0.6942446) *
Hasil pohon keputusan menunjukkan bahwa data sebanyak 24.001 observasi dibagi berdasarkan variabel PAY_0. Pada node akar, kelas yang paling dominan adalah tidak default (0) dengan proporsi 77,88%. Pada cabang PAY_0 < 1,5, sebagian besar nasabah diklasifikasikan sebagai tidak default (0) dengan proporsi 83,39%. Sementara itu, pada cabang PAY_0 ≥ 1,5, nasabah cenderung diklasifikasikan sebagai default (1) dengan proporsi 69,42%. Hal ini menunjukkan bahwa variabel PAY_0 merupakan penentu utama dalam model.
rpart.plot(
dt_model,
type = 2,
extra = 104,
fallen.leaves = TRUE,
main = "Decision Tree: Default Pembayaran"
)
Gambar pohon keputusan menunjukkan bahwa variabel yang menjadi pemisah utama adalah PAY_0. Jika PAY_0 < 2, maka nasabah cenderung diklasifikasikan sebagai tidak default (0). Sebaliknya, jika PAY_0 ≥ 2, maka nasabah cenderung diklasifikasikan sebagai default (1). Hal ini menunjukkan bahwa status pembayaran bulan sebelumnya menjadi faktor utama dalam memprediksi default pembayaran.
pred_class <- predict(dt_model, test_data, type = "class")
pred_prob <- predict(dt_model, test_data, type = "prob")[, 2]
conf_mat <- confusionMatrix(pred_class, test_data$DEFAULT, positive = "1")
conf_mat
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 4484 887
## 1 188 440
##
## Accuracy : 0.8208
## 95% CI : (0.8109, 0.8304)
## No Information Rate : 0.7788
## P-Value [Acc > NIR] : 5.474e-16
##
## Kappa : 0.359
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.33157
## Specificity : 0.95976
## Pos Pred Value : 0.70064
## Neg Pred Value : 0.83485
## Prevalence : 0.22120
## Detection Rate : 0.07335
## Detection Prevalence : 0.10468
## Balanced Accuracy : 0.64567
##
## 'Positive' Class : 1
##
Berdasarkan confusion matrix, model menghasilkan akurasi sebesar 82,08%. Nilai ini menunjukkan bahwa model cukup baik dalam mengklasifikasikan data secara keseluruhan. Namun, sensitivity sebesar 33,16% menunjukkan bahwa model masih kurang baik dalam mendeteksi nasabah yang mengalami default. Sementara itu, specificity sebesar 95,98% menunjukkan bahwa model sangat baik dalam mengidentifikasi nasabah yang tidak default. Hal ini berarti model cenderung lebih tepat dalam memprediksi kelas tidak default dibandingkan kelas default.
roc_obj <- roc(response = test_data$DEFAULT, predictor = pred_prob, levels = c("0", "1"), direction = "<")
auc(roc_obj)
## Area under the curve: 0.6457
plot(roc_obj, main = "ROC Curve - Decision Tree")
Kurva ROC berada di atas garis diagonal, sehingga model memiliki kemampuan klasifikasi. Namun, karena kurva belum jauh dari diagonal, performa model masih tergolong sedang.
accuracy <- conf_mat$overall["Accuracy"]
sensitivity <- conf_mat$byClass["Sensitivity"]
specificity <- conf_mat$byClass["Specificity"]
precision <- conf_mat$byClass["Precision"]
recall <- conf_mat$byClass["Recall"]
evaluation_result <- data.frame(
Metrik = c("Accuracy", "Sensitivity", "Specificity", "Precision", "Recall", "AUC"),
Nilai = c(
as.numeric(accuracy),
as.numeric(sensitivity),
as.numeric(specificity),
as.numeric(precision),
as.numeric(recall),
as.numeric(auc(roc_obj))
)
)
evaluation_result
## Metrik Nilai
## 1 Accuracy 0.8208035
## 2 Sensitivity 0.3315750
## 3 Specificity 0.9597603
## 4 Precision 0.7006369
## 5 Recall 0.3315750
## 6 AUC 0.6456676
Model menghasilkan accuracy 82,08% dengan precision 70,06%, specificity 95,98%, dan AUC 0,646. Namun, sensitivity/recall yang hanya 33,16% menunjukkan bahwa model masih kurang baik dalam mendeteksi nasabah default.
Contoh prediksi untuk satu nasabah baru.
new_client <- data.frame(
LIMIT_BAL = 20000,
SEX = factor(2, levels = levels(data$SEX)),
EDUCATION = factor(2, levels = levels(data$EDUCATION)),
MARRIAGE = factor(1, levels = levels(data$MARRIAGE)),
AGE = 28,
PAY_0 = 0,
PAY_2 = 0,
PAY_3 = 0,
PAY_4 = 0,
PAY_5 = 0,
PAY_6 = 0,
BILL_AMT1 = 15000,
BILL_AMT2 = 14500,
BILL_AMT3 = 14000,
BILL_AMT4 = 13500,
BILL_AMT5 = 13000,
BILL_AMT6 = 12500,
PAY_AMT1 = 2000,
PAY_AMT2 = 2000,
PAY_AMT3 = 1500,
PAY_AMT4 = 1500,
PAY_AMT5 = 1000,
PAY_AMT6 = 1000
)
new_pred_class <- predict(dt_model, new_client, type = "class")
new_pred_prob <- predict(dt_model, new_client, type = "prob")
new_pred_class
## 1
## 0
## Levels: 0 1
new_pred_prob
## 0 1
## 1 0.8338527 0.1661473
Berdasarkan analisis menggunakan tahapan CRISP-DM, model Decision Tree berhasil dibangun untuk memprediksi default pembayaran kartu kredit. Hasil evaluasi menunjukkan bahwa model memiliki accuracy sebesar 82,08% dengan AUC sebesar 0,646, sehingga kemampuan klasifikasinya tergolong sedang. Model lebih baik dalam mengidentifikasi nasabah yang tidak default dibandingkan nasabah yang default, terlihat dari specificity yang tinggi namun sensitivity yang masih rendah. Hasil prediksi pada data baru juga menunjukkan bahwa nasabah tersebut termasuk kategori tidak default dengan probabilitas 83,39%. Dengan demikian, model dapat digunakan sebagai alat bantu awal dalam mengidentifikasi risiko gagal bayar nasabah.