1  Woe Binning

2 Woe Binning

Курс: “Матетичне моделювання в R”


# install.packages("openxlsx")
Sys.setlocale("LC_CTYPE", "ukrainian")
options(warn = -1)
'Ukrainian_Ukraine.1251'

2.1 Набір даних

Джерело: https://github.com/gastonstat/CreditScoring/blob/master/CleanCreditScoring.csv

Завантажимо дані:

library(openxlsx)
data <- openxlsx::read.xlsx("data/CreditScoring.xlsx", sheet = 1, startRow = 1, colNames = TRUE, rowNames = FALSE)
str(data)
'data.frame':   4446 obs. of  16 variables:
 $ Status   : chr  "good" "good" "bad" "good" ...
 $ Seniority: num  9 17 10 0 0 1 29 9 0 0 ...
 $ Home     : chr  "rent" "rent" "owner" "rent" ...
 $ Time     : num  60 60 36 60 36 60 60 12 60 48 ...
 $ Age      : num  30 58 46 24 26 36 44 27 32 41 ...
 $ Marital  : chr  "married" "widow" "married" "single" ...
 $ Records  : chr  "no_rec" "no_rec" "yes_rec" "no_rec" ...
 $ Job      : chr  "freelance" "fixed" "freelance" "fixed" ...
 $ Expenses : num  73 48 90 63 46 75 75 35 90 90 ...
 $ Income   : num  129 131 200 182 107 214 125 80 107 80 ...
 $ Assets   : num  0 0 3000 2500 0 3500 10000 0 15000 0 ...
 $ Debt     : num  0 0 0 0 0 0 0 0 0 0 ...
 $ Amount   : num  800 1000 2000 900 310 650 1600 200 1200 1200 ...
 $ Price    : num  846 1658 2985 1325 910 ...
 $ Finrat   : num  94.6 60.3 67 67.9 34.1 ...
 $ Savings  : num  4.2 4.98 1.98 7.93 7.08 ...

Опишемо дані:

  • Status - credit status (Target)
  • Seniority job seniority (years)
  • Home type of home ownership
  • Time time of requested loan
  • Age client’s age
  • Marital marital status
  • Records existance of records
  • Job type of job
  • Expenses amount of expenses
  • Income amount of income
  • Assets amount of assets
  • Debt amount of debt
  • Amount amount requested of loan
  • Price price of good

2.2 Простий бінінг змінних

2.2.1 Категоріальна змінна

Створимо дата-фрейм для зберігання інформації про групи змінної Home (як приклад біннігу категоріального показника):

home_groups <- data.frame(Group = unique(data$Home), 
                          Good = c(0), Bad = c(0), 
                          GoodP = c(0), BadP = c(0),
                          WOE = c(0), IV = c(0))
home_groups
A data.frame: 6 × 7
GroupGoodBadGoodPBadPWOEIV
<chr><dbl><dbl><dbl><dbl><dbl><dbl>
rent 000000
owner 000000
parents000000
priv 000000
other 000000
ignore 000000

Переглянемо можливі варіанти показника Status, який є залежною бінарною змінною поточної задачі:

library(gmodels)
CrossTable(data$Status)

 
   Cell Contents
|-------------------------|
|                       N |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  4446 

 
          |       bad |      good | 
          |-----------|-----------|
          |      1249 |      3197 | 
          |     0.281 |     0.719 | 
          |-----------|-----------|



 

Сформуємо групи та обчислимо значення по кожній групі:

for(i in 1:nrow(home_groups)) {
  
  group <- home_groups$Group[i]
  
  home_groups$Good[i] <- nrow(data[data$Home == group & data$Status == "good", ])
  home_groups$Bad[i] <- nrow(data[data$Home == group & data$Status == "bad", ])
  
  home_groups$GoodP[i] <- home_groups$Good[i]/nrow(data[data$Status == "good", ])
  home_groups$BadP[i] <- home_groups$Bad[i]/nrow(data[data$Status == "bad", ])
  
  home_groups$WOE[i] <- log( home_groups$GoodP[i] / home_groups$BadP[i])
  home_groups$IV[i] <- (home_groups$GoodP[i] - home_groups$BadP[i])*home_groups$WOE[i]
}

home_groups
A data.frame: 6 × 7
GroupGoodBadGoodPBadPWOEIV
<chr><dbl><dbl><dbl><dbl><dbl><dbl>
rent 5853880.1829840480.310648519-0.529263130.067568098
owner 17163900.5367532060.312249800 0.541734900.121621331
parents 5502320.1720362840.185748599-0.076688730.001051580
priv 162 840.0506725050.067253803-0.283090100.004694001
other 1731460.0541132310.116893515-0.770184670.048352412
ignore 11 90.0034407260.007205765-0.739198940.002783113

Переглянемо сумарний IV:

home_iv <- sum(home_groups$IV)
home_iv
0.246070534510342

Візуалізуємо групи:

barplot(home_groups$WOE, 
        col="brown", 
        names.arg=c(as.character(home_groups$Group)), 
        xlab="Group",
        ylab="WOE"
)

Створимо датафрейм для нових WOE-даних:

