Introducción y Procesamiento de Datos

Author

Juan Isaula

Introducción y Preprocesamiento de Datos

Este capítulo comienza con una introducción general a los modelos de riesgo crediticio. Exploraremos un conjunto de datos de la vida real y luego procesaremos previamente el conjunto de datos para que esté en el formato apropiado antes de aplicar los mdelos de riesgo crediticio.

¿Qué es el incumplimiento del Préstamo?

El área de modelado del riesgo crediticio tiene que ver con el caso de incumplimiento del préstamo. Ahora bien, ¿qué es incumplimiento de un préstamo? Cuando un banco concede un préstamo a un prestatario, que podría ser un individuo o una empresa, el banco normalmente transferirá el monto total del préstamo al prestatario.

Luego, el prestatario reembolsará esta cantidad en partes más pequeñas, incluidos algunos pagos de interes, con el tiempo. Por lo general, estos pagos se realizan mensualmente, trimestralmente o anualmente. Por supuesto, existe un cierto riesgo de que el prestatario no pueda reembolsar completamente este préstamo. Esto resulta en una pérdida para el banco.

Componentes de la pérdida esperada (EL)

La pérdida esperada en la que incurrá un banco se compone de tres elementos:

  • La probabilidad de incumplimiento (PI), que es la probabilidad de que el prestatario no pueda pagar la totalidad del préstamo.

  • Exposición en caso de incumplimiento (EXI), que es el valor esperado del préstamo en el momento de incumplimiento. También puede considerarse esto como el monto del préstamo que aún debe reembolsarse en el momento del incumplimiento.

  • La pérdida en caso de incumplimiento (PEI), expresado como porcentaje de la exposición en caso de incumplimiento.

Multiplicando estos tres elementos se obtiene la fórmula de pérdida esperada.

\[ EL = PI\times EXI\times PEI \]

En este curso nos centraremos en la probabilidad de incumplimiento.

Información utilizada por los Bancos

Los bancos mantiene información sobre el comportamiento de incumplimiento de clientes anteriores, que puede usarse para predecir el incumplimiento de nuevos clientes. A grandes rasgos, esta información se puede clasificar en dos tipos:

  • Información de la Aplicación

    • Ejemplos de información de la solicitud son los ingresos, el estado civil, etc.
  • Información de comportamiento

    • Este tipo de información rastrea el comportamiento pasado de los clientes, por ejemplo, el saldo de la cuenta actual y el historial de pagos atrasados.

Echemos un vistazo a las primeras diez lineas de nuestro conjunto de datos.

loan_data <- readRDS("C:/Users/juani/OneDrive/Documentos/2023/Ficohsa/libro_curso_ficohsa/loan_data_ch1.rds") 
head(loan_data,10)
   loan_status loan_amnt int_rate grade emp_length home_ownership annual_inc
1            0      5000    10.65     B         10           RENT      24000
2            0      2400       NA     C         25           RENT      12252
3            0     10000    13.49     C         13           RENT      49200
4            0      5000       NA     A          3           RENT      36000
5            0      3000       NA     E          9           RENT      48000
6            0     12000    12.69     B         11            OWN      75000
7            1      9000    13.49     C          0           RENT      30000
8            0      3000     9.91     B          3           RENT      15000
9            1     10000    10.65     B          3           RENT     100000
10           0      1000    16.29     D          0           RENT      28000
   age
1   33
2   31
3   24
4   39
5   24
6   28
7   22
8   22
9   28
10  22

Este conjunto de datos contiene información sobre préstamos anteriores. Cada fila representa un cliente y su información, junto con un indicador del estado del préstamo, que equivale a 1 si el cliente incumplió y 0 si el cliente no incumplió. El estado del préstamo se utilizará como variable de respuesta, y las variables explicativas son:

  • monto del préstamo (loan_amnt),

  • la tasa de interés (int_rate),

  • el grado (grade),

  • la duración del empleo (emp_length),

  • el estado de propiedad de la vivienda (home_ownership),

  • el ingreso anual (annual_inc) y

  • la edad (age). El grado es el puntaje del cliente, donde

    • A: indica la clase más alta de solvencia

    • G: la mas baja

Esta puntuación de la oficina refleja el historial de crédtio del individuo y es la única variable de comportamiento en el conjunton de datos.

Tabla Cruzada

Para obtener una descripción general de la estructura de datos de variables categóricas, puede utilizar la función CrossTable() perteneciente al paquete gmodels

library(gmodels) 
Warning: package 'gmodels' was built under R version 4.3.1

Aplicando esta función a la variable home_ ownership y se obtiene una tabla con cada una de las categorías de esta variable, con el número de casos y proporciones.

CrossTable(loan_data$home_ownership)

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

 
Total Observations in Table:  29092 

 
          |  MORTGAGE |     OTHER |       OWN |      RENT | 
          |-----------|-----------|-----------|-----------|
          |     12002 |        97 |      2301 |     14692 | 
          |     0.413 |     0.003 |     0.079 |     0.505 | 
          |-----------|-----------|-----------|-----------|



 

Utilizando el estado del préstamo como segundo argumento, se puede observar la relación entre esta varianle de factor y la respuesta. Al establecer la propiedad prop.r en TRUE y las demás proporciones enumeradas aquí en FALSE, se obtienen las proporciones por filas.

