Objetivo

Desarrollar un modelo de regresión logística binaria y Random forest, que permita predecir la probabilidad de morosidad crediticia en función de variables explicativas clave, con el fin de optimizar las políticas de otorgamiento de crédito, segmentar clientes según su perfil de riesgo y apoyar la toma de decisiones en instituciones financieras.

Introducción

La gestión del riesgo crediticio constituye uno de los pilares fundamentales en la sostenibilidad del sistema bancario. En un entorno económico dinámico, caracterizado por la volatilidad de los ingresos y el endeudamiento creciente de los hogares, las instituciones financieras enfrentan el reto de diseñar políticas de otorgamiento de crédito basadas en evidencia empírica. La predicción de morosidad a partir de variables observables se ha convertido en una necesidad estratégica para garantizar la salud de las carteras de crédito.

El presente trabajo aborda esta problemática mediante la aplicación de técnicas de modelado estadístico, para estimar la probabilidad de incumplimiento en función de variables explicativas clave. Esta aproximación permite simular escenarios que se ajusten a las tendencias observadas, con el objetivo de minimizar el riesgo y optimizar la toma de decisiones.

Se espera que los modelos identifiquen de manera significativa las variables con mayor poder explicativo sobre el riesgo de incumplimiento. Asimismo, se anticipa una segmentación efectiva de los clientes en perfiles de riesgo, lo que permitirá a las entidades financieras adoptar decisiones más informadas respecto a la asignación de crédito, políticas de tasa de interés y medidas de mitigación.

La implementación de estas métricas contribuyen a diseñar productos financieros personalizados y a fortalecer las capacidades analíticas de las instituciones crediticias en la etapa de evaluación de solicitudes.

Análisis exploratorio de datos

El presente informe tiene como objetivo proporcionar un análisis detallado y argumentado de los datos disponibles, con miras a establecer una comprensión profunda de su comportamiento y estructura. Este paso es fundamental para guiar las decisiones relacionadas con el modelado predictivo o clasificaciones posteriores.

Descripción de variables

Con base en los datos extraidos de [Hoffman, 2024] a continuación se describen las variables del data set “german_credit.csv”.

  1. Title: German Credit data

  2. Source Information

Professor Dr. Hans Hofmann
Institut f”ur Statistik und “Okonometrie
Universit”at Hamburg
FB Wirtschaftswissenschaften
Von-Melle-Park 5
2000 Hamburg 13

  1. Number of Instances: 1000

  2. Attribute description for german

default: (numerical)
Category of customer
0 : Good customer
1 : Bad customer

account_check_status: (qualitative)
Status of existing checking account
1 : < 0 DM
2 : 0 <= … < 200 DM
3 : >= 200 DM / salary assignments for at least 1 year
4 : no checking account

duration_in_month: (numerical)
Duration in month
1 : 1 <= … < 12
2 : 12 <= … < 24
3 : 24 <= … < 36
4 : 36 <= … < 48
5 : 48 <= … < 60
6 : 60 <= … < 72

credit_history: (qualitative)
Credit history
1 : no credits taken/all credits paid back duly
2 : all credits at this bank paid back duly
3 : existing credits paid back duly till now
4 : delay in paying off in the past
5 : critical account/other credits existing (not at this bank)

purpose: (qualitative)
Purpose
1 : car (new)
2 : car (used)
3 : furniture/equipment
4 : radio/television
5 : domestic appliances
6 : repairs
7 : education
8 : (vacation - does not exist?)
9 : retraining
10 : business
11 : others

credit_amount: (numerical)
Credit amount
1 : 1 <= … < 1000
2 : 1000 <= … < 2000
3 : 2000 <= … < 3000
4 : 3000 <= … < 4000
5 : 4000 <= … < 5000
6 : 5000 <= … < 6000
7 : 6000 <= … < 7000
8 : 7000 <= … < 8000
9 : 8000 <= … < 9000
10 : 9000 <= … < 10000
11 : 10000 <= … < 11000
12 : 11000 <= … < 12000
13 : 12000 <= … < 13000
14 : 13000 <= … < 14000
15 : 14000 <= … < 15000
16 : 15000 <= … < 16000
17 : 16000 <= … < 17000
18 : 17000 <= … < 18000
19 : 18000 <= … < 19000
20 : 19000 <= … < 20000

savings: (qualitative)
Savings account/bonds
5 : … < 100 DM
4 : 100 <= … < 500 DM
3 : 500 <= … < 1000 DM
2 : .. >= 1000 DM
1 : unknown/ no savings account

present_emp_since: (qualitative)
Present employment since
5 : unemployed
4 : … < 1 year
3 : 1 <= … < 4 years  2 : 4 <= … < 7 years
1 : .. >= 7 years

installment_as_income_perc: (numerical)
Installment rate in percentage of disposable income

personal_status_sex: (qualitative)
Personal status and sex
1 : male : divorced/separated
2 : female : divorced/separated/married
3 : male : single
4 : male : married/widowed
5 : female : single

other_debtors: (qualitative)
Other debtors / guarantors
1 : none
2 : co-applicant
3 : guarantor

present_res_since: (numerical)
Present residence since

property: (qualitative)
Property
1 : real estate
2 : if not A121 : building society savings agreement/life insurance
3 : if not A121/A122 : car or other, not in attribute 6
4 : unknown / no property

age: (numerical)
Age in years
1 : 18 <= … < 30
2 : 30 <= … < 40
3 : 40 <= … < 50
4 : 50 <= … < 60
5 : 60 <= … < 70
6 : 70 <= … < 80

other_installment_plans: (qualitative)
Other installment plans
1 : bank
2 : stores
3 : none

housing: (qualitative)
Housing
1 : rent
2 : own
3 : for free

credits_this_bank: (numerical)
Number of existing credits at this bank

job: (qualitative)
Job
1 : unemployed/ unskilled - non-resident
2 : unskilled - resident
3 : skilled employee / official
4 : management/ self-employed/highly qualified employee/ officer

people_under_maintenance: (numerical)
Number of people being liable to provide maintenance for

telephone: (qualitative)
Telephone
0 : none
1 : yes, registered under the customers name

foreign_worker: (qualitative)
foreign worker
1 : yes
0 : no

german_credit <- read.csv("german_credit.csv")
german_credit

Librerías

