If any issues, questions or suggestions feel free to reach me out via e-mail or Linkedin. You can also visit my Github.

This is R replication of the code and exercises from the Udemy course “Credit Risk Modeling in Python 2022”.

if(!require('pacman')) install.packages('pacman')
pacman::p_load(dplyr, stringr, lubridate, fastDummies)

Dataset comes from kaggle.com and consists of data on 466285 consumer loans and has 75 attributes.

data <- read.csv('loan_data_2007_2014.csv')
head(data)

First, we convert attribute emp_length to numeric, but instead of using multiple times str_extract function (as is done in the course) we utilize regular expression here.

data$emp_length %>% unique()
 [1] "10+ years" "< 1 year"  "1 year"    "3 years"   "8 years"   "9 years"   "4 years"   "5 years"   "6 years"  
[10] "2 years"   "7 years"   ""         
data <- data %>%
  mutate(emp_length = if_else(
    emp_length %in% c('', '< 1 year')
    ,0L
    ,str_extract(emp_length, '[0-9]{1,2}') %>% as.integer())
  )

data$emp_length %>% unique()
 [1] 10  0  1  3  8  9  4  5  6  2  7

Similarly as above with the attribute term, but this part was left as an exercise for the learner.

data$term %>% unique()
[1] " 36 months" " 60 months"
data <- data %>%
  mutate(term = str_extract(term, '[0-9]{2}') %>% as.integer())

data$term %>% unique()
[1] 36 60

Here we convert attribute earliest_cr_line to the date class and then calculate number of months between this attribute and 01-Dec-2017. Some results are negative bcos e.g. Jan-68 is converted to January 2068. In such cases we set months_since_earliest_cr_line to max of this attribute.

data<- data %>%
  mutate(earliest_cr_line = as.Date(paste0('01-', earliest_cr_line), format = '%d-%b-%y')) %>%
  mutate(months_since_earliest_cr_line = interval(earliest_cr_line, ymd(171201)) %/% months(1)) %>%
  mutate(months_since_earliest_cr_line = if_else(months_since_earliest_cr_line < 0
                                                 ,max(months_since_earliest_cr_line)
                                                 ,months_since_earliest_cr_line))

summary(data$months_since_earliest_cr_line)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
   73.0   183.0   225.0   241.5   285.0   587.0    1198 

Similarly as above with the attribute issue_d, but this part was left as an exercise for the learner.

data <- data %>%
  mutate(issue_d = as.Date(paste0('01-', issue_d), format = '%d-%b-%y')) %>%
  mutate(months_since_issue_d = interval(issue_d, ymd(171201)) %/% months(1))

summary(data$months_since_issue_d)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  36.00   41.00   47.00   51.26   57.00  126.00 

Few categorical columns are converted to dummy variables using function dummy_cols from the package fastDummies.

data <- dummy_cols(data, select_columns = c('grade', 'sub_grade', 'home_ownership', 'verification_status', 'loan_status', 'purpose', 'addr_state', 'initial_list_status'))

colnames(data)[78:ncol(data)] %>% cat()
grade_A grade_B grade_C grade_D grade_E grade_F grade_G sub_grade_A1 sub_grade_A2 sub_grade_A3 sub_grade_A4 sub_grade_A5 sub_grade_B1 sub_grade_B2 sub_grade_B3 sub_grade_B4 sub_grade_B5 sub_grade_C1 sub_grade_C2 sub_grade_C3 sub_grade_C4 sub_grade_C5 sub_grade_D1 sub_grade_D2 sub_grade_D3 sub_grade_D4 sub_grade_D5 sub_grade_E1 sub_grade_E2 sub_grade_E3 sub_grade_E4 sub_grade_E5 sub_grade_F1 sub_grade_F2 sub_grade_F3 sub_grade_F4 sub_grade_F5 sub_grade_G1 sub_grade_G2 sub_grade_G3 sub_grade_G4 sub_grade_G5 home_ownership_ANY home_ownership_MORTGAGE home_ownership_NONE home_ownership_OTHER home_ownership_OWN home_ownership_RENT verification_status_Not Verified verification_status_Source Verified verification_status_Verified loan_status_Charged Off loan_status_Current loan_status_Default loan_status_Does not meet the credit policy. Status:Charged Off loan_status_Does not meet the credit policy. Status:Fully Paid loan_status_Fully Paid loan_status_In Grace Period loan_status_Late (16-30 days) loan_status_Late (31-120 days) purpose_car purpose_credit_card purpose_debt_consolidation purpose_educational purpose_home_improvement purpose_house purpose_major_purchase purpose_medical purpose_moving purpose_other purpose_renewable_energy purpose_small_business purpose_vacation purpose_wedding addr_state_AK addr_state_AL addr_state_AR addr_state_AZ addr_state_CA addr_state_CO addr_state_CT addr_state_DC addr_state_DE addr_state_FL addr_state_GA addr_state_HI addr_state_IA addr_state_ID addr_state_IL addr_state_IN addr_state_KS addr_state_KY addr_state_LA addr_state_MA addr_state_MD addr_state_ME addr_state_MI addr_state_MN addr_state_MO addr_state_MS addr_state_MT addr_state_NC addr_state_NE addr_state_NH addr_state_NJ addr_state_NM addr_state_NV addr_state_NY addr_state_OH addr_state_OK addr_state_OR addr_state_PA addr_state_RI addr_state_SC addr_state_SD addr_state_TN addr_state_TX addr_state_UT addr_state_VA addr_state_VT addr_state_WA addr_state_WI addr_state_WV addr_state_WY initial_list_status_f initial_list_status_w

