1 Pendahuluan

1.1 Tujuan

Pemanfaatan data analitik untuk pengembangan kasus kredit dan perbankan telah meningkatkan serta merevolusi pengambilan keputusan oleh departemen kredit dalam meningkatkan efisiensi pengambilan kebijakan.

Pemanfaatan bidang analitik telah digunakan selama bertahun-tahun. Namun, karena sistem yang dimiliki oleh perbankan itu sendiri bersifat dinamis, pengumpulan, pemrosesan dan analisa terhadap data yang digunakan sebagian besar bersifat manual. Pendekatan yang bersifat manual ini memberikan kesulitan tersendiri untuk tim. Untuk mengatasi permasalahan ini, dibutuhkan suatu metode yang bersifat cepat dan dinamis dan mampu memanfaatkan informasi yang dimiliki secara optimal. Metode Machine Learning menjadi salah satu jawaban untuk mengatasi problem tersebut.

Application vs Behavior Scorecard

Terdapat 2 jenis scorecard yang biasa dikembangkan oleh suatu institusi perbankan maupun kredit:

Application Scoring dalam konteks kredit merujuk pada metode evaluasi penentuan keputusan pengajuan aplikasi terhadap individu atau peminjam. Skor aplikasi (Application score) digunakan oleh lembaga keuangan sebagai acuan dasar pemberian kredit berdasarkan kriteria yang ada.Skor ini dapat digunakan sebagai awal penetapan pemberian pinjamann.

Faktor-faktor yang dapat dipertimbangkan dalam application scoring melibatkan pengamatan atas kebiasaan sehari-hari. Beberapa contoh faktor yang dapat diperhitungkan dalam behavior scoring meliputi:

  • Besaran Gaji,
  • Jenis Pendapatan: Upah yang berdasarkan bulanan atau tidak
  • Riwayat Pendidikan: Jenjang pendidikan atau pendidikan terakhir yang dimiliki
  • Umur: Usia peminjam
  • Status Pernikahan: Apakah sudah ada tanggungan atau belum
  • Jenis Tempat Tinggal: Apakah sudah memmiliki tempat tinggal sendiri atau belum beserta jenisnya
  • Jenis Pekerjaan: Jenis pekerjaan apakah tetap atau tidak tetap.

1.2 Library loading

Berikut beberapa library yang digunakan selama pemodelan klasifikasi dan pembentukan scorecard

# Memuat Semua Package yang Diperlukan:
library(tidyverse)
library(UBL) #For SMOTE 
library(dplyr) # Wrangling data
library(inspectdf) # add on wrangling data
library(caret) # classification & regression training
library(scorecard) # credit risk scorecard
library(rsample) # resampling data, Train - Test
library(gtools) # programming tools
library(readxl)
library(GGally) # plot heatmap correlation 
library(knitr) # reporting in R
library(scales) # manipulasi skala data untuk visualisasi
library(car) # check multicollinearity

Istilah scorecard atau ‘predictive scorecard’ mengacu pada sebuah model kredit kuantitatif yang digunakan untuk menilai kelayakan dan kemampuan bayar calon pemilik kartu kredit berdasarkan credit score yang dihasilkan.

Denggan menggunakan data yang berasal dari: https://www.kaggle.com/datasets/rohit265/credit-card-eligibility-data-determining-factors dengan judul ‘Description of the Credit Card Eligibility Data: Determining Factors

#read data
dataset <- read.csv("data_input/dataset.csv")
dataset

Dataset memiliki 20 kolom dan 9,709 baris data dengan penjelasan sebagai berikut:

  • id = id calon pemilik kartu
  • Gender = jenis kelamin
    • 0 = laki-laki
    • 1 = perempuan
  • Own_car = Memiliki kendaraan atau tidak. 0 = Tidak, 1 = Iya
  • Own_property = Memiliki unit properti sendiri atau tidak. 0 = Tidak, 1 = Iya
  • Work_phone = Kepemilikan telepon kantor. 0 = Tidak, 1 = Iya
  • Email = Kepemilikan Email. 0 = Tidak, 1 = Iya
  • Unemployed = Status pekerjaan. 0 = Tidak memiliki kerja, 1 = Memiliki kerja
  • Num_children = Jumlah anak
  • Num_family = Jumlah anggota keluarga
  • Account_length = Jumlah kepemilikan akun bank atau institusi keuangan lain
  • Total_income = Jumlah pendapatan
  • Age = Umur
  • Years_employed = Lama bekerja
  • Income_type = Jenis pendapatan
  • Education_type = Jenjang pendidikan
  • Family_status = Status pernikahan
  • Housing_type = Jenis tempat tinggal
  • Occupation_type = Jenis pekerjaan
  • Target = Variabel target untuk klasifikasi, apakah individu masuk kriteria untuk memiliki kartu kredit atau tidak. 0 = Tidak, 1 = Iya

1.3 Data Cleansing & Exploratory Data Analytic

summary(dataset)
##        ID              Gender          Own_car        Own_property   
##  Min.   :5008804   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
##  1st Qu.:5036955   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000  
##  Median :5069449   Median :0.0000   Median :0.0000   Median :1.0000  
##  Mean   :5076105   Mean   :0.3487   Mean   :0.3677   Mean   :0.6715  
##  3rd Qu.:5112986   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:1.0000  
##  Max.   :5150479   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000  
##    Work_phone         Phone            Email           Unemployed    
##  Min.   :0.0000   Min.   :0.0000   Min.   :0.00000   Min.   :0.0000  
##  1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.00000   1st Qu.:0.0000  
##  Median :0.0000   Median :0.0000   Median :0.00000   Median :0.0000  
##  Mean   :0.2174   Mean   :0.2877   Mean   :0.08755   Mean   :0.1747  
##  3rd Qu.:0.0000   3rd Qu.:1.0000   3rd Qu.:0.00000   3rd Qu.:0.0000  
##  Max.   :1.0000   Max.   :1.0000   Max.   :1.00000   Max.   :1.0000  
##   Num_children       Num_family     Account_length   Total_income    
##  Min.   : 0.0000   Min.   : 1.000   Min.   : 0.00   Min.   :  27000  
##  1st Qu.: 0.0000   1st Qu.: 2.000   1st Qu.:13.00   1st Qu.: 112500  
##  Median : 0.0000   Median : 2.000   Median :26.00   Median : 157500  
##  Mean   : 0.4228   Mean   : 2.183   Mean   :27.27   Mean   : 181228  
##  3rd Qu.: 1.0000   3rd Qu.: 3.000   3rd Qu.:41.00   3rd Qu.: 225000  
##  Max.   :19.0000   Max.   :20.000   Max.   :60.00   Max.   :1575000  
##       Age        Years_employed    Income_type        Education_type    
##  Min.   :20.50   Min.   : 0.0000   Length:9709        Length:9709       
##  1st Qu.:34.06   1st Qu.: 0.9282   Class :character   Class :character  
##  Median :42.74   Median : 3.7619   Mode  :character   Mode  :character  
##  Mean   :43.78   Mean   : 5.6647                                        
##  3rd Qu.:53.57   3rd Qu.: 8.2000                                        
##  Max.   :68.86   Max.   :43.0207                                        
##  Family_status      Housing_type       Occupation_type        Target      
##  Length:9709        Length:9709        Length:9709        Min.   :0.0000  
##  Class :character   Class :character   Class :character   1st Qu.:0.0000  
##  Mode  :character   Mode  :character   Mode  :character   Median :0.0000  
##                                                           Mean   :0.1321  
##                                                           3rd Qu.:0.0000  
##                                                           Max.   :1.0000
glimpse(dataset)
## Rows: 9,709
## Columns: 20
## $ ID              <int> 5008804, 5008806, 5008808, 5008812, 5008815, 5008819, …
## $ Gender          <int> 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, …
## $ Own_car         <int> 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, …
## $ Own_property    <int> 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ Work_phone      <int> 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ Phone           <int> 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, …
## $ Email           <int> 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, …
## $ Unemployed      <int> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, …
## $ Num_children    <int> 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 1, 0, 2, 1, 0, 2, 0, 0, …
## $ Num_family      <int> 2, 2, 1, 1, 2, 2, 2, 2, 2, 5, 3, 2, 4, 3, 2, 4, 1, 1, …
## $ Account_length  <int> 15, 29, 4, 20, 5, 17, 25, 31, 44, 24, 39, 43, 39, 24, …
## $ Total_income    <dbl> 427500, 112500, 270000, 283500, 270000, 135000, 130500…
## $ Age             <dbl> 32.86857, 58.79382, 52.32140, 61.50434, 46.19397, 48.6…
## $ Years_employed  <dbl> 12.435574, 3.104787, 8.353354, 0.000000, 2.105450, 3.2…
## $ Income_type     <chr> "Working", "Working", "Commercial associate", "Pension…
## $ Education_type  <chr> "Higher education", "Secondary / secondary special", "…
## $ Family_status   <chr> "Civil marriage", "Married", "Single / not married", "…
## $ Housing_type    <chr> "Rented apartment", "House / apartment", "House / apar…
## $ Occupation_type <chr> "Other", "Security staff", "Sales staff", "Other", "Ac…
## $ Target          <int> 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, …

Dari pengecekan dengan fungsi Summary dan Glimpse dilakukan langkah berikut:

  1. Tidak menggunakan kolom ID

  2. Mengubah kolom Income_type, Education_type, Family_status, Housing_type, Occupation_type menjadi angka

  3. Membulatkan umur dan tahun kerja ke atas

  4. Mengubah kesemua jenis kolom kecuali Account_length, Total_income, Age, Years_employed menjadi kategorikal

dataset_clean <- dataset %>%
  #Langkah 1
  select(-ID) %>%
  #Langkah 2 dan 3
  mutate(across(c(Income_type, Education_type, Family_status, Housing_type, Occupation_type), ~dense_rank(.)),
         Age = round(Age),
         Years_employed = round(Years_employed)) %>% 
  #Langkah 4
  mutate_at(vars(Gender, Own_car, Own_property, Work_phone, Phone, Email, Unemployed, Num_children, Num_family, Income_type, Education_type, Family_status, Housing_type, Occupation_type, Target),as.factor) 
dataset_clean

Melakukan pemeriksaan distribusi data

Library scorecard menyediakan suatu fungsi describe() dimana menyediakan statistik deskriptif untuk analisis data eksploratif. Periksa proporsi kelas Target

describe(dataset_clean)

1.4 Data Pre-Processing

1.4.1 Mengecek Distribusi Variabel Target

Tujuannya untuk:

  • Jika distribusi kelas target tidak seimbang, di mana satu kelas memiliki frekuensi yang jauh lebih tinggi atau lebih rendah dibandingkan kelas lainnya, maka model dapat menjadi bias.

  • Model yang dilatih pada dataset tidak seimbang dapat memiliki kecenderungan untuk memprediksi dengan benar kelas mayoritas dan mengabaikan kelas minoritas.

❓ Mari kita cek data application yang kita miliki. Berapa banyak applicant dengan status Yes dan No?

  • No = 0
  • Yes = 1
prop.table(table(dataset_clean$Target))
## 
##         0         1 
## 0.8678546 0.1321454

Data tidak seimbang 86:13 dimana yang diharapkan setidaknya berada di perbandingan 60:40 sehingga harus diseimbangkan dengan menggunakan metode SMOTE.

Dikutip dari https://rpubs.com/VicNP/UBL-SmoteClassif, berikut penjelasan penggunaan fungsi SMOTE:

  • Parameter form: Parameter ini digunakan untuk memberitahu fungsi SmoteClassif kolom apa yang menjadi target variabel dan kolom apa saja yang menjadi kolom prediktor. Contoh penulisan untuk mengisi parameter form, form = nama kolom target variabel ~ nama kolom prediktor(jika semua kolom lain dipilih bisa diwakili dengan simbol titik/dot).
  • Parameter dat: Parameter ini digunakan untuk memberitahu kolom apa saja yang ingin dibuat data sintesis dari dataframe yang digunakan. Contoh penulisan untuk mengisi parameter dat dat = nama_object_dataframe[, range_kolom].
  • Parameter C.perc: Parameter ini adalah parameter yang akan berisikan list untuk mengatur persentase under- atau/dan over-sampling untuk diterapkan ke setiap kelas. Persentase over-sampling adalah angka di atas 1 sedangkan persentase under-sampling harus angka di bawah 1. Jika angka 1 diberikan untuk kelas tertentu maka kelas itu tetap tidak berubah. Atau mungkin bisa langsung mengisikan “balanced” untuk membuat proporsinya menjadi seimbang.
  • Parameter dist: Parameter ini memungkinkan pengguna untuk menentukan metrik jarak yang akan digunakan dalam k nearest neighbors. Meskipun defaultnya adalah jarak Euclidean akan tetapi ada beberapa opsi lagi yang dapat digunakan untuk menghitung selain data numeric yaitu untuk data kategorikal Berikut adalah opsi yang bisa digunakan:
  • Untuk data dengan fitur numerik saja: “Euclidean”
  • Untuk data dengan fitur kategorikal saja: “Overlap”.
  • Untuk menangani fitur kategorikal dan numerik: “HVDM”
#Gunaka library(UBL)
dataset_smote <- SmoteClassif(form = Target ~ ., 
                                dat = dataset_clean, 
                                C.perc = "balance",  
                                dist = "HVDM")

Kita periksa kembali balance datanya

prop.table(table(dataset_smote$Target))
## 
##         0         1 
## 0.5000515 0.4999485

Data sudah menjadi balance

1.4.2 Cross Validation

Cross Validation adalah metode yang digunakan untuk mengetahui seberapa baik performa model kita memprediksi terhadap data baru.

Lalu, bagaimana cara mengetahui apakah model yang dibuat telah baik dalam memprediksi data baru? Di sinilah dilakukan Train-test splitting. Data dibagi menjadi 2 kelompok, yaitu data train dan test.

  • Data train: Data yang model gunakan untuk training.
  • Data test/validation: Data untuk evaluasi model (Untuk melihat seberapa baik model memprediksi terhadap data yang tidak digunakan untuk training)

Fungsi initial_split() dari paket rsample memiliki beberapa parameter:

  • data = data frame awal
  • prop = proporsi dari data yang akan digunakan sebagai data train

Selanjutnya, fungsi training() and testing() dilakukan untuk mengekstrak data train dan test dari hasil split.

#kembalikan kolom Target dari Faktor ke Numerical

#Factor levels in R are stored as integers starting from 1. 
#So, if your factor levels are "0" and "1", the underlying integer codes for these levels would be 1 and 2, respectively. 
#This is why applying as.numeric() directly to a factor results in 1 and 2 instead of 0 and 1.

#To convert the factor to numeric values correctly, we should first convert the factor to a character and then to numeric.

dataset_smote2 <- dataset_smote %>% 
  mutate(Target = as.numeric(as.character(Target)))

RNGkind(sample.kind= "Rounding")
set.seed(123) # mengunci kerandoman data