Para llevar a cabo el presente análisis, se utilizaron diversas librerías especializadas en el entorno de programación R. Estas herramientas permitieron realizar manipulación eficiente de datos, así como la generación de gráficos y su visualización.

library(readxl)
library(stats)
library(ggplot2)
library(pROC)
library(haven)
library(factoextra)
library(FactoMineR)
library(readr)
library(rgl)
library(fpc)
library(psych)
library(randomForest) 
library(caret)
library(dplyr)
library(corrplot)
library(RColorBrewer)

Estadísticas

Se calcularon métricas estadísticas para cada variable numérica, tales como la media, la mediana, los cuartiles y la desviación estándar. Estas métricas proporcionan una visión general del comportamiento de cada atributo, permitiendo identificar tendencias centrales y variabilidad. Por ejemplo, variables como ‘rango_edad’ muestran una distribución equilibrada, mientras que otras como ‘credits_this_bank’ tienen una concentración más baja en los valores mayores.

df <- read.csv("df_banco_final.csv")
summary(df)
##     default    account_check_status credit_history     savings     
##  Min.   :0.0   Min.   :1.000        Min.   :1.000   Min.   :1.000  
##  1st Qu.:0.0   1st Qu.:1.000        1st Qu.:3.000   1st Qu.:3.000  
##  Median :0.5   Median :2.000        Median :3.000   Median :5.000  
##  Mean   :0.5   Mean   :2.309        Mean   :3.361   Mean   :3.986  
##  3rd Qu.:1.0   3rd Qu.:4.000        3rd Qu.:4.000   3rd Qu.:5.000  
##  Max.   :1.0   Max.   :4.000        Max.   :5.000   Max.   :5.000  
##  present_emp_since installment_as_income_perc other_debtors      property    
##  Min.   :1.000     Min.   :1.000              Min.   :1.000   Min.   :1.000  
##  1st Qu.:2.000     1st Qu.:2.000              1st Qu.:1.000   1st Qu.:1.000  
##  Median :3.000     Median :3.000              Median :1.000   Median :2.000  
##  Mean   :2.593     Mean   :2.941              Mean   :1.111   Mean   :2.319  
##  3rd Qu.:3.000     3rd Qu.:4.000              3rd Qu.:1.000   3rd Qu.:3.000  
##  Max.   :5.000     Max.   :4.000              Max.   :3.000   Max.   :4.000  
##  other_installment_plans    housing      credits_this_bank      job       
##  Min.   :1.000           Min.   :1.000   Min.   :1.000     Min.   :1.000  
##  1st Qu.:2.000           1st Qu.:2.000   1st Qu.:1.000     1st Qu.:2.000  
##  Median :3.000           Median :2.000   Median :1.000     Median :3.000  
##  Mean   :2.601           Mean   :1.864   Mean   :1.334     Mean   :2.831  
##  3rd Qu.:3.000           3rd Qu.:2.000   3rd Qu.:2.000     3rd Qu.:3.000  
##  Max.   :3.000           Max.   :3.000   Max.   :4.000     Max.   :4.000  
##    telephone      foreign_worker    estado_civil    rango_plazos_credito
##  Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :1.000       
##  1st Qu.:0.0000   1st Qu.:1.0000   1st Qu.:0.0000   1st Qu.:1.000       
##  Median :0.0000   Median :1.0000   Median :0.0000   Median :2.000       
##  Mean   :0.3321   Mean   :0.9671   Mean   :0.4707   Mean   :2.051       
##  3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:3.000       
##  Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :5.000       
##    rango_edad   
##  Min.   :1.000  
##  1st Qu.:1.000  
##  Median :2.000  
##  Mean   :1.936  
##  3rd Qu.:3.000  
##  Max.   :5.000
describe(df)

Valores faltantes

Una evaluación exhaustiva muestra que no existen valores faltantes en ninguna de las variables del dataset, lo que representa una ventaja importante al no requerir imputaciones adicionales ni eliminación de registros incompletos.

colSums(is.na(df))
##                    default       account_check_status 
##                          0                          0 
##             credit_history                    savings 
##                          0                          0 
##          present_emp_since installment_as_income_perc 
##                          0                          0 
##              other_debtors                   property 
##                          0                          0 
##    other_installment_plans                    housing 
##                          0                          0 
##          credits_this_bank                        job 
##                          0                          0 
##                  telephone             foreign_worker 
##                          0                          0 
##               estado_civil       rango_plazos_credito 
##                          0                          0 
##                 rango_edad 
##                          0

Valores atípicos

Los valores atípicos fueron detectados a través del rango intercuartílico (IQR). Estas observaciones extremas pueden influir en las métricas y afectar el rendimiento de algunos modelos estadísticos. Su identificación temprana permite decidir si deben ser eliminados, transformados o mantenidos según el contexto del problema. A continuación, se listan los recuentos de valores atípicos por variable:

detect_outliers <- function(x) {
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR <- Q3 - Q1
  sum(x < Q1 - 1.5 * IQR | x > Q3 + 1.5 * IQR, na.rm = TRUE)
}

sapply(df[, sapply(df, is.numeric)], detect_outliers)
##                    default       account_check_status 
##                          0                          0 
##             credit_history                    savings 
##                         75                          0 
##          present_emp_since installment_as_income_perc 
##                         64                          0 
##              other_debtors                   property 
##                        104                          0 
##    other_installment_plans                    housing 
##                          0                        444 
##          credits_this_bank                        job 
##                          6                          0 
##                  telephone             foreign_worker 
##                          0                         46 
##               estado_civil       rango_plazos_credito 
##                          0                          0 
##                 rango_edad 
##                          0

Histogramas y gráficos

Las siguientes gráficas ilustran patrones y relaciones en los datos: Figura 1: El histograma de ‘rango_edad’ muestra una distribución moderadamente uniforme, indicando una representación equilibrada de clientes jóvenes y adultos. Esta diversidad etaria es útil para desarrollar modelos robustos.

# Histograma de rango_edad
ggplot(df, aes(x = rango_edad)) +
  geom_histogram(binwidth = 1, fill = "skyblue", color = "black") +
  ggtitle("Distribución de la variable rango_edad") +
  xlab("Rango Edad") +
  ylab("Frecuencia")