Imputing missing values left as an exercise for the learner:
- annual_income missing values replaced with average income
- missing values for few other attributes replaced with 0.

data <- data %>%
  mutate(annual_inc = if_else(is.na(annual_inc), mean(annual_inc, na.rm = TRUE), annual_inc))

cols <- c('months_since_earliest_cr_line', 'acc_now_delinq', 'total_acc', 'pub_rec'
          ,'open_acc', 'inq_last_6mths', 'delinq_2yrs')

data %>% select(cols) %>% sapply(function(x) is.na(x) %>% sum())
months_since_earliest_cr_line                acc_now_delinq                     total_acc 
                         1198                            29                            29 
                      pub_rec                      open_acc                inq_last_6mths 
                           29                            29                            29 
                  delinq_2yrs 
                           29 
for (col in cols) {
  data[[col]] <-  if_else(is.na(data[[col]]), 0, data[[col]])
}

data %>% select(cols) %>% sapply(function(x) is.na(x) %>% sum())
months_since_earliest_cr_line                acc_now_delinq                     total_acc 
                            0                             0                             0 
                      pub_rec                      open_acc                inq_last_6mths 
                            0                             0                             0 
                  delinq_2yrs 
                            0 
write.csv(data, 'preprocessed_loan_data_2007_2014.csv', row.names = FALSE)
LS0tDQp0aXRsZTogIkNyZWRpdCBSaXNrIE1vZGVsaW5nIC0gRGF0YSBQcmVwcm9jZXNzaW5nIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KSWYgYW55IGlzc3VlcywgcXVlc3Rpb25zIG9yIHN1Z2dlc3Rpb25zIGZlZWwgZnJlZSB0byByZWFjaCBtZSBvdXQgdmlhIGUtbWFpbCA8d2llY3p5bnNraXBhd2VsQGdtYWlsLmNvbT4gb3IgW0xpbmtlZGluXShodHRwczovL3d3dy5saW5rZWRpbi5jb20vaW4vcGF3ZWwtd2llY3p5bnNraS8pLiBZb3UgY2FuIGFsc28gdmlzaXQgbXkgW0dpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3Bhd2VsLXdpZWN6eW5za2kpLg0KDQpUaGlzIGlzIFIgcmVwbGljYXRpb24gb2YgdGhlIGNvZGUgYW5kIGV4ZXJjaXNlcyBmcm9tIHRoZSBVZGVteSBjb3Vyc2UgWyJDcmVkaXQgUmlzayBNb2RlbGluZyBpbiBQeXRob24gMjAyMiJdKGh0dHBzOi8vd3d3LnVkZW15LmNvbS9jb3Vyc2UvY3JlZGl0LXJpc2stbW9kZWxpbmctaW4tcHl0aG9uLykuDQoNCmBgYHtyIGxvYWRfbGlicmFyaWVzfQ0KaWYoIXJlcXVpcmUoJ3BhY21hbicpKSBpbnN0YWxsLnBhY2thZ2VzKCdwYWNtYW4nKQ0KcGFjbWFuOjpwX2xvYWQoZHBseXIsIHN0cmluZ3IsIGx1YnJpZGF0ZSwgZmFzdER1bW1pZXMpDQpgYGANCg0KRGF0YXNldCBjb21lcyBmcm9tIGthZ2dsZS5jb20gYW5kIGNvbnNpc3RzIG9mIGRhdGEgb24gNDY2Mjg1IGNvbnN1bWVyIGxvYW5zIGFuZCBoYXMgNzUgYXR0cmlidXRlcy4NCmBgYHtyIGxvYWRfZGF0YX0NCmRhdGEgPC0gcmVhZC5jc3YoJ2xvYW5fZGF0YV8yMDA3XzIwMTQuY3N2JykNCmhlYWQoZGF0YSkNCmBgYA0KDQpGaXJzdCwgd2UgY29udmVydCBhdHRyaWJ1dGUgKmVtcF9sZW5ndGgqIHRvIG51bWVyaWMsIGJ1dCBpbnN0ZWFkIG9mIHVzaW5nIG11bHRpcGxlIHRpbWVzIHN0cl9leHRyYWN0IGZ1bmN0aW9uIChhcyBpcyBkb25lIGluIHRoZSBjb3Vyc2UpIHdlIHV0aWxpemUgcmVndWxhciBleHByZXNzaW9uIGhlcmUuDQpgYGB7ciBlbXBfbGVuZ3RofQ0KZGF0YSRlbXBfbGVuZ3RoICU+JSB1bmlxdWUoKQ0KDQpkYXRhIDwtIGRhdGEgJT4lDQogIG11dGF0ZShlbXBfbGVuZ3RoID0gaWZfZWxzZSgNCiAgICBlbXBfbGVuZ3RoICVpbiUgYygnJywgJzwgMSB5ZWFyJykNCiAgICAsMEwNCiAgICAsc3RyX2V4dHJhY3QoZW1wX2xlbmd0aCwgJ1swLTldezEsMn0nKSAlPiUgYXMuaW50ZWdlcigpKQ0KICApDQoNCmRhdGEkZW1wX2xlbmd0aCAlPiUgdW5pcXVlKCkNCmBgYA0KDQpTaW1pbGFybHkgYXMgYWJvdmUgd2l0aCB0aGUgYXR0cmlidXRlICp0ZXJtKiwgYnV0IHRoaXMgcGFydCB3YXMgbGVmdCBhcyBhbiBleGVyY2lzZSBmb3IgdGhlIGxlYXJuZXIuDQpgYGB7ciB0ZXJtfQ0KZGF0YSR0ZXJtICU+JSB1bmlxdWUoKQ0KDQpkYXRhIDwtIGRhdGEgJT4lDQogIG11dGF0ZSh0ZXJtID0gc3RyX2V4dHJhY3QodGVybSwgJ1swLTldezJ9JykgJT4lIGFzLmludGVnZXIoKSkNCg0KZGF0YSR0ZXJtICU+JSB1bmlxdWUoKQ0KYGBgDQoNCkhlcmUgd2UgY29udmVydCBhdHRyaWJ1dGUgKmVhcmxpZXN0X2NyX2xpbmUqIHRvIHRoZSBkYXRlIGNsYXNzIGFuZCB0aGVuIGNhbGN1bGF0ZSBudW1iZXIgb2YgbW9udGhzIGJldHdlZW4gdGhpcyBhdHRyaWJ1dGUgYW5kIDAxLURlYy0yMDE3LiBTb21lIHJlc3VsdHMgYXJlIG5lZ2F0aXZlIGJjb3MgZS5nLiBKYW4tNjggaXMgY29udmVydGVkIHRvIEphbnVhcnkgMjA2OC4gSW4gc3VjaCBjYXNlcyB3ZSBzZXQgKm1vbnRoc19zaW5jZV9lYXJsaWVzdF9jcl9saW5lKiB0byBtYXggb2YgdGhpcyBhdHRyaWJ1dGUuDQpgYGB7ciBlYXJsaWVzdF9jcl9saW5lfQ0KZGF0YTwtIGRhdGEgJT4lDQogIG11dGF0ZShlYXJsaWVzdF9jcl9saW5lID0gYXMuRGF0ZShwYXN0ZTAoJzAxLScsIGVhcmxpZXN0X2NyX2xpbmUpLCBmb3JtYXQgPSAnJWQtJWItJXknKSkgJT4lDQogIG11dGF0ZShtb250aHNfc2luY2VfZWFybGllc3RfY3JfbGluZSA9IGludGVydmFsKGVhcmxpZXN0X2NyX2xpbmUsIHltZCgxNzEyMDEpKSAlLyUgbW9udGhzKDEpKSAlPiUNCiAgbXV0YXRlKG1vbnRoc19zaW5jZV9lYXJsaWVzdF9jcl9saW5lID0gaWZfZWxzZShtb250aHNfc2luY2VfZWFybGllc3RfY3JfbGluZSA8IDANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsbWF4KG1vbnRoc19zaW5jZV9lYXJsaWVzdF9jcl9saW5lKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICxtb250aHNfc2luY2VfZWFybGllc3RfY3JfbGluZSkpDQoNCnN1bW1hcnkoZGF0YSRtb250aHNfc2luY2VfZWFybGllc3RfY3JfbGluZSkNCmBgYA0KDQpTaW1pbGFybHkgYXMgYWJvdmUgd2l0aCB0aGUgYXR0cmlidXRlICppc3N1ZV9kKiwgYnV0IHRoaXMgcGFydCB3YXMgbGVmdCBhcyBhbiBleGVyY2lzZSBmb3IgdGhlIGxlYXJuZXIuDQpgYGB7ciBpc3N1ZV9kYXRlfQ0KZGF0YSA8LSBkYXRhICU+JQ0KICBtdXRhdGUoaXNzdWVfZCA9IGFzLkRhdGUocGFzdGUwKCcwMS0nLCBpc3N1ZV9kKSwgZm9ybWF0ID0gJyVkLSViLSV5JykpICU+JQ0KICBtdXRhdGUobW9udGhzX3NpbmNlX2lzc3VlX2QgPSBpbnRlcnZhbChpc3N1ZV9kLCB5bWQoMTcxMjAxKSkgJS8lIG1vbnRocygxKSkNCg0Kc3VtbWFyeShkYXRhJG1vbnRoc19zaW5jZV9pc3N1ZV9kKQ0KYGBgDQoNCkZldyBjYXRlZ29yaWNhbCBjb2x1bW5zIGFyZSBjb252ZXJ0ZWQgdG8gZHVtbXkgdmFyaWFibGVzIHVzaW5nIGZ1bmN0aW9uICpkdW1teV9jb2xzKiBmcm9tIHRoZSBwYWNrYWdlICpmYXN0RHVtbWllcyouDQpgYGB7ciBkdW1teX0NCmRhdGEgPC0gZHVtbXlfY29scyhkYXRhLCBzZWxlY3RfY29sdW1ucyA9IGMoJ2dyYWRlJywgJ3N1Yl9ncmFkZScsICdob21lX293bmVyc2hpcCcsICd2ZXJpZmljYXRpb25fc3RhdHVzJywgJ2xvYW5fc3RhdHVzJywgJ3B1cnBvc2UnLCAnYWRkcl9zdGF0ZScsICdpbml0aWFsX2xpc3Rfc3RhdHVzJykpDQoNCmNvbG5hbWVzKGRhdGEpWzc4Om5jb2woZGF0YSldICU+JSBjYXQoKQ0KYGBgDQoNCkltcHV0aW5nIG1pc3NpbmcgdmFsdWVzIGxlZnQgYXMgYW4gZXhlcmNpc2UgZm9yIHRoZSBsZWFybmVyOiBcDQogLSAqYW5udWFsX2luY29tZSogbWlzc2luZyB2YWx1ZXMgcmVwbGFjZWQgd2l0aCBhdmVyYWdlIGluY29tZSBcDQogLSBtaXNzaW5nIHZhbHVlcyBmb3IgZmV3IG90aGVyIGF0dHJpYnV0ZXMgcmVwbGFjZWQgd2l0aCAwLg0KYGBge3IgaW1wdXRhdGlvbn0NCmRhdGEgPC0gZGF0YSAlPiUNCiAgbXV0YXRlKGFubnVhbF9pbmMgPSBpZl9lbHNlKGlzLm5hKGFubnVhbF9pbmMpLCBtZWFuKGFubnVhbF9pbmMsIG5hLnJtID0gVFJVRSksIGFubnVhbF9pbmMpKQ0KDQpjb2xzIDwtIGMoJ21vbnRoc19zaW5jZV9lYXJsaWVzdF9jcl9saW5lJywgJ2FjY19ub3dfZGVsaW5xJywgJ3RvdGFsX2FjYycsICdwdWJfcmVjJw0KICAgICAgICAgICwnb3Blbl9hY2MnLCAnaW5xX2xhc3RfNm10aHMnLCAnZGVsaW5xXzJ5cnMnKQ0KDQpkYXRhICU+JSBzZWxlY3QoY29scykgJT4lIHNhcHBseShmdW5jdGlvbih4KSBpcy5uYSh4KSAlPiUgc3VtKCkpDQoNCmZvciAoY29sIGluIGNvbHMpIHsNCiAgZGF0YVtbY29sXV0gPC0gIGlmX2Vsc2UoaXMubmEoZGF0YVtbY29sXV0pLCAwLCBkYXRhW1tjb2xdXSkNCn0NCg0KZGF0YSAlPiUgc2VsZWN0KGNvbHMpICU+JSBzYXBwbHkoZnVuY3Rpb24oeCkgaXMubmEoeCkgJT4lIHN1bSgpKQ0KYGBgDQoNCmBgYHtyIHdyaXRlfQ0Kd3JpdGUuY3N2KGRhdGEsICdwcmVwcm9jZXNzZWRfbG9hbl9kYXRhXzIwMDdfMjAxNC5jc3YnLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQo=