Integrantes:
Fecha: 08 de enero de 2021
Profesora: Dra. Adriana Uquillas
Las entidades bancarias ofrecen préstamos a sus clientes, tomando en cuenta varia información sobre los mismos; así, lo deseable para el banco, es que sus clientes sean puntuales con sus pagos. Es por ello que, los bancos analizan la información para obtener una mejor visión sobre qué personas son puntuales y cuáles no, para así desarrollar metodologías estratégicas y, que el banco no pierda su dinero.
Entonces, para obtener una visión sobre los clientes, se puede realiza lo que se conoce como Modelo de Calificación Crediticia (Credit Scoring), siendo esta una herramienta de gestión de riesgos que evalúa la solvencia crediticia de un solicitante de préstamo, mediante la estimación de su probabilidad de incumplimiento según datos históricos. Utiliza herramientas numéricas para clasificar los casos de pedidos utilizando datos integrados en un valor único que intenta medir el riesgo o la solvencia crediticia.
En el presente trabajo, desarrollaremos el modelo de credit scoring por medio de los datos de la base bank-additional-full, están relacionados con campañas de marketing directo de una institución bancaria portuguesa. Estas campañas de marketing se basaron en llamadas telefónicas. A menudo, se requería más de un contacto con el mismo cliente, para poder acceder si el producto (depósito bancario a plazo) estaría (‘yes’) o no (‘no’) suscrito.
Así, una vez obtenidas las probabilidades de mora de los individuos, por medio del modelo de regresión logística donde se consideraron las variables idiosincráticas, sistémicas y políticas; ahora, nuestra tarea consiste en obtener grupos tales que las probabilidades de mora sean lo más homogéneas posibles.
Desarrollar el modelo de credit scoring por medio de las filosofías PIT y TTC, utilizando la información de la base bank-additionalfull, considerando la variable y, como dependiente;
Predecir la aceptación del cliente según el modelo obtenido
Formar grupos de riesgo homogéneos a través del modelo de Credit Scoring obtenido
Establecer el comportamiento esperado de los grupos homogéneos con relación a la variable temporal dada.
La mayoría de los modelos de calificación crediticia (Credit Scoring) se desarrollan utilizando modelos o metodologías de clasificación comprobados que utilizan “predictores” de campos de datos específicos para estimar la probabilidad de incumplimiento. Dos técnicas comunes son: la regresión logística y los árboles de clasificación o decisión; en el presente trabajo utilizaremos la primera técnica.
El modelo de regresión logística estima la probabilidad de una variable respuesta binaria/dicotómica basada en una o más variables o características predictoras. Más formalmente, un modelo logístico o logit es aquel en el que las probabilidades logarítmicas de la probabilidad de un evento son una combinación lineal de variables predictoras independientes.
Consideremos una colección de \(n\) variables independientes (explicativas) denotadas por el vector \(X = (X_1,X_2,...,X_n)\). Sea \(P(Y=1 | X) = \pi (x)\) la probabilidad condicional de que el resultado esté presente; el modelo de regresión logística o logit múltiple viene dado por la ecuación:
\[ g(X) = \beta_0 + \beta_1 X_1 + \cdots + \beta_n X_n \] en cuyo caso el modelo de regresión viene dada por la función logística: \[ \pi (X) = \frac{\exp \left\{g(X) \right\} }{1+ \exp \left \{ g(X) \right \} }. \] donde \(\pi\) es la función que toma valores estrictamente entre cero y uno, para todos los números reales.
Si algunas de las variables independientes son cualitativass, variables de escala nominal como etnia, sexo, grupo de tratamiento, etc., no es apropiado incluirlas en el modelo como si fueran variables cuantitativas. Los números utilizados para representar los distintos niveles de estas variables de escala nominal son simplemente identificadores y no tienen significado numérico.
En esta situación, el método de elección es utilizar una colección de variables de diseño o variables ficticias (dummy variables). Supongamos, por ejemplo, que una de las variables independientes es la etnia, que se ha codificado como “mestizo”, “indígena” y “otro”; en este caso, son necesarias dos variables dummy. Una posible estrategia de codificación es que cuando el encuestado es “mestizo”, las dos variables de diseño, \(Z_1\) y \(Z_2\), se fijarían ambas en cero; cuando el encuestado es 'indígena’, \(Z_1\) sería igual a 1 mientras que \(Z_2\) aún sería igual a 0; cuando la raza del encuestado es “otra”, usaríamos \(Z_1 = 0\) y \(Z_2 = 1\).
En general, si una variable cualitativa tiene \(m\) valores posibles, entonces se necesitarán \(m-1\) variables dummy. Esto es cierto ya que, a menos que se indique lo contrario, todos nuestros modelos tienen un término constante. Para ilustrar la notación utilizada para las variables dummy en este trabajo, supongamos que la \(j\_\)ésima variable independiente \(X_j\) tiene \(m_j\) niveles. Las variables dummy \(m_j -1\) se denotarán como \(Z_{jk}\) con \(k \in \{1,2,...,m_j-1\}\) y los coeficientes para estas variables de diseño se denotarán como \(\beta_{jk}\); por lo tanto, el modelo logit con \(n\) variables y la \(j\_\)ésima variable cualitativa con \(m\) niveles sería:
\[ g(X) = \beta_0 + \beta_1 X_1 + \cdots +\sum_{k=1}^{m_j-1} \beta_{jk}X_{jk} + \cdots + \beta_n X_n \]
Al discutir el modelo de regresión logística múltiple, en general, suprimiremos la suma y el doble subíndice necesarios para indicar cuándo se están utilizando las variables dummy. La excepción a esto será la discusión de las estrategias de modelado cuando necesitemos usar el valor específico de los coeficientes para cualquier variable dummy en el modelo.
Para analizar y tratar los datos de la base dada, trabajaremos con el software estadístico R, ya que nos proporciona una gran variedad de paquetes y comandos para desarrollar el trabajo.
Cargamos todas las librerías que vamos a utilizar en el transcurso del trabajo y, la base de datos:
library(openxlsx)
library(ggplot2)
library(tidyverse)
library(fastDummies)
base_banco <- as.data.frame(read.xlsx("data/bank-additional-full.xlsx"))
c('filas' = nrow(base_banco), 'columnas' = ncol(base_banco))
## filas columnas
## 41188 21
En primer lugar, notamos que la base de datos consta de 41188 objetos, organizada en 21 variables; así, veamos si los nombres de los atributos están en un formato adecuado:
names(base_banco)
## [1] "age" "job" "marital" "education"
## [5] "default" "housing" "loan" "contact"
## [9] "month" "day_of_week" "duration" "campaign"
## [13] "pdays" "previous" "poutcome" "emp.var.rate"
## [17] "cons.price.idx" "cons.conf.idx" "euribor3m" "nr.employed"
## [21] "y"
Vemos que, en efecto, los nombres de los atributos son adecuados; por tanto, ahora veamos los tipos de datos de cada atributo para poder describir a los mismos y, si es que se lo amerita, cambiar al tipo conveniente.
str(base_banco)
## 'data.frame': 41188 obs. of 21 variables:
## $ age : num 56 57 37 40 56 45 59 41 24 25 ...
## $ job : chr "housemaid" "services" "services" "admin." ...
## $ marital : chr "married" "married" "married" "married" ...
## $ education : chr "basic.4y" "high.school" "high.school" "basic.6y" ...
## $ default : chr "no" "unknown" "no" "no" ...
## $ housing : chr "no" "no" "yes" "no" ...
## $ loan : chr "no" "no" "no" "no" ...
## $ contact : chr "telephone" "telephone" "telephone" "telephone" ...
## $ month : chr "may" "may" "may" "may" ...
## $ day_of_week : chr "mon" "mon" "mon" "mon" ...
## $ duration : num 261 149 226 151 307 198 139 217 380 50 ...
## $ campaign : num 1 1 1 1 1 1 1 1 1 1 ...
## $ pdays : num 999 999 999 999 999 999 999 999 999 999 ...
## $ previous : num 0 0 0 0 0 0 0 0 0 0 ...
## $ poutcome : chr "nonexistent" "nonexistent" "nonexistent" "nonexistent" ...
## $ emp.var.rate : chr "1.1" "1.1" "1.1" "1.1" ...
## $ cons.price.idx: chr "93994" "93994" "93994" "93994" ...
## $ cons.conf.idx : chr "-36.4" "-36.4" "-36.4" "-36.4" ...
## $ euribor3m : chr "4857" "4857" "4857" "4857" ...
## $ nr.employed : chr "5191" "5191" "5191" "5191" ...
## $ y : chr "no" "no" "no" "no" ...
Vemos que es necesario cambiar los tipos de datos desde la variable emp.var.rate hasta nr.employed:
base_banco[,16:20] <- lapply(base_banco[,16:20], as.numeric)
Antes de realizar el análisis exploratorio y hacer algún cambio necesario en las variables, dividamos a la base de datos, tomando el 80\(\%\) de los clientes de la base para el modelamiento y, con el otro 20\(\%\) de infromación realizar la validez del modelo.
library(caret)
set.seed(1984)
training <- createDataPartition(base_banco$y, p = 0.8, list=FALSE)
banco <- base_banco[training,]
banco_test <- base_banco[-training,]
Es decir, ahora, nuestra base de datos tiene la siguiente dimensión:
dim(banco)
## [1] 32951 21
Antes de ir con la descripción completa de cada variable, veamos si en la base de datos se presentan valores perdidos:
sum(is.na(banco))
## [1] 0
Como podemos notar, dentro de toda la base de datos, toda información está registrada. Entonces, en la siguiente sección se analizará a cada variable, de donde también, veremos los valores atípicos de aquellas variables que pueden presentar.
A continuación, se describe a cada una de las variables existentes donde, además, se mostrará la frecuencia de cada dato que aparece en cada variable. Por otro lado, se estudiarán los valores atípicos de las variables numéricas.
En primer lugar, veamos qué ocurre con la variable respuesta para el modelo.
Descripción: si el cliente ha suscrito un depósito o no a plazo.
Total de valores distintos: se registran dos datos distinto, yes y no.
Tabla de frecuencias: mostramos la frecuencia con que aparece esta información
## Var1 Freq Porcent
## 1 no 29239 88.73479
## 2 yes 3712 11.26521
Así, podemos ver que, hay varios clientes a los que no les han dado un depósito; esto puede deberse a la infromación restante que se tiene de los mismos.
Descripción: Edad del cliente, en años.
Total de valores distintos:
length(unique(banco[,1]))
## [1] 78
a <- table(banco[,1])
b <- prop.table(a)*100
b <- as.data.frame(b)
names(b)[2] <- 'Porcent'
full_join(as.data.frame(a),b) %>% arrange(desc(Freq)) %>% head(10)
## Var1 Freq Porcent
## 1 31 1538 4.667537
## 2 32 1476 4.479378
## 3 33 1473 4.470274
## 4 36 1418 4.303360
## 5 34 1413 4.288185
## 6 35 1398 4.242663
## 7 30 1381 4.191072
## 8 37 1186 3.599284
## 9 29 1157 3.511274
## 10 38 1129 3.426300
banco %>% ggplot(aes(age)) + geom_bar(col = 'blue', fill='blue') + labs(x = 'Edad', y = 'frecuencia') + scale_x_continuous(breaks = seq(0,95, by = 15)) + theme_light()
banco %>% ggplot(aes(x=0, y=age)) + geom_boxplot() + theme_light() +
labs(y = 'Edad', title = 'Boxplot Edad')
grupos <- kmeans(x = banco[, c('age')], centers = 5, nstart = 1)
banco <- banco %>% mutate(gr.edad = grupos$cluster)
banco <- banco %>% mutate(gr.edad = as.factor(gr.edad))
Entonces, veamos el mínimo y máximo de cada grupo formado:
A <- banco %>% filter(gr.edad == 1)
B <- banco %>% filter(gr.edad == 2)
C <- banco %>% filter(gr.edad == 3)
D <- banco %>% filter(gr.edad == 4)
E <- banco %>% filter(gr.edad == 5)
c('minA' = min(A$age), 'maxA' = max(A$age),
'minB' = min(B$age), 'maxB' = max(B$age),
'minC' = min(C$age), 'maxC' = max(C$age),
'minD' = min(D$age), 'maxD' = max(D$age),
'minE' = min(E$age), 'maxE' = max(E$age))
## minA maxA minB maxB minC maxC minD maxD minE maxE
## 42 51 28 33 17 27 52 98 34 41
Entonces, vamos a renombrar a dichos grupos de una mejor manera:
library(plyr)
banco[,22] <- revalue(banco[,22], c('1' = 'D', '2' = 'B', '3' = 'A',
'4' = 'E', '5' = 'C'))
detach(package:plyr)
banco[,1] <- banco[,22]
banco <- banco[,-22]
donde se tienen los grupos:
A: Menores a 27 años;
B: De 28 a 33 años;
C: De 34 a 41 años;
D: De 42 a 51 años;
E: De 52 años en adelante.
banco %>% ggplot(aes(age)) + geom_bar(col = 'blue', fill='blue') + labs(x = 'Rangos de edad', y = 'frecuencia') + theme_light()
Descripción: tipo de trabajo.
Total de valores distintos: esta variable consta de 12 categorías
## [1] 12
## Var1 Freq Porcent
## 1 admin. 8347 25.3315529
## 2 blue-collar 7431 22.5516676
## 3 technician 5377 16.3181694
## 4 services 3183 9.6597979
## 5 management 2317 7.0316531
## 6 retired 1389 4.2153501
## 7 entrepreneur 1139 3.4566477
## 8 self-employed 1125 3.4141604
## 9 housemaid 845 2.5644138
## 10 unemployed 804 2.4399866
## 11 student 718 2.1789931
## 12 unknown 276 0.8376074
no <- banco %>% filter(y=='no') %>% group_by(job, y) %>% dplyr::summarise(n=n())
yes <- banco %>% group_by(job) %>% dplyr::summarise(n=n())
names(yes)[2] <- 'Total'
cl <- full_join(no, yes, by='job')
cl$NO <- cl$n/cl$Total
cl$YES <- (cl$Total - cl$n)/cl$Total
cl <- cl[,-c(2,3,4)]
clust <-data.frame('NO' = cl$NO, 'YES' = cl$YES,
row.names = cl$job)
cat <- scale(clust)
d1 <- dist(cat, method = "euclidean") # Matriz de distancias
hc1 <- hclust(d1, method = "centroid" ) # Cluster Jeráriquico
plot(hc1, cex = 0.6, hang = -1) # Dendograma y grupos
rect.hclust(hc1, k =4, border = 2:10)
Entonces, se tienen 4 grupos para recategorizar a los tipos de empleos y reducir la cardinalidad de esta variable; denominaremos a los grupos como:
T1: housemaid, self-employed, technician, management, unknown, admin y unemployed.
T2: blue-collar, entrepreneur y services.
T3: student.
T4: retired.
library(plyr)
banco[,2] <- revalue(banco[,2], c( 'student' = 'T3', 'retired' = 'T4',
'housemaid' = 'T1', 'unknown' = 'T1',
'management' = 'T1' , 'entrepreneur' = 'T2',
'blue-collar' = 'T2', 'unemployed' = 'T1',
'admin.' = 'T1', 'services' = 'T2',
'self-employed' = 'T1', 'technician' = 'T1'))
detach(package:plyr)
Descripción: estado civil.
Total de valores distintos: se tienen 4 categorías
## [1] 4
## Var1 Freq Porcent
## 1 married 19970 60.605141
## 2 single 9213 27.959698
## 3 divorced 3705 11.243968
## 4 unknown 63 0.191193
Descripción: nivel de educación.
Total de valores distintos: se tienen 8 categorías
## [1] 8
## Var1 Freq Porcent
## 1 university.degree 9760 29.61973840
## 2 high.school 7574 22.98564535
## 3 basic.9y 4860 14.74917301
## 4 professional.course 4192 12.72192043
## 5 basic.4y 3332 10.11198446
## 6 basic.6y 1840 5.58404904
## 7 unknown 1380 4.18803678
## 8 illiterate 13 0.03945252
A estos grupos, los denotaremos como:
E1: basic.4y, high.school, professional.course.
E2: basic.6y, basic.9y.
E3: university,degree, illiterate, unknown.
Descripción: si el cliente tiene o no crédito en incumplimiento.
Total de valores distintos: tiene tres categorías
## [1] 3
## Var1 Freq Porcent
## 1 no 26063 79.096233802
## 2 unknown 6886 20.897696580
## 3 yes 2 0.006069619
Descripción: si el cliente tiene o no préstamo de vivienda.
Total de valores distintos:
## [1] 3
## Var1 Freq Porcent
## 1 yes 17259 52.377773
## 2 no 14888 45.182240
## 3 unknown 804 2.439987
Descripción: si el cliente tiene o no préstamo préstamos.
Total de valores distintos:
## [1] 3
## Var1 Freq Porcent
## 1 no 27149 82.392037
## 2 yes 4998 15.167977
## 3 unknown 804 2.439987
Descripción: tipo de comunicación de contacto.
Total de valores distintos: se tienen dos categorías.
## [1] 2
## Var1 Freq Porcent
## 1 cellular 20836 63.23329
## 2 telephone 12115 36.76671
Descripción: último mes de contacto del año.
Total de valores distintos: se tienen 10 meses registrados.
## [1] 10
## Var1 Freq Porcent
## 1 may 10995 33.3677278
## 2 jul 5747 17.4410488
## 3 aug 4897 14.8614610
## 4 jun 4282 12.9950533
## 5 nov 3261 9.8965130
## 6 apr 2099 6.3700646
## 7 oct 599 1.8178507
## 8 sep 471 1.4293952
## 9 mar 455 1.3808382
## 10 dec 145 0.4400473
Descripción: último día de contacto de la semana.
Total de valores distintos: se tienen registrados 5 días.
## [1] 5
## Var1 Freq Porcent
## 1 thu 6839 20.75506
## 2 mon 6827 20.71864
## 3 wed 6499 19.72323
## 4 tue 6454 19.58666
## 5 fri 6332 19.21641
Descripción: duración del último contacto, en segundos.
Total de valores distintos: el número de datos distintos es de:
## [1] 1466
## Var1 Freq Porcent
## 1 85 141 0.4279081
## 2 73 135 0.4096993
## 3 135 133 0.4036296
## 4 72 132 0.4005948
## 5 87 132 0.4005948
## 6 136 132 0.4005948
## 7 92 131 0.3975600
## 8 124 129 0.3914904
## 9 93 128 0.3884556
## 10 89 127 0.3854208
Notamos que hay más frecuencia en los tiempos de, aproximandamente, 125 y 250 segundos y, hay muy pocas llamdas que ha durado un tiempo superior a 1000 segundos.
Por otro lado, veamos si existen valores atípicos en esta variable, observando el diagrama de cajas:
banco %>% ggplot(aes(x=0, y=duration)) + geom_boxplot() + theme_light() +
labs(y = 'Duración', title = 'Boxplot duration')
En esta variable, tenemos un total de
boxplot.stats(banco$duration)$out %>% length()
## [1] 2374
valores atípicos; entonces, un un criterio para sustituir estos valores es el siguiente:
Si los datos se encuentran por debajo del umbral inferior \((Q_1 − 1.5IQR)\), los reemplezamos por la media del conjunto de los datos.
Si los datos se encuentra por encima del umbral superior \((Q_3 + 1.5IQR)\), los reemplazamos por la mediana del conjunto de los datos.
reemplazo <- function(x, BI, BS){
x[x < BI] <- mean(x)
x[x > BS] <- median(x)
x
}
BI <- boxplot.stats(banco$duration)$stats[1]
BS <- boxplot.stats(banco$duration)$stats[5]
banco$duration <- reemplazo(banco$duration, BI, BS)
c('mínimo' = min(banco$duration), 'Q1'=quantile(banco$duration,0.25),
'mediana Q2'=median(banco$duration), 'media'=mean(banco$duration), 'Q3'=quantile(banco$duration,0.75),
'maximo'=max(banco$duration), 'rango'=max(banco$duration)-min(banco$duration), 'desv.est'=sd(banco$duration))
## mínimo Q1.25% mediana Q2 media Q3.75% maximo rango
## 0.0000 102.0000 180.0000 201.3386 264.5000 642.0000 642.0000
## desv.est
## 135.2699
Descripción: número de contactos realizados durante esa campaña y para ese cliente.
Total de valores distintos: el número de datos distintos es de:
## [1] 40
## Var1 Freq Porcent
## 1 1 14073 42.7088707
## 2 2 8473 25.7139389
## 3 3 4286 13.0071925
## 4 4 2118 6.4277260
## 5 5 1269 3.8511730
## 6 6 791 2.4005341
## 7 7 512 1.5538223
## 8 8 327 0.9923826
## 9 9 224 0.6797973
## 10 10 178 0.5401960
banco %>% ggplot(aes(x=0, y=campaign)) + geom_boxplot() + theme_light() +
labs(y = '# Contactos', title = 'Boxplot campaign')
En esta variable, tenemos un total de
boxplot.stats(banco$campaign)$out %>% length()
## [1] 1941
valores atípicos; entonces los sustituimos utilizando el criterio anterior.
BI <- boxplot.stats(banco$campaign)$stats[1]
BS <- boxplot.stats(banco$campaign)$stats[5]
banco$campaign <- reemplazo(banco$campaign, BI, BS)
c('mínimo' = min(banco$campaign), 'Q1'=quantile(banco$campaign,0.25),
'mediana Q2'=median(banco$campaign), 'media'=mean(banco$campaign), 'Q3'=quantile(banco$campaign,0.75),
'maximo'=max(banco$campaign), 'rango'=max(banco$campaign)-min(banco$campaign), 'desv.est'=sd(banco$campaign))
## mínimo Q1.25% mediana Q2 media Q3.75% maximo rango
## 1.000000 1.000000 2.000000 2.043094 3.000000 6.000000 5.000000
## desv.est
## 1.242236
999 significa que el cliente no fue contactado previamente
## [1] 26
## Var1 Freq Porcent
## 1 999 31749 96.352159267
## 2 3 350 1.062183242
## 3 6 336 1.019695912
## 4 4 85 0.257958787
## 5 7 50 0.151740463
## 6 9 49 0.148705654
## 7 12 46 0.139601226
## 8 2 45 0.136566417
## 9 5 37 0.112287943
## 10 10 36 0.109253133
## 11 13 30 0.091044278
## 12 11 24 0.072835422
## 13 1 22 0.066765804
## 14 14 19 0.057661376
## 15 15 17 0.051591757
## 16 8 14 0.042487330
## 17 0 11 0.033382902
## 18 16 10 0.030348093
## 19 17 7 0.021243665
## 20 18 5 0.015174046
## 21 19 2 0.006069619
## 22 21 2 0.006069619
## 23 22 2 0.006069619
## 24 20 1 0.003034809
## 25 25 1 0.003034809
## 26 26 1 0.003034809
banco$pdays <- if_else(banco$pdays == 999, 1, 0)
Descripción: número de contactos realizados antes de esta campaña y para este cliente.
Total de valores distintos: el número de datos distintos es de:
## [1] 8
## Var1 Freq Porcent
## 1 0 28491 86.464750690
## 2 1 3602 10.931382963
## 3 2 608 1.845164031
## 4 3 174 0.528056812
## 5 4 59 0.179053746
## 6 5 12 0.036417711
## 7 6 4 0.012139237
## 8 7 1 0.003034809
banco$previous <- if_else(banco$previous == 0, 1, 0)
15. poutcome: ; :
Descripción: resultado de la campaña de marketing anterior.
Total de valores distintos: se tienen 3 categorías:
## [1] 3
## Var1 Freq Porcent
## 1 nonexistent 28491 86.464751
## 2 failure 3373 10.236412
## 3 success 1087 3.298838
Una vez analizadas y tratadas las variables que más inconvenientes nos hubiesen producido en el modelamiento, realizaremos los modelos de regresión logística en base a las dos filosofías, para así dar resultados respecto a la puntuación de calidad crediticia.
El sistema de clasificación PIT (Punto en el tiempo) es la clasificación de riesgo de los clientes cambia que cambia de acuerdo a épocas de expansión y recesión de la economía, y de cambios en la política de crédito.
Así, para desarrollar el modelo, tomaremos, en principio, todas las variables presentes en la base de datos, que son las idiosincrácticas, sistémicas y macroeconómicas.
Así, como se había visto, a las variable categóricas las convertimos en dummies; de donde, en \(R\), este proceso es rápido ya que solo utilizamos un comando:
categoricas <- c('age', 'job', 'marital', 'education', 'housing', 'loan', 'poutcome','contact' )
banco_training <- banco %>% dummy_cols(select_columns = categoricas) %>% select(-categoricas, -month, -day_of_week, -default)
Así entonces, corremos el modelo de regresión logística, especificando que la variable dependiente es \(y\) que, para esto, necesitamos cambiar el valor de ‘no’ por 0 y el valor de ‘yes’ por 1:
banco_training$y <- if_else(banco_training$y == 'yes', 1, 0)
banco_training$y <- as.numeric(banco_training$y)
#write.xlsx(banco_training, '00/training.xlsx')
logit_banco <-glm(y~., data = banco_training, family='binomial')
Así, veamos qué variables son explicativas para el modelo:
summary(logit_banco)
##
## Call:
## glm(formula = y ~ ., family = "binomial", data = banco_training)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.3124 -0.4175 -0.2850 -0.2184 2.9419
##
## Coefficients: (10 not defined because of singularities)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -5.940e+01 1.496e+01 -3.971 7.16e-05 ***
## duration 4.302e-03 1.321e-04 32.563 < 2e-16 ***
## campaign 6.201e-03 1.723e-02 0.360 0.718962
## pdays -1.062e+00 2.107e-01 -5.040 4.65e-07 ***
## previous -2.416e-01 2.225e-01 -1.086 0.277374
## emp.var.rate -7.387e-01 6.873e-02 -10.747 < 2e-16 ***
## cons.price.idx 8.671e-04 1.012e-04 8.566 < 2e-16 ***
## cons.conf.idx 3.543e-02 5.605e-03 6.322 2.59e-10 ***
## euribor3m 2.324e-01 7.223e-02 3.218 0.001293 **
## nr.employed -4.512e-03 1.298e-03 -3.477 0.000507 ***
## age_A 1.158e-01 9.485e-02 1.221 0.222100
## age_B 4.499e-02 7.322e-02 0.614 0.538908
## age_C -1.822e-01 7.007e-02 -2.600 0.009322 **
## age_D -1.785e-01 7.277e-02 -2.453 0.014160 *
## age_E NA NA NA NA
## job_T1 -3.089e-01 9.394e-02 -3.288 0.001009 **
## job_T2 -5.314e-01 1.004e-01 -5.294 1.20e-07 ***
## job_T3 -1.581e-01 1.428e-01 -1.107 0.268170
## job_T4 NA NA NA NA
## marital_divorced -1.618e-03 4.249e-01 -0.004 0.996962
## marital_married 4.292e-02 4.210e-01 0.102 0.918804
## marital_single 8.348e-02 4.219e-01 0.198 0.843140
## marital_unknown NA NA NA NA
## education_E1 -1.273e-01 4.645e-02 -2.741 0.006127 **
## education_E2 -1.389e-01 6.631e-02 -2.095 0.036174 *
## education_E3 NA NA NA NA
## housing_no 2.827e-03 4.101e-02 0.069 0.945050
## housing_unknown 1.317e-01 1.368e-01 0.963 0.335679
## housing_yes NA NA NA NA
## loan_no 3.369e-02 5.695e-02 0.591 0.554204
## loan_unknown NA NA NA NA
## loan_yes NA NA NA NA
## poutcome_failure -7.591e-01 2.134e-01 -3.557 0.000375 ***
## poutcome_nonexistent NA NA NA NA
## poutcome_success NA NA NA NA
## contact_cellular 7.912e-01 6.171e-02 12.821 < 2e-16 ***
## contact_telephone NA NA NA NA
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 23199 on 32950 degrees of freedom
## Residual deviance: 17492 on 32924 degrees of freedom
## AIC: 17546
##
## Number of Fisher Scoring iterations: 6
Quitamos las variables que no aportan en nada al modelo y luego, vamos retirando una a una, las variables que tienen \(p\_valores\) mayores al \(5\%\); pues, el \(p\_valor\) de cada coeficiente evalúa la hipótesis nula \((H_0)\) de que el coeficiente es igual a cero (no hay efecto), por lo que si un \(p\_valor\) es bajo (usualmente \(< 0,05\)) indica que se puede rechazar la hipótesis nula.
En otras palabras, es probable que un predictor que tenga un \(p\_valor\) bajo sea una adición significativa al modelo porque los cambios en el valor del predictor se relacionan con cambios en la variable de respuesta; por el contrario, un \(p\_valor\) más grande (insignificante) sugiere que los cambios en el predictor no están asociados con cambios en la respuesta.
Así, se obtuvo el siguiente modelo::
banco_training <- banco_training %>% select(-age_E, -job_T4, -marital_unknown, -education_E3, -housing_yes, -loan_unknown, -loan_yes, -poutcome_nonexistent, -poutcome_success, -contact_telephone, -marital_divorced , -housing_no, -campaign, -loan_no, -housing_unknown, -marital_married, -previous, -job_T3, -marital_single, -age_B, -age_A, -euribor3m, -nr.employed)
logit_banco <-glm(y~., data = banco_training, family='binomial')
summary(logit_banco)
##
## Call:
## glm(formula = y ~ ., family = "binomial", data = banco_training)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.3128 -0.4172 -0.2839 -0.2196 2.9881
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.025e+02 4.836e+00 -21.191 < 2e-16 ***
## duration 4.287e-03 1.318e-04 32.521 < 2e-16 ***
## pdays -1.285e+00 7.484e-02 -17.175 < 2e-16 ***
## emp.var.rate -7.512e-01 1.989e-02 -37.764 < 2e-16 ***
## cons.price.idx 1.094e-03 5.167e-05 21.166 < 2e-16 ***
## cons.conf.idx 4.846e-02 4.215e-03 11.496 < 2e-16 ***
## age_C -2.348e-01 4.966e-02 -4.728 2.27e-06 ***
## age_D -2.355e-01 5.510e-02 -4.273 1.93e-05 ***
## job_T1 -2.417e-01 6.887e-02 -3.510 0.000449 ***
## job_T2 -4.744e-01 7.675e-02 -6.181 6.37e-10 ***
## education_E1 -1.291e-01 4.615e-02 -2.798 0.005141 **
## education_E2 -1.434e-01 6.596e-02 -2.174 0.029716 *
## poutcome_failure -5.361e-01 6.127e-02 -8.750 < 2e-16 ***
## contact_cellular 8.089e-01 5.898e-02 13.714 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 23199 on 32950 degrees of freedom
## Residual deviance: 17511 on 32937 degrees of freedom
## AIC: 17539
##
## Number of Fisher Scoring iterations: 6
Así, una vez generado nuestro modelo crediticio, vamos a utilizar nuestra base de entrenamiento para comparar los resultados y validar adecuamente nuestro modelo. Entonces, tratemos a la base de entrenamiento para poder predecir sus valores:
## AGE
for(i in 1:length(banco_test[,1])){
if(banco_test[i,1] <= 27){banco_test[i,1] = 'A'}
else if(banco_test[i,1] > 27 & banco_test[i,1] <= 33){banco_test[i,1] = 'B'}
else if(banco_test[i,1] > 33 & banco_test[i,1] <= 42){banco_test[i,1] = 'C'}
else if(banco_test[i,1] > 41 & banco_test[i,1] <= 51){banco_test[i,1] = 'D'}
else if(banco_test[i,1] > 51){banco_test[i,1] = 'E'}
}
library(plyr)
## JOB
banco_test[,2] <- revalue(banco_test[,2], c( 'student' = 'T3', 'retired' = 'T4',
'housemaid' = 'T1', 'unknown' = 'T1',
'management' = 'T1' , 'entrepreneur' = 'T2',
'blue-collar' = 'T2', 'unemployed' = 'T1',
'admin.' = 'T1', 'services' = 'T2',
'self-employed' = 'T1', 'technician' = 'T1'))
## EDUCATION
banco_test[,4] <- revalue(banco_test[,4], c('university.degree' = 'E3', 'illiterate' = 'E3',
'unknown' = 'E3', 'basic.6y' = 'E2',
'basic.9y' = 'E2', 'basic.4y' = 'E1',
'high.school' = 'E1', 'professional.course' = 'E1'))
detach(package:plyr)
## PDAYS
banco_test$pdays <- if_else(banco_test$pdays == 999, 1, 0)
## PREVIOUS
banco_test$previous <- if_else(banco_test$previous == 0, 1, 0)
Una vez organizada nuestra base de datos, utilizamos las mismas variables expliciativas del modelo obtenido, para la predicción de las probabilidades de cada individuo:
categoricas <- c('age', 'job', 'marital', 'education', 'housing', 'loan', 'poutcome','contact')
banco_test <- banco_test %>% dummy_cols(select_columns = categoricas) %>% select(-categoricas, -month, -day_of_week, -default)
banco_test <- banco_test %>% select(-age_E, -job_T4, -marital_unknown, -education_E3, -housing_yes, -loan_unknown, -loan_yes, -poutcome_nonexistent, -poutcome_success, -contact_telephone, -marital_divorced , -housing_no, -campaign, -loan_no, -housing_unknown, -marital_married, -previous, -job_T3, -marital_single, -age_B, -age_A, -euribor3m, -nr.employed)
banco_test$y <- if_else(banco_test$y == 'yes', 1, 0)
banco_test$y <- as.numeric(banco_test$y)
En la regresión lineal simple, se modela el valor de la variable dependiente \(Y\) en función del valor de la variable independiente \(X\). Sin embargo, en la regresión logística, tal como se ha descrito, se modela la probabilidad de que la variable respuesta \(Y\) pertenezca al nivel de referencia 1 en función del valor que adquieran los predictores, mediante el uso del logaritmo de odds.
La transformación de probabilidades a ODDs es monótnoa, si la probabilidad aumenta también lo hacen los ODDs, y viceversa. El rango de valores que pueden tomar los ODDs es de \([0,1]\), pues se recurre a la trasformación logit explicada anteriormente.
odds <- predict(logit_banco, banco_test, type = 'response')
odds[1:20]
## 1 2 3 4 5 6 7
## 0.05472909 0.03433924 0.01887779 0.06438036 0.03404255 0.02254449 0.02624662
## 8 9 10 11 12 13 14
## 0.06237745 0.07263059 0.01873508 0.04990452 0.03076028 0.03394286 0.06704541
## 15 16 17 18 19 20
## 0.03663850 0.02954750 0.03047631 0.03860607 0.02666302 0.03067402
Así, una vez predichas las probabilidades de cada individuo de la base de testeo, realizaremos algunas pruebas estadísticas para validar nuestro modelo:
Este método de clasificación nos ayuda a medir cómo se comporta nuestro modelo cuando se lo aplicamos a base de testeo. La matriz tiene la siguiente estructura:
donde:
VP: es la cantidad de positivos que fueron clasificados correctamente como positivos por el modelo.
VN: es la cantidad de negativos que fueron clasificados correctamente como negativos por el modelo.
FN: es la cantidad de positivos que fueron clasificados incorrectamente como negativos.
FP: es la cantidad de negativos que fueron clasificados incorrectamente como positivos.
Ahora, presentamos la matriz de confusión para la base test.Se tiene un total de 501 falsos positivos y 247 falsos negativos. Esto representa una precisión del 90%. Entonces el modelo es adecuado.
test.pred <- ifelse(odds > .5, "1", "0")
matriz<-confusionMatrix(as.factor(banco_test$y), as.factor(test.pred))
matriz$table
## Reference
## Prediction 0 1
## 0 7062 247
## 1 501 427
matriz$overall[1]
## Accuracy
## 0.9091902
A continuación, se presentan los gráficos de las curvas ROC para la base de datos de entrenamiento y la base test. También se presenta el área bajo las curvas, que es el coeficiente AUC y el estadístico de Kolmogorov-Smirnov.
require(ROCR)
train.prob <- predict(logit_banco, type = "response")
train.pred <- ifelse(train.prob > .5, "1", "0")
train.roc <- prediction(train.prob, banco_training$y)
plot(performance(train.roc, "tpr", "fpr"), col = "red", main = "Curva ROC para la base de entrenamiento")
abline(0, 1, lty = 8, col = "blue")
# AUC
train.auc <- performance(train.roc, "auc")
c(AUC=slot(train.auc, "y.values"))
## $AUC
## [1] 0.81948
# KS
ks.train <- performance(train.roc, "tpr", "fpr")
train.ks <- max(attr(ks.train, "y.values")[[1]] - (attr(ks.train, "x.values")[[1]]))
c(KS = train.ks)
## KS
## 0.513674
test.prob <- predict(logit_banco, newdata = banco_test, type = "response")
test.pred <- ifelse(test.prob > .5, "1", "0")
test.roc <- prediction(test.prob, banco_test$y)
plot(performance(test.roc, "tpr", "fpr"), col = "red", main = "Curva ROC para la base test")
abline(0, 1, lty = 8, col = "blue")
# AUC
test.auc <- performance(test.roc, "auc")
c(AUC=slot(test.auc, "y.values"))
## $AUC
## [1] 0.8980304
# KS
ks.test <- performance(test.roc, "tpr", "fpr")
test.ks <- max(attr(ks.test, "y.values")[[1]] - (attr(ks.test, "x.values")[[1]]))
c(KS=test.ks)
## KS
## 0.7050205
Notemos que el valor AUC de la base de entrenamiento es 0,82 y de la base test es 0,89; por lo tanto, el modelo se considera adecuado. Notemos que el estadístico KS de la base de entrenamiento es 0,51 y el de la base de test es 0,7. En general, el modelo se considera válido.
A continuación, prcedemos a unir los valores predichos en la base original y, vamos a predecir los valores para la base de entrenamiento para, de igual manera, juntarlos en una sola base. Además, vamos a unir la información de la variable indicadora para posteriormente realizar el ciclo económico de cada uno de los grupos.
indicadora <- as.data.frame(read.xlsx("data/bank-additional-full-1.xlsx"))
ind_training <- indicadora[training,] ## training, índices de la muestra
ind_test <- indicadora[-training,]
## TESTING
Testing <- banco_test
Testing$probs <- odds
Testing$month <- ind_test$month1
## TRAINING
Training <- banco_training
odds <- predict(logit_banco, banco_training, type = 'response')
Training$probs <- odds
Training$month <- ind_training$month1
banco <- rbind(Training, Testing)
Para la formación de los grupos homogéneos vamos a agrupar las probabilidades, utilizando el método de \(k-\)medias; de donde, se formarán 4 grupos:
grupos <- kmeans(x = banco[, c('probs')], centers = 4, nstart = 1)
## GH = grupos homogeneos
banco <- banco %>% mutate(GH = grupos$cluster)
banco <- banco %>% mutate(GH = as.factor(GH))
Entonces, veamos los valores máximos y mínimos de cada grupo formado para organizarlos:
C1 <- banco %>% filter(GH == 1)
C2 <- banco %>% filter(GH == 2)
C3 <- banco %>% filter(GH == 3)
C4 <- banco %>% filter(GH == 4)
c('minC1' = min(C1$probs), 'maxC1' = max(C1$probs),
'minC2' = min(C2$probs), 'maxC2' = max(C2$probs),
'minC3' = min(C3$probs), 'maxC3' = max(C3$probs),
'minC4' = min(C4$probs), 'maxC4' = max(C4$probs))
## minC1 maxC1 minC2 maxC2 minC3 maxC3
## 2.832617e-01 5.671461e-01 2.220446e-16 1.057109e-01 1.057462e-01 2.832166e-01
## minC4 maxC4
## 5.674784e-01 9.999724e-01
Entonces, notamos que el primer grupo es el \(C_2\), el segundo grupo es \(C_3\), el tercer grupo es \(C_1\) y último grupo, los que tiene altas probabilidades de mora, es \(C_4\).
Para evitarnos confusiones, renombramos estos grupos respecto a su orden. Así, Una vez especificados los grupos, veamos las pruebas de diferencias de medias para cada uno; además, para asegurarnos que los grupos no son homogéneos entre sí. Entonces, para aplicar una de las pruebas estadísticas que contrastan la hipótesis de igualdad de medias, veamos si la varianza de los grupos, en cuanto a las probabilidades, son iguales o no:
Para esto, haremos uso de la prueba de Barlet, la cual contrasta la hipótesis nula de si todas las varianzas de los grupos son iguales:
G1 <- C2; G2 <- C3; G3 <- C1; G4 <- C4
G1$GH <- as.character(G1$GH)
G2$GH <- as.character(G2$GH)
G1$GH <- as.character(G1$GH)
G2$GH <- as.character(G2$GH)
variables <- c(G1$probs, G2$probs)
categorias <- c(G1$GH, G2$GH)
bartlett.test(variables, categorias)
##
## Bartlett test of homogeneity of variances
##
## data: variables and categorias
## Bartlett's K-squared = 7815.2, df = 1, p-value < 2.2e-16
Podemos ver que, como el \(p\_valor\) es menor a un \(\alpha = 0.05\), se rechaza la hipótesis nula y, en consecuencia, las varianzas entre el grupo 1 y grupo 2 son diferentes. De manera análoga, se contrasta para los otros grupos, dándonos para todos los casos que el \(p\_valor\) es menor al nivel de significancia \(\alpha\).
variables <- c(G1$probs, G3$probs)
categorias <- c(G1$GH, G3$GH)
bartlett.test(variables, categorias)
##
## Bartlett test of homogeneity of variances
##
## data: variables and categorias
## Bartlett's K-squared = 13892, df = 1, p-value < 2.2e-16
variables <- c(G1$probs, G4$probs)
categorias <- c(G1$GH, G4$GH)
bartlett.test(variables, categorias)
##
## Bartlett test of homogeneity of variances
##
## data: variables and categorias
## Bartlett's K-squared = 18325, df = 1, p-value < 2.2e-16
variables <- c(G2$probs, G3$probs)
categorias <- c(G2$GH, G3$GH)
bartlett.test(variables, categorias)
##
## Bartlett test of homogeneity of variances
##
## data: variables and categorias
## Bartlett's K-squared = 1001.9, df = 1, p-value < 2.2e-16
variables <- c(G2$probs, G4$probs)
categorias <- c(G2$GH, G4$GH)
bartlett.test(variables, categorias)
##
## Bartlett test of homogeneity of variances
##
## data: variables and categorias
## Bartlett's K-squared = 2241.9, df = 1, p-value < 2.2e-16
variables <- c(G3$probs, G4$probs)
categorias <- c(G3$GH, G4$GH)
bartlett.test(variables, categorias)
##
## Bartlett test of homogeneity of variances
##
## data: variables and categorias
## Bartlett's K-squared = 228.06, df = 1, p-value < 2.2e-16
Con esto, realizamos la prueba de Games y Howell, para la comparación de medias entre los grupos:
library(userfriendlyscience)
banco$GH <- as.factor(banco$GH)
GH <- oneway(banco$GH, y = banco$probs, posthoc = 'games-howell')
GH
## ### Oneway Anova for y=probs and x=GH (groups: 1, 2, 3, 4)
##
## Omega squared: 95% CI = [.94; .94], point estimate = .94
## Eta Squared: 95% CI = [.94; .94], point estimate = .94
##
## SS Df MS F p
## Between groups (error + effect) 1085.22 3 361.74 207730.75 <.001
## Within groups (error only) 71.72 41184 0
##
##
## ### Post hoc test: games-howell
##
## diff ci.lo ci.hi t df p
## 2-1 -0.36 -0.36 -0.35 239.87 2910.65 <.001
## 3-1 -0.23 -0.23 -0.22 143.81 3805.82 <.001
## 4-1 0.34 0.33 0.35 114.24 2897.03 <.001
## 3-2 0.13 0.13 0.13 209.97 7752.35 <.001
## 4-2 0.69 0.69 0.70 268.48 1766.89 <.001
## 4-3 0.57 0.56 0.57 214.31 1942.44 <.001
En consecuencia, como los \(p\_valores\) de la comparación de cada dos grupos son menores al \(5\%\), concluimos que los grupos tienen medias distintas; es decir, los 4 grupos formados son homogéneos pero heterogéneos entre sí.
Ahora entonces, por medio del uso de la variable indicadora month, tenemos 8 tiempos para realizar el ciclo económico; de donde, por cada grupo y por cada mes, se calculará el procentaje de clientes malos \(y = no = 0\) en ese grupo:
banco$GH <- as.character(banco$GH)
library(plyr)
banco$GH <- revalue(banco$GH, c('1' = 'G3', '2' = 'G1', '3' = 'G2',
'4' = 'G4'))
detach(package:plyr)
for(i in 1:length(banco$month)){
if(banco$month[i] == 6 | banco$month[i] == 7 | banco$month[i] == 8){
banco$month[i] <- 5
}else if(banco$month[i] == 9){
banco$month[i] <- 6
}else if(banco$month[i] == 10){
banco$month[i] <- 7
}else if(banco$month[i] == 11 | banco$month[i] == 12 | banco$month[i] == 13 |
banco$month[i] == 14 | banco$month[i] == 15 | banco$month[i] == 16 |
banco$month[i] == 17 | banco$month[i] == 18 | banco$month[i] == 19 |
banco$month[i] == 20 | banco$month[i] == 21 | banco$month[i] == 22 |
banco$month[i] == 23 | banco$month[i] == 24 | banco$month[i] == 25 |
banco$month[i] == 26){
banco$month[i] <- 8
}
}
no <- banco %>% filter(y == 0) %>% group_by(GH, month) %>% dplyr::summarise(tmalos = n())
yes <- banco %>% filter(y == 1) %>% group_by(GH, month) %>% dplyr::summarise(tbuenos = n())
mes <- full_join(no, yes, by = c('GH', 'month'))
mes$tmalos <- mes$tmalos/(mes$tmalos + mes$tbuenos)
Así también, asignamos la PD de cada grupo:
PD <- mes %>% group_by(GH) %>% dplyr::summarise(PD = mean(tmalos, na.rm = TRUE))
PD
## # A tibble: 4 x 2
## GH PD
## <chr> <dbl>
## 1 G1 0.963
## 2 G2 0.826
## 3 G3 0.589
## 4 G4 0.386
ggplot(data=mes) + geom_line(aes(x = month, y = tmalos, color = GH), size=1) +
labs(x = "Periodo", y = "PD",
title = "Ciclo Económico") +
theme_bw()+
theme(legend.background = element_rect()) +
scale_color_hue(name = 'GH')
mes <- full_join(no, yes, by = c('GH', 'month'))
mes$total <- mes$tmalos + mes$tbuenos
bdd <- mes %>% group_by(month) %>% mutate(porc = round(100*(total/sum(total,na.rm=T)),2))
ggplot(data=bdd,
aes(x = factor(month, level= c("1","2","3","4","5","6","7","8")),
y = porc, fill = GH)) +
geom_bar(stat="identity") +
theme_minimal() +
xlab("Meses") + ylab("Porcentaje") +
labs(title = "Porcentaje de clientes por grupos en cada mes",
fill= "Grupos") +
scale_fill_brewer(palette="Spectral")
## Warning: Removed 1 rows containing missing values (position_stack).
De los gráficos podemos ver que, en el grupo G1, no existe información para el mes 5.
El sistema de clasificación TTC (A través del ciclo) es un sistema donde la clasificación de los clientes en su mayoría no se ve afectado por las condiciones macroeconómicas del país
Para realizar este modelo utilizando la filsofía TTC, vamos tomar las variables idiosincráticas, es decir, tomaremos las variables: age, job, marital, education, housing, loan, contact y duration
Previamente se había guardado la base de entrenamiento y testeo con los datos ya tratados, para desarrollar el modelo de credit scoring usando la filosofía TTC. Así, en primer lugar, formulamos el modelo utilizando las variables antes mencionadas siguiendo el mismo procedimiento para el modelo anterior.
trainingTTC <- trainingTTC[,c(1,2,3,4,6,7,8,11,21)]
categoricas <- c('age', 'job', 'marital', 'education', 'housing', 'loan', 'contact')
banco_training <- trainingTTC %>% dummy_cols(select_columns = categoricas) %>% select(-categoricas)
banco_training$y <- if_else(banco_training$y == 'yes', 1, 0)
banco_training$y <- as.numeric(banco_training$y)
logit_banco <-glm(y~., data = banco_training, family='binomial')
Así, veamos qué variables son explicativas para el modelo:
summary(logit_banco)
##
## Call:
## glm(formula = y ~ ., family = "binomial", data = banco_training)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.7944 -0.5000 -0.3773 -0.2520 2.8271
##
## Coefficients: (8 not defined because of singularities)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -2.7655230 0.4100562 -6.744 1.54e-11 ***
## duration 0.0042704 0.0001186 35.993 < 2e-16 ***
## age_A 0.1251227 0.0848045 1.475 0.14010
## age_B -0.1833562 0.0654346 -2.802 0.00508 **
## age_C -0.3620118 0.0626473 -5.779 7.53e-09 ***
## age_D -0.4407489 0.0653480 -6.745 1.53e-11 ***
## age_E NA NA NA NA
## job_T1 -0.8016449 0.0814685 -9.840 < 2e-16 ***
## job_T2 -1.1667824 0.0870962 -13.396 < 2e-16 ***
## job_T3 0.0390040 0.1257665 0.310 0.75646
## job_T4 NA NA NA NA
## marital_divorced -0.0126858 0.4021515 -0.032 0.97483
## marital_married 0.0832394 0.3987470 0.209 0.83464
## marital_single 0.2094749 0.3995897 0.524 0.60012
## marital_unknown NA NA NA NA
## education_E1 -0.2083156 0.0419486 -4.966 6.84e-07 ***
## education_E2 -0.3001179 0.0614558 -4.883 1.04e-06 ***
## education_E3 NA NA NA NA
## housing_no -0.0148573 0.0375335 -0.396 0.69222
## housing_unknown 0.1812860 0.1264645 1.433 0.15172
## housing_yes NA NA NA NA
## loan_no 0.0552194 0.0519925 1.062 0.28821
## loan_unknown NA NA NA NA
## loan_yes NA NA NA NA
## contact_cellular 1.0333090 0.0467471 22.104 < 2e-16 ***
## contact_telephone NA NA NA NA
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 23199 on 32950 degrees of freedom
## Residual deviance: 20476 on 32933 degrees of freedom
## AIC: 20512
##
## Number of Fisher Scoring iterations: 5
De igual manera, vamos retirando una a una las variables que no aportan al modelo:
banco_training <- banco_training %>% select(-age_E, -job_T4, -marital_unknown, -education_E3, -housing_yes, -loan_unknown, -loan_yes, -contact_telephone, -marital_divorced, -job_T3, -housing_no, -loan_no, -housing_unknown, -marital_married, -age_A)
logit_banco <-glm(y~., data = banco_training, family='binomial')
summary(logit_banco)
##
## Call:
## glm(formula = y ~ ., family = "binomial", data = banco_training)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.7478 -0.4996 -0.3782 -0.2521 2.8363
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -2.6220007 0.0787946 -33.276 < 2e-16 ***
## duration 0.0042695 0.0001186 36.008 < 2e-16 ***
## age_B -0.2329333 0.0531352 -4.384 1.17e-05 ***
## age_C -0.4052630 0.0529558 -7.653 1.97e-14 ***
## age_D -0.4799909 0.0578124 -8.303 < 2e-16 ***
## job_T1 -0.8058047 0.0632386 -12.742 < 2e-16 ***
## job_T2 -1.1639380 0.0701931 -16.582 < 2e-16 ***
## marital_single 0.1807164 0.0408463 4.424 9.68e-06 ***
## education_E1 -0.2071977 0.0418668 -4.949 7.46e-07 ***
## education_E2 -0.2911222 0.0613017 -4.749 2.04e-06 ***
## contact_cellular 1.0342564 0.0466346 22.178 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 23199 on 32950 degrees of freedom
## Residual deviance: 20484 on 32940 degrees of freedom
## AIC: 20506
##
## Number of Fisher Scoring iterations: 5
Una vez obtenido el modelo con la base de entranimiento, ahora, vamos a validar el mismo, utilizando la base de testeo guardad previamente.
testingTTC <- testingTTC[,c(1,2,3,4,6,7,8,11,21)]
categoricas <- c('age', 'job', 'marital', 'education', 'housing', 'loan', 'contact')
banco_test <- testingTTC %>% dummy_cols(select_columns = categoricas) %>% select(-categoricas)
banco_test <- banco_test %>% select(-age_E, -job_T4, -marital_unknown, -education_E3, -housing_yes, -loan_unknown, -loan_yes, -contact_telephone, -marital_divorced, -job_T3, -housing_no, -loan_no, -housing_unknown, -marital_married, -age_A)
banco_test$y <- if_else(banco_test$y == 'yes', 1, 0)
banco_test$y <- as.numeric(banco_test$y)
odds <- predict(logit_banco, banco_test, type = 'response')
odds[1:20]
## 1 2 3 4 5 6 7
## 0.06401966 0.04558393 0.02662430 0.08439756 0.04265887 0.02399327 0.03197234
## 8 9 10 11 12 13 14
## 0.10355166 0.08150926 0.01881222 0.06580304 0.02668990 0.03840487 0.07619754
## 15 16 17 18 19 20
## 0.04546208 0.02743823 0.03436322 0.04682742 0.03831409 0.02848246
Así, una vez predichas las probabilidades de cada individuo de la base de testeo, realizaremos algunas pruebas estadísticas para validar nuestro modelo:
Corremos la matriz de confusión y vemos la precisión que tiene el modelo respecto a la base de datos.
test.pred <- ifelse(odds > .5, "1", "0")
matriz<-confusionMatrix(as.factor(banco_test$y), as.factor(test.pred))
matriz$table
## Reference
## Prediction 0 1
## 0 7089 220
## 1 653 275
matriz$overall[1]
## Accuracy
## 0.8940148
Vemos que se tiene un total de 653 falsos positivos y 220 falsos negativos. Además, se tiene que representa una precisión del 90%. Entonces el modelo es adecuado.
require(ROCR)
train.prob <- predict(logit_banco, banco_training, type = "response")
train.pred <- ifelse(train.prob > .5, "1", "0")
train.roc <- prediction(train.prob, banco_training$y)
plot(performance(train.roc, "tpr", "fpr"), col = "red", main = "Curva ROC para la data de entrenamiento")
abline(0, 1, lty = 8, col = "blue")
# AUC
train.auc <- performance(train.roc, "auc")
c(AUC=slot(train.auc, "y.values"))
## $AUC
## [1] 0.7506053
# KS
ks.train <- performance(train.roc, "tpr", "fpr")
train.ks <- max(attr(ks.train, "y.values")[[1]] - (attr(ks.train, "x.values")[[1]]))
c(KS = train.ks)
## KS
## 0.3966375
De los valores AUC y KS obtenidos para la base de entrenamiento, podemos concluir que el modelo es adecuado; como también, si se usa la base de testeo:
test.prob <- predict(logit_banco, newdata = banco_test, type = "response")
test.pred <- ifelse(test.prob > .5, "1", "0")
test.roc <- prediction(test.prob, banco_test$y)
plot(performance(test.roc, "tpr", "fpr"), col = "red", main = "Curva ROC para la data de testeo")
abline(0, 1, lty = 8, col = "blue")
# AUC
test.auc <- performance(test.roc, "auc")
c(AUC=slot(test.auc, "y.values"))
## $AUC
## [1] 0.8626456
# KS
ks.test <- performance(test.roc, "tpr", "fpr")
test.ks <- max(attr(ks.test, "y.values")[[1]] - (attr(ks.test, "x.values")[[1]]))
c(KS=test.ks)
## KS
## 0.5815846
A continuación, procedemos a unir los valores predichos en la base original y, vamos a predecir los valores para la base de entrenamiento para, de igual manera, juntarlos en una sola base. Además, vamos a unir la información de la variable indicadora para posteriormente realizar el ciclo económico de cada uno de los grupos.
## TESTING
Testing <- banco_test
Testing$probs <- odds
Testing$month <- ind_test$month1
## TRAINING
Training <- banco_training
odds <- predict(logit_banco, banco_training, type = 'response')
Training$probs <- odds
Training$month <- ind_training$month1
banco <- rbind(Training, Testing)
str(banco)
## 'data.frame': 41188 obs. of 13 variables:
## $ duration : num 261 149 226 151 198 217 380 55 222 137 ...
## $ y : num 0 0 0 0 0 0 0 0 0 0 ...
## $ age_B : int 0 0 0 0 0 0 0 0 0 1 ...
## $ age_C : int 0 0 1 1 0 1 0 1 0 0 ...
## $ age_D : int 0 0 0 0 1 0 0 0 0 0 ...
## $ job_T1 : int 1 0 0 1 0 0 1 0 0 0 ...
## $ job_T2 : int 0 1 1 0 1 1 0 1 1 1 ...
## $ marital_single : int 0 0 0 0 0 0 1 0 1 1 ...
## $ education_E1 : int 1 1 1 0 0 0 1 0 1 1 ...
## $ education_E2 : int 0 0 0 1 1 0 0 0 0 0 ...
## $ contact_cellular: int 0 0 0 0 0 0 0 0 0 0 ...
## $ probs : num 0.0744 0.0337 0.0313 0.0299 0.0239 ...
## $ month : num 1 1 1 1 1 1 1 1 1 1 ...
Para la formación de los grupos homogéneos vamos a agrupar las probabilidades, utilizando el método de \(k-\)medias; de donde, se formarán 4 grupos:
grupos <- kmeans(x = banco[, c('probs')], centers = 4, nstart = 1)
## GH = grupos homogeneos
banco <- banco %>% mutate(GH = grupos$cluster)
banco <- banco %>% mutate(GH = as.factor(GH))
Entonces, veamos los valores máximos y mínimos de cada grupo formado para organizarlos:
C1 <- banco %>% filter(GH == 1)
C2 <- banco %>% filter(GH == 2)
C3 <- banco %>% filter(GH == 3)
C4 <- banco %>% filter(GH == 4)
c('minC1' = min(C1$probs), 'maxC1' = max(C1$probs),
'minC2' = min(C2$probs), 'maxC2' = max(C2$probs),
'minC3' = min(C3$probs), 'maxC3' = max(C3$probs),
'minC4' = min(C4$probs), 'maxC4' = max(C4$probs))
## minC1 maxC1 minC2 maxC2 minC3 maxC3 minC4 maxC4
## 0.1029139 0.2424824 0.0104721 0.1029029 0.5076631 0.9999902 0.2425322 0.5057944
Entonces, notamos que el primer grupo es el \(C_2\), el segundo grupo es \(C_1\), el tercer grupo es \(C_4\) y último grupo, los que tiene altas probabilidades de mora, es \(C_3\).
Así, veamos si cumplen las pruebas estadísticas mencionadas anteriormente:
G1 <- C2; G2 <- C1; G3 <- C4; G4 <- C3
G1$GH <- as.character(G1$GH)
G2$GH <- as.character(G2$GH)
G1$GH <- as.character(G1$GH)
G2$GH <- as.character(G2$GH)
variables <- c(G1$probs, G2$probs)
categorias <- c(G1$GH, G2$GH)
bartlett.test(variables, categorias)
##
## Bartlett test of homogeneity of variances
##
## data: variables and categorias
## Bartlett's K-squared = 2533.5, df = 1, p-value < 2.2e-16
Podemos ver que, como el \(p\_valor\) es menor a un \(\alpha = 0.05\), se rechaza la hipótesis nula y, en consecuencia, las varianzas entre el grupo 1 y grupo 2 son diferentes. De manera igual manera se corroboró para las otras combinaciones de grupos.
Con esto, realizamos la prueba de Games y Howell, para la comparación de medias entre los grupos:
banco$GH <- as.factor(banco$GH)
GH <- oneway(banco$GH, y = banco$probs, posthoc = 'games-howell')
GH
## ### Oneway Anova for y=probs and x=GH (groups: 1, 2, 3, 4)
##
## Omega squared: 95% CI = [.89; .89], point estimate = .89
## Eta Squared: 95% CI = [.89; .89], point estimate = .89
##
## SS Df MS F p
## Between groups (error + effect) 528.74 3 176.25 108103.54 <.001
## Within groups (error only) 67.14 41184 0
##
##
## ### Post hoc test: games-howell
##
## diff ci.lo ci.hi t df p
## 2-1 -0.10 -0.10 -0.09 250.12 17543.34 <.001
## 3-1 0.53 0.52 0.54 101.50 763.60 <.001
## 4-1 0.18 0.18 0.19 157.10 4593.70 <.001
## 3-2 0.62 0.61 0.64 119.92 758.49 <.001
## 4-2 0.28 0.28 0.28 246.48 4015.39 <.001
## 4-3 -0.35 -0.36 -0.33 64.85 828.51 <.001
En consecuencia, vemos que los grupos homogéneos son heterogéneos entre sí; entonces, por medio del uso de la variable indicadora month, tenemos 8 tiempos para realizar el ciclo económico; de donde, por cada grupo y por cada mes, se calculará el procentaje de clientes malos \(y = no = 0\) en ese grupo:
banco$GH <- as.character(banco$GH)
library(plyr)
banco$GH <- revalue(banco$GH, c('1' = 'G2', '2' = 'G1', '3' = 'G4',
'4' = 'G3'))
detach(package:plyr)
for(i in 1:length(banco$month)){
if(banco$month[i] == 6 | banco$month[i] == 7 | banco$month[i] == 8){
banco$month[i] <- 5
}else if(banco$month[i] == 9){
banco$month[i] <- 6
}else if(banco$month[i] == 10){
banco$month[i] <- 7
}else if(banco$month[i] == 11 | banco$month[i] == 12 | banco$month[i] == 13 |
banco$month[i] == 14 | banco$month[i] == 15 | banco$month[i] == 16 |
banco$month[i] == 17 | banco$month[i] == 18 | banco$month[i] == 19 |
banco$month[i] == 20 | banco$month[i] == 21 | banco$month[i] == 22 |
banco$month[i] == 23 | banco$month[i] == 24 | banco$month[i] == 25 |
banco$month[i] == 26){
banco$month[i] <- 8
}
}
no <- banco %>% filter(y == 0) %>% group_by(GH, month) %>% dplyr::summarise(tmalos = n())
yes <- banco %>% filter(y == 1) %>% group_by(GH, month) %>% dplyr::summarise(tbuenos = n())
mes <- full_join(no, yes, by = c('GH', 'month'))
mes$tmalos <- mes$tmalos/(mes$tmalos + mes$tbuenos)
Así también, asignamos la PD de cada grupo:
PD <- mes %>% group_by(GH) %>% dplyr::summarise(PD = mean(tmalos, na.rm = TRUE))
PD
## # A tibble: 4 x 2
## GH PD
## <chr> <dbl>
## 1 G1 0.938
## 2 G2 0.856
## 3 G3 0.716
## 4 G4 0.495
ggplot(data=mes) + geom_line(aes(x = month, y = tmalos, color = GH), size=1) +
labs(x = "Periodo", y = "PD",
title = "Ciclo Económico") +
theme_bw()+
theme(legend.background = element_rect()) +
scale_color_hue(name = 'GH')
mes <- full_join(no, yes, by = c('GH', 'month'))
mes$total <- mes$tmalos + mes$tbuenos
bdd <- mes %>% group_by(month) %>% mutate(porc = round(100*(total/sum(total,na.rm=T)),2))
ggplot(data=bdd,
aes(x = factor(month, level= c("1","2","3","4","5","6","7","8")),
y = porc, fill = GH)) +
geom_bar(stat="identity") +
theme_minimal() +
xlab("Meses") + ylab("Porcentaje") +
labs(title = "Porcentaje de clientes por grupos en cada mes",
fill= "Grupos") +
scale_fill_brewer(palette="Spectral")
La implementación de nuevas variables creadas a partir de variables originarias en el modelo, genera en este una mejor predicción entre buenos y malos clientes.
El método de Score permite reducir la exposición al riesgo crediticio, al mejorar la discriminación de las personas con comportamiento moroso contra aquellas con buen comportamiento.
Para la creación de los grupos homogéneos se utilizó el método de k medias tanto para la filosofía PIT como para la filosofía TTC.
Se justificó la validez de los grupos homogéneos creados mediante la prueba estadística de Games y Howell, comprobando que la variabilidad de cada grupo es distinta, por medio de la prueba estadística de Bartlett.