Figura 2: El boxplot de ‘installment_as_income_perc’ revela que la mayoría de los clientes destinan entre un 25% y 75% de su ingreso a pagar el crédito, lo que refleja una política moderada de otorgamiento. No se observan valores extremos significativos.

# Boxplot de installment_as_income_perc
ggplot(df, aes(y = installment_as_income_perc)) +
  geom_boxplot(fill = "lightgreen") +
  ggtitle("Boxplot: installment_as_income_perc")

Figura 3: La variable ‘housing’ indica la preferencia de los clientes en relación con la vivienda. La mayor parte vive en propiedad o alquiler, mientras que otras formas de vivienda son menos frecuentes.

# Gráfico de barras para housing
ggplot(df, aes(x = factor(housing))) +
  geom_bar(fill = "lightcoral") +
  ggtitle("Frecuencia de Housing") +
  xlab("Tipo de Housing") +
  ylab("Frecuencia")

Figura 4: Distribución del estado civil de los clientes. Una proporción significativa corresponde a un único grupo, lo que puede correlacionar con decisiones de riesgo crediticio.

# Histograma de estado civil
ggplot(df, aes(x = factor(estado_civil))) +
  geom_bar(fill = "orchid") +
  ggtitle("Distribución del Estado Civil") +
  xlab("Estado Civil (0 = Soltero, 1 = Casado)") +
  ylab("Frecuencia")

Figura 5: Dispersión entre la proporción de ingreso destinada a pagos y el plazo del crédito. Se observa que mayores cuotas tienden a asociarse a plazos más bajos, lo cual es lógico en términos financieros. Los colores reflejan el estado de incumplimiento (‘default’), mostrando una ligera tendencia a mayor riesgo con mayores cuotas.

# Diagrama de dispersión con color por default
ggplot(df, aes(x = installment_as_income_perc, y = rango_plazos_credito, color = factor(default))) +
  geom_point(alpha = 0.7) +
  ggtitle("Dispersión: Cuotas como % ingreso vs Plazo de crédito") +
  xlab("installment_as_income_perc") +
  ylab("rango_plazos_credito") +
  scale_color_manual(values = c("0" = "blue", "1" = "red"))

Figura 6: El mapa de calor de correlación permite identificar asociaciones lineales entre variables. Algunas relaciones destacan, como la existente entre ‘estado_civil’ y ‘housing’, lo cual puede ser relevante en etapas posteriores de modelado supervisado.

# Mapa de calor de correlación
numeric_vars <- df[, sapply(df, is.numeric)]
corr_matrix <- cor(numeric_vars, use = "complete.obs")
corrplot(corr_matrix, method = "color", col = brewer.pal(8, "RdBu"), 
         type = "upper", tl.col = "black", tl.srt = 45, addCoef.col = "black")

Metodología

En esta sección se describe la metodología empleada para el desarrollo, implementación y evaluación de los modelos predictivos.

Base de datos

Con la intención de trabajar mejor los datos, se procedió a codificar las variables categóricas. En consecuencia, a cada categoría de una variable cualitativa se le asignó un número entero único, como se muestra a continuación:

datos <- read.csv("df_banco_final.csv")
datos

Normalizar datos

Asimismo, se normalizó la base de datos, ya que una variable con una escala mayor puede influir de manera desproporcionada en los resultados del análisis. Esta técnica permite que todas las variables estén centradas y tengan una escala comparable, lo que contribuye a una interpretación más equitativa entre ellas.

datos_norm <- scale(datos[,-1])

Prueba KMO

Se implementó la prueba KMO (Kaiser-Meyer-Olkin) para evaluar la idoneidad del conjunto de datos antes de aplicar el Análisis de Componentes Principales (ACP).

Esta prueba permite determinar si las correlaciones entre las variables son lo suficientemente altas como para justificar la reducción de la dimensionalidad mediante el ACP.

psych::KMO(datos_norm)
## Kaiser-Meyer-Olkin factor adequacy
## Call: psych::KMO(r = datos_norm)
## Overall MSA =  0.66
## MSA for each item = 
##       account_check_status             credit_history 
##                       0.67                       0.60 
##                    savings          present_emp_since 
##                       0.60                       0.71 
## installment_as_income_perc              other_debtors 
##                       0.62                       0.53 
##                   property    other_installment_plans 
##                       0.68                       0.52 
##                    housing          credits_this_bank 
##                       0.70                       0.61 
##                        job                  telephone 
##                       0.65                       0.66 
##             foreign_worker               estado_civil 
##                       0.58                       0.78 
##       rango_plazos_credito                 rango_edad 
##                       0.66                       0.68

Inmediatamente, se puede observar que el valor general del índice KMO es de 0.66, lo cual indica una adecuación muestral regular. De acuerdo con los criterios establecidos por Kaiser (1974), este valor es aceptable, aunque no óptimo, lo que permite continuar con el Análisis de Componentes Principales (ACP), pero con precaución al interpretar los resultados.

No obstante, algunas variables presentan valores individuales de KMO bajos, por ejemplo, other_debtors, other_installment_plans y foreign_worker, lo que indica una baja correlación con el resto de las variables. En consecuencia, estas podrían considerarse para su eliminación o para un análisis por separado, a fin de mejorar la calidad del modelo.

ACP

Se realizó el Análisis de Componentes Principales (ACP) con el objetivo de reducir la dimensionalidad de los datos y construir un modelo más parsimonioso. El resultado obtenido muestra la desviación estándar explicada por cada componente principal.

De acuerdo con el criterio de Kaiser, se deben conservar únicamente aquellos componentes cuya desviación sea mayor a 1, ya que estos explican una proporción significativa de la variabilidad total del conjunto de datos.

pca<-princomp(datos_norm)
pca
## Call:
## princomp(x = datos_norm)
## 
## Standard deviations:
##    Comp.1    Comp.2    Comp.3    Comp.4    Comp.5    Comp.6    Comp.7    Comp.8 
## 1.5653421 1.3031915 1.1536776 1.0754162 1.0505652 1.0057883 0.9970080 0.9495112 
##    Comp.9   Comp.10   Comp.11   Comp.12   Comp.13   Comp.14   Comp.15   Comp.16 
## 0.9294547 0.9075760 0.8594696 0.8358020 0.7907762 0.7577617 0.7296079 0.6963413 
## 
##  16  variables and  1400 observations.