CrossTable(loan_data$home_ownership, loan_data$loan_status, 
           prop.r = TRUE,            
           prop.c = FALSE,
           prop.t = FALSE,
           prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|-------------------------|

 
Total Observations in Table:  29092 

 
                         | loan_data$loan_status 
loan_data$home_ownership |         0 |         1 | Row Total | 
-------------------------|-----------|-----------|-----------|
                MORTGAGE |     10821 |      1181 |     12002 | 
                         |     0.902 |     0.098 |     0.413 | 
-------------------------|-----------|-----------|-----------|
                   OTHER |        80 |        17 |        97 | 
                         |     0.825 |     0.175 |     0.003 | 
-------------------------|-----------|-----------|-----------|
                     OWN |      2049 |       252 |      2301 | 
                         |     0.890 |     0.110 |     0.079 | 
-------------------------|-----------|-----------|-----------|
                    RENT |     12915 |      1777 |     14692 | 
                         |     0.879 |     0.121 |     0.505 | 
-------------------------|-----------|-----------|-----------|
            Column Total |     25865 |      3227 |     29092 | 
-------------------------|-----------|-----------|-----------|

 

Ahora bien, ¿qué te dice este resultado? Parece que la tasa de impago en el grupo de propietario de vivientas OTHER es bastante mayor que la tasa de impago en, por ejemplo, el grupo de propietarios de vivienda MORTAGE (hipoteca), con 17.5% frente a 9.8% de impagos en estos grupos respectivamente.

Histograma y Valores atípicos

Ahora veamos algunas variables continuas usando histogramas y gráficos.

Función hist()

Para un histograma básico, llamas a la función hist() con la variable de interés, en este caso, tasa de interés.

hist(loan_data$int_rate, main = "Histograma de tasas de interés", xlab = "Tasa interes")

Aquí puede ver que todos los préstamos tenían una tasa de interés superior al 5% y muy pocos préstamos tenían una tasa de interés superir al 20%.

Echemos un vistazo al histograma de ingresos anuales.

hist(loan_data$annual_inc, xlab = "Ingreso anual", main = "Histograma de ingresos anuales")

Notamos que aquí obtenemos un resultado extraño, aparentemente con solo una barra grande.

Al almacenar el histograma en hist_Income y utilizar las rupturas del signo del dólar, obtenemos información sobre la ubicación de las rupturas del histogram,a. Para tener una idea clara de la estructura de datos,

hist_income <- hist(loan_data$annual_inc, xlab = "Ingrso anual",          
                    main = "Histograma ingreso anual") 

hist_income$breaks
 [1]       0  500000 1000000 1500000 2000000 2500000 3000000 3500000 4000000
[10] 4500000 5000000 5500000 6000000

Argumento breaks

puede cambiar el número de pausas utilizando el argumento de pausas, de modo que obtenga un gráfico más intuitivo. Esto se puede hacer eligiendo un número que parezca más apropiado o utilizando una regla general, como la raíz cuadrada del número de observaciones en el conjunto de datos.

n_breaks <- sqrt(nrow(loan_data)) 
hist_income_n <- hist(loan_data$annual_inc,            
                      breaks = n_breaks,
                      xlab = "Ingreso anual",              
                      main = "Histograma de ingreso anual")

Esto da como resultado un vector de rupturas mucho más largo. Sin embargo, el resultado aquí todavia no se ve muy bien, con mucho espacio en blanco. el eje x del histograma oscila automáticamente desde el valor observado más pequeño hasta el más grande.

anual_inc

Veamos u histograma de dispersión para ver que esta pasando.

plot(loan_data$annual_inc, ylab = "Ingreso anual")

En este gráfico el ingreso anual se muestra en el eje y, y el número indice de la observación se muestra en el eje x.

Veamos que hay un salario enorme de 6 millones y ninguno de los otros supera los 2 millones. Entonces consideramos que este es un caso atípico.

Valores atípicos

En estadística, un valor atípico es una observación que está anormalmente distante de otros valores. Pero ¿cuándo una distancia es anormal? En general, los cientificos de datos utilizaran su criterio de experto, reglas generales o una combinación de ambos.

Juicio de Experto

Se podría utilizar el juicio de expertos si el cientifico de datos se considera un experto en el ámbito de la modelización del riesgo crediticio. Entonces puede juzgar que un salario anual superior a, digamos, 3 millones de dólares debe ser un error y debe eliminarse del conjunto de datos.

index_outlier_expert <- which(loan_data$annual_inc > 3000000) 
loan_data_expert <- loan_data[-index_outlier_expert,]

Si un cientifico de datos quiero confiar en una regla general, podría eliminar todos los valores que sean mayores o menores que 1.5 veces el rango intercuantil, que es el rango entre el primer y tercer cuartil de la distribución de la variable. Como aquí no se produjeron valores atípicos en el rango negativo, solo eliminamos los que se encuentran en el rango positivo.

\[ Q_3 + 1.5\times IQR \]

outlier_cutoff <- quantile(loan_data$annual_inc, 0.75) + 1.5*IQR(loan_data$annual_inc)  
index_outlier_ROT <- which(loan_data$annual_inc > outlier_cutoff)  
# Remove outliers
loan_data_ROT <- loan_data[-index_outlier_ROT,]

Histogramas

Después de eliminar los valores atípicos, obtiene los siguientes resultados.

hist(loan_data_expert$annual_inc,       
     sqrt(nrow(loan_data_expert)),      
     xlab = "Ingresos anuales")

hist(loan_data_ROT$annual_inc,    
     sqrt(nrow(loan_data_ROT)),   
     xlab = "Ingresos Anuales")

Estos resultados son más informativos que los iniciales, incluidos los valores atípicos, especialmente el histograma que se construyo utilizando la regla general. Tenga en cuenta que se eliminaron bastantes observaciones utilizando esta regla general. Incluso si no planea omitir estos valores atípicos en su análisis, podría resultar útil eliminarlos temporalmente al visualizar los datos.

Gráfico bivariado

Concluyamos mirando un gráfico bivariado. Cuando incluye una segunda variable en la función plot(), el primer argumento se trazará en el eje x y el segundo argumento en el eje y. Aquí se muestra un gráfico bivariado para la duración del empleo y los ingresos anuales.

plot(loan_data$emp_length, loan_data$annual_inc,    
     xlab = "Duración de empleo",
     ylab = "Ingresps anuales")

Echar un vistazo a los gráficos bivariados puede resultar interesante para realizar un seguimiento de los valores atípicos bivariados, que son valores atípicos en dos dimensiones de los datos.

Para la combinación trazada aquí, solo vemos un valor atípico en la escala de ingresos anuales y no en la duración del empleo.

Datos Faltantes y Clasificación Aproximada

Antes de entrar en materia eliminemos del conjunto de datos la observación que contiene un valor atípico bivariado para la edad y el ingreso anual.

index_highage <- which(loan_data$age>122)
new_data <- loan_data[-index_highage, ]

Entradas Faltantes

Lo que no hemos discutido antes es respecto a los datos faltantes (o NA, que significa no disponible) para dos variables: duración de empleo y tasa de interés. Mostraré algunos métodos para manejar datos faltantes sobre la variable de duración de empleo.

Lo primero que tiene que saber es cuántas entradas faltan, ya que esto afectará lo que haga con ellas. Una forma sencilla de averiguarlo es con la función summary().

summary(loan_data$emp_length)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  0.000   2.000   4.000   6.145   8.000  62.000     809 

Note que existen 809 NA.

Estrategías: Insumos faltantes

Generalmente existen tres formas de tratar las entradas faltantes:

  • eliminarlas,

  • reemplazarlas

  • conservarlas

Ilustremos estos métodos sobre la variable de duración del empleo. Al eliminar, puede eliminar las observaciones en las que se detectan entradas faltantes o eliminar una variable completa. Por lo general, solo querrá eliminar observaciones si solo hay una pequeña cantidad de entradas y solo considerará eliminar una variable completa cuando falten muchos casos.

Eliminar filas

Para eliminar toda la variable duración del empleo, simplemente establezca la variable duración del empleo en los datos del préstamo en NULL. Aquí guardamos el resultado en una copia del conjunto de datos llamado loan_data_delete_employ. Hacer una copia de los datos originales antes de eliminarlos puede ser una buena forma de evitar la pérdida de información, pero puede resultar costoso si se trabaja con conjuntos de datos muy grandes.

index_NA <- which(is.na(loan_data$emp_length)) 
loan_data_no_NA <- loan_data[-c(index_NA),]

Reemplazar: imputación de mediana

En segundo lugar, al reemplazar una variable, la práctica común es reemplazar los valores faltantes.

index_NA <- which(is.na(loan_data$emp_length)) 
loan_data_replace <- loan_data
loan_data_replace$emp_length[index_NA] <- median(loan_data$emp_length, na.rm = TRUE)

como la mediana de los valores que realmente se observan. Esto se llama imputación de la mediana.

Conservar

Por último, puedes conservar los valores faltantes, ya que en algunos casos, el hecho de que falte un valor es información importante. Desafortunadamente, no siempre es posible mantener las NA como tales, ya que algunos métodos eliminarán automáticamente filas con NA porque no pueden manejarlas. Entonces, ¿cómo podeosm conservar las NA? Una solución popular es la clasificación aproximada. Con este método, básicamente colocas una variable continua en los llamados contenedores. Comencemos creando una nueva variable variable emp_cat, que será la variable que reemplazará a emp_length. La duración del empleo en nuestro conjunto de datos oscila entre 0 y 62 años. Pondremos la duración del empleo en grupos de aproximadamente 15 años, con grupos de 0 a 15, de 15 a 30, de 30 a 45, de 45 o más y un grupo faltante, que representa a los NA. Veamos cómo esto cambia nuestros datos.

loan_data$emp_cat <- rep(NA, length(loan_data$emp_length))  
loan_data$emp_cat[which(loan_data$emp_length <= 15)] <- "0-15"
loan_data$emp_cat[which(loan_data$emp_length > 15 & loan_data$emp_length <= 30)] <- "15-30" 
loan_data$emp_cat[which(loan_data$emp_length > 30 & loan_data$emp_length <= 45)] <- "30-45"
loan_data$emp_cat[which(loan_data$emp_length > 45)] <- "45+"
loan_data$emp_cat[which(is.na(loan_data$emp_length))] <- "Missing" 

loan_data$emp_cat <- as.factor(loan_data$emp_cat)

plot(loan_data$emp_cat)

loan_data$ir_cat <- rep(NA, length(loan_data$int_rate)) 
loan_data$ir_cat[which(loan_data$int_rate <= 8)] <- "0-8" 
loan_data$ir_cat[which(loan_data$int_rate > 8 & loan_data$int_rate <= 11)] <- "8-11" 
loan_data$ir_cat[which(loan_data$int_rate > 11 & loan_data$int_rate <= 13.5)] <- "11-13.5"
loan_data$ir_cat[which(loan_data$int_rate > 13.5)] <- "13.5+"
loan_data$ir_cat[which(is.na(loan_data$int_rate))] <- "Missing"  

loan_data$ir_cat <- as.factor(loan_data$ir_cat)  


plot(loan_data$ir_cat)

Observaciones Finales

Antes de probar todo esto en R, terminemos un par de comentarios.

  • Todos los métodos para el manejo de datos faltantes también se pueden aplicar a los valores atípicos. Si cree que un valor atípico es incorrecto puede tratarlo como NA y utilizar cualquiera de los métodos que hemos analizado en este capítulo.

  • Habrá notado que en este capítulo solo le hable de la falta de variables continuas ¿Qué pasa con las variables factoriales? Este es el enfoque básico. Para las variables categóricas, la eliminación funciona exactamente de la misma manera que para las variables continuas, eliminando observaciones o variables completas. Cuando deseamos reemplazar una variable de factor faltante, esto se hace asignándola a la clase modal, que es la clase con mayor frecuencia. Para mantener los NA de una variable categórica se incluye una categoría faltante.

Matrices de confusión y División de Datos

Hemos visto varias técnicas para procesar los datos. Cuando los datos estén completamente preprocesados, podrá continuar y comenzar el análisis.

iniciar análisis

Puede ejecutar el modelo en todo el conjunto de datos y utilizar el mismo conjunto de datos para evaluar el resultado, pero lo más probable es que esto conduzca a un resultado demasiado optimista.

Conjunto de entrenamiento (training set) y Prueba (test set)

Una alternativa es dividir los datos en dos partes. La primera parte de los datos, el llamado conjunto de entrenamiento, se puede usar para construir el modelo, y la segunda parte de los datos, el conjunto de prueba, se puede usar para probar los resultados.

Una forma común de hacerlo es utilizar dos tercios de los datos para un conjunto de entrenamiento y un tercio para el conjunto de prueba. Por supuesto, puede haber mucha variación en la estimación de rendimiento, dependiendo de qué dos tercios de los datos seleccione para el conjunto de entrenamiento. Una forma de reducir esta variación es mediante el uso de validación cruzada.

Validación cruzada (Cross - Validation)

Para el ejemplo del conjunto de entrenamiento de dos tercios y del conjunto de prueba de un tercio, una variable de validación cruzada se vería así.

Los datos se dividirían en tres partes iguales y, cada vez, dos de estas partes actuarían como un conjunto de entrenamiento y una parte actuaría como un conjunto de prueba. Por supuesto, podríamos usar tantas piezas como queramos, pero tendríamos que ejecutar el modelo muchas veces si usamos muchas piezas. Esto puede resultar computacionalmente pesado. En este curso, solo usaremos un conjunto de entrenamiento y un conjunto de prueba que contienen dos tercios versus un tercio de los datos, respectivamente. Imagine que acabamos de ejecutar un modelo y ahora lo aplicamos a nuestro conjunto de pruebas para ver qué tan buenos son los resultados.

Evaluar un modelo

Evaluar un modelo de riesgo crediticio signifca comparar los resultados observados de incumplimiento versus no incumplimiento, almacenados en la variable loan_status m del conjunto de prueba, con los resultados previstos según el modelo. Si se trata de una gran cantidad de predicciones, un método popular para resumir los resultados utiliza algo llamado matriz de confusión.

Una matriz de confusión es una tabla de contingencia de clasificaciones correctas e incorrectas. Las clasificaciones correctas se encuentran en la diagonal de la matriz de confusión.

No default(0) Defaul(1)
No default (0) 8 2
Default (1) 1 3

Veamos, por ejemplo, que ocho no morosos fueron clasificados correctamente como no morosos, y tres morosos fueron clasificados correctamente como morosos. Sin embargo, vemos que dos no morosos fueron clasificados erróneamente como morosos, y un moroso fue clasificado erróneamente como no moroso.

Los elementos en las diagonales también se llaman verdaderos positivos y verdaderos negativos. Las diagonales fuera de la diagonal se denominan falsos positivos versus faltos negativos.

No default (0) Default (1)
No default (0) TN FP
Default (1) FN TP

Algunas medidas….

Se pueden derivar varias medidas de la matriz de confusión. Discutiremos la precisión de la clasificación, la sensibilidad y la especificidad.

  • La precisión de la clasificación (accuracy) es el porcentaje de instancias clasificadas correctamente, que equivale al 78.57% en este ejemplo.

    \[ \frac{8+3}{14} = 78.57\% \]

  • La sensibilidad (sensitivy) es el porcentaje de malos clientes que se clasifican correctamente o el 75% en este ejemplo.

    \[ \frac{3}{1+3} = 75\% \]

  • La especificidad es el porcentaje de buenos clientes que se clasifican correctamente o el 80% en este ejemplo.

    \[ \frac{8}{8+2} = 80\% \]

Ejercicios

Dividiendo el conjunto de datos

Para realizar sus conjuntos de entrenamiento y prueba, primero debe configurar una semilla usando set.seed(). Las semillas le permiten crear un punto de partida para números generados aleatoriamente, de modo que cada vez que se ejecute su código se genere la misma respuesta. La ventaja de hacer esto en su muestreo es que usted o cualquier otra persona puede recrear exactamente los mismo conjuntos de entrenamiento y prueba usando la misma semilla.

Con sample(), puede asignar observaciones aleatoriamente al conjunto de entrenamiento y prueba.

Para este ejercicio utilizará los dos primeros argumentos de la función sample():

  • El primer argumento es el vector del cual tomaremos muestras de valores. Seleccionaremos aleatoriamente números de fila como índices; puedes utilizar 1:nrow(loan_data) para crear el vector de números de fila.

  • El segundo argumento es el número de elementos a elegir. Entraremos 2/3*nrow(loan_data), ya que primero construimos el conjunto de entrenamiento.

# Establecemos una semilla de 567
set.seed(567) 
# index_train: almacena los indices del conjunto de entrenamiento 
index_train <- sample(1:nrow(loan_data), 2/3*nrow(loan_data)) 
# Creamos el conjunto de entrenamiento seleccionando los números de fila almacenados en index_train. y guardamos en training_set. 
training_set <- loan_data[index_train, ] 
# conjunto de prueba, son las filas que no estan en index_train 
test_set <- loan_data[-index_train, ]

Creando la matriz de confusión

En este ejemplo, supongamos que ejecutó un modelo y almacenó los resultados pronosticados en un vector llamado model_pred. Quiere ver cómo se desempeño el modelo, por lo que construirá una matriz de confusión. Comparará la columna de estado del préstamo real (loan_status) con los valores pronosticados (model_pred), utilizando la función table(), donde los argumentos y valores verdaderos y los valores pronosticados. Recuerde la estructura de la matriz de confusión y fórmulas.

\[ \begin{eqnarray*} accuracy &=& \frac{(TP + TN)}{(TP + FP + TN + FN)}\\[0.2cm] sensitivy &=& \frac{TP}{TP + FN}\\[0.2cm] specificity &=& \frac{TN}{TN + FP} \end{eqnarray*} \]

# Creamos la matriz de confusión 
conf_matrix <- table(test_set$loan_status,model_pred)

# cálculo del accuracy
(6092 + 349)/nrow(test_set)

# computo de sensitivy
349/1037

Regresión Logística

Estructura de datos final

Echemos un vistazo a la estructura del conjunto de entrenamiento. Recuerde que loan_status es nuestra variable de respuesta y ahora tenemos variables de cuatro y tres factores continuas como variables explicativas.

str(training_set)
'data.frame':   19394 obs. of  10 variables:
 $ loan_status   : int  0 0 1 0 0 0 0 0 0 0 ...
 $ loan_amnt     : int  7000 10000 10000 2500 20000 12000 3500 15000 6550 4500 ...
 $ int_rate      : num  14 16 18.4 NA NA ...
 $ grade         : Factor w/ 7 levels "A","B","C","D",..: 3 4 5 4 1 2 2 1 1 1 ...
 $ emp_length    : int  6 0 9 4 8 16 35 4 3 0 ...
 $ home_ownership: Factor w/ 4 levels "MORTGAGE","OTHER",..: 4 4 4 3 4 4 1 3 4 4 ...
 $ annual_inc    : num  21600 46000 22000 24000 60000 90000 57700 40000 29500 40000 ...
 $ age           : int  21 42 32 31 24 22 23 39 29 26 ...
 $ emp_cat       : Factor w/ 5 levels "0-15","15-30",..: 1 1 1 1 1 2 3 1 1 1 ...
 $ ir_cat        : Factor w/ 5 levels "0-8","11-13.5",..: 3 3 3 5 5 2 4 1 1 1 ...

¿Qué es la regresión Logística?

Es similar en muchos aspectos a la regresión lineal, excepto que el resultado del modelo es un valor entre cero y uno. Esto es necesario ya que estamos interesados en predecir la probabilidad de incumplimiento, que, por definición, está entre cero y uno. En un modelo de regresión logística, la probabilidad de incumplimiento también se puede escribir como la probabilidad de que el estado del préstamo sea igual a uno, condicionado a las variables \(x_1, . . . , x_m\), que en el caso de nuestro conjunto de datos son monto del préstamo,. grado, antiguedad, etc.

\[ P(loan\_status = 1 | x_1, . . . , x_m) = \frac{1}{1 + e^{-(\beta_0+\beta_1x_1 + . . . + \beta_mx_m)}} \]

Además se estiman algunos parámetros \(\beta_0, . . . , \beta_m\). La combinación de los parámetros y las variables se denominan predictor lineal.

Ajustar un modelo logístico en R

Puede ajustar un modelo de regresión logística y obtener las estimaciones de los parámetros en R usando la función glm(), que significa modelo lineal generalizado, con el argumento de familia igual a binomial. Veamos un ejemplo en el que solo incluimos la variable edad como predictor. Al observar el resultado, obtenemos algunos coeficientes y diagnósticos del modelo. Por ahora, nos centraremos en los coeficientes. La intersección es la estimación de \(\beta_0\) y el valor debajo de la edad es la estimación de \(\beta_1\).

\[ p(loan\_status = 1 | age) = \frac{1}{1 + e^{-(\hat{\beta_0} + \hat{\beta_1}\cdot age)}} \]

log_model <- glm(loan_status~age,
                 family = "binomial",
                 data = training_set)

log_model

Call:  glm(formula = loan_status ~ age, family = "binomial", data = training_set)

Coefficients:
(Intercept)          age  
  -1.885449    -0.007342  

Degrees of Freedom: 19393 Total (i.e. Null);  19392 Residual
Null Deviance:      13460 
Residual Deviance: 13460    AIC: 13460

Entonces, ¿cómo interpretamos estos números ? Para responder a esta pregunta, primero damos un paso atrás y hacemos algunos cálculos básicos.

Probabilidades de incumplimiento

Has visto la expresión de la probabilidad de incumplimiento. Al multiplicar esta expresión por la función exponencial del predictor lineal tanto en el numerador como en el denominador, obtenemos este resultado.

\[ P(loan\_status = 1 | x_1, . . . , x_m) = \frac{1}{1 + e^{-(\beta_0 + \beta_1x_1 + . . . + \beta_mx_m)}} = \frac{e^{\beta_0 + \beta_1x_1 + . . . + \beta_mx_m}}{1 + e^{\beta_0 + \beta_1x_1 + . . . + \beta_mx_m}} \]

La probabilidad de que el estado del préstamo sea igual a 0. o la probabilidad de no incumplimiento, viene dada por uno menos la probabilidad de incumplimiento.

\[ P(loan\_status = 0 | x_1, . . . , x_m) = 1 - \frac{e^{\beta_0 + \beta_1x_1 + . . . + \beta_mx_m}}{1 + e^{\beta_0 + \beta_1x_1 + . . . + \beta_mx_m}} = \frac{1}{1 + e^{\beta_0 + \beta_1x_1 + . . . + \beta_mx_m}} \]

Ahora bien, si dividimos la probabilidad de incumplimiento por la probabilidad de no incumplimiento, obtenemos las probabilidades a favor del incumplimiento. Convenientemente, esto es simplemente igual a la función exponencial de nuestro predictor lineal. Veamos,

\[ \frac{P(loan\_status = 1 | x_1, . . . , x_m)}{P(loan\_status = 0 | x_1, . . . , x_m)} = e^{\beta_0 + \beta_1x_1 + . . . + \beta_mx_m} \]

Bien, ¿Cómo interpretamos esto?

Interpretación del coeficiente

Supongamos que el valor de la variable \(x_j\) aumenta en 1, mientras que todas las demas variables permanecen iguales. Si esto sucede, las probabilidades de incumplimiento se multiplicarán por la exponencial de \(e^{\beta_j}\). Tenga en cuenta lo siguiente:

  • Las probabilidades DISMINUIRAN si aumenta \(x_j\) cuando \(\beta_j < 0\), \(e^{\beta_j} <1\)y

  • las probabilidades AUMENTARÁN si aumenta \(x_j\) cuando \(\beta_j > 0\), \(e^{\beta_j}>1\).

Volviendo al modelo de regresión logística que construimos, vemos que el coeficiente de edad es \(e^{-0.007342}\). Si la edad aumenta en uno, las probabilidades de incumplimiento se multiplicarán por aproximadamente \(-0.734\). Esto significa que un año más de edad reduce la probabilidad de impago en casi un 80%.

Ejercicios

Construyamos un modelo glm con variable ir_cat como predictor

log_model_cat <- glm(formula = loan_status ~ ir_cat, family = "binomial", data = training_set)

Imprimamos las estimaciones de los parámetros

log_model_cat

Call:  glm(formula = loan_status ~ ir_cat, family = "binomial", data = training_set)

Coefficients:
  (Intercept)  ir_cat11-13.5    ir_cat13.5+     ir_cat8-11  ir_catMissing  
      -2.9091         1.0071         1.3874         0.5933         0.7937  

Degrees of Freedom: 19393 Total (i.e. Null);  19389 Residual
Null Deviance:      13460 
Residual Deviance: 13050    AIC: 13060

Ahora, veamos las diferentes categorías en ir_cat usando la table()

table(loan_data$ir_cat)

    0-8 11-13.5   13.5+    8-11 Missing 
   7130    6954    6002    6230    2776 

Múltiples variables en un modelo de regresión logística

La interpretación de un solo parámetro sigue siendo válidad cuando se incluyen varias variables en un modelo. Cuando incluye varias variables y solicita la interpretación cuando una determinada variable cambia, se supone que las otras variables permanecen constantes o sin cambios. Hay una elegante frase latina para esto, ceterís paribus, que literalmente significa mantener a todos los demás iguales.

Para construir un modelo de regresión logística con múltiples variables, puede usar el signo + para agregar variables.

Para evaluar el modelo hay una serie de cosas a tener en cuenta. Ya miraste los valores de los parámetros, pero eso no es lo único importante. También es importante la importancia estadística de la estimación de un determinado parámetro. La importancia de un parámetro a menudo se denomina valor p; sin embargo, en la salida de un modelo lo verá indicado como p(>|t|). En glm, el significado leve se indica con un “.” a un significado muy fuerte denotado por “**”. Cuando un parámetro no es significativo, significa que no se puede asegurar que este parámetro sea significativamente diferente de 0. La significancia estadística es importante. En general, sólo tiene sentido interpretar el efecto sobre el incumplimiento para parámetros significativos.

Ahora, constuyamos el mdoelo de regresión logística

log_model_multi <- glm(loan_status ~ age + ir_cat + grade + loan_amnt + annual_inc , family = "binomial", data = training_set)
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred

Obtener los niveles de significancia usando summary()

summary(log_model_multi)

Call:
glm(formula = loan_status ~ age + ir_cat + grade + loan_amnt + 
    annual_inc, family = "binomial", data = training_set)

Coefficients:
                Estimate Std. Error z value Pr(>|z|)    
(Intercept)   -2.428e+00  1.284e-01 -18.920  < 2e-16 ***
age           -3.980e-03  3.863e-03  -1.030   0.3029    
ir_cat11-13.5  7.018e-01  1.313e-01   5.344 9.08e-08 ***
ir_cat13.5+    6.556e-01  1.462e-01   4.485 7.31e-06 ***
ir_cat8-11     4.867e-01  1.160e-01   4.196 2.71e-05 ***
ir_catMissing  5.067e-01  1.275e-01   3.973 7.09e-05 ***
gradeB         1.427e-01  1.041e-01   1.370   0.1707    
gradeC         4.806e-01  1.200e-01   4.006 6.18e-05 ***
gradeD         8.033e-01  1.365e-01   5.887 3.93e-09 ***
gradeE         9.689e-01  1.643e-01   5.896 3.73e-09 ***
gradeF         1.512e+00  2.296e-01   6.584 4.58e-11 ***
gradeG         2.072e+00  3.443e-01   6.019 1.76e-09 ***
loan_amnt     -8.438e-06  4.208e-06  -2.005   0.0449 *  
annual_inc    -4.788e-06  7.365e-07  -6.502 7.95e-11 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 13460  on 19393  degrees of freedom
Residual deviance: 12892  on 19380  degrees of freedom
AIC: 12920

Number of Fisher Scoring iterations: 5

Predecir la probabilidad de Incumplimiento

Ya aprendio cómo construir un modelo de regresión logística. Pero, por supuesto, le gustaria saber cómo utilizar el modelo obtenido y sus estimaciones de parámetros para calcular estimaciones de probabilidad de incumplimiento para los cados del conjunto de prueba. Las predicciones del conjunto de prueba se pueden comparar con sus resultados reales, lo cual es necesario para validar el modelo.

Un ejemplo con edad y propiedad de la vivienda

Para guiarlo a través de este principio, veamos un ejemplo bastante simple, que contiene las variables edad (age) y propiedad de casa (home_ownership). Las estimaciones de los parámetros se obtuvieron utilizando el conjunto de entrenamiento de nuestros datos de préstamo.

log_model_small <- glm(loan_status~age+home_ownership,
                       family = "binomial",
                       data = loan_data)
log_model_small

Call:  glm(formula = loan_status ~ age + home_ownership, family = "binomial", 
    data = loan_data)

Coefficients:
        (Intercept)                  age  home_ownershipOTHER  
          -1.988296            -0.008153             0.655718  
  home_ownershipOWN   home_ownershipRENT  
           0.117132             0.228233  

Degrees of Freedom: 29091 Total (i.e. Null);  29087 Residual
Null Deviance:      20270 
Residual Deviance: 20230    AIC: 20240

Usando la fórmula discutida en la sección anterior

\[ P(loan\_status = 1 | age, home\_ownership) = \frac{1}{1 + e^{-(\hat{\beta_0} + \hat{\beta_1}age + \hat{\beta_2}OTHER + \hat{\beta_3}OWN + \hat{\beta_4}RENT)}} \]

y las estimaciones de parámetros, ahora podemos calcular una probabilidad de incumplimiento usando los valores de covariables de los casos que están en el conjunto de prueba. Nuievamente, tenga en cuenta que tenemos una estimación de parámetros para cada una de las categorías en la variable categórica, excepto una para la categoría de referencia, que es la categoría de propiedad de vivienda HIPOTECA en este modelo.

Ejemplo de conjunto de prueba

Veamos un ejemplo específico. El cliente de la primera línea del conjunto de prueba tiene 45 años hy es inquilino. Utilizando el modelo que tenemos a mano, puede calcular la probabilidad de incumplimiento de esta manera.

\[ \begin{eqnarray} P(loan\_status = 1 | age = 31, home\_ownership = RENT) &=& \frac{1}{1 + e^{-(\hat{\beta_0} + \hat{\beta_1}\cdot 31 + \hat{\beta_2}\cdot 0 + \hat{\beta_4}\cdot 1)}} \\[0.2cm] &=& \frac{1}{1 + e^{-(-1.988296\: + (-0.008153)\times 31) + (0.228233)\times 1)}}\\[0.2cm] &=& \fbox{11.80%} \end{eqnarray} \]