# membuat binary split data menjadi set data training dan testing dengan proporsi 80:20
splitter <- initial_split(data = dataset_smote2,
                          prop = 0.8) # data train

# splitting
train <- training(splitter)
test <- testing(splitter)

Wajib! Mengecek proporsi kelas target yang ada pada data TRAIN. Karena harapannya model belajar sama (adil) untuk tipe kelas target. Sehingga tidak ada kelas target yang lebih dominan menghindari adanya missclassification

prop.table(table(train$Target))
## 
##         0         1 
## 0.5008371 0.4991629

2 Initial Characteristic Analysis

Initial Characteristic Analysis merupakan langkah awal dalam mengidentifikasi dan menganalisis karakteristik atau variabel yang potensial untuk dimasukkan ke dalam model application scoring. Tujuan utama dari langkah ini adalah untuk membuat kategorisasi variabel dan memahami hubungan antara variabel-variabel yang tersedia dengan perilaku pemberian kredit yang akan diprediksi oleh skor kredit.

Tahapan ini sering disebut sebagai tahapan EDA pada pembuatan scorecard.

2.1 Weight of Evidence (WoE) or Fine Classing

Pembentukan WOE bertujuan melakukan binning / kategorisasi seluruh variabel prediktor yang ada pada data, kemudian menghitung kekuatan masing-masing hasil kategori dalam memisahkan kelas positif dan kelas negatif.

Hal ini penting karena:

  • Membuat analisis lebih mudah dianalisis karena memisahkan beberapa nilai outlier, missing, dan kategori dengan frekuensi rendah.
  • Proses characteristic binning ini membantu user memahami resiko behaviour applicant

📐 Formula WOE:

\[ log(\frac{Distr_{Good}}{Distr_{Bad}}) \]

Keterangan:

  • \(Distr_{Good}\) : proporsi target applicant yes (default) pada kategori bin
  • \(Distr_{Bad}\) : proporsi target applicant no (not_default) pada kategori bin

🛑 Important Points:

Nilai WoE negatif menunjukkan bahwa banyak sebaran kelas negatif pada kelompok tersebut. Begitupula sebaliknya.

📌 Cara membuat binning dan menghitung WOE:

Gunakan fungsi woebin(), dengan parameter:

  • dt : data frame train
  • y : nama variabel y pada data
  • positive: kelas positive (applicant Good)
# your code here
binning <- woebin(dt = train,
                  y = 'Target',
                  positive = 1) #Yes = 1, No = 0