Como se puede observar, los primeros seis componentes cumplen con el criterio de Kaiser, al presentar una desviación estándar superior a 1. No obstante, esta evidencia no es suficiente para confirmar de manera concluyente que se deba continuar con ese número de componentes.

Por ello, se consultó el resumen que muestra la proporción de varianza acumulada explicada por cada componente, como se ilustra en el siguiente recuadro.

summary(pca)
## Importance of components:
##                          Comp.1    Comp.2    Comp.3     Comp.4     Comp.5
## Standard deviation     1.565342 1.3031915 1.1536776 1.07541621 1.05056523
## Proportion of Variance 0.153253 0.1062201 0.0832452 0.07233417 0.06902976
## Cumulative Proportion  0.153253 0.2594731 0.3427183 0.41505246 0.48408223
##                            Comp.6     Comp.7     Comp.8     Comp.9    Comp.10
## Standard deviation     1.00578831 0.99700796 0.94951116 0.92945469 0.90757599
## Proportion of Variance 0.06327083 0.06217096 0.05638849 0.05403147 0.05151768
## Cumulative Proportion  0.54735305 0.60952401 0.66591251 0.71994398 0.77146166
##                           Comp.11    Comp.12    Comp.13    Comp.14    Comp.15
## Standard deviation     0.85946955 0.83580199 0.79077620 0.75776167 0.72960787
## Proportion of Variance 0.04620099 0.04369152 0.03911087 0.03591332 0.03329426
## Cumulative Proportion  0.81766266 0.86135418 0.90046505 0.93637837 0.96967263
##                           Comp.16
## Standard deviation     0.69634135
## Proportion of Variance 0.03032737
## Cumulative Proportion  1.00000000

De acuerdo con el diagnóstico anterior, las seis primeras componentes principales retienen el 54.73 % de la varianza acumulada explicada. En contraste, al considerar las primeras siete, se alcanza un 60.95 % de varianza. Además, se observa que la desviación estándar del séptimo componente es cercana a 1, lo que refuerza su relevancia.

De esta manera, para no perder mucha información se optó por conservar los primeros 7 eigenvalores.

Para ilustrar las afirmaciones anteriores, los siguientes gráficos facilitan la identificación del punto de corte adecuado para la selección de componentes, así como la la contribución de cada una al modelo. Por un lado, los eigenvalores permiten cuantificar la importancia de cada componente en la explicación de la varianza total. En términos simples, un eigenvalor alto señala que el componente correspondiente recoge una gran proporción de la información estructural de los datos.

Eigenvalores y varianza explicada

fviz_eig(pca, choice = "variance")

fviz_eig(pca, choice = "eigenvalue")

Puntuaciones factoriales

una vez que el ACP ha transformado las variables originales en nuevas dimensiones (componentes), cada individuo obtiene una puntuación que indica qué tan lejos o cerca está de los demás en esa nueva dimensión reducida. Estas puntuaciones permiten comparar observaciones, agruparlas o detectar patrones que no eran visibles en el espacio original.

fviz_pca_ind(pca,
             col.ind ="cos2",
             gradient.cols=c("red", "yellow", "green"),
             repel=FALSE)

El gráfico presenta una representación bidimensional basada en los dos primeros componentes principales. El eje X (Factor 1) representa el primer componente principal, que explica la mayor proporción de varianza de los datos; el eje Y (Factor 2) corresponde al segundo componente principal, que explica la segunda mayor proporción. Ambos ejes suelen etiquetarse con el porcentaje de varianza explicada, por ejemplo: Dim1 (15.3 %).

Cada punto en el gráfico representa una observación (individuo, caso o muestra) del conjunto de datos. Los puntos ubicados cerca del centro (0,0) indican observaciones con baja contribución a ambos componentes, mientras que aquellos más alejados del centro reflejan observaciones influyentes o atípicas, que tienen mayor peso en la definición de los componentes.

En términos prácticos, valores altos de cos² (cercanos a 1) sugieren que la observación está bien representada en el plano factorial, mientras que valores bajos reflejan una representación deficiente.

Gráfico de cargas

En el análisis de componentes principales (ACP), las cargas factoriales constituyen un elemento central para la interpretación de los componentes extraídos. Estas cargas representan los coeficientes de correlación entre las variables originales y los componentes principales. Por lo que, permiten conocer qué tan fuertemente una variable contribuye a un componente determinado y, por tanto, facilitan la construcción de un significado o interpretación para cada uno de los componentes obtenidos.

fviz_pca_var(pca,
             col.var ="contrib",
             gradient.cols=c("red", "yellow", "green"),
             repel=FALSE)

fviz_pca_biplot(pca,
                col.var = "red",
                col.ind = "black")

En el biplot, los ángulos menores a 90° entre vectores indican que las variables están correlacionadas positivamente. Los vectores largos reflejan una alta contribución de la variable a los componentes principales. Además, los puntos (observaciones) que se proyectan en la dirección de un vector específico indican que dichos individuos presentan valores elevados en la variable correspondiente.

Es facil saber que los dos primeros componentes explican aproximadamente el 25.9 % de la variabilidad total en los datos. Esta proporción, si bien no es dominante, permite una visualización de las relaciones entre las variables.

Las variables credit_history, credit_this_bank y account_check_status presentan vectores largos y orientados en direcciones similares, por lo que tienen una alta contribución. Estas variables están asociadas principalmente con el historial crediticio y la relación del solicitante con la institución bancaria.

Por otro lado, las variables como rango_edad, estado_civil, telefonone, housing, job, property y rango_plazos_credito, se encuentran alineadas en una misma dirección. Juntas explican una parte específica de la variabilidad, vinculada con el perfil de edad, estado civil, tipo de vivienda, empleo y duración del crédito.

Matríz de correlaciones

Cuando las variables muestran una fuerte correlación entre sí, indica que comparten información común, permitiendo que puedan ser agrupadas o representadas mediante un número reducido de componentes principales. Dichas correlaciones facilitan que el ACP identifique combinaciones lineales que concentren gran parte de la varianza total del sistema, logrando así una simplificación efectiva y una mejor interpretación de la estructura de los datos originales.

En contraste, cuando las variables están débilmente correlacionadas o son esencialmente independientes, la eficacia del ACP disminuye considerablemente.