Note que se llego a una probabilidad de incumplimiento del 10.97% para este cliente en específico.

Hacer predicciones en R

Vamos a utilizar la función predict para predecir la probabilidad de incumplimiento para uno o varios casos de conjunto de pruebas. Comencemos seleccionando el primer caso del conjunto de prueba.

test_case <- as.data.frame(test_set[1,])
test_case
  loan_status loan_amnt int_rate grade emp_length home_ownership annual_inc age
2           0      2400       NA     C         25           RENT      12252  31
  emp_cat  ir_cat
2   15-30 Missing

Ya se a que esté utilizando uno o varios casos, siempre debe asegurarse de que los casos de prueba estén almacenados en un marco de datos. Exploremos este caso de prueba. La edad es de 31 años y la categoría de propiedad de vivienda es alquiler. Usando la función predict(), el primer argumento contiene el modelo ajustado. El segundo argumento, proporciona los nuevos datos en test_case.

predict(log_model_small, newdata = test_case)
        2 
-2.012814 

Al hacer esto nota que obtienes un resultado de -2.010561. Esta aún no es la probabilidad de incumplimiento sino el predictor lineal. Al cambiar el argumento de respuesta, obtiene la probabilidad de incumplimiento predicha real para este cliente.

\[ \hat{\beta_0} + \hat{\beta_1}age + \hat{\beta_2}OTHER + \hat{\beta_3}OWN + \hat{\beta_4}RENT \]