## ℹ Creating woe binning ...
## ✔ Binning on 7765 rows and 19 columns in 00:00:03
binning
## $Gender
##    variable bin count count_distr  neg  pos   posprob         woe       bin_iv
## 1:   Gender   0  5007   0.6448165 2539 2468 0.4929099 -0.02501384 0.0004034270
## 2:   Gender   1  2758   0.3551835 1350 1408 0.5105149  0.04541403 0.0007324445
##       total_iv breaks is_special_values
## 1: 0.001135871      0             FALSE
## 2: 0.001135871      1             FALSE
## 
## $Own_car
##    variable bin count count_distr  neg  pos   posprob         woe       bin_iv
## 1:  Own_car   0  4961   0.6388925 2449 2512 0.5063495  0.02874783 0.0005279812
## 2:  Own_car   1  2804   0.3611075 1440 1364 0.4864479 -0.05087319 0.0009343345
##       total_iv breaks is_special_values
## 1: 0.001462316      0             FALSE
## 2: 0.001462316      1             FALSE
## 
## $Own_property
##        variable bin count count_distr  neg  pos   posprob         woe
## 1: Own_property   0  2658   0.3423052 1257 1401 0.5270880  0.11180670
## 2: Own_property   1  5107   0.6576948 2632 2475 0.4846289 -0.05815526
##         bin_iv    total_iv breaks is_special_values
## 1: 0.004275015 0.006498627      0             FALSE
## 2: 0.002223611 0.006498627      1             FALSE
## 
## $Work_phone
##      variable bin count count_distr  neg  pos   posprob          woe
## 1: Work_phone   0  6096   0.7850612 3048 3048 0.5000000  0.003348361
## 2: Work_phone   1  1669   0.2149388  841  828 0.4961055 -0.012230144
##          bin_iv    total_iv breaks is_special_values
## 1: 8.801747e-06 4.09508e-05      0             FALSE
## 2: 3.214905e-05 4.09508e-05      1             FALSE
## 
## $Phone
##    variable bin count count_distr  neg  pos   posprob         woe       bin_iv
## 1:    Phone   0  5564   0.7165486 2758 2806 0.5043134  0.02060256 0.0003041447
## 2:    Phone   1  2201   0.2834514 1131 1070 0.4861427 -0.05209519 0.0007690537
##       total_iv breaks is_special_values
## 1: 0.001073198      0             FALSE
## 2: 0.001073198      1             FALSE
## 
## $Email
##    variable bin count count_distr  neg  pos   posprob          woe       bin_iv
## 1:    Email   0  7073  0.91088216 3551 3522 0.4979500 -0.004851883 2.144273e-05
## 2:    Email   1   692  0.08911784  338  354 0.5115607  0.049599379 2.192028e-04
##        total_iv breaks is_special_values
## 1: 0.0002406455      0             FALSE
## 2: 0.0002406455      1             FALSE
## 
## $Unemployed
##      variable bin count count_distr  neg  pos   posprob         woe
## 1: Unemployed   0  6483   0.8349002 3192 3291 0.5076354  0.03389215
## 2: Unemployed   1  1282   0.1650998  697  585 0.4563183 -0.17182520
##          bin_iv    total_iv breaks is_special_values
## 1: 0.0009589668 0.005820702      0             FALSE
## 2: 0.0048617354 0.005820702      1             FALSE
## 
## $Num_children
##        variable                    bin count count_distr  neg  pos   posprob
## 1: Num_children                      0  5356   0.6897618 2740 2616 0.4884242
## 2: Num_children                      1  1543   0.1987122  742  801 0.5191186
## 3: Num_children 2%,%3%,%4%,%5%,%7%,%14   866   0.1115261  407  459 0.5300231
##            woe      bin_iv    total_iv                 breaks is_special_values
## 1: -0.04296313 0.001272941 0.004241053                      0             FALSE
## 2:  0.07986007 0.001266724 0.004241053                      1             FALSE
## 3:  0.12358539 0.001701388 0.004241053 2%,%3%,%4%,%5%,%7%,%14             FALSE
## 
## $Num_family
##      variable                    bin count count_distr  neg  pos   posprob
## 1: Num_family                      1  1521   0.1958789  764  757 0.4976989
## 2: Num_family                      2  4088   0.5264649 2091 1997 0.4885029
## 3: Num_family                      3  1334   0.1717965  652  682 0.5112444
## 4: Num_family 4%,%5%,%6%,%7%,%9%,%15   822   0.1058596  382  440 0.5352798
##             woe       bin_iv    total_iv                 breaks
## 1: -0.005856175 6.717573e-06 0.003578358                      1
## 2: -0.042648005 9.573826e-04 0.003578358                      2
## 3:  0.048333457 4.012759e-04 0.003578358                      3
## 4:  0.144702479 2.212982e-03 0.003578358 4%,%5%,%6%,%7%,%9%,%15
##    is_special_values
## 1:             FALSE
## 2:             FALSE
## 3:             FALSE
## 4:             FALSE
## 
## $Account_length
##          variable       bin count count_distr  neg  pos   posprob          woe
## 1: Account_length  [-Inf,5)   403  0.05189955  323   80 0.1985112 -1.392277327
## 2: Account_length     [5,9)   545  0.07018674  319  226 0.4146789 -0.341307742
## 3: Account_length    [9,38)  4265  0.54925950 2127 2138 0.5012896  0.008506638
## 4: Account_length [38, Inf)  2552  0.32865422 1120 1432 0.5611285  0.249091744
##          bin_iv  total_iv breaks is_special_values
## 1: 8.689890e-02 0.1153253      5             FALSE
## 2: 8.095374e-03 0.1153253      9             FALSE
## 3: 3.974604e-05 0.1153253     38             FALSE
## 4: 2.029133e-02 0.1153253    Inf             FALSE
## 
## $Total_income
##        variable             bin count count_distr neg pos   posprob        woe
## 1: Total_income   [-Inf,100000)  1193   0.1536381 653 540 0.4526404 -0.1866596
## 2: Total_income [100000,130000)  1157   0.1490019 541 616 0.5324114  0.1331760
## 3: Total_income [130000,140000)   905   0.1165486 480 425 0.4696133 -0.1183486
## 4: Total_income [140000,180000)  1193   0.1536381 508 685 0.5741827  0.3022858
## 5: Total_income [180000,210000)  1129   0.1453960 643 486 0.4304694 -0.2765877
## 6: Total_income [210000,270000)  1059   0.1363812 461 598 0.5646837  0.2635411
## 7: Total_income   [270000, Inf)  1129   0.1453960 603 526 0.4658990 -0.1332676
##         bin_iv   total_iv breaks is_special_values
## 1: 0.005336711 0.04659064 100000             FALSE
## 2: 0.002639072 0.04659064 130000             FALSE
## 3: 0.001630361 0.04659064 140000             FALSE
## 4: 0.013936506 0.04659064 180000             FALSE
## 5: 0.011049994 0.04659064 210000             FALSE
## 6: 0.009419826 0.04659064 270000             FALSE
## 7: 0.002578169 0.04659064    Inf             FALSE
## 
## $Age
##    variable       bin count count_distr  neg  pos   posprob         woe
## 1:      Age [-Inf,35)  2212  0.28486800  958 1254 0.5669078  0.27259430
## 2:      Age   [35,37)   416  0.05357373  241  175 0.4206731 -0.31666260
## 3:      Age   [37,51)  2776  0.35750161 1411 1365 0.4917147 -0.02979588
## 4:      Age   [51,54)   574  0.07392144  260  314 0.5470383  0.19205972
## 5:      Age [54, Inf)  1787  0.23013522 1019  768 0.4297706 -0.27943894
##          bin_iv   total_iv breaks is_special_values
## 1: 0.0210425337 0.04725517     35             FALSE
## 2: 0.0053262710 0.04725517     37             FALSE
## 3: 0.0003173566 0.04725517     51             FALSE
## 4: 0.0027188202 0.04725517     54             FALSE
## 5: 0.0178501878 0.04725517    Inf             FALSE
## 
## $Years_employed
##          variable         bin count count_distr  neg  pos   posprob         woe
## 1: Years_employed  [-Inf,0.5)  1481  0.19072762  810  671 0.4530722 -0.18491675
## 2: Years_employed   [0.5,1.5)   773  0.09954926  394  379 0.4902975 -0.03546634
## 3: Years_employed   [1.5,6.5)  3069  0.39523503 1387 1682 0.5480613  0.19618878
## 4: Years_employed  [6.5,10.5)  1231  0.15853187  634  597 0.4849716 -0.05678348
## 5: Years_employed [10.5, Inf)  1211  0.15595621  664  547 0.4516928 -0.19048499
##          bin_iv  total_iv breaks is_special_values
## 1: 0.0065022551 0.0279458    0.5             FALSE
## 2: 0.0001252023 0.0279458    1.5             FALSE
## 3: 0.0151664866 0.0279458    6.5             FALSE
## 4: 0.0005110028 0.0279458   10.5             FALSE
## 5: 0.0056408524 0.0279458    Inf             FALSE
## 
## $Income_type
##       variable   bin count count_distr  neg  pos   posprob         woe
## 1: Income_type     1  1873  0.24121056  915  958 0.5114789  0.04927207
## 2: Income_type     2  1333  0.17166774  697  636 0.4771193 -0.08823849
## 3: Income_type     3   549  0.07070187  297  252 0.4590164 -0.16095469
## 4: Income_type 4%,%5  4010  0.51641983 1980 2030 0.5062344  0.02828731
##          bin_iv    total_iv breaks is_special_values
## 1: 0.0005855016 0.004161796      1             FALSE
## 2: 0.0013356451 0.004161796      2             FALSE
## 3: 0.0018274420 0.004161796      3             FALSE
## 4: 0.0004132069 0.004161796  4%,%5             FALSE
## 
## $Education_type
##          variable       bin count count_distr  neg  pos   posprob         woe
## 1: Education_type 1%,%2%,%3  2360   0.3039279 1136 1224 0.5186441  0.07795922
## 2: Education_type     4%,%5  5405   0.6960721 2753 2652 0.4906568 -0.03402879
##          bin_iv    total_iv    breaks is_special_values
## 1: 0.0018463499 0.002652272 1%,%2%,%3             FALSE
## 2: 0.0008059221 0.002652272     4%,%5             FALSE
## 
## $Family_status
##         variable   bin count count_distr  neg  pos   posprob         woe
## 1: Family_status     1   718  0.09246619  336  382 0.5320334  0.13165781
## 2: Family_status     2  5135  0.66130071 2642 2493 0.4854917 -0.05470104
## 3: Family_status     3   427  0.05499034  221  206 0.4824356 -0.06693817
## 4: Family_status 4%,%5  1485  0.19124276  690  795 0.5353535  0.14499888
##          bin_iv   total_iv breaks is_special_values
## 1: 0.0016006535 0.00783938      1             FALSE
## 2: 0.0019781627 0.00783938      2             FALSE
## 3: 0.0002462905 0.00783938      3             FALSE
## 4: 0.0040142732 0.00783938  4%,%5             FALSE
## 
## $Housing_type
##        variable       bin count count_distr  neg  pos   posprob         woe
## 1: Housing_type     1%,%2  6912  0.89014810 3501 3411 0.4934896 -0.02269478
## 2: Housing_type 3%,%4%,%5   458  0.05898261  198  260 0.5676856  0.27576296
## 3: Housing_type         6   395  0.05086929  190  205 0.5189873  0.07933427
##          bin_iv    total_iv    breaks is_special_values
## 1: 0.0004584450 0.005236624     1%,%2             FALSE
## 2: 0.0044581584 0.005236624 3%,%4%,%5             FALSE
## 3: 0.0003200209 0.005236624         6             FALSE
## 
## $Occupation_type
##           variable                               bin count count_distr  neg
## 1: Occupation_type 1%,%2%,%3%,%4%,%5%,%6%,%7%,%8%,%9  3444   0.4435287 1685
## 2: Occupation_type                      10%,%11%,%12   931   0.1198970  443
## 3: Occupation_type  13%,%14%,%15%,%16%,%17%,%18%,%19  3390   0.4365744 1761
##     pos   posprob         woe       bin_iv   total_iv
## 1: 1759 0.5107433  0.04632826 0.0009518158 0.00457833
## 2:  488 0.5241676  0.10009400 0.0012003234 0.00457833
## 3: 1629 0.4805310 -0.07456714 0.0024261907 0.00457833
##                               breaks is_special_values
## 1: 1%,%2%,%3%,%4%,%5%,%6%,%7%,%8%,%9             FALSE
## 2:                      10%,%11%,%12             FALSE
## 3:  13%,%14%,%15%,%16%,%17%,%18%,%19             FALSE

Output hasil woebin:

  • variable : nama variabel
  • bin : hasil binning kategori
  • count : jumlah data pada masing-masing bin
  • count_distr : distribusi masing-masing bin/total data
  • neg : banyaknya kelas negatif pada bin (not_default)
  • pos : banyaknya kelas postif pada bin
  • posprob : pos/count masing-masing bin
  • woe : nilai woe untuk bin tersebut.

🛑 Note:

  • Cara woebin dalam melakukan binning adalah dengan metode tree -> default. Metode lainnya:
    • chimerge -> metode binning menggunakan analisis chi-squared
    • freq -> akan menghasilkan proporsi nilai yang cenderung sama antara binningnya (hanya untuk var numerik)
    • width -> akan menghasilkan binning yang secara rentang akan mirip antara satu dengan yang lain (hanya untuk var numerik)
  • Minimum proporsi yang diberikan untuk menghasilkan 1 binning adalah di angka 0.05 (default) -> set manual (0.01 sampai 0.2)

Mari kita pahami menggunakan variable Age!

# your code here
binning$Age

💡 Insight:

  • distribusi usia didominasi 35 - 53
  • woe negatif -> dia memiliki banyak applicant not_default (Di bin 35-53 serta 54-Inf)
    • bin -Inf ~ 34 -> binning secara potensi default paling tinggi

2.2 Mengubah dataframe ke dalam WOE

Setelah kita melakukan binning pada data dalam bentuk WOE value, values di data train maupun test perlu dikonversi ke nilai WOE sesuai kategori/bins yang sudah dihitung.

Kita bisa gunakan fungsi woebin_ply() dari library scorecard, dengan parameter:

  • dt : dataframe yang akan ditransformasi -> data train
  • bins : informasi binning yang dihasilkan dari woebin
# data train
train_woe <- woebin_ply(dt = train,
                        bins = binning)
## ℹ Converting into woe values ...
## ✔ Woe transformating on 7765 rows and 18 columns in 00:00:11
train_woe
# data test
test_woe <- woebin_ply(dt = test,
                        bins = binning)
## ℹ Converting into woe values ...
## ✔ Woe transformating on 1942 rows and 18 columns in 00:00:11

2.3 Information Value (IV)

Information Value digunakan untuk melihat feature importance pada data. Nilai IV yang tinggi mengindikasikan kekuatan suatu feature/variabel dalam memprediksi target.

📐 Formula IV:

\[ \sum_{i=1}^n (Distr_{Good_i} - Distr_{Bad_i}) \times ln(\frac{Distr_{Good_i}}{Distr_{Bad_i}}) \]

📌 Cara mengeluarkan nilai information value (iv) pada data:

Gunakan fungsi iv(), dengan parameter:

  • dt : data frame train hasil woe
  • y : nama variabel y pada data
  • positive: kelas positive (applicant good)
#untuk mengubah tampilan exponential notation ke tampilan angka normal (0 koma sekian)
options("scipen"=100, "digits"=4)

#penggunaan fungsi iv()
iv(dt = train_woe,
   y ='Target',
   positive = 1)

Menurut (Siddiqi, Naeem), skor IV dapat dikategorikan menjadi nilai berikut:

  • IV dibawah 0.02 -> unpredictive
  • IV diantara 0.02 - 0.1 -> ‘weak’
  • IV diantara 0.1 sampai 0.3 dikategorikan sebagai ‘medium’
  • IV diatas 0.3 dikategorikan sebagai ‘strong’

❓ Nilai IV dapat digunakan sebagai feature elimination kecuali:

Account_length_woe, Total_income_woe, Age_woe, Years_employed_woe dan target; variable selebihnya dapat dihilangkan.

# hasil data setelah feature elimination
train_woe_final <- train_woe %>% 
  select(c(Target, Account_length_woe, Age_woe, Total_income_woe, Years_employed_woe))
  
test_woe_final <- test_woe %>% 
  select(c(Target, Account_length_woe, Age_woe, Total_income_woe, Years_employed_woe))

2.4 Logical Trend and Business Consideration / Coarse Classing

Setelah WOE binning berhasil dihasilkan dan di-apply untuk data train dan test, terkadang visualisasi dibutuhkan dalam perspektif bisnis karena lebih mudah untuk dipresentasikan.

Plot trend bertujuan untuk mengidentifikasi dan memahami pola atau trend dalam data hasil WOE/binning.

📌 Di R, untuk mengecek logical trend kita dapat gunakan fugsi woebin_plot(), dengan parameter:

  • bins = data frame hasil woebin
# logical trend plot
plot <- woebin_plot(bins = binning)
plot
## $Gender

## 
## $Own_car

## 
## $Own_property

## 
## $Work_phone

## 
## $Phone

## 
## $Email

## 
## $Unemployed

## 
## $Num_children

## 
## $Num_family

## 
## $Account_length

## 
## $Total_income

## 
## $Age

## 
## $Years_employed

## 
## $Income_type

## 
## $Education_type

## 
## $Family_status

## 
## $Housing_type

## 
## $Occupation_type

💡 Insight:

  • Garis biru merupakan garis hubungan antar masing-masing binning menunjukan positive probability.
  • Visualisasi Age menandakan range umur -Inf~34, memiliki positive probability yang tertinggi sebesar 55.8%. artinya pada range umur tersebut, potensi pemberian kartu kredit pada applicant dapat dilakukan lebih tinggi jika dibandingkan range umur yang lain.

3 Modeling - Logistic Regression

3.1 📐 Theory - Linear Regression vs Logistic Regression

Logistic regression merupakan salah satu metode klasifikasi yang konsepnya hampir mirip dengan regresi linear. Hanya saja, dalam binary logistic regression tidak menghitung secara spesifik nilai prediksi target variabel, namun menghitung kemungkinan pada masing-masing kelas target.

  • Range regression: -inf s.d inf
  • Range probability: 0 s.d 1

3.1.1 Logistic Regression Model

📌 Cara membuat model Logistic Regression:

glm(target ~ prediktor, data, family = "binomial")

terdapat 3 paramater yang kita gunakan yaitu:

  • formula : tempat mendefinisikan target dan predictor (y~x)
  • data : data yang digunakan untuk membuat model
  • family : gunakan “binomial” bila ingin menggunakan logistic regression pada pemodelan ini
train_woe_final
model <- glm(formula = Target ~ .,
             data = train_woe_final,
             family = "binomial")

summary(model) 
## 
## Call:
## glm(formula = Target ~ ., family = "binomial", data = train_woe_final)
## 
## Coefficients:
##                    Estimate Std. Error z value             Pr(>|z|)    
## (Intercept)        -0.00387    0.02334   -0.17                 0.87    
## Account_length_woe  1.05851    0.07657   13.82 < 0.0000000000000002 ***
## Age_woe             0.92954    0.11475    8.10  0.00000000000000055 ***
## Total_income_woe    0.94077    0.10806    8.71 < 0.0000000000000002 ***
## Years_employed_woe  0.59486    0.14884    4.00  0.00006426160363769 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 10765  on 7764  degrees of freedom
## Residual deviance: 10347  on 7760  degrees of freedom
## AIC: 10357
## 
## Number of Fisher Scoring iterations: 4