x11()
psych::cor.plot(datos_norm)
Mapa correlaciones
Mapa correlaciones

###Determinante El análisis de la matriz de correlaciones reveló un valor de determinante de 0.2206302. Este valor, considerablemente cercano a cero, refleja la existencia de fuertes correlaciones lineales entre las variables consideradas, lo que implica una elevada multicolinealidad. Por lo tanto, se evidencia que las variables comparten una cantidad significativa de información común.

La proximidad del determinante a cero indica que la matriz de correlaciones pueden ser expresadas como combinaciones lineales de otras. Esta condición favorece el uso de técnicas de reducción de dimensionalidad (ACP).

det(cor(datos_norm))
## [1] 0.2206302

Varimax

Se usó la rotación factorial (Varimax) para facilitar la interpretación de estas cargas. Esta técnica busca redistribuir las cargas factoriales de manera que se maximicen las altas y se minimicen las bajas, promoviendo componentes con estructuras más claras y fáciles de interpretar.

varimax <- psych::principal(datos_norm, nfactors = 7 , residuals = FALSE, rotate = "varimax",
                             scores = TRUE, oblique.scores = FALSE, method = "regression",
                             use = "pairwise", cor = "cor", weight = NULL)
varimax
## Principal Components Analysis
## Call: psych::principal(r = datos_norm, nfactors = 7, residuals = FALSE, 
##     rotate = "varimax", scores = TRUE, oblique.scores = FALSE, 
##     method = "regression", use = "pairwise", cor = "cor", weight = NULL)
## Standardized loadings (pattern matrix) based upon correlation matrix
##                              RC1   RC3   RC2   RC4   RC5   RC7   RC6   h2   u2
## account_check_status        0.06  0.11  0.35 -0.60  0.06 -0.05  0.16 0.53 0.47
## credit_history              0.11  0.02  0.80 -0.10 -0.03  0.04  0.15 0.69 0.31
## savings                    -0.08 -0.04  0.12  0.82 -0.07 -0.02  0.08 0.71 0.29
## present_emp_since          -0.51  0.02 -0.06  0.24  0.09 -0.20  0.25 0.43 0.57
## installment_as_income_perc  0.14  0.01 -0.02 -0.01 -0.05  0.87  0.11 0.78 0.22
## other_debtors              -0.03  0.05  0.02  0.15 -0.83  0.08 -0.12 0.73 0.27
## property                    0.42  0.51 -0.18  0.16  0.31 -0.07 -0.02 0.59 0.41
## other_installment_plans    -0.06  0.02  0.01  0.00  0.06  0.07  0.86 0.75 0.25
## housing                     0.71  0.21  0.01  0.12  0.15 -0.09  0.11 0.61 0.39
## credits_this_bank           0.12  0.03  0.82  0.04  0.02 -0.02 -0.13 0.71 0.29
## job                         0.04  0.77  0.04 -0.09 -0.08  0.07  0.11 0.63 0.37
## telephone                   0.03  0.69  0.18 -0.21 -0.03 -0.04  0.03 0.55 0.45
## foreign_worker             -0.12  0.14  0.09  0.07  0.53  0.49 -0.22 0.62 0.38
## estado_civil                0.61  0.12  0.13 -0.04 -0.16  0.15  0.02 0.46 0.54
## rango_plazos_credito        0.04  0.53 -0.14  0.19  0.13  0.08 -0.33 0.47 0.53
## rango_edad                  0.67 -0.09  0.12 -0.12  0.04 -0.03 -0.08 0.50 0.50
##                            com
## account_check_status       1.9
## credit_history             1.2
## savings                    1.1
## present_emp_since          2.4
## installment_as_income_perc 1.1
## other_debtors              1.1
## property                   3.2
## other_installment_plans    1.0
## housing                    1.4
## credits_this_bank          1.1
## job                        1.1
## telephone                  1.4
## foreign_worker             2.7
## estado_civil               1.5
## rango_plazos_credito       2.4
## rango_edad                 1.2
## 
##                        RC1  RC3  RC2  RC4  RC5  RC7  RC6
## SS loadings           1.85 1.71 1.58 1.27 1.15 1.09 1.09
## Proportion Var        0.12 0.11 0.10 0.08 0.07 0.07 0.07
## Cumulative Var        0.12 0.22 0.32 0.40 0.47 0.54 0.61
## Proportion Explained  0.19 0.18 0.16 0.13 0.12 0.11 0.11
## Cumulative Proportion 0.19 0.37 0.53 0.66 0.78 0.89 1.00
## 
## Mean item complexity =  1.6
## Test of the hypothesis that 7 components are sufficient.
## 
## The root mean square of the residuals (RMSR) is  0.09 
##  with the empirical chi square  2643.52  with prob <  0 
## 
## Fit based upon off diagonal values = 0.5

La prueba de hipótesis indica que siete factores son suficientes (p < 0), lo que respalda la aplicación del modelo factorial.

El análisis con rotación Varimax muestra que el modelo tiene una calidad aceptable, aunque puede mejorar. La prueba de suficiencia confirma que los factores son adecuados con un (p < 0.05), pero la prueba chi-cuadrada (2643.52) afirma diferencias entre los datos observados y los esperados, que es común en muestras grandes.

El índice RMSR fue de 0.09, dentro del límite aceptable (< 0.10), pero no ideal (< 0.05), lo que mantiene diferencias moderadas entre las correlaciones reales y las del modelo. El ajuste basado en correlaciones fuera de la diagonal fue de 0.5, mostrando una capacidad media para reflejar las relaciones entre variables, aunque se esperaría un valor más cercano a 1. La complejidad promedio de los ítems fue de 1.6, por lo que algunos ítems están influidos por más de un factor.

Pesos

Adicionalmente, se calcularon los pesos de cada uno de los componentes principales. Tal como se detalló en el apartado del gráfico de cargas, las agrupaciones observadas permitieron identificar qué variables aportan mayor información a los nuevos componentes, lo que facilita su interpretación y comprensión dentro del modelo reducido.