predict(log_model_small, newdata = test_case, type = "response")
       2 
0.117864 

Ejercicio

Hagamos predicciones para todos los elementos del conjunto de prueba usando el modelo log_model_small.

predictions_all_small <- predict(log_model_small, newdata = test_set, type = "response")

Miremos el rango del objeto predictions_all_small

range(predictions_all_small)
[1] 0.0740231 0.1818599

Evaluación del resultado del model de regresión logística

Utilizando las probabilidades predichas, le gustaría hacer una matriz de confusión. Como vio en secciones anteriores, puede crear una matriz de confusión comparando la columna loan_status en el conjunto de pruena con las predicciones del modelo para el estado del préstamo.

El problema es que la probabilidad predicha de incumplimiento se situa entre cero y uno. ¿Cómo haremos ahora una matriz de confusión?

La respuesta está en la especificación de un valor umbral o de corte. Necesitamos determinar un valor entre 0 y 1, y si el valor predicho está por encima de este valor, la predicción se establece en ; si no, la predicción se establece en 0.

Limite = 0.5.

Un valor de corte lógico podría ser el 0.5: si las probabilidades son mayores a favor del incumplimiento, o mayores que el 0.5, la predicción se establece en 1; si no, se establece en 0.

Echemos un vistazo a la matriz de confusión para el subconjunto de estos 9698 casos.

library(tidyverse)
Warning: package 'tidyverse' was built under R version 4.3.1
Warning: package 'ggplot2' was built under R version 4.3.1
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.2     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.2     ✔ tibble    3.2.1
✔ lubridate 1.9.2     ✔ tidyr     1.3.0
✔ purrr     1.0.1     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
resultados <- test_set %>% mutate(predicciones = predictions_all_small) %>% 
  select(loan_status, predicciones) %>% 
  mutate(predicciones = ifelse(predicciones>0.5, 1, 0))
head(resultados,14)
   loan_status predicciones
2            0            0
6            0            0
7            1            0
10           0            0
11           0            0
13           0            0
14           0            0
15           1            0
16           0            0
19           0            0
20           1            0
24           0            0
25           0            0
26           0            0
No default (0) Defaul (1)
No default (0) 11 0
Defaul (1) 3 0

\[ \begin{eqnarray} sensitivity &=& 0/(3+0) = 0\\[0.2cm] accuracy &=& 11/(11+3+0+0) = 78.57\% \end{eqnarray} \]

Queda claro que este límite no conducirá a ningún cumplimiento previsto, lo que dará una columna de ceros, por lo tanto, tenemos una sensivilidad del 0%, sin que ninguno de los incumplimientos reales se clasifique correctamente. Como ha visto en los ejercicios anteriores, no es raro tener probabilidades de incumplimiento predichas bastante bajas, ya que el incumplimiento de los préstamos es un evento poco común. Por lo tanto, se debe tener cuidado al decidir un valor de corte, no simplemente establecerlo en el 0.5.