💡 Interpretasi model:

  • Signifikansi variabel : Account_length_woe, Age_woe, Total_income_woe dan Years_employed_woe

  • Model dapat dibuat dengan hanya memasukan/ menggunakan variable yang signifikan, hanya saja perlu dilakukan pertimbangan secara bisnis apakah variable lain tersebut dapat kita buang secara cuma2.

  • Deviance null -> error model ketika model tanpa prediktor apapun

  • residual deviance -> error model ketika prediktor sama dengan prediktor model

3.2 Model Asumsi - Multikolinearitas

Multicollinearity: antar prediktor tidak saling berkorelasi terlalu kuat (lebih dari +- 0.9)

y ~ x1, x2 , x3 -> kuat x1 ~ x2 -> tidak kuat x2 ~ x3 ->tidak kuat

  • Gunakan ujivif() dari library(car)
  • Nilai vif variabel < 10 dikatakan variabel tidak memiliki multikolinearitas dari variabel lainnya.
vif(model)
## Account_length_woe            Age_woe   Total_income_woe Years_employed_woe 
##              1.008              1.143              1.003              1.142

💡 Insight: tidak ada multikolinearitas pada data

3.3 Prediction

Setelah model didapatkan, prediksi dapat dilakukan terhadap data test yang sudah ditransformasi ke dalam WOE. Prediksi ini dilakukan dengan tujuan 2 hal:

  • Mengetahui hasil prediksi applicant akan ditolak hasil pengajuannya (applicant not default) atau tidak
  • Memberikan hasil evaluasi dari model apakah prediksi yang dihasilkan sudah akurat dengan realitanya atau belum.

Pada tahapan ini dapat menggunakan fungsi predict.

📌 Syntax: predict(model, newdata, type = "response")

Keterangan:

  • object : model logistic regression
  • newdata : data untuk diprediksi bisa data test ataupun data baru
  • type = "response" untuk menghasilkan prediksi berupa peluang, “link” = prediksi log of odds

❓ Prediksikan data test hasil transformasi WOE. Simpan pada kolom baru bernama pred_risk.

# Melakukan prediksi pada data test
test_woe_final$pred_risk <- predict(object = model,
                                    newdata = test_woe_final,
                                    type = "response")
test_woe_final

3.4 Evaluation Model

perf_eva() dari library scorecard menghitung metrik untuk evaluasi model klasifikasi binomial.

Untuk melakukan model evaluation, kita dapat menggunakan fungsi perf_eva() dengan parameter:

  • pred = list dari vector prediksi
  • label = list dari vector label actual -> wajib numerik
  • confusion_matrix = TRUE -> membuat tabel confusion matrix
  • threshold = 0.5
  1. Membuat list untuk hasil prediksi resiko dari masing-masing data train dan test
list_pred <- list(test = test_woe_final$pred_risk)
  1. Membuat list label pada data train dan test
list_label <- list(test = test_woe_final$Target)
  1. Menggunakan function perf_eva untuk melakukan evaluasi
perf_eva(pred = list_pred,
         label = list_label,
         confusion_matrix = TRUE,
         threshold = 0.5,
         show_plot = c("ks", "roc"))
## ℹ The threshold of confusion matrix is 0.5000.
## Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as
## of ggplot2 3.3.4.
## ℹ The deprecated feature was likely used in the scorecard package.
##   Please report the issue at <https://github.com/ShichenXie/scorecard/issues>.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

## $binomial_metric
## $binomial_metric$test
##       MSE   RMSE LogLoss      R2     KS    AUC   Gini
## 1: 0.2402 0.4901  0.6725 0.03899 0.1539 0.6047 0.2094
## 
## 
## $confusion_matrix
## $confusion_matrix$test
##    label pred_0 pred_1  error
## 1:     0    504    461 0.4777
## 2:     1    388    589 0.3971
## 3: total    892   1050 0.4372
## 
## 
## $pic
## TableGrob (1 x 2) "arrange": 2 grobs
##   z     cells    name           grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]

3.5 KS Statistics

KS (Kolmogorov-Smirnov) Statistics adalah metode statistik yang digunakan untuk mengukur perbedaan antara dua distribusi kumulatif empiris, sering digunakan untuk membandingkan distribusi skor antara dua kelompok, misalnya dalam analisis kinerja credit model (membedakan antara good dan bad customers).

- Satu kurva untuk distribusi kumulatif dari satu kelompok (misalnya, good customers). - Kurva lainnya untuk distribusi kumulatif dari kelompok kedua (misalnya, bad customers). - Garis vertikal pada titik di mana perbedaan antara kedua kurva adalah yang terbesar. Ini adalah titik di mana KS Statistics diukur.

Nilai KS yang tinggi menunjukkan model yang baik dalam membedakan antara kedua kelompok (misalnya, good vs bad customers).

Biasanya, nilai KS > 0.4 dianggap baik dalam kredit scoring.

💡 Interpretasi:

  • Model Logistic Regression dengan data yang ada masih belum mampu membedakan antara kelas default dengan not default dengan baik, langkah selanjutnya dapat dilakukan tuning model maupun penambahan data (untuk saat ini hanya berjumlah 9.700 baris data)

3.5.1 AUC Score

AUC adalah Area under curve atau luas dibawah kurva ROC. Jika nilai AUC semakin mendekati nilai 1, model semakin baik dalam memisahkan kelas positif dan negatif.

  • AUC = 1, model memiliki performa ideal
  • AUC = 0.5, model tidak lebih baik dalam memprediksi default dibandingkan kalau kita menebak secara acak

💡 Interpretasi:

  • Modelnya memiliki AUC sebesar 0.6061, artinya model baru mampu membedakan antara kelas default dengan not default dari yang aktualnya default/not default sebesar 60.61%

3.6 Scorecard Creation

Pada model logistic regression, output yang dihasilkan berupa prediksi apakah default atau tidak default dalam bentuk probabilitas.

Untuk mempermudah pengambilan keputusan apakah seorang applicant akan default dan tidak default maka hasil binning dapat dibuat menjadi penilaian dalam bentuk score point untuk setiap bin yang diakumulasikan menjadi scorecard.

Manfaat pembentukan scorecard antara lain:

  • Pengambilan keputusan yang konsisten: Hal ini mengurangi tingkat subjektivitas dalam proses pengambilan keputusan dan memberikan dasar yang lebih objektif untuk menilai kredit.
  • Penilaian risiko individu: Scorecard membantu memberikan gambaran tentang seberapa besar peluang default yang dimiliki oleh applicant berdasarkan karakteristiknya.
  • Pengelolaan risiko lebih baik: Dengan scorecard dapat mengidentifikasi peluang dan menentukan keketatan risiko berdasarkan pemahaman yang lebih mendalam tentang profil risiko applicant.

Rumus umum pembentukan score dalam setiap binning (Siddiqi, Naeem)

\[ Points_{ij} = (\frac{pdo}{ln(2)}) \times -Coef_i \times WOE_{ij} \] Keterangan:

  • Coef : Hasil nilai coefficient dari logistic regression
  • WOE : Nilai WOE dari setiap binning untuk setiap variabelnya

📌 Untuk membuat scorecard dapat menggunakan fungsi scorecard() dengan parameter:

  • bins: object binning hasil woebin()
  • model: object model hasil glm()
  • odds0: Tingkat keketatan / target odds, rasio kemungkinan default terhadap kemungkinan tidak default. Contoh: 1/19, artinya 1 yg akan default dibanding 19 yang tidak default. Semakin kecil, semakin ketat (ditentukan oleh user)
  • points0: Target points ketika odds0 tercapai. Contoh: points0 = 600 dan odds0 = 1/19, maka pada poin 600 berarti terdapat 1 applicant default terhadap 19 applicant lain yang tidak default (ditentukan oleh user)
  • pdo: points to double the odds, ketika skor kredit suatu individu naik sebesar PDO, itu mengindikasikan bahwa peluang (odds) untuk individu itu berhasil bayar mengalami perubahan sebanyak dua kali lipat. Sebaliknya, jika skor kredit turun sebesar PDO, peluangnya mengalami penurunan setengahnya (ditentukan oleh user).

❓ Contoh kasus:

Ingin dihasilkan sebuah scorecard sesuai dengan kebutuhan user dengan odds 19:1 pada poin 600 dan ingin peluangnya berlipat ganda setiap kenaikan 20 poin, maka bagaimana pembentukan scorecardnya?

# membentuk scorecard
score_card <- scorecard(bins = binning, 
                        model = model,
                        odds0 = 1/19,
                        points0 = 600,
                        pdo = 20)
score_card
## $basepoints
##      variable bin woe points
## 1: basepoints  NA  NA    515
## 
## $Account_length
##          variable       bin count count_distr  neg  pos posprob       woe
## 1: Account_length  [-Inf,5)   403     0.05190  323   80  0.1985 -1.392277
## 2: Account_length     [5,9)   545     0.07019  319  226  0.4147 -0.341308
## 3: Account_length    [9,38)  4265     0.54926 2127 2138  0.5013  0.008507
## 4: Account_length [38, Inf)  2552     0.32865 1120 1432  0.5611  0.249092
##        bin_iv total_iv breaks is_special_values points
## 1: 0.08689890   0.1153      5             FALSE     43
## 2: 0.00809537   0.1153      9             FALSE     10
## 3: 0.00003975   0.1153     38             FALSE      0
## 4: 0.02029133   0.1153    Inf             FALSE     -8
## 
## $Age
##    variable       bin count count_distr  neg  pos posprob     woe    bin_iv
## 1:      Age [-Inf,35)  2212     0.28487  958 1254  0.5669  0.2726 0.0210425
## 2:      Age   [35,37)   416     0.05357  241  175  0.4207 -0.3167 0.0053263
## 3:      Age   [37,51)  2776     0.35750 1411 1365  0.4917 -0.0298 0.0003174
## 4:      Age   [51,54)   574     0.07392  260  314  0.5470  0.1921 0.0027188
## 5:      Age [54, Inf)  1787     0.23014 1019  768  0.4298 -0.2794 0.0178502
##    total_iv breaks is_special_values points
## 1:  0.04726     35             FALSE     -7
## 2:  0.04726     37             FALSE      8
## 3:  0.04726     51             FALSE      1
## 4:  0.04726     54             FALSE     -5
## 5:  0.04726    Inf             FALSE      7
## 
## $Total_income
##        variable             bin count count_distr neg pos posprob     woe
## 1: Total_income   [-Inf,100000)  1193      0.1536 653 540  0.4526 -0.1867
## 2: Total_income [100000,130000)  1157      0.1490 541 616  0.5324  0.1332
## 3: Total_income [130000,140000)   905      0.1165 480 425  0.4696 -0.1183
## 4: Total_income [140000,180000)  1193      0.1536 508 685  0.5742  0.3023
## 5: Total_income [180000,210000)  1129      0.1454 643 486  0.4305 -0.2766
## 6: Total_income [210000,270000)  1059      0.1364 461 598  0.5647  0.2635
## 7: Total_income   [270000, Inf)  1129      0.1454 603 526  0.4659 -0.1333
##      bin_iv total_iv breaks is_special_values points
## 1: 0.005337  0.04659 100000             FALSE      5
## 2: 0.002639  0.04659 130000             FALSE     -4
## 3: 0.001630  0.04659 140000             FALSE      3
## 4: 0.013937  0.04659 180000             FALSE     -8
## 5: 0.011050  0.04659 210000             FALSE      8
## 6: 0.009420  0.04659 270000             FALSE     -7
## 7: 0.002578  0.04659    Inf             FALSE      4
## 
## $Years_employed
##          variable         bin count count_distr  neg  pos posprob      woe
## 1: Years_employed  [-Inf,0.5)  1481     0.19073  810  671  0.4531 -0.18492
## 2: Years_employed   [0.5,1.5)   773     0.09955  394  379  0.4903 -0.03547
## 3: Years_employed   [1.5,6.5)  3069     0.39524 1387 1682  0.5481  0.19619
## 4: Years_employed  [6.5,10.5)  1231     0.15853  634  597  0.4850 -0.05678
## 5: Years_employed [10.5, Inf)  1211     0.15596  664  547  0.4517 -0.19048
##       bin_iv total_iv breaks is_special_values points
## 1: 0.0065023  0.02795    0.5             FALSE      3
## 2: 0.0001252  0.02795    1.5             FALSE      1
## 3: 0.0151665  0.02795    6.5             FALSE     -3
## 4: 0.0005110  0.02795   10.5             FALSE      1
## 5: 0.0056409  0.02795    Inf             FALSE      3

3.6.1 Mengubah Karakteristik menjadi Score

Semisal 2 data pertama pada data train:

📌 Untuk mengubah nilai karakteristik applicant menjadi points dan total points (score) secara otomatis, dapat menggunakan fungsi scorecard_ply() dengan parameter:

  • dt: dataframe yang berisi karakteristik asli applicant.
  • card: Objek hasil fungsi scorecard().
  • only_total_score: TRUE/FALSE, apakah hanya menampilkan total score atau tidak.

Mari kita terapkan scorecard_ply() pada dataframe train dan test.

train %>% head()
score_train <- scorecard_ply(dt = train,
                       card = score_card,
                       only_total_score = F)
score_train %>% head()

Mari kita terapkan scorecard_ply() pada dataframe test.

score_test <- scorecard_ply(dt = test,
                       card = score_card,
                       only_total_score = F)
score_test %>% head()

Dapat dilihat dari setiap karakteristik asli applicant diubah menjadi bentuk points dan setiap points tersebut dijumlahkan ke dalam kolom score yang menyatakan total points dari applicant tersebut.

3.7 Performance Evaluation Scorecard using perf_psi()

Population Stability Index (PSI) adalah suatu metrik yang digunakan dalam analisis kredit dan risiko kredit untuk mengevaluasi stabilitas atau perubahan dalam distribusi poin atau skor kredit dari suatu populasi dari waktu ke waktu.

  • Sebuah PSI yang tinggi dapat mengindikasikan bahwa ada perubahan substansial dalam distribusi skor kredit, yang dapat berarti peningkatan risiko kredit atau perlu pemantauan lebih lanjut.
  • Sebaliknya, PSI yang rendah menunjukkan stabilitas dalam populasi skor kredit.
  • Metrik ini berguna untuk validasi manajemen risiko dan analisis portofolio kredit setelah scorecard dihasilkan

Langkah-langkah:

  1. Menyiapkan list score untuk data train dan juga test.

Membuat list dengan function list()

score_list <- list(train = score_train$score, 
                   test = score_test$score)
  1. Menyiapkan list label untuk data train dan juga test
label_list <- list(train = train_woe_final$Target, 
                   test = test_woe_final$Target)
  1. Menghitung score psi dengan function perf_psi()

Parameter:

  • score : list berisikan score antara data train dan test
  • label : list berisikan label antara data train dan test (tidak wajib)
  • positive : kelas positive
psi <- perf_psi(score = score_list, 
                label = label_list, 
                positive = 1)
psi$psi # psi data frame

📌 Menurut Siddqi Naeem, index PSI memiliki rentang:

  • PSI < 0.10: Biasanya dianggap sebagai tanda bahwa tidak ada perubahan yang signifikan dalam distribusi skor kredit, dan populasi skor cenderung stabil.

  • PSI 0.10 s.d 0.25: Menunjukkan perubahan yang relatif kecil yang mungkin memerlukan penyelidikan lebih lanjut. Meskipun tidak menciptakan alarm besar, tetapi tetap penting untuk memahami faktor-faktor apa yang mungkin menyebabkan perubahan tersebut.

  • PSI > 0.25: Menandakan pergeseran yang signifikan dalam distribusi scorecard dan dapat dianggap sebagai sinyal bahwa ada perubahan yang patut diperhatikan dalam profil risiko kredit. Ini mungkin memerlukan analisis mendalam dan tindakan lebih lanjut.

3.8 Cutoff

Penentuan seorang applicant termasuk dalam “GOOD” atau “BAD” applicant, harus ditetapkan nilai cutoff (batas) dari total score applicant. Untuk menentukan nilai cutoff dibutuhkan informasi approval rate dan juga bad rate sehingga dapat menentukan persentase applicant yang dianggap GOOD dan juga tingkat risiko dari applicant yang dianggap GOOD.

Untuk mendapatkan nilai approval rate dan bad rate kita membutuhkan function approval_rate().

approval_rate() akan menampilkan tabel berupa pilihan nilai cutoff, approval rate, negative rate (bad rate) beserta detail lainnya. Adapun parameter approval_rate() antara lain:

  • score: Kolom score total dari data applicant
  • label: Kolom label yang menyatakan apakah applicant default/not default.
  • positive: Kelas positif dari kolom label.
#Fungsi Approval_rate
approval_rate <- function(score, label, positive = 0){
  # membuat list
  score_list <- list(data = score)
  label_list <- list(data = label)
  # membuat gains_table
  g <- gains_table(score = score_list, label = label_list, positive = positive)
  
  final_df <- g %>% 
    mutate(
      count_approved = max(cum_count) - cum_count,
      neg_approved = max(cum_neg) - cum_neg,
      neg_rate = round((neg_approved / count_approved), 4)
    ) %>% 
    replace(is.na(.), 0) %>% 
    select(bin, approval_rate, neg_rate, 
           count_approved, neg_approved,
           count, neg, pos)
  
  final_df
}
# using score test
approval_rate(score = score_test$score,
              label = test_woe_final$Target,
              positive = 0)

Keterangan:

  1. bin : hasil binning dari score
  • [-inf,501) -> cuttof pada < 501
  1. approval_rate : tingkat approve apabila kita menggunakan cutoff di titik 501.

Rumus = count_approve/total applicant

5576/nrow(test)
## [1] 2.871
  1. neg_rate : proporsi applicant yang diapprove tetapi menghasilkan flag negatif (default)

Rumus = neg_approved/count_approved

852/1763
## [1] 0.4833
  1. count_approve : Banyaknya applicant yang diapprove
# banyaknya data train - banyaknya applicant pada binning tersebut
nrow(test) - 179
## [1] 1763
  1. neg_approved : Banyaknya applicant yang diapprove tetapi flag negatif (default)
  2. neg : Banyaknya applicant default pada bin tertentu
  3. pos : Banyaknya applicant not default pada bin tertentu

Interpretasi

  • Ketika nilai cutoff = 501 (baris 1):
    • approval rate = 90%
    • bad rate = 48%

📝 Notes

  • Semakin rendah tingkat approval rate maka semakin rendah juga tingkat bad rate. Sehingga nilai cutoff dapat ditentukan berdasarkan kebutuhan dan tingkat risiko dari masing-masing institusi.
  • Terdapat thread off antara nilai approval rate dan juga bad rate, dimana apabila kita perbesar nilai approval rate, maka bad ratenya pun cenderung cukup tinggi. Tinggal disesuaikan apa tujuan dari bisnisnya, apakah untuk memperbesar market share atau untuk meminimalisir kerugian.

💡 Kasus:

Semisal risiko yang dapat ditanggung oleh institusi hanyalah di bawah 44%, maka:

  • Cutoff: 512
  • Berapa tingkat approval ratenya: 53.76%

3.9 Predict Score

Penentuan nilai cutoff dapat digunakan sebagai acuan apakah seorang applicant dianggap “GOOD” ketika total points scorecard di atas nilai cutoff, begitupun akan mendapatkan “BAD” behaviour jika total points di bawah nilai cutoff.

❓ Kasus: semisal kita mempunyai debitur dengan karakteristik nilai asli sebagai berikut. Bagaimana keputusan scorenya?

new_data <- data.frame(list(
  Gender = 0,
  Own_car = 1,
  Own_property = 1,
  Work_phone = 1,
  Phone = 1,
  Email = 1,
  Unemployed = 0,
  Num_children = 0,
  Num_family = 0,
  Account_length = 100,
  Total_income = 300000,
  Age = 40,
  Years_employed = 15,
  Income_type = 1,
  Education_type = 1,
  Family_status = 4,
  Housing_type = 2,
  Occupation_type = 11
))

new_data
#Use to check dense_rank of several columns

# unique(dataset)
# unique(dataset_clean)
# unique(dataset$Housing_type)
# 
# dataset %>%
#   mutate(rank = dense_rank(Occupation_type))

Untuk mengubah karakteristik nilai asli hingga menjadi total points dan penentuan behaviour, maka perlu dilakukan tahapan sebagai berikut:

  • Mengubah data asli menjadi bentuk points menggunakan scorecard_ply()
  • Menentukan behaviour dengan aturan:
    • “GOOD”: ketika score > cutoff
    • “BAD”: ketika score < cutoff

Beberapa tahapan ini akan sering digunakan, maka dari itu sebaiknya dibuatkan sebuah fungsi sehingga dapat digunakan berulang kali dengan mudah.

📌 Fungsi yang dibuat bernama predict_behaviour() dengan parameter berikut:

  • data: Data karakteristik nilai asli debitur yang akan diprediksi skor dan behaviour.
  • score_card: Objek pembentukan score_card hasil dari fungsi scorecard()
  • cutoff: Nilai cutoff ditentukan user

Selanjutnya tinggal memanggil fungsi predict_behaviour() dengan memasukkan data debitur beserta objek score_card.

# DO NOT CHANGE
predict_behaviour <- function(data, score_card, cutoff = 529){
  new_score <- scorecard_ply(data, score_card)
  new_score <- new_score %>%
    mutate(
      recommendation = case_when(
        score > cutoff ~ "GOOD",
        TRUE ~ "BAD"
      )
    )
  new_score
}
# predict behaviour
result <- predict_behaviour(data = new_data, 
                  score_card = score_card, 
                  cutoff = 512)
result

3.10 Kesimpulan

Dengan menggunakan library ScoreCard, pengguna dapat dimudahkan untuk menentukan batasan penerimaan/ penolakan berdasarkan karakteristik data.Beberapa fungsi dapat disesuaikan berdasarkan kebutuhan pengguna dengan pertimbangan bisnis masing-masing.

3.11 Referensi

  1. Package scorecard
  2. Siddiqi, Naeem - Credit Risk Scorecards (Developing and Implementing Intelligent Credit Scoring) __ (2012, John Wiley & Sons, Inc.)
  3. Scorecard github ref
  4. Buku sampling
  5. Referensi scorecard
  6. Scorecard xgboost adaboost
  7. ChiMerge Discretization
  8. Balance Scorecard