varimax$weights[,1]
##       account_check_status             credit_history 
##               -0.043153644               -0.016562305 
##                    savings          present_emp_since 
##                0.006862865               -0.264696631 
## installment_as_income_perc              other_debtors 
##                0.042575996               -0.016601984 
##                   property    other_installment_plans 
##                0.209478554                0.009954507 
##                    housing          credits_this_bank 
##                0.420240566               -0.019832820 
##                        job                  telephone 
##               -0.084541090               -0.106356033 
##             foreign_worker               estado_civil 
##               -0.149003926                0.333597419 
##       rango_plazos_credito                 rango_edad 
##               -0.049767371                0.395286637
varimax$weights[,2]
##       account_check_status             credit_history 
##                0.050436435               -0.006812205 
##                    savings          present_emp_since 
##                0.003265992                0.088784221 
## installment_as_income_perc              other_debtors 
##               -0.038796096                0.108575407 
##                   property    other_installment_plans 
##                0.243884089                0.024425991 
##                    housing          credits_this_bank 
##                0.023858305               -0.005566310 
##                        job                  telephone 
##                0.487158664                0.430372559 
##             foreign_worker               estado_civil 
##                0.035480094                0.001476031 
##       rango_plazos_credito                 rango_edad 
##                0.313922215               -0.158403613
varimax$weights[,3]
##       account_check_status             credit_history 
##                0.148530359                0.509310917 
##                    savings          present_emp_since 
##                0.189454861                0.036002066 
## installment_as_income_perc              other_debtors 
##               -0.054778736                0.000708822 
##                   property    other_installment_plans 
##               -0.127340624               -0.038151590 
##                    housing          credits_this_bank 
##               -0.050979315                0.562412840 
##                        job                  telephone 
##               -0.008467389                0.085658152 
##             foreign_worker               estado_civil 
##                0.113455918                0.009424489 
##       rango_plazos_credito                 rango_edad 
##               -0.051430905                0.004286030
varimax$weights[,4]
##       account_check_status             credit_history 
##               -0.434161496                0.038362554 
##                    savings          present_emp_since 
##                0.693483684                0.179371344 
## installment_as_income_perc              other_debtors 
##               -0.002309524                0.086487435 
##                   property    other_installment_plans 
##                0.159143321                0.047643503 
##                    housing          credits_this_bank 
##                0.162879414                0.137883688 
##                        job                  telephone 
##               -0.052411315               -0.141721982 
##             foreign_worker               estado_civil 
##                0.061196764                0.022528372 
##       rango_plazos_credito                 rango_edad 
##                0.133471107               -0.041846031
varimax$weights[,5]
##       account_check_status             credit_history 
##                0.039576218                0.003746581 
##                    savings          present_emp_since 
##               -0.024222832                0.102215169 
## installment_as_income_perc              other_debtors 
##               -0.098034280               -0.736545327 
##                   property    other_installment_plans 
##                0.217661896                0.041184871 
##                    housing          credits_this_bank 
##                0.107891259                0.057009369 
##                        job                  telephone 
##               -0.154047858               -0.087374018 
##             foreign_worker               estado_civil 
##                0.444856228               -0.164644752 
##       rango_plazos_credito                 rango_edad 
##                0.059207498                0.039759227
varimax$weights[,6]
##       account_check_status             credit_history 
##                -0.05694965                 0.02454510 
##                    savings          present_emp_since 
##                -0.02053151                -0.15199250 
## installment_as_income_perc              other_debtors 
##                 0.80393447                 0.11240654 
##                   property    other_installment_plans 
##                -0.12412133                 0.08751731 
##                    housing          credits_this_bank 
##                -0.13428956                -0.05014173 
##                        job                  telephone 
##                 0.04441731                -0.06324110 
##             foreign_worker               estado_civil 
##                 0.41976009                 0.10930063 
##       rango_plazos_credito                 rango_edad 
##                 0.03888899                -0.06482996
varimax$weights[,7]
##       account_check_status             credit_history 
##                 0.09221756                 0.09474494 
##                    savings          present_emp_since 
##                 0.10642489                 0.21099607 
## installment_as_income_perc              other_debtors 
##                 0.13594653                -0.10108848 
##                   property    other_installment_plans 
##                 0.03545273                 0.80457779 
##                    housing          credits_this_bank 
##                 0.15733368                -0.16899234 
##                        job                  telephone 
##                 0.10636310                 0.01403412 
##             foreign_worker               estado_civil 
##                -0.20649330                 0.05729896 
##       rango_plazos_credito                 rango_edad 
##                -0.27666652                -0.04766594

Los valores de carga cercanos a 1 o -1 indican una fuerte relación entre la variable y el componente principal. Cuando los pesos son negativos, reflejan una relación inversa con el factor; por el contrario, valores cercanos a cero indican que la variable no contribuye de manera significativa a ese componente.

Scores a data frame

Para continuar con este estudio, se agregaron los scores obtenidos tras aplicar la rotación Varimax para contruir la nueva base de los modelos propuestos.

varimax$scores
scores <- as.data.frame(varimax$scores)

Agregamos columna “default”

Dado que durante el proceso de normalización se eliminó la columna default, la cual indica si un cliente tiene un buen perfil (0: Good customer, 1: Bad customer), esta fue reincorporada a la base de datos “scores” para poder utilizarla en los análisis posteriores.

scores$default <- datos$default
base_scores <- read.csv("Scores_Credit_risk2.csv")
base_scores

De este modo, los componentes principales se declararon como nuevas variables en la base de datos, asignándoles los nombres “RC1”, “RC3”, “RC2”, “RC4”, “RC5”, “RC7” y “RC6”, según el orden en que fueron seleccionados para el análisis.

Propuesta del Modelo

Regresión logística

Varios autores mencionan que se trata de un modelo estadístico utilizado para predecir la probabilidad o clasificar un evento binario en función de una o más variables independientes. Este tipo de regresión pertenece a los Modelos Lineales Generalizados (MLG) y tiene como finalidad modelar la relación entre una o más variables independientes (categóricas o continuas) y una variable dependiente dicotómica.

Para ello, se utilizó la función glm, especificando la familia binomial, ya que la variable default actuó como variable dicotomica. Los resultados obtenidos a partir de este modelo se presentan a continuación.

modelo_logit1 <- glm( default ~ RC1 + RC2 +  RC3 + RC4 + RC5 + RC6 +RC7,
                     data = scores, family = binomial)
modelo_logit1
## 
## Call:  glm(formula = default ~ RC1 + RC2 + RC3 + RC4 + RC5 + RC6 + RC7, 
##     family = binomial, data = scores)
## 
## Coefficients:
## (Intercept)          RC1          RC2          RC3          RC4          RC5  
##     -0.0548      -0.4183      -0.8222      -0.1247       0.9536       0.4594  
##         RC6          RC7  
##     -0.7136       0.1672  
## 
## Degrees of Freedom: 1399 Total (i.e. Null);  1392 Residual
## Null Deviance:       1941 
## Residual Deviance: 1431  AIC: 1447
summary(modelo_logit1)
## 
## Call:
## glm(formula = default ~ RC1 + RC2 + RC3 + RC4 + RC5 + RC6 + RC7, 
##     family = binomial, data = scores)
## 
## Coefficients:
##             Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -0.05480    0.06533  -0.839   0.4016    
## RC1         -0.41826    0.06607  -6.331 2.44e-10 ***
## RC2         -0.82222    0.07126 -11.538  < 2e-16 ***
## RC3         -0.12466    0.06465  -1.928   0.0538 .  
## RC4          0.95363    0.07436  12.825  < 2e-16 ***
## RC5          0.45938    0.07334   6.264 3.76e-10 ***
## RC6         -0.71362    0.07057 -10.112  < 2e-16 ***
## RC7          0.16720    0.06991   2.391   0.0168 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1940.8  on 1399  degrees of freedom
## Residual deviance: 1431.0  on 1392  degrees of freedom
## AIC: 1447
## 
## Number of Fisher Scoring iterations: 5

Prueba de razón de verosimilitud

modelo_nulo <- glm(default ~ 1, data = scores, family = binomial)
prueba_lrt <- anova(modelo_nulo, modelo_logit1, test = "Chisq")
prueba_lrt

El modelo de regresión logística reveló que los componentes RC1, RC2 y RC6 tienen un efecto significativo, con coeficientes negativos entre -0.418 y -0.822 (todos con p < 0.001), destacando RC2 como el componente con mayor influencia. En cambio, los componentes RC4 y RC5 se asocian con un mayor riesgo de default, mostrando coeficientes positivos de 0.954 y 0.459 respectivamente (p < 0.001), siendo RC4 el principal factor de riesgo identificado. El componente RC7 también incrementa el riesgo, aunque de forma más moderada (coeficiente = 0.167, p = 0.017). Por su parte, RC3 es el único que no presenta significancia estadística pero se mantiene cerca de un ( p=0.05).

P-seudo R2 de Mcfadden

Posteriormente, se calculó el pseudo R² de McFadden, cuyo valor se ubicó entre 0.2 y 0.4. Este rango indica un buen nivel de ajuste del modelo, lo que refuerza la validez de los resultados obtenidos a partir de la regresión logística.

logLik_nulo <-  logLik(modelo_nulo)
logLik_modelo <- logLik(modelo_logit1)
pseudo_r2_mcfadden <- 1-(logLik_modelo / logLik_nulo)
pseudo_r2_mcfadden
## 'log Lik.' 0.2626837 (df=8)

Curva ROC

El área bajo la curva (AUC) fue de 0.8281, lo que indica que el modelo presenta un buen poder predictivo y una adecuada capacidad de clasificación entre clientes buenos y malos. Este valor hace referencia a que el modelo discrimina correctamente en aproximadamente el 83 % de los casos.

prob_pred <- predict(modelo_logit1, type = "response")
roc_obj <- roc(scores$default, prob_pred)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
roc_obj
## 
## Call:
## roc.default(response = scores$default, predictor = prob_pred)
## 
## Data: prob_pred in 700 controls (scores$default 0) < 700 cases (scores$default 1).
## Area under the curve: 0.8281

Para una mejor comprensión, véase el siguiente gráfico.

plot(roc_obj, col="red", main = "Curva ROC del Modelo Logit")

Tabla de confusión

Finalmente la tabla de confusión separa las clasifiaciones según la especificidad y sensibilidad a los datos de entrenamiento

pred_clase <- ifelse(prob_pred > 0.5, 1, 0)
tabla_confusion <- table(Real = scores$default, prediccion = pred_clase)
tabla_confusion
##     prediccion
## Real   0   1
##    0 506 194
##    1 154 546

La tabla de confusión identifica correctamente el 82% de los casos positivos (sensibilidad) y el 87% de los negativos (especificidad). Sin embargo, comete algunos errores: clasifica erróneamente como positivos el 13% de casos que en realidad son negativos (falsos positivos), y no detecta el 18% de verdaderos positivos (falsos negativos). El equilibrio entre sensibilidad y especificidad arroja que el modelo funciona razonablemente bien, aunque podría optimizarse dependiendo del contexto.

Random Forest

En este trabajo se tomó la base de datos una vez realizado el análisis de componentes principales, formado por 1 400 clientes y siete indicadores numéricos de comportamiento crediticio (RC1–RC7), para construir un modelo que anticipe el impago reflejado en la variable binaria “default”. Primero se depuró la hoja eliminando una columna-índice que no sumaba al modelo y se transformó “default” en factor, paso importante para abordar el problema como clasificación y no como regresión. A continuación los datos se dividieron, de manera estratificada, en un 70 % de entrenamiento y un 30 % de prueba; esto garantiza que el balance 50-50 entre morosos y no morosos esté representado en ambos subconjuntos.

set.seed(123)         

df <- read.csv("df_banco_final.csv")
df$default <- factor(df$default,
                     levels = c(0, 1),
                     labels  = c("No", "Yes"))

# División entrenamiento / prueba (70-30)
idx  <- createDataPartition(df$default, p = 0.70, list = FALSE)
train <- df[idx, ]
test  <- df[-idx, ]

Entrenamiento de Random Forest

El algoritmo elegido fue un Random Forest, es decir, un ensamble de 500 árboles CART (Classification And Regression Trees).

Cada árbol se entrena sobre una muestra bootstrap (aproximadamente el 63 % de los registros originales seleccionados con reemplazo) y, en cada nodo, examina solo √7 ≈ 2 variables tomadas al azar; esta doble aleatorización (filas y variables) reduce la correlación entre árboles y, con ella, el riesgo de sobre-ajuste. Los árboles son de tipo clasificación (no regresión) y emplean el índice de Gini como criterio de pureza: las particiones sucesivas buscan la mayor caída de impureza posible.

Al no imponer un límite de profundidad, los árboles crecen hasta que un nodo es puro o queda una sola observación, alcanzando en la práctica entre seis y un conjunto de veinte niveles; el bosque compensa la alta varianza inherente a estos árboles profundos mediante el promedio de sus votaciones. Entre tanto, las observaciones que no entran en la muestra bootstrap de un árbol funcionan como casos out-of-bag y proporcionan una estimación interna del error sin tocar el conjunto de prueba.

mtry ≈ √p para clasificación; aquí p = 7 predictores

rf_model <- randomForest(default ~ .,
                         data      = train,
                         ntree     = 500,       # nº de árboles
                         mtry      = floor(sqrt(ncol(train) - 1)),
                         importance = TRUE)

rf_model      # Error OOB estimado
## 
## Call:
##  randomForest(formula = default ~ ., data = train, ntree = 500,      mtry = floor(sqrt(ncol(train) - 1)), importance = TRUE) 
##                Type of random forest: classification
##                      Number of trees: 500
## No. of variables tried at each split: 4
## 
##         OOB estimate of  error rate: 19.49%
## Confusion matrix:
##      No Yes class.error
## No  388 102   0.2081633
## Yes  89 401   0.1816327

Predicción y métricas

Probabilidades y clases

prob_test  <- predict(rf_model, test, type = "prob")[ , "Yes"]
pred_class <- factor(ifelse(prob_test > 0.50, "Yes", "No"),
                     levels = c("No", "Yes"))

Matriz de confusión

confusionMatrix(pred_class, test$default, positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  No Yes
##        No  165  27
##        Yes  45 183
##                                           
##                Accuracy : 0.8286          
##                  95% CI : (0.7891, 0.8634)
##     No Information Rate : 0.5             
##     P-Value [Acc > NIR] : < 2e-16         
##                                           
##                   Kappa : 0.6571          
##                                           
##  Mcnemar's Test P-Value : 0.04513         
##                                           
##             Sensitivity : 0.8714          
##             Specificity : 0.7857          
##          Pos Pred Value : 0.8026          
##          Neg Pred Value : 0.8594          
##              Prevalence : 0.5000          
##          Detection Rate : 0.4357          
##    Detection Prevalence : 0.5429          
##       Balanced Accuracy : 0.8286          
##                                           
##        'Positive' Class : Yes             
## 

Curva ROC y AUC

Al evaluar el modelo sobre las 420 observaciones restantes, se obtuvo una exactitud global del 76,9 % y, lo más revelador, una AUC (o curva ROC) de 0.895: el modelo discrimina satisfactoriamente entre clientes morosos y solventes. En términos de la matriz de confusión, capturó la mayoría de los impagos (sensibilidad) y se equivocó con una minoría de sobresaltos falsos, un equilibrio razonable cuando la prioridad es no dejar morosos sin señalar. El análisis de importancia de variables (medido como disminución media de Gini) mostró que RC4, RC2 y RC7 concentran más de la mitad del poder explicativo del bosque, sugiriendo que esos indicadores resumen la mayor parte del riesgo crediticio latente.

roc_obj <- roc(test$default, prob_test, levels = c("No", "Yes"))
## Setting direction: controls < cases
auc(roc_obj)            # Valor AUC
## Area under the curve: 0.8948
plot(roc_obj, print.auc = TRUE, main = "ROC – Random Forest")

Importancia de variables

varImpPlot(rf_model, main = "Importancia de variables (Gini)")

Conclusiones

El análisis exploratorio ha sido clave para identificar la estructura y comportamiento del dataset. Se confirmó la ausencia de datos faltantes, y se identificaron valores atípicos relevantes que podrían representar perfiles de riesgo o comportamientos no comunes. Las visualizaciones complementan las estadísticas al evidenciar patrones complejos como correlaciones cruzadas, agrupamientos implícitos y sesgos de clase. Toda esta información es fundamental para preparar el terreno hacia técnicas de modelado más avanzadas como PCA, regresión logística o árboles de decisión.

En síntesis, el proceso conformó un modelo competente: un bosque de 500 árboles CART profundos, alimentados por bootstrapping y limitados a dos características por nodo, capaz de identificar cuatro de cada cinco impagos potenciales y de ofrecer un criterio numérico (probabilidad) con gran capacidad discriminante. Las próximas mejoras podrían pasar por ajustar el umbral de decisión al costo real de falsos positivos y falsos negativos, afinar hiperparámetros como mtry o min_samples_leaf, e incorporar explicaciones locales (SHAP) que traduzcan la lógica del bosque a reglas comprensibles para analistas y reguladores.

Del mismo modo, el modelo de regresión logística desarrollado demostró un buen desempeño predictivo, con un valor de AUC-ROC de 0.83, lo que indica una sólida capacidad para discriminar entre casos de default y no default. Los coeficientes estimados señalan que el componente RC4 (β = 0.95, p < 0.001) representa el principal factor de riesgo, mientras que RC2 (β = -0.82, p < 0.001) y RC6 (β = -0.71, p < 0.001) actúan como factores protectores frente al incumplimiento. Aunque RC3 presentó un efecto marginalmente significativo (p = 0.054), su contribución al modelo fue limitada.

La reducción sustancial en la devianza, de 1940.8 a 1431.0, confirma que el modelo logra explicar una proporción importante de la variabilidad en los datos. Este ajuste se ve respaldado por un valor de AIC de 1447, lo que sugiere un equilibrio adecuado entre capacidad explicativa y simplicidad del modelo. No obstante, la presencia de devianza residual indica que aún existe variabilidad no explicada, posiblemente atribuible a factores no considerados en el modelo actual.

Referencias

Castaño, H. F., & Ramírez, F. O. P. (2005). El modelo logístico: una herramienta estadística para evaluar el riesgo de crédito. Revista Ingenierías Universidad de Medellín, 4(6), 55-75. https://www.redalyc.org/pdf/750/75040605.pdf

Pardo Carrillo, O. S. (2020). Perfil de riesgo de crédito para una coopertiva en Villavicencio a partir de un modelo logit. Revista Universidad y Empresa, 22(38), 237-256. http://www.scielo.org.co/scielo.php?pid=S0124-46392020000100237&script=sci_arttext