Límite = 0.1

resultados <- test_set %>% mutate(predicciones = predictions_all_small) %>% 
  select(loan_status, predicciones) %>% 
  mutate(predicciones = ifelse(predicciones>0.1, 1, 0))
head(resultados,14)
   loan_status predicciones
2            0            1
6            0            1
7            1            1
10           0            1
11           0            1
13           0            0
14           0            1
15           1            1
16           0            1
19           0            1
20           1            1
24           0            1
25           0            1
26           0            1
No default (0) default (1)
No default (0) 11 1
Default (1) 3 13

\[ \begin{eqnarray} Sensitivity &=& 13/(13+1) = 92.86\% \\ accuracy &=& 12/(14) = 85.71\% \end{eqnarray} \]

Este resultado conduce a una sensibilidad significativamente mejor, mientras que la precisión de la clasificación mejora también.

Árboles de decisión

Los árboles de clasificación son otro método popular en el mundo de la modelización del riesgo crediticio. En este capítulo aprenderá cómo crear árboles de clasificación utilizando datos crediticios en R.

¿Qué es un árbol de decisión?

Otra forma de clasificar a los clinetes en grupos de incumplimientos y no incumplimientos en mediante árboles de decisión. Una razón importante de la popularidad de los árboles de decisión es su interpretabilidad.

Ejemplo de árbol de decisión

Aquí hay un árbol de ejemplo. Al responder las preguntas desde la parte superior (o la Raíz) del árbol hasta la parte inferior (o los nodos de la hoja), se toma una decisión sobre si clasificar una determinada instancia como un caso predeterminado o no predeterminado. Existen varios paquetes en R para construir árboles de decisión automáticamente.

¿Cómo tomar una decisión dividida?

Antes de hacerlo, me gustaría explicar cómo se toma la llamada decisión dividida. Para cada nodo del árbol, la decisión de división afectará la estructura final del árbol. Veamos dos posibles decisiones de división para la variable categórica. ¿Cómo decidimos si la pregunta que se debe hacer es si alguien, digamos alquila una casa o no, O si alguien alquila una casa o pertenece a la categoría “otros”, o no?

Para una variable continua (digamos edad), ¿cómo definimos el valor límite para la edad que define el límite entre un valor predeterminado o no? La respuesta se puede encontrar en la definición de una medida para la impureza. En resumen, le gustaría minimizar las impurezas en cada nodo del árbol. Una medida popular (y la medida por defecto para la impureza en el popular paquete de reparto) es la medida Gini. Veamos un ejemplo.

Ejemplo

Suponga que tiene un conjunto de capacitación con 500 casos, 250 valores predeterminados y 250 casos no predeterminados. A menudo verá la cantidad de casos en cada nodo representados así:

con el número real de incumplimientos en el lado izquierdo, y el número real de valores predeterminados en el lado derecho.

Por supuesto, el escenario ideal, aunque poco realista, conducirá a una división perfecta entre incumplimiento y no incumplimientos a la edad de 40 años. Como este no es el caso, calculemos la impureza usando la medida Gini.

La impureza en un determinado nodo según la medida de Gini viene dada por dos veces la proporción de no incumplimientos en un nodo, multiplicada por la proporción de incumplimientos en este nodo.

\[ Gini = 2*prop(default)*prop(non-default) \]

Al aplicar está fórmula al nodo raíz, esto conduce a una impuerza de 0.5 (que es la cantidad máxima posible, con exactamente tantos valores predeterminados como no predeterminados). Ya que

\[ Gini_R = 2*(250/500)*(250/500) = 0.5 \]

una impureza del 0.4536 en el nodo inzquierdo,

\[ Gini\_N2 = 2*(80/230)*(150/230) = 0.4536 \]

y una impuerza del 0.4664 en el nodo derecho,

\[ Gini\_N1 = 2*(170/270)*(100/270) = 0.4664 \]

Una métrica importantes es la ganancia en pureza que se logra desde el nodo raíz hasta los dos nodos N1 y N2.

Esta ganancia se puede calcular restando las medidas de Gini ponderadas en los nodos N1 y N2 de la medida de Gini en la raíz.

\[ 0.5 - 270/500*0.4664 - 230/500*0.4536 = 0.04 \]

En nuestro ejemplo, esto conduce a una ganancia de casi 0.04. El algoritmo selecciona la división que conduce a la mayor ganancia.

Ejercicio

# La medida de Gini del nodo raíz se da a continuación 
gini_root <- 2 * 89 / 500 * 411 / 500

# Calcular el Gini medida para el nodo de hoja izquierdo
gini_ll <- 2 * 401/446 * 45/446

# Calcular el Gini medida para el nodo de hoja derecho
gini_rl <- 2 * 10/54 * 44/54

