1 Introducción
La base de datos consiste en clientes de una institución bancaria donde el objetivo es entender el default de pago en tarjetas de crédito en el mercado de Taiwan con el fin de dar claridad acerca de la base de clientes al equipo de riesgo de crédito.
La variable dependiente (target) es ‘default payment next month’ consiste en una variable binaria definida como
\[ y = \begin{cases} 1 & \text{El cliente ha tenido default en pago TC} \\ 0 & \text{El cliente no ha tenido default en pago TC} \end{cases} \]
Source: https://archive.ics.uci.edu/dataset/350/default+of+credit+card+clients
2 Descripción de Variables
Las variables que provienen en el dataset se componen en la sig. estructura
| Variable | Descripción |
|---|---|
| ID | Identificador único por cliente |
| LIMIT_BAL | Balance aprobado en TC |
| SEX | Género cliente (1 = masculino; 2 = femenino) |
| EDUCATION | Nivel Educativo (0=sin escolaridad; 1 =escuela; 2 = universidad; 3 = bachillerato) |
| MARRIAGE | Estado Civil (1 = casado; 2 = soltero; 3 = otros) |
| AGE | Edad |
| PAY_0,PAY_2 - PAY_6 | Histórico de pagos desde Abril a Sep 2005. PAY_6: pago mes Sep, PAY_5: pago mes Ago, … , PAY_1: pago Abr. Los valores de status de pago: -1 = pago al día, 1 = retraso pago 1 mes, 2 = retraso pago 2 meses, … , 9= retraso de pago por ≥ 9 meses |
| BILL_AMT1 - BILL_AMT6 | Monto total adeudado por mes desde Abril a Sep 2005 |
| PAY_AMT1 - PAY_AMT6 | Monto total pagado por mes desde Abril a Sep 2005 |
3 Tratamiento de los Datos
Se realizarán correcciones y ajustes a los datos que permitan generar el EDA (Exploratory Data Analysis) de las variables. El primer paso consistirá en leer los datos, clasificar las variables cuantitativas, cualitativas, target, limpieza de algunas variables, determinar si los ID de los clientes son únicos
library(tidyverse)
library(dplyr)
library(readxl)
library(ggplot2)
library(tidymodels)
library(skedastic)
library(tseries)
library(regclass)
library(lmtest)
library(vip)
library(ranger)
library(xgboost)
setwd("D:/Documents/Big Data Master/Visualización de Datos/Default Credit Card")
data <- read_excel("default of credit card clients.xls",
sheet = "Data", skip = 1)
str(data)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 ...
Las variables Sex, Marriage, default payment next month son categóricas nominales, Education categórica ordinal, se procederán a transformar en factor
# Esto garantiza cambiar el tipo de dato para múltiples variables al mismo tiempo
data<- data %>%
mutate_at(.vars = vars(SEX,EDUCATION,MARRIAGE,"default payment next month"),.fun = factor)
summary(data) ID LIMIT_BAL SEX EDUCATION MARRIAGE
Min. : 1 Min. : 10000 1:11888 0: 14 0: 54
1st Qu.: 7501 1st Qu.: 50000 2:18112 1:10585 1:13659
Median :15000 Median : 140000 2:14030 2:15964
Mean :15000 Mean : 167484 3: 4917 3: 323
3rd Qu.:22500 3rd Qu.: 240000 4: 123
Max. :30000 Max. :1000000 5: 280
6: 51
AGE PAY_0 PAY_2 PAY_3
Min. :21.00 Min. :-2.0000 Min. :-2.0000 Min. :-2.0000
1st Qu.:28.00 1st Qu.:-1.0000 1st Qu.:-1.0000 1st Qu.:-1.0000
Median :34.00 Median : 0.0000 Median : 0.0000 Median : 0.0000
Mean :35.49 Mean :-0.0167 Mean :-0.1338 Mean :-0.1662
3rd Qu.:41.00 3rd Qu.: 0.0000 3rd Qu.: 0.0000 3rd Qu.: 0.0000
Max. :79.00 Max. : 8.0000 Max. : 8.0000 Max. : 8.0000
PAY_4 PAY_5 PAY_6 BILL_AMT1
Min. :-2.0000 Min. :-2.0000 Min. :-2.0000 Min. :-165580
1st Qu.:-1.0000 1st Qu.:-1.0000 1st Qu.:-1.0000 1st Qu.: 3559
Median : 0.0000 Median : 0.0000 Median : 0.0000 Median : 22382
Mean :-0.2207 Mean :-0.2662 Mean :-0.2911 Mean : 51223
3rd Qu.: 0.0000 3rd Qu.: 0.0000 3rd Qu.: 0.0000 3rd Qu.: 67091
Max. : 8.0000 Max. : 8.0000 Max. : 8.0000 Max. : 964511
BILL_AMT2 BILL_AMT3 BILL_AMT4 BILL_AMT5
Min. :-69777 Min. :-157264 Min. :-170000 Min. :-81334
1st Qu.: 2985 1st Qu.: 2666 1st Qu.: 2327 1st Qu.: 1763
Median : 21200 Median : 20089 Median : 19052 Median : 18105
Mean : 49179 Mean : 47013 Mean : 43263 Mean : 40311
3rd Qu.: 64006 3rd Qu.: 60165 3rd Qu.: 54506 3rd Qu.: 50191
Max. :983931 Max. :1664089 Max. : 891586 Max. :927171
BILL_AMT6 PAY_AMT1 PAY_AMT2 PAY_AMT3
Min. :-339603 Min. : 0 Min. : 0 Min. : 0
1st Qu.: 1256 1st Qu.: 1000 1st Qu.: 833 1st Qu.: 390
Median : 17071 Median : 2100 Median : 2009 Median : 1800
Mean : 38872 Mean : 5664 Mean : 5921 Mean : 5226
3rd Qu.: 49198 3rd Qu.: 5006 3rd Qu.: 5000 3rd Qu.: 4505
Max. : 961664 Max. :873552 Max. :1684259 Max. :896040
PAY_AMT4 PAY_AMT5 PAY_AMT6
Min. : 0 Min. : 0.0 Min. : 0.0
1st Qu.: 296 1st Qu.: 252.5 1st Qu.: 117.8
Median : 1500 Median : 1500.0 Median : 1500.0
Mean : 4826 Mean : 4799.4 Mean : 5215.5
3rd Qu.: 4013 3rd Qu.: 4031.5 3rd Qu.: 4000.0
Max. :621000 Max. :426529.0 Max. :528666.0
default payment next month
0:23364
1: 6636
Se puede observar que las variables han sido transformadas en factor, se puede detallar valores de variables que se encuentran fuera del contexto de descripción observado en la tabla de referencia anterior, se procederá al tratamiento de las mismas. Se identificará unicidad de ID lo cual indica que 1 cliente aparece sólo 1 vez y no hay duplicidad de clientes
# Verificando unicidad de ID
length(data$ID)[1] 30000
n_distinct(data$ID)[1] 30000
En efecto, los clientes son únicos. Acorde a las descripciones de la fuente de datos, los valores de la variable EDUCATION poseen los valores 0=sin escolaridad; 1 =escuela; 2 = universidad; 3 = bachillerato; por tanto,los valores 5,6 que aparecen en la BD serán reemplazados por la categoría 2 - universidad. Para esta variable, se codificará con los valores ordinales definiendo, así, el orden de los valores.
Los valores MARRIAGE sólo poseen las descripciones Estado Civil (1 = casado; 2 = soltero; 3 = otros) el valor 0 no pertenece a ninguna categoría, por tanto, se colocará dentro de la categoría otros
La variable sexo será transformada a los valores Género del cliente (1 = masculino; 2 = femenino)
data<- data %>%
mutate(EDUCATION = recode(EDUCATION,'0'='Sin Escolaridad','4'='Universidad','5'='Universidad','6'='Universidad','1'='Escuela','2'='Universidad','3'='Bachillerato')) %>%
mutate(SEX = recode(SEX,'1'='Masculino','2'='Femenino')) %>%
mutate(MARRIAGE = recode(MARRIAGE,'0'='Otros','1'='Casado','2'='Soltero','3'='Otros'))
# Establecer EDUCATION categórica ordinal
data$EDUCATION <- factor(data$EDUCATION,
levels = c("Sin Escolaridad","Escuela","Bachillerato","Universidad"),
ordered = TRUE)
levels(data$EDUCATION)[1] "Sin Escolaridad" "Escuela" "Bachillerato" "Universidad"
La variable PAY_i posee rangos de valores defasados por 1 unidad, se incrementarán en 1 para ajustar acorde a la descripción de la base de datos. PAY_0 será renombrado como PAY_1 como inicio de pago
for(i in 7:12){
data[i]<-data[i]+1
}
data <- data %>% rename(PAY_1 = PAY_0)
min(data$PAY_2)[1] -1
max(data$PAY_2)[1] 9
Se revisarán si existen valores nulos en los datos
sum(apply(data,2, function(x) is.na(x)))[1] 0
El total de registros con NA es 0, por tanto, no es necesario hacer limpieza de valores NA
Se chequeará revisión de correlación entre las variables, determinando aquellas que posean un índice mayor de 80%. Primero, se definen los tipos de variables
continuas <- c("LIMIT_BAL","AGE","BILL_AMT1", "BILL_AMT2", "BILL_AMT3","BILL_AMT4", "BILL_AMT5", "BILL_AMT6","PAY_AMT1", "PAY_AMT2", "PAY_AMT3","PAY_AMT4", "PAY_AMT5", "PAY_AMT6")
discretas <- c("PAY_1", "PAY_2", "PAY_3",
"PAY_4", "PAY_5", "PAY_6")
categoricas <- c("SEX","EDUCATION","MARRIAGE")
target <- "default payment next month"
res <- cor(data[c(continuas,discretas)])
round(res, 2) LIMIT_BAL AGE BILL_AMT1 BILL_AMT2 BILL_AMT3 BILL_AMT4 BILL_AMT5
LIMIT_BAL 1.00 0.14 0.29 0.28 0.28 0.29 0.30
AGE 0.14 1.00 0.06 0.05 0.05 0.05 0.05
BILL_AMT1 0.29 0.06 1.00 0.95 0.89 0.86 0.83
BILL_AMT2 0.28 0.05 0.95 1.00 0.93 0.89 0.86
BILL_AMT3 0.28 0.05 0.89 0.93 1.00 0.92 0.88
BILL_AMT4 0.29 0.05 0.86 0.89 0.92 1.00 0.94
BILL_AMT5 0.30 0.05 0.83 0.86 0.88 0.94 1.00
BILL_AMT6 0.29 0.05 0.80 0.83 0.85 0.90 0.95
PAY_AMT1 0.20 0.03 0.14 0.28 0.24 0.23 0.22
PAY_AMT2 0.18 0.02 0.10 0.10 0.32 0.21 0.18
PAY_AMT3 0.21 0.03 0.16 0.15 0.13 0.30 0.25
PAY_AMT4 0.20 0.02 0.16 0.15 0.14 0.13 0.29
PAY_AMT5 0.22 0.02 0.17 0.16 0.18 0.16 0.14
PAY_AMT6 0.22 0.02 0.18 0.17 0.18 0.18 0.16
PAY_1 -0.27 -0.04 0.19 0.19 0.18 0.18 0.18
PAY_2 -0.30 -0.05 0.23 0.24 0.22 0.22 0.22
PAY_3 -0.29 -0.05 0.21 0.24 0.23 0.23 0.23
PAY_4 -0.27 -0.05 0.20 0.23 0.24 0.25 0.24
PAY_5 -0.25 -0.05 0.21 0.23 0.24 0.27 0.27
PAY_6 -0.24 -0.05 0.21 0.23 0.24 0.27 0.29
BILL_AMT6 PAY_AMT1 PAY_AMT2 PAY_AMT3 PAY_AMT4 PAY_AMT5 PAY_AMT6 PAY_1
LIMIT_BAL 0.29 0.20 0.18 0.21 0.20 0.22 0.22 -0.27
AGE 0.05 0.03 0.02 0.03 0.02 0.02 0.02 -0.04
BILL_AMT1 0.80 0.14 0.10 0.16 0.16 0.17 0.18 0.19
BILL_AMT2 0.83 0.28 0.10 0.15 0.15 0.16 0.17 0.19
BILL_AMT3 0.85 0.24 0.32 0.13 0.14 0.18 0.18 0.18
BILL_AMT4 0.90 0.23 0.21 0.30 0.13 0.16 0.18 0.18
BILL_AMT5 0.95 0.22 0.18 0.25 0.29 0.14 0.16 0.18
BILL_AMT6 1.00 0.20 0.17 0.23 0.25 0.31 0.12 0.18
PAY_AMT1 0.20 1.00 0.29 0.25 0.20 0.15 0.19 -0.08
PAY_AMT2 0.17 0.29 1.00 0.24 0.18 0.18 0.16 -0.07
PAY_AMT3 0.23 0.25 0.24 1.00 0.22 0.16 0.16 -0.07
PAY_AMT4 0.25 0.20 0.18 0.22 1.00 0.15 0.16 -0.06
PAY_AMT5 0.31 0.15 0.18 0.16 0.15 1.00 0.15 -0.06
PAY_AMT6 0.12 0.19 0.16 0.16 0.16 0.15 1.00 -0.06
PAY_1 0.18 -0.08 -0.07 -0.07 -0.06 -0.06 -0.06 1.00
PAY_2 0.22 -0.08 -0.06 -0.06 -0.05 -0.04 -0.04 0.67
PAY_3 0.22 0.00 -0.07 -0.05 -0.05 -0.04 -0.04 0.57
PAY_4 0.24 -0.01 0.00 -0.07 -0.04 -0.03 -0.03 0.54
PAY_5 0.26 -0.01 0.00 0.01 -0.06 -0.03 -0.02 0.51
PAY_6 0.29 0.00 -0.01 0.01 0.02 -0.05 -0.03 0.47
PAY_2 PAY_3 PAY_4 PAY_5 PAY_6
LIMIT_BAL -0.30 -0.29 -0.27 -0.25 -0.24
AGE -0.05 -0.05 -0.05 -0.05 -0.05
BILL_AMT1 0.23 0.21 0.20 0.21 0.21
BILL_AMT2 0.24 0.24 0.23 0.23 0.23
BILL_AMT3 0.22 0.23 0.24 0.24 0.24
BILL_AMT4 0.22 0.23 0.25 0.27 0.27
BILL_AMT5 0.22 0.23 0.24 0.27 0.29
BILL_AMT6 0.22 0.22 0.24 0.26 0.29
PAY_AMT1 -0.08 0.00 -0.01 -0.01 0.00
PAY_AMT2 -0.06 -0.07 0.00 0.00 -0.01
PAY_AMT3 -0.06 -0.05 -0.07 0.01 0.01
PAY_AMT4 -0.05 -0.05 -0.04 -0.06 0.02
PAY_AMT5 -0.04 -0.04 -0.03 -0.03 -0.05
PAY_AMT6 -0.04 -0.04 -0.03 -0.02 -0.03
PAY_1 0.67 0.57 0.54 0.51 0.47
PAY_2 1.00 0.77 0.66 0.62 0.58
PAY_3 0.77 1.00 0.78 0.69 0.63
PAY_4 0.66 0.78 1.00 0.82 0.72
PAY_5 0.62 0.69 0.82 1.00 0.82
PAY_6 0.58 0.63 0.72 0.82 1.00
Para acceder visualmente a las correlaciones más altas, se realizará un mapa de calor
col<- colorRampPalette(c("blue", "white", "red"))(20)
heatmap(x = res, col = col, symm = TRUE)Se puede observar tanto en la matriz de correlación, como en el mapa de calor, que las variables BILL_AMT1 a BILL_AMT6 se encuentran altamente correlacionadas, índice de correlación >0.8 se quitarán del modelo previamente en el tratamiento de recipes
summary(data) ID LIMIT_BAL SEX EDUCATION
Min. : 1 Min. : 10000 Masculino:11888 Sin Escolaridad: 14
1st Qu.: 7501 1st Qu.: 50000 Femenino :18112 Escuela :10585
Median :15000 Median : 140000 Bachillerato : 4917
Mean :15000 Mean : 167484 Universidad :14484
3rd Qu.:22500 3rd Qu.: 240000
Max. :30000 Max. :1000000
MARRIAGE AGE PAY_1 PAY_2
Otros : 377 Min. :21.00 Min. :-1.0000 Min. :-1.0000
Casado :13659 1st Qu.:28.00 1st Qu.: 0.0000 1st Qu.: 0.0000
Soltero:15964 Median :34.00 Median : 1.0000 Median : 1.0000
Mean :35.49 Mean : 0.9833 Mean : 0.8662
3rd Qu.:41.00 3rd Qu.: 1.0000 3rd Qu.: 1.0000
Max. :79.00 Max. : 9.0000 Max. : 9.0000
PAY_3 PAY_4 PAY_5 PAY_6
Min. :-1.0000 Min. :-1.0000 Min. :-1.0000 Min. :-1.0000
1st Qu.: 0.0000 1st Qu.: 0.0000 1st Qu.: 0.0000 1st Qu.: 0.0000
Median : 1.0000 Median : 1.0000 Median : 1.0000 Median : 1.0000
Mean : 0.8338 Mean : 0.7793 Mean : 0.7338 Mean : 0.7089
3rd Qu.: 1.0000 3rd Qu.: 1.0000 3rd Qu.: 1.0000 3rd Qu.: 1.0000
Max. : 9.0000 Max. : 9.0000 Max. : 9.0000 Max. : 9.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
0:23364
1: 6636
Se realizarán estudios gráficos de las variables para determinar posibles outliers, distribuciones, comportamientos en los datos
#---------------------------------------
# Histogramas
plot_hist <- function(x)
{
ggplot(data,aes(.data[[x]])) +
geom_histogram(aes(y=..density..),colour="darkcyan", fill="white") +
geom_density(alpha=0.08,fill="darkslategray1",colour="aquamarine2",linewidth=0.6)
}
names(continuas) <- continuas
names(discretas) <- discretas
lapply(c(continuas,discretas), plot_hist)$LIMIT_BAL
$AGE
$BILL_AMT1
$BILL_AMT2
$BILL_AMT3
$BILL_AMT4
$BILL_AMT5
$BILL_AMT6
$PAY_AMT1
$PAY_AMT2
$PAY_AMT3
$PAY_AMT4
$PAY_AMT5
$PAY_AMT6
$PAY_1
$PAY_2
$PAY_3
$PAY_4
$PAY_5
$PAY_6
Las variables en general, son sesgadas a derecha para las variables continuas dado que se confirma en los summary de datos que la mediana < media por lo cual confirma el sesgo a derecha. Para las variables discretas PAY_i i=1,6, se determina el comportamiento de conteo, con mayores datos representados en 1 mes de atraso para los meses de Abril - Sep 2005.
Se testearán con otros bines del histograma para observar en detalle.
Histogramas a división de 50 bines
plot_hist_50 <- function(x)
{
ggplot(data,aes(.data[[x]])) +
geom_histogram(bins=50,aes(y=..density..),colour="darkcyan", fill="white") +
geom_density(alpha=0.08,fill="darkslategray1",colour="aquamarine2",linewidth=0.6)
}
lapply(c(continuas,discretas), plot_hist_50)$LIMIT_BAL
$AGE
$BILL_AMT1
$BILL_AMT2
$BILL_AMT3
$BILL_AMT4
$BILL_AMT5
$BILL_AMT6
$PAY_AMT1
$PAY_AMT2
$PAY_AMT3
$PAY_AMT4
$PAY_AMT5
$PAY_AMT6
$PAY_1
$PAY_2
$PAY_3
$PAY_4
$PAY_5
$PAY_6
Histogramas a división de 15 bines
plot_hist_15 <- function(x)
{
ggplot(data,aes(.data[[x]])) +
geom_histogram(bins=15,aes(y=..density..),colour="darkcyan", fill="white") +
geom_density(alpha=0.08,fill="darkslategray1",colour="aquamarine2",linewidth=0.6)
}
lapply(c(continuas,discretas), plot_hist_15)$LIMIT_BAL
$AGE
$BILL_AMT1
$BILL_AMT2
$BILL_AMT3
$BILL_AMT4
$BILL_AMT5
$BILL_AMT6
$PAY_AMT1
$PAY_AMT2
$PAY_AMT3
$PAY_AMT4
$PAY_AMT5
$PAY_AMT6
$PAY_1
$PAY_2
$PAY_3
$PAY_4
$PAY_5
$PAY_6
Se observan los mismos patrones de sesgo a derecha para las variables a diferentes bines de división. Para el caso de las variables discretas, se puede observar un mejor agrupamiento de los datos numéricos en los bines = 15, dado que la representación muestra un sesgo a derecha de los datos.
Para detección de outliers, se graficarán los boxplots
plot_box <- function(x)
{
ggplot(data,aes(`default payment next month`,.data[[x]])) +
geom_boxplot(fill = "white", colour = "cadetblue3",outlier.colour = "deeppink", outlier.shape = 1)}
lapply(c(continuas,discretas), plot_box)$LIMIT_BAL
$AGE
$BILL_AMT1
$BILL_AMT2
$BILL_AMT3
$BILL_AMT4
$BILL_AMT5
$BILL_AMT6
$PAY_AMT1
$PAY_AMT2
$PAY_AMT3
$PAY_AMT4
$PAY_AMT5
$PAY_AMT6
$PAY_1
$PAY_2
$PAY_3
$PAY_4
$PAY_5
$PAY_6
Con el fin de estudiar relaciones existentes con la variable dependiente, se puede observar que no hay diferencia estadística representativa entre las variables númericas dado que las cajas se solapan entre ellas afirman que no hay diferencia significativa, excepto por la variable límite, que presenta un leve distanciamiento, lo cual parece indicar que a mayores límites de TC aprobados, menor es la propensión a default, mientras que límites inferiores de aprobación de TC pueden tender a casos de default. Los outliers son en su gran mayoría presentes en los datos, lo cual puede indicar un mercado volátil en Taiwán o clientes con comportamientos atípicos en la entidad financiera que no se estandariza hacia un comportamiento con agrupamientos naturales. Para el caso de las variables PAY_AMT_i, se realizarán zooms para las variables donde los boxplots han quedado más pequeños y no se pueden visualizar correctamente dada la alta concentración de valores outliers
data_box <- data %>%
select(`default payment next month`,PAY_AMT1,PAY_AMT2,PAY_AMT3,PAY_AMT4,PAY_AMT5,PAY_AMT6) %>%
filter(PAY_AMT1 <= 5000 &
PAY_AMT2 <= 5000 &
PAY_AMT3 <= 5000 &
PAY_AMT4 <= 5000 &
PAY_AMT5 <= 5000 &
PAY_AMT6 <= 5000)
min(data_box$PAY_AMT6)[1] 0
max(data_box$PAY_AMT6)[1] 5000
plot_box_pay <- function(x)
{
ggplot(data_box,aes(`default payment next month`,.data[[x]])) +
geom_boxplot(fill = "white", colour = "cadetblue3",outlier.colour = "deeppink", outlier.shape = 1)
}
pay_amount_cols<-colnames(data_box[2:7])
names(pay_amount_cols) <- pay_amount_cols
lapply(pay_amount_cols, plot_box_pay)$PAY_AMT1
$PAY_AMT2
$PAY_AMT3
$PAY_AMT4
$PAY_AMT5
$PAY_AMT6
Hacemos facets para visualizar los histogramas con respecto a la target, en estas variables que se harán enfoque
plot_hist_target <- function(x)
{
ggplot(data_box,aes(.data[[x]],fill=`default payment next month`)) +
geom_histogram()+
facet_wrap(~ `default payment next month`,ncol=1)
}
lapply(pay_amount_cols, plot_hist_target)$PAY_AMT1
$PAY_AMT2
$PAY_AMT3
$PAY_AMT4
$PAY_AMT5
$PAY_AMT6
Se pueden observar valores de sesgo a derecha para las variables de PAY_AMT_i los valores más altos de montos pagados por los clientes se observan en promedio para los meses de Abril - Mayo, Julio - Septiembre, el mes de Junio posee una reducción sobre los montos pagados respecto a los valores promedio de los otros meses.
Para el caso de las variables PAY_AMT1 y PAY_AMT2 son estadísticamente significativas respecto a la variable de default de TC dado que no hay solapamiento completo de las cajas, en PAY_AMT3 hay un pequeño distanciamiento pero no es tan evidente como los distanciamientos de PAY_AMT1 y PAY_AMT2, esto quiere decir que, los meses de Abril y Mayo 2005 son representativos para influir sobre los valores de la target.
Estudio de pairplots para ver relaciones entre las variables respecto a la target.
Se seleccionarán para realizar las gráficas de scatter plot las variables contínuas AGE, LIMIT_BAL con BILL_AMOUNT_1,2 y BILL_AMOUNT_5,BILL_AMOUNT_6. En un 2° gráfico, se visualizarán las variables contínuas AGE, LIMIT_BAL con PAY_AMOUNT_1,2 y PAY_AMOUNT_5,PAY_AMOUNT_6 para mejor visualización de los datos
names(target) <- target
continuas2<-c("LIMIT_BAL","AGE","BILL_AMT1","BILL_AMT2","BILL_AMT5", "BILL_AMT6")
continuas3<-c("LIMIT_BAL","AGE","PAY_AMT1", "PAY_AMT2","PAY_AMT5", "PAY_AMT6")
names(continuas2) <- continuas2
names(continuas3) <- continuas3
pairs(data[continuas2],
col = c( "cornflowerblue", "purple")[data$`default payment next month`])Se pueden observar relaciones lineales entre las variables BILL_AMT_i, hecho que se puede determinar desde la matriz de correlación donde las correlaciones son mayores al 80%
pairs(data[continuas3],
col = c( "cornflowerblue", "purple")[data$`default payment next month`])Los valores de PAY_AMT_i con LIMIT_BAL y AGE no poseen relación lineal. No existen separaciones de grupos representativos por target entre las variables cuantitativas.
data_scatter1 <- data[c(discretas,target)]
pairs(data_scatter1,
col = c( "cornflowerblue", "purple")[data_scatter1$`default payment next month`])Se visualizan sobre las variables PAY_3, PAY_4, PAY_5, PAY_6 una ligera relación lineal, efectos que pueden visualizarse en la correlación entre dichas variables con correlaciones promedio de 55%.
#---------------------------------------
# Bar plots
plot_bar <- function(x)
{
ggplot(data = data,aes(x = .data[[x]], fill = `default payment next month`)) +
geom_bar(alpha=0.6) +
scale_fill_brewer(palette="Pastel2") +
geom_text(stat = 'count', aes(label=..count..), position=position_dodge(width = 1)
,vjust=-0.5, size=3,color="midnightblue")
}
names(categoricas) <- categoricas
lapply(categoricas, plot_bar)$SEX
$EDUCATION
$MARRIAGE
La población se distribuye en mayor volumen a mujeres que a hombres, a nivel porcentual, la tasa de default en hombres es del 24% comparado con la tasa de default en mujeres que representa un 21%.
A nivel educativo, la población posee en su mayoría grados universitarios seguidos de escuela, a nivel relativo, las tasas de default son mayores para las personas con bachillerato representando un 25%, mientras que los universitarios poseen una tasa del 23% y las personas con escolaridad representan la menor tasa del 19%, esto podría inferir que existe un mercado informal que maneja negocios propios y posee un manejo adecuado del dinero apesar de poseer sólo grado escolar.
En cuanto al estado civil, se puede observar que más de 15.000 clientes se distribuye en estado civil soltero, porcentualmente, las tasas de default mayor ocurren para el estado civil de casado con un 23%, mientras que para los solteros, la tasa es de 21% se podría inferir que al adquirir mayores responsabilidades como hijos, hipotecas podría implicar un default mayor para pagos de tarjetas de crédito.
#---------------------------------------
# Polar coord
plot_polar <- function(x)
{
bar <- ggplot(data = data) +
geom_bar(
mapping = aes(x = .data[[x]], fill = .data[[x]]),
show.legend = FALSE,
width = 1
) +
scale_fill_brewer(palette="Pastel2")+
theme(aspect.ratio = 1) +
labs(x = NULL, y = NULL) +
ggtitle(x) +
theme(plot.title = element_text(size=10, face="bold"))
bar + coord_flip()
bar + coord_polar()
}
lapply(c(categoricas,target), plot_polar)$SEX
$EDUCATION
$MARRIAGE
$`default payment next month`
table(data$`default payment next month`)
0 1
23364 6636
4 Fase de Modelado - Modelo GLM
Se procede al ajuste del modelo, se puede observar que la variable respuesta tiene una distribución en menor proporción de los clientes con default vs. los clientes sin default, la relación de datos es de un 22% para los casos de interés y 78% para los casos sin default, la relación puede ser considerada para la aplicación del modelo sin realizar reajuste de balanceo de la muestra, la variable target es una variable dicotómica, por tal razón, se deben aplicar modelos de clasificación supervisados lo que indica que son modelos predictivos en base a los valores de default o no en TC. Se aplicará un modelo lineal generalizado GLM con función vínculo logit para evaluar los ajustes a la fuente de datos. Se utilizará un método denominado ‘recipes’ de la librería tidymodels que ajusta los pasos a través de secuencias ordenadas
Previo al ajuste, se seleccionarán las muestras de entrenamiento y test
# Fijar semilla para reproducción de resultados
set.seed(123)
# Corte a un 80% training, 20$ test
data_split <- rsample::initial_split(data,prop=0.8)
data_train <- rsample::training(data_split)
data_test <- rsample::testing(data_split)
# Ajuste con cross validation
set.seed(123)
k<-5
folds<-rsample::vfold_cv(data_train,v=k)
# Ajuste de recipe
recip1 <- recipe(`default payment next month`~., data=data_train) %>% # Se consideran todas las variables del modelo con la data de train
update_role(ID,new_role = "ID") %>% # Se identifica que la variable ID es un ID y por tanto no se toma en cuenta como variable predictora
step_corr(all_numeric_predictors(), threshold = .8) %>% # Elimina variables con más del 80% de correlación
step_normalize(all_numeric_predictors()) %>% # Estandarización de valores para comparación de escalas entre variables numéricas
step_dummy(all_nominal_predictors(), one_hot=FALSE) %>% # Transformación de las variables categóricas nominales, usa internamente la decodificación de las clases en binarias quitando una de las variables y ordinales que al ser declarada ordinal previamente se asigna un contrast_poly un ajuste polinómico para dar orden
step_zv(all_predictors()) # Remueve variables que sólo contengan 1 valor en los datos, por ello se aplica luego del paso step_dummy
print(summary(recip1),n=25)# A tibble: 25 × 4
variable type role source
<chr> <list> <chr> <chr>
1 ID <chr [2]> ID original
2 LIMIT_BAL <chr [2]> predictor original
3 SEX <chr [3]> predictor original
4 EDUCATION <chr [2]> predictor original
5 MARRIAGE <chr [3]> predictor original
6 AGE <chr [2]> predictor original
7 PAY_1 <chr [2]> predictor original
8 PAY_2 <chr [2]> predictor original
9 PAY_3 <chr [2]> predictor original
10 PAY_4 <chr [2]> predictor original
11 PAY_5 <chr [2]> predictor original
12 PAY_6 <chr [2]> predictor original
13 BILL_AMT1 <chr [2]> predictor original
14 BILL_AMT2 <chr [2]> predictor original
15 BILL_AMT3 <chr [2]> predictor original
16 BILL_AMT4 <chr [2]> predictor original
17 BILL_AMT5 <chr [2]> predictor original
18 BILL_AMT6 <chr [2]> predictor original
19 PAY_AMT1 <chr [2]> predictor original
20 PAY_AMT2 <chr [2]> predictor original
21 PAY_AMT3 <chr [2]> predictor original
22 PAY_AMT4 <chr [2]> predictor original
23 PAY_AMT5 <chr [2]> predictor original
24 PAY_AMT6 <chr [2]> predictor original
25 default payment next month <chr [3]> outcome original
Las variables predictoras y target son separadas durante el proceso de steps de recipe, lo que garantiza que no se está incluyendo como variable independiente, al igual que la variable ID
mod_glm <- logistic_reg() %>%
set_engine("glm")
workflow_glm <- workflow() %>%
add_model(mod_glm) %>%
add_recipe(recip1)
keep_pred <- control_resamples(save_pred = TRUE)
perf_metr<-metric_set(specificity, recall, accuracy,precision)
cv_glm <- fit_resamples(workflow_glm,
resamples=folds,
metrics = perf_metr,
control = keep_pred)
collect_metrics(cv_glm)# A tibble: 4 × 6
.metric .estimator mean n std_err .config
<chr> <chr> <dbl> <int> <dbl> <chr>
1 accuracy binary 0.810 5 0.00388 Preprocessor1_Model1
2 precision binary 0.818 5 0.00386 Preprocessor1_Model1
3 recall binary 0.972 5 0.000732 Preprocessor1_Model1
4 specificity binary 0.244 5 0.00790 Preprocessor1_Model1
fit_glm<-fit(workflow_glm, data_train)
fit_glm══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: logistic_reg()
── Preprocessor ────────────────────────────────────────────────────────────────
4 Recipe Steps
• step_corr()
• step_normalize()
• step_dummy()
• step_zv()
── Model ───────────────────────────────────────────────────────────────────────
Call: stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)
Coefficients:
(Intercept) LIMIT_BAL AGE PAY_1
-4.21897 -0.10913 0.05682 0.65391
PAY_2 PAY_3 PAY_6 BILL_AMT1
0.09819 0.12796 0.05043 -0.12684
PAY_AMT1 PAY_AMT2 PAY_AMT3 PAY_AMT4
-0.19650 -0.13445 -0.03803 -0.09156
PAY_AMT5 PAY_AMT6 SEX_Femenino EDUCATION_1
-0.05751 -0.04130 -0.12245 7.20050
EDUCATION_2 EDUCATION_3 MARRIAGE_Casado MARRIAGE_Soltero
-5.41583 2.52395 0.22545 0.04276
Degrees of Freedom: 23999 Total (i.e. Null); 23980 Residual
Null Deviance: 25460
Residual Deviance: 22350 AIC: 22390
print(tidy(fit_glm), n=27)# A tibble: 20 × 5
term estimate std.error statistic p.value
<chr> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) -4.22 21.5 -0.196 8.45e- 1
2 LIMIT_BAL -0.109 0.0229 -4.77 1.80e- 6
3 AGE 0.0568 0.0191 2.97 2.96e- 3
4 PAY_1 0.654 0.0222 29.5 2.79e-191
5 PAY_2 0.0982 0.0268 3.66 2.49e- 4
6 PAY_3 0.128 0.0264 4.85 1.25e- 6
7 PAY_6 0.0504 0.0209 2.41 1.59e- 2
8 BILL_AMT1 -0.127 0.0222 -5.72 1.09e- 8
9 PAY_AMT1 -0.196 0.0396 -4.96 6.88e- 7
10 PAY_AMT2 -0.134 0.0424 -3.17 1.53e- 3
11 PAY_AMT3 -0.0380 0.0285 -1.34 1.82e- 1
12 PAY_AMT4 -0.0916 0.0303 -3.02 2.54e- 3
13 PAY_AMT5 -0.0575 0.0262 -2.19 2.84e- 2
14 PAY_AMT6 -0.0413 0.0250 -1.65 9.84e- 2
15 SEX_Femenino -0.122 0.0343 -3.57 3.50e- 4
16 EDUCATION_1 7.20 57.8 0.125 9.01e- 1
17 EDUCATION_2 -5.42 43.1 -0.126 9.00e- 1
18 EDUCATION_3 2.52 19.3 0.131 8.96e- 1
19 MARRIAGE_Casado 0.225 0.151 1.50 1.34e- 1
20 MARRIAGE_Soltero 0.0428 0.152 0.281 7.79e- 1
drop1(fit_glm |> extract_fit_engine(), .~., test = "Chisq")Single term deletions
Model:
..y ~ LIMIT_BAL + AGE + PAY_1 + PAY_2 + PAY_3 + PAY_6 + BILL_AMT1 +
PAY_AMT1 + PAY_AMT2 + PAY_AMT3 + PAY_AMT4 + PAY_AMT5 + PAY_AMT6 +
SEX_Femenino + EDUCATION_1 + EDUCATION_2 + EDUCATION_3 +
MARRIAGE_Casado + MARRIAGE_Soltero
Df Deviance AIC LRT Pr(>Chi)
<none> 22347 22387
LIMIT_BAL 1 22371 22409 23.26 1.418e-06 ***
AGE 1 22356 22394 8.79 0.0030232 **
PAY_1 1 23214 23252 866.67 < 2.2e-16 ***
PAY_2 1 22361 22399 13.34 0.0002593 ***
PAY_3 1 22371 22409 23.33 1.367e-06 ***
PAY_6 1 22353 22391 5.82 0.0158879 *
BILL_AMT1 1 22381 22419 33.54 6.985e-09 ***
PAY_AMT1 1 22382 22420 34.15 5.108e-09 ***
PAY_AMT2 1 22360 22398 12.60 0.0003850 ***
PAY_AMT3 1 22349 22387 1.94 0.1634766
PAY_AMT4 1 22359 22397 11.22 0.0008109 ***
PAY_AMT5 1 22353 22391 5.46 0.0194274 *
PAY_AMT6 1 22350 22388 2.93 0.0872075 .
SEX_Femenino 1 22360 22398 12.74 0.0003579 ***
EDUCATION_1 1 22352 22390 4.09 0.0431580 *
EDUCATION_2 1 22352 22390 4.46 0.0347038 *
EDUCATION_3 1 22354 22392 6.48 0.0109315 *
MARRIAGE_Casado 1 22350 22388 2.31 0.1283877
MARRIAGE_Soltero 1 22348 22386 0.08 0.7779573
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
El modelo en base a la devianza D^2=((25460-22350)/25460)*100=12,45% el modelo explica el 12,45% de la variabilidad. Se observan que las variables PAY_AMT3, PAY_AMT6, las variables asociadas a MARRIAGE no son significativas p_valor_variable > 0.05, esto implica que no se puede rechazar H0: beta_i =0, por tanto, dichas variables no aportan al modelo, se eliminarán estas variables del modelo para determinar si se observan mejoría en las métricas de performance del modelo, sin embargo, se observa que el coeficiente del criterio de información de Akaike AIC no disminuye si se elimina algunas de las variables del modelo, al no presentar de reducción de AIC, parece no ser recomendada la eliminación, a modo de testing se realiza la exclusión de variables no significativas
recip2 <- recipe(`default payment next month`~ LIMIT_BAL+AGE+PAY_1+ PAY_2+ PAY_3+PAY_6+
BILL_AMT1+PAY_AMT1+ PAY_AMT2+PAY_AMT4+PAY_AMT5+SEX+
EDUCATION, data=data_train) %>%
step_normalize(all_numeric_predictors()) %>%
step_dummy(all_nominal_predictors(), one_hot=FALSE) %>%
step_zv(all_predictors())
mod_glm2 <- logistic_reg() %>%
set_engine("glm")
workflow_glm2 <- workflow() %>%
add_model(mod_glm2) %>%
add_recipe(recip2)
cv_glm <- fit_resamples(workflow_glm2,
resamples=folds,
metrics = perf_metr,
control = keep_pred)
collect_metrics(cv_glm)# A tibble: 4 × 6
.metric .estimator mean n std_err .config
<chr> <chr> <dbl> <int> <dbl> <chr>
1 accuracy binary 0.808 5 0.00348 Preprocessor1_Model1
2 precision binary 0.817 5 0.00355 Preprocessor1_Model1
3 recall binary 0.972 5 0.000446 Preprocessor1_Model1
4 specificity binary 0.240 5 0.00705 Preprocessor1_Model1
fit_glm2<-fit(workflow_glm2, data_train)
fit_glm2══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: logistic_reg()
── Preprocessor ────────────────────────────────────────────────────────────────
3 Recipe Steps
• step_normalize()
• step_dummy()
• step_zv()
── Model ───────────────────────────────────────────────────────────────────────
Call: stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)
Coefficients:
(Intercept) LIMIT_BAL AGE PAY_1 PAY_2
-4.11315 -0.10646 0.09567 0.65653 0.09786
PAY_3 PAY_6 BILL_AMT1 PAY_AMT1 PAY_AMT2
0.12864 0.04908 -0.13401 -0.20534 -0.14322
PAY_AMT4 PAY_AMT5 SEX_Femenino EDUCATION_1 EDUCATION_2
-0.09695 -0.06465 -0.10813 7.25043 -5.43472
EDUCATION_3
2.52289
Degrees of Freedom: 23999 Total (i.e. Null); 23984 Residual
Null Deviance: 25460
Residual Deviance: 22380 AIC: 22410
print(tidy(fit_glm2), n=27)# A tibble: 16 × 5
term estimate std.error statistic p.value
<chr> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) -4.11 21.5 -0.191 8.49e- 1
2 LIMIT_BAL -0.106 0.0225 -4.73 2.19e- 6
3 AGE 0.0957 0.0171 5.61 2.07e- 8
4 PAY_1 0.657 0.0222 29.6 7.54e-193
5 PAY_2 0.0979 0.0268 3.65 2.59e- 4
6 PAY_3 0.129 0.0264 4.88 1.07e- 6
7 PAY_6 0.0491 0.0209 2.35 1.87e- 2
8 BILL_AMT1 -0.134 0.0220 -6.09 1.09e- 9
9 PAY_AMT1 -0.205 0.0398 -5.17 2.40e- 7
10 PAY_AMT2 -0.143 0.0425 -3.37 7.58e- 4
11 PAY_AMT4 -0.0970 0.0305 -3.18 1.49e- 3
12 PAY_AMT5 -0.0646 0.0264 -2.45 1.43e- 2
13 SEX_Femenino -0.108 0.0341 -3.17 1.51e- 3
14 EDUCATION_1 7.25 57.8 0.125 9.00e- 1
15 EDUCATION_2 -5.43 43.1 -0.126 9.00e- 1
16 EDUCATION_3 2.52 19.3 0.131 8.96e- 1
drop1(fit_glm2 |> extract_fit_engine(), .~., test = "Chisq")Single term deletions
Model:
..y ~ LIMIT_BAL + AGE + PAY_1 + PAY_2 + PAY_3 + PAY_6 + BILL_AMT1 +
PAY_AMT1 + PAY_AMT2 + PAY_AMT4 + PAY_AMT5 + SEX_Femenino +
EDUCATION_1 + EDUCATION_2 + EDUCATION_3
Df Deviance AIC LRT Pr(>Chi)
<none> 22376 22408
LIMIT_BAL 1 22399 22429 22.87 1.733e-06 ***
AGE 1 22407 22437 31.21 2.314e-08 ***
PAY_1 1 23250 23280 873.58 < 2.2e-16 ***
PAY_2 1 22390 22420 13.27 0.0002698 ***
PAY_3 1 22400 22430 23.63 1.169e-06 ***
PAY_6 1 22382 22412 5.53 0.0186821 *
BILL_AMT1 1 22415 22445 38.24 6.259e-10 ***
PAY_AMT1 1 22414 22444 37.47 9.306e-10 ***
PAY_AMT2 1 22391 22421 14.46 0.0001434 ***
PAY_AMT4 1 22389 22419 12.53 0.0004015 ***
PAY_AMT5 1 22383 22413 6.92 0.0085203 **
SEX_Femenino 1 22386 22416 10.04 0.0015352 **
EDUCATION_1 1 22381 22411 4.38 0.0364592 *
EDUCATION_2 1 22381 22411 4.61 0.0316990 *
EDUCATION_3 1 22383 22413 6.45 0.0110779 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
El modelo en base a la devianza D^2=((25460-22380)/25460)*100=12,10% el modelo explica el 12,10% de la variabilidad. El coeficiente de AIC aumenta lo cual indica que el modelo saturado (que incluye todas las variables) es más significativo dado que presenta menor AIC y explica un poco más en comparación con el modelo reducido
Al analizar los valores de AIC del primer modelo, se puede determinar que el AIC no posee un descenso al retirar cualquier variable, esto indica que quitar alguna variable no significativa no implica la mejora de AIC, se verifica con un test anova para comparar ambos modelos 1 y 2
anova(fit_glm2 |> extract_fit_engine(),fit_glm |> extract_fit_engine(), test = "Chisq")Analysis of Deviance Table
Model 1: ..y ~ LIMIT_BAL + AGE + PAY_1 + PAY_2 + PAY_3 + PAY_6 + BILL_AMT1 +
PAY_AMT1 + PAY_AMT2 + PAY_AMT4 + PAY_AMT5 + SEX_Femenino +
EDUCATION_1 + EDUCATION_2 + EDUCATION_3
Model 2: ..y ~ LIMIT_BAL + AGE + PAY_1 + PAY_2 + PAY_3 + PAY_6 + BILL_AMT1 +
PAY_AMT1 + PAY_AMT2 + PAY_AMT3 + PAY_AMT4 + PAY_AMT5 + PAY_AMT6 +
SEX_Femenino + EDUCATION_1 + EDUCATION_2 + EDUCATION_3 +
MARRIAGE_Casado + MARRIAGE_Soltero
Resid. Df Resid. Dev Df Deviance Pr(>Chi)
1 23984 22376
2 23980 22347 4 28.817 8.517e-06 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
El test de ANOVA indica con un p_valor de 1.283e-05 se puede rechazar H0 lo que indica que el modelo con menor cantidad de variables no es adecuado para explicar la variable dependiente
Se procederá a realizar el análisis de residuos
#Chequeo de normalidad de los residuos
hist(rstandard(fit_glm |> extract_fit_engine()), freq=FALSE, col="red2",xlab="Residuos estandarizados", ylab="Frecuencia", main="Histograma de los errores estandarizados")boxplot(rstandard(fit_glm |> extract_fit_engine()),main="Residuos estandarizados",ylab="Residuos estandarizados",col="lightgoldenrodyellow")qqnorm(rstandard(fit_glm |> extract_fit_engine()),main="Gr?fico Cuantil-Cuantil")
qqline(rstandard(fit_glm |> extract_fit_engine()),lwd=2,col="red")#H0: los residuos son normales
jarque.bera.test(residuals(fit_glm |> extract_fit_engine()))
Jarque Bera Test
data: residuals(extract_fit_engine(fit_glm))
X-squared = 7177.3, df = 2, p-value < 2.2e-16
En el test de Jarque Bera podemos rechazar H0 lo cual indica que los residuos no son normales lo cual es aceptable en un modelo lineal generalizado GLM donde no es exigible el cumplimiento de normalidad de los residuos
#Chequeo de Homocedasticidad
plot(fitted.values(fit_glm |> extract_fit_engine()),rstandard(fit_glm |> extract_fit_engine()),xlab="Valores Ajustados",ylab="Residuos Estandarizados",main="Residuos Estandarizados vs valores ajustados")
abline(0,0,col="red",lwd=2)#H0: los residuos son homocedásticos
breusch_pagan(fit_glm |> extract_fit_engine())# A tibble: 1 × 5
statistic p.value parameter method alternative
<dbl> <dbl> <dbl> <chr> <chr>
1 698. 9.00e-136 19 Koenker (studentised) greater
De igual forma, los residuos no son homocedásticos, al rechazar H0: los residuos son homocedásticos en el test de Breusch Pagan, condición aceptada en un modelo de regresión logística
#Chequeo de Independencia
plot(rstandard(fit_glm |> extract_fit_engine()),main="Residuos estandarizados")
abline(0,0,col="red",lwd=2)Los residuos se encuentran en el rango entre -3 y 3, se encuentran aglomerados en la parte inferior lo cual indica que no son independientes, en el caso de los modelos GLM esta condición debe garantizarse.
#Chequeo de Autocorrelación de los residuos
#H0: los residuos no son autocorrelacionados
dwtest(fit_glm |> extract_fit_engine(),alternative = "two.sided",data=data_train)
Durbin-Watson test
data: extract_fit_engine(fit_glm)
DW = 1.9955, p-value = 0.7295
alternative hypothesis: true autocorrelation is not 0
El p_valor = 0.8128 es demasiado alto como para rechazar H0, por tanto, los residuos no son autocorrelacionados
5 Evaluación - Modelo GLM
Se valuarán los resultados del modelo tanto para la data train como el data test para evaluar el performance y descartar overfitting o underfitting en los resultados
# Resultados data train - GLM
pred_train_glm <- predict(fit_glm, data_train)
pred_test_glm <- predict(fit_glm, data_test)
glm_aug <- augment(fit_glm,data_train)
# Visual de las clases predichas con su probabilidad
glm_aug %>%
select(`default payment next month`,.pred_class,.pred_1)# A tibble: 24,000 × 3
`default payment next month` .pred_class .pred_1
<fct> <fct> <dbl>
1 0 0 0.180
2 0 0 0.187
3 0 0 0.230
4 0 0 0.213
5 0 0 0.102
6 0 0 0.215
7 1 0 0.0573
8 0 0 0.219
9 1 0 0.233
10 0 0 0.269
# ℹ 23,990 more rows
Se obtienen tanto las clases predichas 0, 1 como las probabilidades de default, en este caso, para la data train.
Se procede a realizar el cálculo de ROC AUC sobre data train
# ROC data train
glm_aug %>%
roc_auc(truth = `default payment next month`,.pred_0)# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 roc_auc binary 0.726
glm_aug %>%
roc_curve(truth = `default payment next month`,.pred_0) %>%
autoplot()result_train <- glm_aug %>%
roc_curve(truth = `default payment next month`,.pred_0)Se procede a realizar el cálculo de ROC AUC sobre data test
# Resultados data test - GLM
glm_aug2 <- augment(fit_glm,data_test)
glm_aug2 %>%
roc_auc(truth = `default payment next month`,.pred_0)# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 roc_auc binary 0.713
glm_aug2 %>%
roc_curve(truth = `default payment next month`,.pred_0) %>%
autoplot()result_test <- glm_aug2 %>%
roc_curve(truth = `default payment next month`,.pred_0)El valor del área bajo la curva ROC-AUC es de 73% sobre los datos de train, el ROC para los datos de test son 71% el porcentaje sobre los datos de test hacen que no se evidencie un comportamiento de bajo performance sobre los datos de test, obteniéndose resultados muy similares de performance, esto implica, que no hay indicios de overfitting ( o de underfitting) en el modelo acorde a los valores de ROC -AUC. Se evaluarán, análogamente, los valores de métricas como especificidad, sensitividad, acuracidad y precisión
# Confusion Matrix - data train
glm_aug %>%
conf_mat(truth = `default payment next month`, estimate = .pred_class) %>%
autoplot(type = 'heatmap')# Confusion Matrix - data test
glm_aug2 %>%
conf_mat(truth = `default payment next month`, estimate = .pred_class) %>%
autoplot(type = 'heatmap')df_tr_glm<- data.frame(default_payment=data_train$`default payment next month`,preds=pred_train_glm$.pred_class)
df_test_glm<- data.frame(default_payment=data_test$`default payment next month`,preds=pred_test_glm$.pred_class)
dim(df_tr_glm)[1] 24000 2
dim(df_test_glm)[1] 6000 2
eval_metrics <- metric_set(specificity, recall, accuracy,precision)
# Métricas para data train
eval_metrics(data = df_tr_glm , truth = default_payment , estimate =preds)# A tibble: 4 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 specificity binary 0.246
2 recall binary 0.972
3 accuracy binary 0.811
4 precision binary 0.818
# Métricas para data test
eval_metrics(data = df_test_glm , truth = default_payment , estimate =preds)# A tibble: 4 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 specificity binary 0.232
2 recall binary 0.972
3 accuracy binary 0.813
4 precision binary 0.822
Para el caso de default en TC, la clase positiva son los clientes que realizan default target =1, en la matriz de confusión son los casos con True Positives = 1.314 y 299 para data train y test, respectivamente, la clase negativa son los clientes que no hicieron default en TC, en la matriz de confusión son los casos con True Negatives = 18.141 y 4.579 target =0 para data train y test, respectivamente, para el caso de considerar más relevante en el estudio un falso negativo indica que el cliente realiza default pero el modelo lo predice como un cliente que no entra en default, esto hace que los FN sean más importantes que los FP en el contexto del significado de la definición de sensitividad = TP / TP+FN entre más pequeño sea el valor de FN, más grande será el valor de la sensitividad, por tanto, se coloca mayor enfásis en que el modelo tenga un valor más alto de recall que de precisión. Los valores de acuracidad son del 81%, sensitividad 97%, precisión 82% y especificidad de 25% para data train y 81% acuracidad, sensitividad 97%, precisión 82% y especificidad de 23% para data test, lo cual garantiza valores de performance similares para el data train y data test descartando la posibilidad de overfitting o de underfitting en los resultados del modelo.
Se calculan 2 coeficientes adicionales de ajuste el coeficiente de Kolmogorov Smirnov que mide la distancia máxima entre las clases de la variable dependiente, entre mayor sea el coeficiente, mejor el ajuste, viene definido por
\[ KS = \max_{a \in [L, H]} \left| F_{n.buenos}(a) - F_{m.malos}(a) \right| \]
El coeficiente de Gini que mide la separación entre las clases de la target viene calculada con la siguiente fórmula
\[ GINI = 1 - \sum_{k=2}^{n+m} [F_{m.malos_k} - F_{m.malos_{k-1}}][F_{n.buenos_k} + F_{n.buenos_{k-1}}] \]
gini_train = 2*(0.726) -1
gini_train[1] 0.452
gini_test = 2*(0.713) -1
gini_test[1] 0.426
diff_TPRFPR = result_train[['sensitivity']]-(1-result_train[['specificity']])
KS_train = max(diff_TPRFPR)
KS_train[1] 0.3755179
diff_TPRFPR_test = result_test[['sensitivity']]-(1-result_test[['specificity']])
KS_test = max(diff_TPRFPR_test)
KS_test[1] 0.3784625
Los valores de KS y GINI podrían representar mayores valores de discriminación entre clases. Para ello, se buscará implementar la opción a través XGBoost. Para los valores entre train y test no se observan diferencias significativas, lo cual descarta presencia de overfitting o de underfitting.
6 Fase de Modelado - XGBoost
Se ajustará un XGBoost que consiste en una serie de árboles de decisión con boosting donde cada árbol depende de los resultados del árbol previo. Todos los árboles en el ensamble son combinados para producir un resultado final, se detectarán los cores posibles de uso de la máquina para paralelizar la búsqueda cuando se realice el tunning de parámetros, en este caso, con hyperparameter tuning que consiste en buscar los parámatros que mejor optimizan el valor de una métrica obtenida, lo cual forma una grilla de resultados y se elije el mejor
cores <- parallel::detectCores()
cores[1] 16
set.seed(123)
k<-5
folds<-rsample::vfold_cv(data_train,v=k)
recip_rf <- recipe(`default payment next month`~., data=data_train) %>%
update_role(ID,new_role = "ID") %>% # Se identifica que la variable ID es un ID y por tanto no se toma en cuenta como variable predictora
step_corr(all_numeric_predictors(), threshold = .8) %>% # Elimina variables con más del 80% de correlación
step_normalize(all_numeric_predictors()) %>% # Estandarización de valores para comparación de escalas entre variables numéricas
step_dummy(all_nominal_predictors(), one_hot=FALSE) %>% # Transformación de las variables categóricas nominales, usa internamente la decodificación de las clases en binarias quitando una de las variables y ordinales que al ser declarada ordinal previamente se asigna un contrast_poly un ajuste polinómico para dar orden
step_zv(all_predictors()) # Remueve variables que sólo contengan 1 valor en los datos, por ello se aplica luego del paso step_dummy
rf_mod <-
boost_tree(tree_depth = tune(),trees = tune()) %>% # tree_depth y trees hiperparámetros a tunear
set_engine("xgboost", num.threads = cores) %>%
set_mode("classification")rf_workflow <-
workflow() %>%
add_model(rf_mod) %>%
add_recipe(recip_rf)
set.seed(123)
rf_res <-
rf_workflow %>%
tune_grid(folds,
grid = 25,
control = control_grid(save_pred = TRUE),
metrics = metric_set(roc_auc))
rf_res# Tuning results
# 5-fold cross-validation
# A tibble: 5 × 5
splits id .metrics .notes .predictions
<list> <chr> <list> <list> <list>
1 <split [19200/4800]> Fold1 <tibble [25 × 6]> <tibble [0 × 3]> <tibble>
2 <split [19200/4800]> Fold2 <tibble [25 × 6]> <tibble [0 × 3]> <tibble>
3 <split [19200/4800]> Fold3 <tibble [25 × 6]> <tibble [0 × 3]> <tibble>
4 <split [19200/4800]> Fold4 <tibble [25 × 6]> <tibble [0 × 3]> <tibble>
5 <split [19200/4800]> Fold5 <tibble [25 × 6]> <tibble [0 × 3]> <tibble>
autoplot(rf_res) +
scale_color_viridis_d(direction = -1) +
theme(legend.position = "top")rf_best <-
rf_res %>%
select_best(metric = "roc_auc")
rf_best# A tibble: 1 × 3
trees tree_depth .config
<int> <int> <chr>
1 1748 1 Preprocessor1_Model21
Los valores del grid search con máx entriopía se encuentran separados, en efecto, los mejores parámteros son trees = 1748, tree_depth = 1
rf_res %>%
collect_predictions(parameters = rf_best)# A tibble: 24,000 × 8
id .pred_0 .pred_1 .row trees tree_depth default payment next m…¹ .config
<chr> <dbl> <dbl> <int> <int> <int> <fct> <chr>
1 Fold1 0.872 0.128 3 1748 1 0 Prepro…
2 Fold1 0.927 0.0729 5 1748 1 0 Prepro…
3 Fold1 0.834 0.166 7 1748 1 1 Prepro…
4 Fold1 0.856 0.144 8 1748 1 0 Prepro…
5 Fold1 0.879 0.121 11 1748 1 0 Prepro…
6 Fold1 0.717 0.283 12 1748 1 1 Prepro…
7 Fold1 0.391 0.609 18 1748 1 1 Prepro…
8 Fold1 0.685 0.315 28 1748 1 1 Prepro…
9 Fold1 0.800 0.200 31 1748 1 0 Prepro…
10 Fold1 0.887 0.113 32 1748 1 0 Prepro…
# ℹ 23,990 more rows
# ℹ abbreviated name: ¹`default payment next month`
7 Evaluación Modelo - XGBoost
rf_mod <-
boost_tree(tree_depth = 1,trees = 1748) %>%
set_engine("xgboost", num.threads = cores) %>%
set_mode("classification")
rf_workflow <-
workflow() %>%
add_model(rf_mod) %>%
add_recipe(recip_rf)
# Curva ROC-AUC Train
fit_rf_train<- fit(rf_workflow, data_train)[01:27:36] WARNING: src/learner.cc:767:
Parameters: { "num_threads" } are not used.
pred_train_rf <- predict(fit_rf_train, data_train)
pred_test_rf <- predict(fit_rf_train, data_test)
rf_aug <- augment(fit_rf_train,data_train)
rf_aug %>%
select(`default payment next month`,.pred_class,.pred_1)# A tibble: 24,000 × 3
`default payment next month` .pred_class .pred_1
<fct> <fct> <dbl>
1 0 0 0.103
2 0 0 0.0823
3 0 0 0.135
4 0 0 0.0987
5 0 0 0.0671
6 0 0 0.110
7 1 0 0.166
8 0 0 0.135
9 1 0 0.142
10 0 0 0.156
# ℹ 23,990 more rows
rf_aug %>%
roc_auc(truth = `default payment next month`,.pred_0)# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 roc_auc binary 0.797
rf_aug %>%
roc_curve(truth = `default payment next month`,.pred_0) %>%
autoplot()result_train_rf <- rf_aug %>%
roc_curve(truth = `default payment next month`,.pred_0)# Curva ROC-AUC Test
rf_aug_test <- augment(fit_rf_train,data_test)
rf_aug_test %>%
select(`default payment next month`,.pred_class,.pred_1)# A tibble: 6,000 × 3
`default payment next month` .pred_class .pred_1
<fct> <fct> <dbl>
1 1 1 0.788
2 0 0 0.174
3 0 0 0.101
4 0 0 0.148
5 0 0 0.124
6 0 0 0.189
7 0 0 0.0410
8 0 0 0.0615
9 0 0 0.148
10 0 0 0.0760
# ℹ 5,990 more rows
rf_aug_test %>%
roc_auc(truth = `default payment next month`,.pred_0)# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 roc_auc binary 0.767
rf_aug_test %>%
roc_curve(truth = `default payment next month`,.pred_0) %>%
autoplot()result_test_rf <- rf_aug_test %>%
roc_curve(truth = `default payment next month`,.pred_0)Los valores de ROC AUC para train están en un valor del 80% respecto a un 77% de la data de testing, los valores son muy similares respecto a la data de testing, se evaluarán las métricas de acuracidad, recall, precisión
df_tr<- data.frame(default_payment=data_train$`default payment next month`,preds=pred_train_rf$.pred_class)
df_test<- data.frame(default_payment=data_test$`default payment next month`,preds=pred_test_rf$.pred_class)
head(df_tr) default_payment preds
1 0 0
2 0 0
3 0 0
4 0 0
5 0 0
6 0 0
head(df_test) default_payment preds
1 1 1
2 0 0
3 0 0
4 0 0
5 0 0
6 0 0
dim(df_tr)[1] 24000 2
dim(df_test)[1] 6000 2
eval_metrics <- metric_set(specificity, recall, accuracy,precision)
eval_metrics(data = df_tr , truth = default_payment , estimate =preds)# A tibble: 4 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 specificity binary 0.356
2 recall binary 0.954
3 accuracy binary 0.820
4 precision binary 0.838
eval_metrics(data = df_test , truth = default_payment , estimate =preds)# A tibble: 4 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 specificity binary 0.356
2 recall binary 0.951
3 accuracy binary 0.823
4 precision binary 0.843
Los valores de acuracidad son 82% en data train vs. 82.3% data test, recall 95.4% train vs. 95.1% test, precisión 84.8% train vs. 84.3% test, especificidad 35.6% train y test, las métricas no son distantes entre sí lo cual indica un buen performance del modelo, se evaluarán los coeficientes GINI y KS
gini_train = 2*(0.797) -1
gini_train[1] 0.594
gini_test = 2*(0.767) -1
gini_test[1] 0.534
diff_TPRFPR = result_train_rf[['sensitivity']]-(1-result_train_rf[['specificity']])
KS_train = max(diff_TPRFPR)
KS_train[1] 0.4415802
diff_TPRFPR_test = result_test_rf[['sensitivity']]-(1-result_test_rf[['specificity']])
KS_test = max(diff_TPRFPR_test)
KS_test[1] 0.4138844
Finalmente, el modelo de XGBoost presenta mejores métricas de ajuste para los datos el GINI y el KS fueron 59% train, 53% test y 44% train, 41% test, respectivamente. En general, el modelo de XGBoost posee mejor ajuste en comparación con el GLM, no se presentan a nivel general de los indicadores indicios de overfitting o de underfitting sobre el performance del modelo. Se representan las variables que poseen mayor representatividad de importancia en el XGBoost con el top 20
fit_rf_train %>%
extract_fit_parsnip() %>%
vip(num_features = 20)Las variables que mayor impacto generan en la predicción del default en tarjetas de crédito son asociados a las cantidades, montos, días de pago, así como, estatus civil, edad y límites aprobados.