new_df <- data.frame(Status = data$Status, Home = data$Home, HomeWoe = c(0))

Замінимо значення на WOE:

for(i in 1:nrow(home_groups)) {
  group <- home_groups$Group[i]
  woe <- home_groups$WOE[i]  
  new_df[new_df$Home == group, ]$HomeWoe <- woe
}

tail(new_df)
A data.frame: 6 × 3
StatusHomeHomeWoe
<chr><chr><dbl>
4441bad other-0.7701847
4442bad rent -0.5292631
4443goodowner 0.5417349
4444bad owner 0.5417349
4445goodrent -0.5292631
4446goodowner 0.5417349

2.2.2 Числова змінна

Обрахуємо приклад числовиго показника (на прикладі Age):

min_age <- min(data$Age)
max_age <- max(data$Age)

step <- round(max_age - min_age)/10


age_groups <- data.frame(Min = seq(min_age, max_age-step, step), 
                         Max = seq(min_age + step, max_age, step), 
                          Good = c(0), Bad = c(0), 
                          GoodP = c(0), BadP = c(0),
                          WOE = c(0), IV = c(0))

age_groups
A data.frame: 10 × 8
MinMaxGoodBadGoodPBadPWOEIV
<dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl>
1823000000
2328000000
2833000000
3338000000
3843000000
4348000000
4853000000
5358000000
5863000000
6368000000

Сформуємо групи:

for(i in 1:nrow(age_groups)) {
  
  min <- age_groups$Min[i]
  max<- age_groups$Max[i]
  
  age_groups$Good[i] <- nrow(data[data$Age >= min & data$Age < max & data$Status == "good", ])
  age_groups$Bad[i] <- nrow(data[data$Age >= min & data$Age < max & data$Status == "bad", ])
  
  if(i == nrow(age_groups)) {
    age_groups$Good[i] <- age_groups$Good[i] + nrow(data[data$Age == max & data$Status == "good", ])
    age_groups$Bad[i] <- age_groups$Bad[i] + nrow(data[data$Age == max & data$Status == "bad", ])
  }
  
  age_groups$GoodP[i] <- age_groups$Good[i]/nrow(data[data$Status == "good", ])
  age_groups$BadP[i] <- age_groups$Bad[i]/nrow(data[data$Status == "bad", ])
  
  age_groups$WOE[i] <- log( age_groups$GoodP[i] / age_groups$BadP[i])
  
  age_groups$IV[i] <- (age_groups$GoodP[i] - age_groups$BadP[i]) * age_groups$WOE[i]
}

age_groups
A data.frame: 10 × 8
MinMaxGoodBadGoodPBadPWOEIV
<dbl><dbl><dbl><dbl><dbl><dbl><dbl><dbl>
18231691100.052862060.088070456-0.510451290.0179721723
23285052310.157960590.184947958-0.157728920.0042566888
28335642130.176415390.170536429 0.033892450.0001992523
33384912120.153581480.169735789-0.100011790.0016156210
38434431830.138567410.146517214-0.055786020.0004434881
43483201230.100093840.098478783 0.016267000.0000262721
4853317 790.099155460.063250600 0.449584280.0161422597
5358214 580.066937750.046437150 0.365663370.0074963200
5863124 310.038786360.024819856 0.446424720.0062349937
6368 50 90.015639660.007205765 0.774928790.0065356700

Сумарний IV:

age_iv <- sum(age_groups$IV)
age_iv
0.0609227380464588

Візуалізуємо значення груп WOE:

barplot(age_groups$WOE, 
        col="brown", 
        names.arg=c(age_groups$Min), 
        xlab="Min Age",
        ylab="WOE"
)

Замінимо значення на WOE:

new_df$Age <- data$Age
new_df$AgeWoe <- c(0)

for(i in 1:nrow(age_groups)) {
  
  min <- age_groups$Min[i]
  max <- age_groups$Max[i]
  woe <- age_groups$WOE[i]
  
  new_df[new_df$Age >= min & new_df$Age < max, ]$AgeWoe <- woe
  
  if(i == nrow(age_groups)) {
    new_df$AgeWoe[i] <- woe
  }
  
}

head(new_df)
A data.frame: 6 × 5
StatusHomeHomeWoeAgeAgeWoe
<chr><chr><dbl><dbl><dbl>
1goodrent -0.529263130 0.03389245
2goodrent -0.529263158 0.44642472
3bad owner 0.541734946 0.01626700
4goodrent -0.529263124-0.15772892
5goodrent -0.529263126-0.15772892
6goodowner 0.541734936-0.10001179

Видалимо оригінальні значення з набору даних:

new_df$Home <- NULL
new_df$Age <- NULL
head(new_df)
A data.frame: 6 × 3
StatusHomeWoeAgeWoe
<chr><dbl><dbl>
1good-0.5292631 0.03389245
2good-0.5292631 0.44642472
3bad 0.5417349 0.01626700
4good-0.5292631-0.15772892
5good-0.5292631-0.15772892
6good 0.5417349-0.10001179

2.3 Бінінг з використанням woeBin


2.4 Бінінг з використанням rBin


2.5 Бінінг з використанням smbinning


2.6 Бінінг з використанням scorecard