# Calcular la ganancia 
gain <- gini_root - 446 / 500 * gini_ll - 54 / 500 * gini_rl

Construyendo árboles de decisión usando el paquete rpart()

Ya ha visto como se puede encontrar una división perfecta utilizando la medida Gini. En el ejercicio anterior, sólo analizaste una posible división.

Imagínese tener que comprar manualmente todas las divisiones posibles para encontrar la mejor. Eso sería todo un desafio ¿Verdad?

Afortunadamente, existe un paquete en R que crea árboles de decisión para usted, el paquete rpart. Si bien este paquete es generalmente muy útil para crear árboles de decisión, es importante advertirle desde el prinicipio que crear árboles de decisión en el contexto del riesgo crediticio puede ser todo un desafío. La razón principal de esto es el hecho de que los datos sobre el riesgo de crédito generalmente están muy desequilibrados debido al bajismo porcentaje de impagos. Al utilizar la configuración predeterminada en el conjunto de entrenamiento de loan_data, incluidas todas las variables y especificando el método, como la variable de respuesta loan_status es categórica, recibirá un mensaje de advertencia que indica que solo se crea una raíz.

La razón de esto es que, similar a lo que hemos visto al establecer un límite para un modelo de regresión logística; la mayor precisión se logra simplmente prediciendo que todos los caso no serán predeterminados.

Tres técnicas para superar el desequilibrio

  • Una primera opción es sobremuestrear su grupo subrepresentado (en este caso, los predeterminados) o submuestreando el grupo sobrerrepresentado (o no predeterminados). Equilibrar el conjunto de entrenamiento tendrá un efecto positivo en el problema de precisión (accuracy) discutido anteriormente, y generalmente conduce a mejores resultados. Tenga en cuenta que el sobremuestreo o el submuestreo solo debe aplicarse al conjunto de entrenamiento y no al conjunto de prueba.

  • Una segunda opción es cambiar las probabilidades previas en la función rpart(). Por defecto, las probabilidades previas de incumplimiento frente a no predeterminados se establecen iguales a sus proporciones en el conjunto de entrenamiento. Al hacer que las probabilidades previas de incumplimiento sean más grandes, engañas a R en dar más importancia a los incumplimientos, lo que lleva a un mejor árbol de decisiónes.

  • Como tercera opción, la función rpart() permite especificar una matriz de pérdidas. En esta matriz de pérdidas, se pueden asociar diferentes costos con la clasificación erronea de un incumplimiento como no predeterminado frente a la clasificación errónea de un no predeterminado como predeterminado. Al aumentar el costo de clasificación errónea del primero, nuevamente se presta más atención atraídos por la correcta clasificación de los incumplimientos, mejorando la calidad del árbol de decisión.

Como los tres métodos están destinados a superar el problema del desequilibrio de clases, la validación es muy importante. Donde el submuestreo o el sobremuestreo pueden funcionar muy bien para algunos conjuntos de datos, podría funcionar mal para los otros, y lo mismo podría ser cierto para los otros dos métodos.

Cuál es el mejor método para los datos disponibles solo se aclarará con la validación adecuada del modelo.

library(rpart)
Warning: package 'rpart' was built under R version 4.3.1
tree_undersample <- rpart(loan_status ~ ., method = "class",data =  training_set, control = rpart.control(cp = 0.001))

plot(tree_undersample, uniform = TRUE)

text(tree_undersample)

tree_prior <- rpart(loan_status ~ ., method = "class",
                    data = training_set, parms = list(prior = c(0.7, 0.3)),
                    control = rpart.control(cp = 0.001))

# Plot the decision tree
plot(tree_prior, uniform = TRUE)

# Add labels to the decision tree
text(tree_prior)

Podando el árbol de decisiones

Los arboles de decisión resultantes pueden ser muy grandes. Generalmente no se recomienda construir árboles de decisión enormes.

Problemas con árboles de decisión grandes

No sólo son más difíciles de interpretar, sino que también puede ocurrir un sobreajuste, lo que lleva a resultados inferiores al aplicar el modelo al conjunto de prueba. El paquete rpar() proporciona herramientas que facilitan bastante la poda de árboles de decisión. Las funciones útiles son plotcp() y printcp(). Echemos un vistazo alo que hacen estas funciones al aplicarlas al árbol que construimos usando el conjunto de entrenamiento submuestreado. }

Printcp y tree_undersample

Primero hechemos un vistazo a printcp(). Al aplicar esta función a nuestro objeto tree_undersample, obtienes una descripción general de cómo crece el árbol usando más divisiones (dadas en la columna nsplit).

printcp(tree_undersample)

Classification tree:
rpart(formula = loan_status ~ ., data = training_set, method = "class", 
    control = rpart.control(cp = 0.001))

Variables actually used in tree construction:
[1] annual_inc emp_length grade      int_rate   loan_amnt 

Root node error: 2138/19394 = 0.11024

n= 19394 

         CP nsplit rel error xerror     xstd
1 0.0011225      0   1.00000 1.0000 0.020400
2 0.0010000     15   0.98129 1.0005 0.020404

De hecho, puede cambiar el parámetro de complejidad cp, que antes se fijó en el 0.001, para obtener niveles de complejidad correcto. Ahora bien, ¿qué nivel se debe elegir? Desearía minimizar el llamado error de validación cruzada del árbol de decisión. Estos resultados se dan en la columna xerror. Como sugiere el nombre, se utiliza validación cruzada dentro del conjunto de entrenamiento para obtener esta medida de error. Dado que existe un elemento aleatorio en el procesos de validación cruzada, es necesario establecer una semilla si desea que los resultados sean reproducibles.

Plotcp y tree_undersample

También puede trazar el error de validación cruzada en función del parámetro de complejidad cp y el tamaño del árbol utilizando la función plotcp.

El error mínimo de validación cruzada se puede encontrar aquí mismo, donde el parámetro de complejidad cp es igual 0.003653. Para este valor de cp exacto, puedes echar un vistazo a printcp() nuevamente.

Trazar el árbol podado

Ahora, para podar el arbol, se puede usar la función prune(), con el árbol inicial no podado como primer argumento y el valor de cp que minimizo el error de validación cruzada como segundo argumento.

Al trazar el árbol podado se obtiene un árbol de decisión más pequeño. Tenga en cuenta que este árbol no le brinda información sobre el número real de valores predeterminados versus no predeterminados que están presentes en cada hoja.

ptree_undersample <- prune(tree_undersample,
                           cp = 0.003653)

plot(ptree_undersample, 
     uniform = TRUE)

text(ptree_undersample)

Si desea agregar esta información, puede incluir el argumento use.n = TRUE en la función text(). Tenga en cuenta que estos valores se refieren únicamente al conjunto de entrenamiento. Tenga en cuenta que una respuesta SÍ a la declaración de prueba en un nodo lo llevará a seguir la rama de la izquierda, y una respuesta NO a la declaración de prueba lo llevará a seguir la ra,a de la derecha. Esto no es muy intuitivo en el gráfico estándar del paquete rpart. Además, la prueba home_ownership es igual a cd no es muy informativa.

ptree_undersample <- prune(tree_undersample,
                           cp = 0.003653)

plot(ptree_undersample, 
     uniform = TRUE)

text(ptree_undersample,
     use.n = TRUE)

prp() en el paquete rpart.plot

Se obtiene un gráfico más intuitivo utilizando la función prp() del paquete rpart.plot. Al trazar usando prp(), recibe orientación sobre qué rama tomar dependiendo de la respuesta a la pregunta dividida. También obtiene el nombre del factor real para las variables de factor, como puede ver para las categorías de propiedad y alquiler.

library(rpart.plot)
prp(ptree_undersample)

Ejercicio

Usemos plotcp() para visualizar el error de validación cruzada en relación con el parámetro de complejidad para tree_prior.

plotcp(tree_prior)

Utilice printcp() para imprimir una tabla de información sobre CP, divisiones y errores. Vea si puede identificar qué división tiene el error mínimo de validación cruzada en tree_prior.

printcp(tree_prior)

Classification tree:
rpart(formula = loan_status ~ ., data = training_set, method = "class", 
    parms = list(prior = c(0.7, 0.3)), control = rpart.control(cp = 0.001))

Variables actually used in tree construction:
[1] age            annual_inc     emp_cat        emp_length     grade         
[6] home_ownership int_rate       loan_amnt     

Root node error: 5818.2/19394 = 0.3

n= 19394 

          CP nsplit rel error  xerror     xstd
1  0.0052744      0   1.00000 1.00000 0.020400
2  0.0047527      3   0.98418 1.00453 0.020124
3  0.0035727      4   0.97942 1.00476 0.020011
4  0.0029150      8   0.96269 0.99633 0.019652
5  0.0022178      9   0.95977 0.99408 0.019471
6  0.0021406     15   0.94462 0.99386 0.019483
7  0.0018931     18   0.93820 0.98965 0.019295
8  0.0016159     19   0.93630 0.99051 0.019307
9  0.0015332     20   0.93469 0.98970 0.019262
10 0.0013233     23   0.93009 0.99167 0.019207
11 0.0012569     24   0.92877 0.99039 0.019105
12 0.0012458     25   0.92751 0.99155 0.019094
13 0.0012447     27   0.92502 0.99135 0.019089
14 0.0011704     30   0.92108 0.99150 0.019081
15 0.0011582     33   0.91757 0.99252 0.019078
16 0.0011387     37   0.91283 0.99401 0.019079
17 0.0010817     48   0.89841 0.99751 0.019046
18 0.0010804     49   0.89733 0.99970 0.019023
19 0.0010766     58   0.88299 1.00273 0.019038
20 0.0010596     65   0.87405 1.00469 0.019035
21 0.0010374     67   0.87193 1.00485 0.019019
22 0.0010341     69   0.86986 1.00533 0.019016
23 0.0010000     71   0.86779 1.00400 0.019051

Cree un indice para la fila con error mínimo.

index <- which.min(tree_prior$cptable[, "xerror"])

Creamos un tree_min

tree_min <- tree_prior$cptable[index, "CP"]

Podemos el árbol usando tree_min

ptree_prior <- prune(tree_prior, cp = tree_min)

Use prp() para trazar el árbol podado

library(rpart.plot)
Warning: package 'rpart.plot' was built under R version 4.3.1
prp(ptree_prior)

Ejercicio

Establezca una semilla y construir el árbol en la variable tree_loss_matrix

set.seed(345)
tree_loss_matrix  <- rpart(loan_status ~ ., method = "class", data = training_set,
                           parms = list(loss=matrix(c(0, 10, 1, 0), ncol = 2)),
                           control = rpart.control(cp = 0.001))

Trazar la tasa de error de validación cruzada en función del parámetro de complejidad

plotcp(tree_loss_matrix)

Podar el árbol usando cp = 0.0012788

ptree_loss_matrix <- prune(tree_loss_matrix, cp = 0.0012788)

Utilice prp() y el argumento extra = 1 para trazar el árbol podado.

prp(ptree_loss_matrix, extra = 1)

Otras opciones de árboles y la construcción de matrices de confusión

Hemos cubierto varias opciones para facilitar la construcción de un árbol, epecialmente en datos desiquilibrados. Por supuesto, la gama de posibilidaces es infinita y hay algunos otros argumentos que pueden pasar a la función rpart() para dar forma a su árbol de decisión de incumplimiento de préstamo. Repasaré algunos de los más importantes en este video.

Otros argumentos interesantes de rpart()

Un argumento interesante en rpart() son los pesos (weights), que le permiten incluir pesos de caso para cada uno de los casos en el conjunto de entrenamiento. Aumentar las ponderaciones de los casos de incumplimiento habría sido otra estrategía plausible para constrarrestar el desequilibrio en el árbol de decisión. Los otros argumentos interesantes que mencionaré aquí son todos parte de la función rpart.control que se puede pasar al control de argumentos. Usamos está función antes para cambiar el párametro de complejidad cp. Un argumento interesante aquí es minsplit, que le permite cambiar el número mínimo de observaciones que deben existir en un nodo para que se intente una división. El valor predeterminado para este argumento es igual a 20, pero para datos no balanceados, podría resultar útil reducir este valor. El argumento minbucket específica el número mínimo de observaciones en cualquier nodo hoja. El valor predeterminado aquí es un tercio del valor especificado en minsplit, pero puede resultar útil probar otros valores. Tenga en cuenta que reducir demasiado este valor podría provocar un sobreajuste. Lo último que debemos hacer es asegurarnos de evaluar nuestro árbol de decisiones.

Hacer predicciones utilizando el árbol de decisiones

pred_undersample_class <- predict(tree_undersample, newdata = test_set, type = "class")
head(pred_undersample_class)
 2  6  7 10 11 13 
 0  0  0  0  0  0 
Levels: 0 1

Hasta ahora, solo usamos el conjunto de entranamiento, pero queremos usar nuestro conjunto de prueba para evaluar el resultado. Esto se puede hacer usando la función predict(). Aquí se muestra el código para realizar predicciones utilizando el modelo con el conjunto de datos submuestreado. Tenga en cuenta que usamos la versión podada del árbol. Incluyendo el tipo de argumento es igual a “class”, se obtiene un vector de predicciones de inmediato, sin tener que usar más un valor de corte, a diferencia de lo que hicimos con la regresión logística. En algunos casos, sin embargo, le gustaría tener una predicción no binaria y elegir un límite usted mismo. En ese caso, simplemente puede omitir el argumento de tipo en la función predict() y obtendrá probabilidades en lugar de predicciones binarias. Estas probabilidades se derivan de las proporciones del nodo hoja donde termina un caso particular dados sus valores de covariables.

pred_undersample <- predict(tree_undersample, newdata = test_set)
head(pred_undersample)
           0          1
2  0.7985612 0.20143885
6  0.9204698 0.07953023
7  0.8346304 0.16536965
10 0.7628866 0.23711340
11 0.8346304 0.16536965
13 0.9204698 0.07953023

Construyendo una matriz de confusión

Aquí, se da la matriz de confusión para el árbol de decisión que se construyó utilizando datos submuestreados y predicciones binarias.

table(test_set$loan_status, pred_undersample_class)
   pred_undersample_class
       0    1
  0 8570   39
  1 1079   10

Aunque utilizamos submuestreo, se predicen muy pocos valores predeterminados para nuestro conjunto de prueba. Podría resultar útil aquí utilizar predicciones de probabilidad en lugar de predicciones binarias y reducir el límite.

Evaluación de un Modelo de Riesgo Crediticio

En este capítulo, aprenderá cómo evaluar y comparar los resultados obtenidos a través de varios modelos de riesgo crediticio.

Encontrar el límite correcto: la curva estratégica

En los capítulos anteriores, le brindamos una descripción general de cómo se pueden utilizar los modelos de regresión logística y los árboles de decisión para calcular la probabilidad de incumplimiento.

Al utilizar la función predict() en un modelo de regresión logística con type = "response", se obtiene un vector con las probabilidades de incumplimiento previstas.

Usando la función predict() como esta en un modelo de árbol, obtendrá una matriz con dos columnas: