Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Proyecto Integrador — Lending Club Loan Data

Nombres: Juan Camilo Oñoro Araujo - 200177329 y María Carolina Cantillo Orozco - 200179105

Fecha: 27/09/2026

MACHINE LEARNING

EDA

Parte 1. Importar librerías necesarias y cionfuguraciones

pandas 2.1.0 | numpy 1.25.2

Parte 2. Ruta de acceso al archivo y tipos de datos

Archivo : E:\Documents\MachineLearningUN\accepted_2007_to_2018Q4.csv\accepted_2007_to_2018Q4.csv
Tamaño  : 1.68 GB en disco

Parte 3. Carga completa del dataset de préstamos

Cargando dataset completo con pandas...
Filas           : 2,260,701
Columnas        : 151
Memoria en RAM  : 2668 MB
Tiempo de carga : 36.7 s

Primeras fila

Loading...

Última fila

Loading...

Opcional, carga con PySpark

Spark  → filas: 2,260,701 | columnas: 151 | tiempo: 7.6 s
pandas → filas: 2,260,701 | columnas: 151 | tiempo: 36.7 s

Parte 4. Filtro de filas que no son información de préstamos

Filas cuyo id no es numérico: 33
Loading...
Filas antes : 2,260,701
Filas ahora : 2,260,668  (eliminadas: 33)
¿id único?  : True
Loading...

Dimensiones y tipos de datos obtendidos leyendo después del filtro:

DIMENSIONES: 2,260,668 filas × 151 columnas

DISTRIBUCIÓN DE TIPOS DE DATOS
  float32     :  113 columnas
  category    :   25 columnas
  object      :   12 columnas
  Int64       :    1 columnas

Parte 5. info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2260668 entries, 0 to 2260667
Data columns (total 151 columns):
 #    Column                                      Non-Null Count    Dtype   
---   ------                                      --------------    -----   
 0    id                                          2260668 non-null  Int64   
 1    member_id                                   0 non-null        float32 
 2    loan_amnt                                   2260668 non-null  float32 
 3    funded_amnt                                 2260668 non-null  float32 
 4    funded_amnt_inv                             2260668 non-null  float32 
 5    term                                        2260668 non-null  category
 6    int_rate                                    2260668 non-null  float32 
 7    installment                                 2260668 non-null  float32 
 8    grade                                       2260668 non-null  category
 9    sub_grade                                   2260668 non-null  category
 10   emp_title                                   2093699 non-null  object  
 11   emp_length                                  2113761 non-null  category
 12   home_ownership                              2260668 non-null  category
 13   annual_inc                                  2260664 non-null  float32 
 14   verification_status                         2260668 non-null  category
 15   issue_d                                     2260668 non-null  category
 16   loan_status                                 2260668 non-null  category
 17   pymnt_plan                                  2260668 non-null  category
 18   url                                         2260668 non-null  object  
 19   desc                                        126065 non-null   object  
 20   purpose                                     2260668 non-null  category
 21   title                                       2237342 non-null  object  
 22   zip_code                                    2260667 non-null  object  
 23   addr_state                                  2260668 non-null  category
 24   dti                                         2258957 non-null  float32 
 25   delinq_2yrs                                 2260639 non-null  float32 
 26   earliest_cr_line                            2260639 non-null  object  
 27   fico_range_low                              2260668 non-null  float32 
 28   fico_range_high                             2260668 non-null  float32 
 29   inq_last_6mths                              2260638 non-null  float32 
 30   mths_since_last_delinq                      1102166 non-null  float32 
 31   mths_since_last_record                      359156 non-null   float32 
 32   open_acc                                    2260639 non-null  float32 
 33   pub_rec                                     2260639 non-null  float32 
 34   revol_bal                                   2260668 non-null  float32 
 35   revol_util                                  2258866 non-null  float32 
 36   total_acc                                   2260639 non-null  float32 
 37   initial_list_status                         2260668 non-null  category
 38   out_prncp                                   2260668 non-null  float32 
 39   out_prncp_inv                               2260668 non-null  float32 
 40   total_pymnt                                 2260668 non-null  float32 
 41   total_pymnt_inv                             2260668 non-null  float32 
 42   total_rec_prncp                             2260668 non-null  float32 
 43   total_rec_int                               2260668 non-null  float32 
 44   total_rec_late_fee                          2260668 non-null  float32 
 45   recoveries                                  2260668 non-null  float32 
 46   collection_recovery_fee                     2260668 non-null  float32 
 47   last_pymnt_d                                2258241 non-null  category
 48   last_pymnt_amnt                             2260668 non-null  float32 
 49   next_pymnt_d                                915358 non-null   category
 50   last_credit_pull_d                          2260596 non-null  category
 51   last_fico_range_high                        2260668 non-null  float32 
 52   last_fico_range_low                         2260668 non-null  float32 
 53   collections_12_mths_ex_med                  2260523 non-null  float32 
 54   mths_since_last_major_derog                 580775 non-null   float32 
 55   policy_code                                 2260668 non-null  float32 
 56   application_type                            2260668 non-null  category
 57   annual_inc_joint                            120710 non-null   float32 
 58   dti_joint                                   120706 non-null   float32 
 59   verification_status_joint                   115730 non-null   category
 60   acc_now_delinq                              2260639 non-null  float32 
 61   tot_coll_amt                                2190392 non-null  float32 
 62   tot_cur_bal                                 2190392 non-null  float32 
 63   open_acc_6m                                 1394538 non-null  float32 
 64   open_act_il                                 1394539 non-null  float32 
 65   open_il_12m                                 1394539 non-null  float32 
 66   open_il_24m                                 1394539 non-null  float32 
 67   mths_since_rcnt_il                          1350744 non-null  float32 
 68   total_bal_il                                1394539 non-null  float32 
 69   il_util                                     1191818 non-null  float32 
 70   open_rv_12m                                 1394539 non-null  float32 
 71   open_rv_24m                                 1394539 non-null  float32 
 72   max_bal_bc                                  1394539 non-null  float32 
 73   all_util                                    1394320 non-null  float32 
 74   total_rev_hi_lim                            2190392 non-null  float32 
 75   inq_fi                                      1394539 non-null  float32 
 76   total_cu_tl                                 1394538 non-null  float32 
 77   inq_last_12m                                1394538 non-null  float32 
 78   acc_open_past_24mths                        2210638 non-null  float32 
 79   avg_cur_bal                                 2190322 non-null  float32 
 80   bc_open_to_buy                              2185733 non-null  float32 
 81   bc_util                                     2184597 non-null  float32 
 82   chargeoff_within_12_mths                    2260523 non-null  float32 
 83   delinq_amnt                                 2260639 non-null  float32 
 84   mo_sin_old_il_acct                          2121597 non-null  float32 
 85   mo_sin_old_rev_tl_op                        2190391 non-null  float32 
 86   mo_sin_rcnt_rev_tl_op                       2190391 non-null  float32 
 87   mo_sin_rcnt_tl                              2190392 non-null  float32 
 88   mort_acc                                    2210638 non-null  float32 
 89   mths_since_recent_bc                        2187256 non-null  float32 
 90   mths_since_recent_bc_dlq                    519701 non-null   float32 
 91   mths_since_recent_inq                       1965233 non-null  float32 
 92   mths_since_recent_revol_delinq              740359 non-null   float32 
 93   num_accts_ever_120_pd                       2190392 non-null  float32 
 94   num_actv_bc_tl                              2190392 non-null  float32 
 95   num_actv_rev_tl                             2190392 non-null  float32 
 96   num_bc_sats                                 2202078 non-null  float32 
 97   num_bc_tl                                   2190392 non-null  float32 
 98   num_il_tl                                   2190392 non-null  float32 
 99   num_op_rev_tl                               2190392 non-null  float32 
 100  num_rev_accts                               2190391 non-null  float32 
 101  num_rev_tl_bal_gt_0                         2190392 non-null  float32 
 102  num_sats                                    2202078 non-null  float32 
 103  num_tl_120dpd_2m                            2107011 non-null  float32 
 104  num_tl_30dpd                                2190392 non-null  float32 
 105  num_tl_90g_dpd_24m                          2190392 non-null  float32 
 106  num_tl_op_past_12m                          2190392 non-null  float32 
 107  pct_tl_nvr_dlq                              2190237 non-null  float32 
 108  percent_bc_gt_75                            2185289 non-null  float32 
 109  pub_rec_bankruptcies                        2259303 non-null  float32 
 110  tax_liens                                   2260563 non-null  float32 
 111  tot_hi_cred_lim                             2190392 non-null  float32 
 112  total_bal_ex_mort                           2210638 non-null  float32 
 113  total_bc_limit                              2210638 non-null  float32 
 114  total_il_high_credit_limit                  2190392 non-null  float32 
 115  revol_bal_joint                             108020 non-null   float32 
 116  sec_app_fico_range_low                      108021 non-null   float32 
 117  sec_app_fico_range_high                     108021 non-null   float32 
 118  sec_app_earliest_cr_line                    108021 non-null   object  
 119  sec_app_inq_last_6mths                      108021 non-null   float32 
 120  sec_app_mort_acc                            108021 non-null   float32 
 121  sec_app_open_acc                            108021 non-null   float32 
 122  sec_app_revol_util                          106184 non-null   float32 
 123  sec_app_open_act_il                         108021 non-null   float32 
 124  sec_app_num_rev_accts                       108021 non-null   float32 
 125  sec_app_chargeoff_within_12_mths            108021 non-null   float32 
 126  sec_app_collections_12_mths_ex_med          108021 non-null   float32 
 127  sec_app_mths_since_last_major_derog         35942 non-null    float32 
 128  hardship_flag                               2260668 non-null  category
 129  hardship_type                               10917 non-null    category
 130  hardship_reason                             10917 non-null    category
 131  hardship_status                             10917 non-null    category
 132  deferral_term                               10917 non-null    float32 
 133  hardship_amount                             10917 non-null    float32 
 134  hardship_start_date                         10917 non-null    object  
 135  hardship_end_date                           10917 non-null    object  
 136  payment_plan_start_date                     10917 non-null    object  
 137  hardship_length                             10917 non-null    float32 
 138  hardship_dpd                                10917 non-null    float32 
 139  hardship_loan_status                        10917 non-null    category
 140  orig_projected_additional_accrued_interest  8651 non-null     float32 
 141  hardship_payoff_balance_amount              10917 non-null    float32 
 142  hardship_last_payment_amount                10917 non-null    float32 
 143  disbursement_method                         2260668 non-null  category
 144  debt_settlement_flag                        2260668 non-null  category
 145  debt_settlement_flag_date                   34246 non-null    object  
 146  settlement_status                           34246 non-null    category
 147  settlement_date                             34246 non-null    object  
 148  settlement_amount                           34246 non-null    float32 
 149  settlement_percentage                       34246 non-null    float32 
 150  settlement_term                             34246 non-null    float32 
dtypes: Int64(1), category(25), float32(113), object(12)
memory usage: 1.2+ GB

Se observa de que hay algunas columnas con 0 no nulos, como memeber_id. Hay otras que les fatan datos como a desc settlement_percenage y settlement term.

Parte 6. Resumen de nulos

Columnas sin nulos           : 38
Columnas con > 70 % de nulos : 41
Columnas 100 % nulas         : 1
Celdas nulas totales         : 31.78 %
Loading...

Hay que asignar un tratamiento a las columnas con datos nulos según su porcentaje. Si una columna supera el 70 % de nulos se puede eliminar, porque entrenar un modelo con una proporción tan alta de datos imputados no es recomendable. La tabla muestra las 30 columnas con más nulos.

Parte 7. Descripción de variables numéricas

Loading...

La tabla muestra las estadísticas básicas de las 112 variables numéricas. Se excluyeron id y member_id: son identificadores (y member_id está vacía), así que sus estadísticas no tienen significado. Destacan policy_code, constante (siempre 1), que no aporta información y se descartará, y varios valores imposibles que se analizan en la Parte 11: dti entre −1 y 999, annual_inc = 0 y revol_util de hasta 892 %.

Parte 8. Descripción de variables categóricas y de texto

Loading...

El plazo (term) tiene dos valores: 36 meses, el más frecuente (71 %), y 60 meses. Los préstamos se emitieron entre junio de 2007 y diciembre de 2018 (issue_d); las fechas de 2019 que aparecen corresponden a pagos posteriores (last_pymnt_d, next_pymnt_d), no a nuevos préstamos. Las categorías más frecuentes son: grado B, propósito debt_consolidation, vivienda MORTGAGE y “10+ years” de antigüedad laboral. emp_title tiene más de 500,000 valores distintos, lo que confirma que es texto libre.

A. Variables Numéricas

Estilo de gráficas

Parte 9. Convertir emp length a número y elegir las variables clave

El enunciado cuenta emp_length como numérica, pero viene como texto (“10+ years”, “< 1 year”). Se crea emp_length_num, que va de 0 a 10.

Loading...

Parte 10. Estadísticos descriptivos e IQR para las variables numéricas

Se calcula para las 113 variables numéricas y luego se muestran las 12 clave.

Estas 12 variables incluyen las que menciona el enunciado (loan_amnt, int_rate, annual_inc, dti, fico_range_high y emp_length, convertida a emp_length_num) y otras 6 clásicas del riesgo de crédito (installment, revol_util, revol_bal, open_acc, total_acc y pub_rec). Todas se conocen antes de aprobar el crédito, así que no hay fuga de información. Once de ellas tienen menos del 1 % de nulos; emp_length_num tiene 6.5 %. Las estadísticas de las 113 variables numéricas se calculan en la tabla completa (resumen_num).

Esto se verificará a la hora de hacer la correlación r con default. No se ha filtrado o muestrado nada del dataset hasta ahora.

Variables numéricas analizadas: 113
Loading...

Parte 11. Outliers

Loading...
Variables con IQR = 0 (21): cualquier valor distinto de la mediana cuenta como outlier.
['delinq_2yrs', 'pub_rec', 'total_rec_late_fee', 'recoveries', 'collection_recovery_fee', 'collections_12_mths_ex_med', 'policy_code', 'acc_now_delinq', 'tot_coll_amt', 'chargeoff_within_12_mths', 'delinq_amnt', 'num_accts_ever_120_pd', 'num_tl_120dpd_2m', 'num_tl_30dpd', 'num_tl_90g_dpd_24m', 'pub_rec_bankruptcies', 'tax_liens', 'sec_app_chargeoff_within_12_mths', 'sec_app_collections_12_mths_ex_med', 'deferral_term', 'hardship_length']

Lo que sale: pub_rec tiene 15.8 % de outliers porque su IQR es 0 (casi todos valen 0), así que en este caso el método no sirve. Hay valores imposibles: dti va de −1 a 999, annual_inc va de 0 a 110 millones y revol_util llega a 892 %.

Parte 12. Histogramas

Para que la forma se observe con claridad, se omite el 0.5% de cada extremo. Los extremos completos se ven en los box plots

<Figure size 1540x1430 with 12 Axes>

Se observa que:

Los montos más solicitados son 10,000 USD (8.3 % de los préstamos), 20,000 USD (5.8 %) y 15,000 USD (5.5 %): predominan los montos redondos. El máximo permitido es 40,000 USD.

La mayor parte de los créditos tienen tasas entre el 7% y el 15%.

La cuota preferida es de alrededor de 370 dólares.

Más del 90% de las personas que solicitan ganan entre 20.000 y 150.000 dólares al año.

Más del 50% de las personas tienen cupo usado de sus tarjetas entre 30 y 70%.

Parte 13. Box plots (los que tienen asimetría mayor a 3 se muestran en escala log porque si no quedaría aplastada)

<Figure size 1540x1210 with 12 Axes>

La mayor proporción de outliers está en pub_rec (registros públicos negativos). Esto ocurre porque casi todos los valores son 0 (Q1 = Q3 = 0), así que el método IQR marca como outlier cualquier registro ≥ 1; en realidad indica que la mayoría de los solicitantes no tiene antecedentes negativos. En loan_amnt se confirma que el 50 % central de los préstamos está entre 8,000 y 20,000 USD. La cuota mensual (installment) tiene una mediana de 378 USD y una media de 446 USD, coherente con el histograma. annual_inc, dti y revol_bal muestran outliers extremos incluso en escala logarítmica.

Parte 14. Normalidad y transformaciones (Shapiro-Wilk solo admite hasta 5,000 datos, así que se usa la prueba de D’Agostino-Pearson, stats.normaltest, con los datos completos)

Variables evaluadas: 113
Loading...
Loading...

Se evaluó la normalidad de las 113 variables numéricas con la prueba de D’Agostino-Pearson sobre los datos completos. Ninguna es normal (p ≈ 0 en las 110 evaluables). Con más de 2 millones de observaciones, la prueba detecta hasta la desviación más pequeña, así que la decisión de transformar se basa en la asimetría.

84 variables tienen asimetría fuerte (|asimetría| ≥ 1). Las más extremas son tot_coll_amt (852), annual_inc (494), delinq_amnt (103) y dti (29). Como la mayoría contiene ceros (conteos y montos), se recomienda log1p o Yeo-Johnson; Box-Cox solo aplica a las 11 estrictamente positivas. 19 variables son aproximadamente simétricas y no requieren transformación, entre ellas revol_util, bc_util, emp_length_num y mths_since_last_delinq. La transformación es relevante para los modelos sensibles a la escala y a la forma de la distribución (LogisticRegression, LinearSVC, NaiveBayes); los modelos basados en árboles no la necesitan

Ejemplo del efecto de la transformación log en annual_inc:

<Figure size 1320x440 with 2 Axes>

Parte 15. Tasa de faltantes y umbral del 30%

Loading...

Variables por encima del umbral de 30 %:
Loading...

Las variables con más de 30 % de nulos no se imputan de forma simple. La decisión final (Parte 38) es: más de 70 % → eliminar; entre 30 % y 70 % → imputar con la mediana y agregar un indicador de faltante. En las numéricas se usa la mediana y no la moda, por la asimetría (Parte 14).

Gráfica resumen

<Figure size 990x495 with 1 Axes>

B. Variables categóricas

Parte 16. Frecuencia absoluta y relativa

Loading...
Loading...

hardship_type  (1 categorías)
Loading...

term  (2 categorías)  ← variable clave
Loading...

pymnt_plan  (2 categorías)
Loading...

initial_list_status  (2 categorías)
Loading...

application_type  (2 categorías)  ← variable clave
Loading...

hardship_flag  (2 categorías)
Loading...

disbursement_method  (2 categorías)
Loading...

debt_settlement_flag  (2 categorías)
Loading...

verification_status  (3 categorías)  ← variable clave
Loading...

verification_status_joint  (3 categorías)
Loading...

hardship_status  (3 categorías)
Loading...

settlement_status  (3 categorías)
Loading...

hardship_loan_status  (5 categorías)
Loading...

home_ownership  (6 categorías)  ← variable clave
Loading...

grade  (7 categorías)  ← variable clave
Loading...

loan_status  (9 categorías)
Loading...

hardship_reason  (9 categorías)
Loading...

emp_length  (11 categorías)  ← variable clave
Loading...

purpose  (14 categorías)  ← variable clave
Loading...

sub_grade  (35 categorías)
Loading...

addr_state  (51 categorías)  ← variable clave
Loading...

Se definió un umbral de rara de 1%. Hay que agrupar varias clases.

De las 37 columnas no numéricas:

21 son categóricas y se analizan con tablas de frecuencia. 11 son fechas en formato mes-año (issue_d, earliest_cr_line, last_pymnt_d…). Las fechas no se tratan como categorías: en el preprocesamiento se convertirán en variables numéricas, como el año de emisión o la antigüedad del historial crediticio (issue_d − earliest_cr_line). Las fechas posteriores al otorgamiento se eliminarán por fuga. 5 son texto libre o códigos (url, desc, title, emp_title, zip_code), con miles o millones de valores distintos (url es única por préstamo), así que no admiten análisis de frecuencias. Entre las categóricas hay varias casi constantes: pymnt_plan (“n” en 99.97 %), hardship_flag (99.96 %) y disbursement_method (96.5 %), que aportan poca información. Las hardship_* y settlement_* tienen más del 98 % de nulos y son posteriores al préstamo.

Parte 17. Categorías con menos del 1%

Loading...
Loading...

Lo que sale:

purpose: 6 categorías raras (vacation, moving, house, wedding, renewable_energy, educational), que suman 2.2 %. home_ownership: ANY, OTHER y NONE suman solo 0.05 %. grade: G tiene 0.5 %, pero no conviene agruparla, porque es ordinal y es el grado de mayor riesgo. addr_state: 23 estados tienen menos del 1 %; para esta variable es mejor agrupar por región que usar “Otros”.

Parte 18. Gráficos de barras

Las variables ordinales (grade, term, emp_length) se muestran en su orden natural; las demás, por frecuencia. En gris aparecen las categorías con menos del 1 %.

<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x275 with 1 Axes>
<Figure size 1100x294.8 with 1 Axes>
<Figure size 1100x294.8 with 1 Axes>
<Figure size 1100x325.6 with 1 Axes>
<Figure size 1100x387.2 with 1 Axes>
<Figure size 1100x418 with 1 Axes>
<Figure size 1100x479.6 with 1 Axes>
<Figure size 1100x541.2 with 1 Axes>
<Figure size 1100x1188 with 1 Axes>
<Figure size 1100x1680.8 with 1 Axes>

Parte 19. Categorías redundantes

home_ownership: {'MORTGAGE': 1111450, 'RENT': 894929, 'OWN': 253057, 'ANY': 996, 'OTHER': 182, 'NONE': 54}
verification_status: {'Source Verified': 886231, 'Not Verified': 744806, 'Verified': 629631}

loan_status:
loan_status
Fully Paid                                             1076751
Current                                                 878317
Charged Off                                             268559
Late (31-120 days)                                       21467
In Grace Period                                           8436
Late (16-30 days)                                         4349
Does not meet the credit policy. Status:Fully Paid        1988
Does not meet the credit policy. Status:Charged Off        761
Default                                                     40

¿sub_grade empieza siempre por grade?: True
Diferencia fico_range_high − fico_range_low: {4.0: 2260227, 5.0: 441}

emp_title → únicos originales: 512,694
emp_title → únicos tras minúsculas y sin espacios: 411,810
emp_title
Teacher     38824
teacher      6692
Teacher      2115
TEACHER       584
teacher       137
Name: count, dtype: int64

title: 63,154 valores únicos  vs  purpose: 14
title
Debt consolidation         1153293
Credit card refinancing     469691
Home improvement            137437
Other                       127714
Major purchase               44840
Medical expenses             25388
Business                     20804
Car financing                20526
Name: count, dtype: int64

Hallazgos de la Parte 19 — categorías y variables redundantes

Qué se revisóQué se encontróDecisión
home_ownershipMORTGAGE, RENT y OWN suman el 99.95 %; ANY (996), OTHER (182) y NONE (54) son casi nulasUnificar ANY, OTHER y NONE en “OTHER”
verification_statusVerified, Source Verified y Not VerifiedSe mantienen separadas: en la Parte 26 tienen tasas de default distintas (15.9 %, 12.3 % y 8.0 %)
loan_statusIncluye “Does not meet the credit policy. Status: Fully Paid / Charged Off” (2,749 préstamos)Se mantienen como en el dataset original, según la regla del enunciado para default (Parte 21)
grade vs sub_gradesub_grade empieza siempre por grade (100 %)Usar solo una de las dos en el modelo
fico_range_low vs fico_range_highSiempre difieren en 4 puntos (441 casos en 5)Usar solo una
emp_title512,694 valores únicos; 411,810 al normalizar mayúsculas y espaciosNormalizar el texto si se usa
title vs purposetitle (63,154 valores) es texto libre que repite purpose (14 categorías)Eliminar title

En resumen, se identificaron categorías casi vacías que conviene unificar y pares de variables que repiten la misma información (grade/sub_grade, fico_low/fico_high, title/purpose). Estas redundancias se confirman numéricamente en las Partes 30 (V de Cramér = 1.00) y 31 (r = 1.00).

Parte 20. Faltantes en las categóricas y tratamiento

Loading...

Esta tabla es preliminar. El tratamiento definitivo está en la Parte 38. Hay tres diferencias: emp_title y title se eliminan (texto libre); last_pymnt_d, next_pymnt_d y last_credit_pull_d se eliminan por fuga; y emp_length usa la categoría “Missing”.

C. Variable objetivo

Parte 21. Distribución de “default”

Definición de default. Se aplica la regla del enunciado (9.10.3): default = 1 si loan_status es “Charged Off” y default = 0 en cualquier otro caso, sobre los 2.260.668 préstamos del dataset completo, sin eliminar ninguna fila. Así, la clase 0 incluye los préstamos pagados (Fully Paid) y también los que aún están vigentes o en mora al momento del corte (Current, Late, In Grace Period), cuyo desenlace final todavía no se conoce.

Limitación: una parte de los préstamos vigentes podría caer en default en el futuro, así que la tasa observada (11.88 %) es un límite inferior de la tasa real. Tampoco quedan en la clase 1 los 761 préstamos “Does not meet the credit policy. Status: Charged Off”, porque su texto no coincide exactamente con “Charged Off”.

Loading...
Préstamos analizados  : 2,260,668
Razón de desbalance   : 7.42 : 1

Composición de la clase 0:
loan_status
Fully Paid                                             1076751
Current                                                 878317
Late (31-120 days)                                       21467
In Grace Period                                           8436
Late (16-30 days)                                         4349
Does not meet the credit policy. Status:Fully Paid        1988
Does not meet the credit policy. Status:Charged Off        761
Default                                                     40
Charged Off                                                  0
<Figure size 880x495 with 1 Axes>

Lo que sale: 1,992,109 préstamos sin Charged Off (88.1 %) y 268,559 Charged Off (11.9 %), una razón de 7.4 : 1. Es un desbalance importante. Qué implica para el modelado:

  • Accuracy engaña: un modelo que siempre prediga “no default” acertaría el 88 %.

  • Se usarán métricas centradas en la clase minoritaria: AUC-ROC, AUC-PR, F1 y recall de la clase 1.

  • La partición train/test será estratificada (stratify=y), y la validación cruzada, StratifiedKFold.

  • El desbalance se tratará con pesos por clase (class_weight="balanced" en scikit-learn, weightCol en PySpark). No se usará undersampling, porque el enunciado prohíbe reducir filas.

Advertencia metodológica: sesgo temporal de la etiqueta. Con la regla del enunciado, los préstamos vigentes (Current: 878,317) quedan en la clase 0 aunque todavía no se sepa si pagarán. Como la mayoría de ellos es reciente, la tasa de default observada cae artificialmente en los últimos años:

Año de emisión20142015201620172018
% de préstamos aún vigentes5 %10 %31 %59 %86 %
Tasa de default observada17.5 %18.0 %15.7 %8.8 %1.8 %

En consecuencia, las variables que caracterizan a los préstamos recientes aparecen como “protectoras” sin serlo; por ejemplo, disbursement_method = DirectPay (1.8 % de default) y application_type = Joint App (5.3 %), que existen principalmente desde 2017. Estas asociaciones reflejan que el préstamo aún no ha tenido tiempo de caer en default. En el modelado se incluirá el año de emisión como variable de control y se interpretarán con cautela las variables asociadas a la fecha.

Preparación

Parte 22. Subconjunto con desenlace conocido y utilidades

Se trabaja con los 1,345,310 préstamos que tienen default (0 o 1). Todas las figuras y pruebas de esta sección usan los datos completos; la única excepción es el pairplot (Parte 32), que según el enunciado se hace con una muestra representativa.

Préstamos analizados             : 2,260,668
Tasa global de default           : 11.88 %
Variables en escala log10        : ['annual_inc', 'dti', 'revol_bal', 'pub_rec']
Datos para gráficos de densidad  : 2,260,668 (completos)

A. Variables numéricas vs default

Parte 23. Boxplots y plots de violin por clase

<Figure size 1760x1210 with 12 Axes>
<Figure size 1760x1210 with 12 Axes>

Análisis de los boxplots y violin plots por clase

Lectura general. En las 12 variables las distribuciones de ambas clases se superponen en gran medida: ninguna variable separa por sí sola a los préstamos en default de los demás. Las diferencias están en el desplazamiento de la mediana y de los cuartiles. int_rate y fico_range_high son las únicas con desplazamientos claros, lo que coincide con que son las de mayor correlación punto-biserial (Parte 24). Nota: la clase 0 incluye préstamos pagados y vigentes (Parte 21).

1. loan_amnt. La mediana sube de 12,525 USD (clase 0) a 14,350 USD (Charged Off), y Q1 de 8,000 a 9,000; la media pasa de 14,977 a 15,565. La dispersión es similar (desviación ≈ 9,000; IQR 12,000 frente a 11,400). Los violines conservan los picos en montos redondos del histograma (Parte 12). Los outliers (1.7 % y 0.6 %) están cerca del tope de 40,000 USD: son el máximo permitido, no errores. Efecto despreciable (r = 0.021).

2. int_rate. Es la variable con mayor separación: la mediana pasa de 12.1 % a 15.1 %, Q1 de 9.2 % a 12.3 % y Q3 de 15.3 % a 18.6 %. El cuartil inferior de los Charged Off ya está cerca de la mediana de la clase 0. El 16.3 % de los Charged Off paga más de 20 %, frente al 6.7 % de la clase 0. La dispersión es casi igual (IQR ≈ 6.2), así que la diferencia está en la posición. Los outliers (≈ 2 %) son tasas altas legítimas (hasta 31 %) de los grados F y G. Es el mejor predictor numérico (r = 0.199), porque la tasa ya incorpora la evaluación de riesgo de Lending Club.

3. installment. La mediana pasa de 375 a 403 USD, con dispersión similar (IQR ≈ 335). Replica el patrón de loan_amnt, porque la cuota se calcula a partir del monto (r = 0.95, Parte 29). Redundante con loan_amnt (r = 0.027).

4. annual_inc (escala log). Los Charged Off ganan menos: mediana de 65,175 frente a 60,000 USD, y media de 79,016 frente a 70,401. En ambas clases la media supera ampliamente a la mediana (fuerte sesgo, Parte 14). En escala log los violines son casi simétricos y parecidos. Los outliers (≈ 4.7 % por clase) están en los dos extremos: ingresos de 0 y de hasta 11 millones. Efecto pequeño e inverso (r_rank = −0.10); requiere transformación log.

5. dti (escala log). La mediana sube de 17.6 a 19.8 y toda la caja se desplaza (Q1 de 11.7 a 13.6; Q3 de 24.2 a 26.3). En ambas clases aparece el valor imposible de 999 (Parte 11). Efecto pequeño (r_rank = 0.12); hay que tratar los valores −1 y 999 en el preprocesamiento.

6. fico_range_high. Es la segunda variable más relevante, con relación inversa: la mediana baja de 699 a 684, Q3 de 719 a 704 y el P90 de 749 a 724. Los Charged Off tienen menos dispersión (IQR 30 frente a 40) y se concentran en la zona baja: el 52.1 % tiene FICO ≤ 684, frente al 37.2 % de la clase 0. El violín muestra el piso de ≈ 660 que exige la política. Los outliers (≈ 3.6 %) son puntajes altos legítimos. Efecto pequeño (r = −0.119).

7. emp_length_num. Las distribuciones son idénticas: misma mediana (6), Q1 (2) y Q3 (10). El violín tiene forma de reloj de arena, grueso en 10 años (“10+ years”, ≈ 35 % en ambas clases). No hay outliers (variable acotada entre 0 y 10). No discrimina (r = −0.007). Lo informativo es el faltante: el 7.9 % de los Charged Off no declara su antigüedad laboral, frente al 6.3 % de la clase 0.

8. revol_util. La mediana sube de 49.5 % a 55.5 %; la caja se desplaza unos 6 puntos. Los violines son casi simétricos (asimetría ≈ 0, Parte 14), así que no requiere transformación. El 16.6 % de los Charged Off usa más del 80 % de su cupo, frente al 13.2 %. Casi no hay outliers, salvo errores aislados como 892 %. Efecto pequeño (r = 0.066), y es la tercera variable clave más relacionada con default.

9. revol_bal (escala log). Es de las pocas variables donde la clase 0 tiene un valor mayor: media de 16,834 frente a 15,354, con medianas casi iguales (11,360 frente a 11,072). Es la variable clave con más outliers (≈ 6 % por clase). No discrimina (r = −0.021); revol_util, que relativiza el saldo con el límite, es más útil.

10. open_acc. Misma mediana (11); Q3 sube de 14 a 15. Efecto despreciable (r = 0.019).

11. total_acc. Distribuciones casi idénticas (medianas de 22 y 23). Efecto despreciable (r = 0.017).

12. pub_rec (escala log). La caja colapsa en 0 en ambas clases, porque la mayoría no tiene registros públicos. El método IQR marca como outlier cualquier valor ≥ 1 (15.4 % frente a 19.3 %). La diferencia útil es que los Charged Off tienen con más frecuencia al menos un registro (19.3 % frente a 15.4 %). Conviene transformarla en binaria (“tiene o no registros públicos”).

Síntesis

VariableMediana clase 0 → Charged OffEfecto
int_rate12.1 → 15.1 %Mediano (el mayor)
fico_range_high699 → 684Pequeño (inverso)
revol_util49.5 → 55.5 %Pequeño
dti17.6 → 19.8Pequeño
annual_inc65,175 → 60,000Pequeño (inverso)
loan_amnt / installment12,525 → 14,350 / 375 → 403Despreciable; redundantes entre sí
pub_rec0 → 0 (19.3 % frente a 15.4 % con ≥ 1)Despreciable; mejor como binaria
emp_length_num, revol_bal, open_acc, total_acc≈ igualesDespreciable

Los outliers aparecen en proporciones similares en ambas clases, así que no son propios de los malos pagadores: reflejan la asimetría de las variables. Por eso no se eliminan; se tratarán con transformaciones (log / Yeo-Johnson) y, en los valores imposibles (dti = −1 / 999, revol_util = 892 %), con un tratamiento puntual.

Parte 24. Diferencias de medias, t de Welch, Mann-Whiteny y correlación punto biserial

En la Parte 14 ninguna variable resultó normal, así que la prueba principal es Mann-Whitney; la t de Welch se reporta para comparar. Con más de un millón de datos, casi cualquier diferencia sale con p ≈ 0. Por eso también se reporta el tamaño del efecto (rank-biserial y punto-biserial), que indica si la diferencia es grande y no solo si existe.

Variables evaluadas: 113  ·  elegibles para el modelo (sin fuga, ≤ 70 % nulos): 73
Loading...
Loading...
Loading...
<Figure size 1100x1100 with 1 Axes>

Análisis de las pruebas estadísticas por clase

1. Elección de la prueba. Como ninguna variable es normal (Parte 14), la prueba principal es Mann-Whitney U, que compara la posición de las distribuciones a partir de rangos, sin suponer normalidad. La t de Welch se reporta como complemento: con muestras tan grandes (1,992,109 y 268,559), el teorema central del límite hace válida la comparación de medias. En las 12 variables clave ambas pruebas coinciden.

2. Significancia. Las 12 variables tienen p < 0.05 (p ≈ 0) en ambas pruebas. Sin embargo, con 2.26 millones de observaciones cualquier diferencia resulta significativa: total_acc difiere en solo 0.6 cuentas y aun así tiene p ≈ 0. Por eso la relevancia se mide con el tamaño del efecto.

3. Tamaño del efecto. Se usan la r rank-biserial (asociada a Mann-Whitney; mide la probabilidad de que un Charged Off tenga un valor mayor que uno de la clase 0, reescalada entre −1 y 1) y la r punto-biserial (Pearson con la variable binaria; su cuadrado indica el % de variabilidad de default que explica). Umbrales: |r| < 0.1 despreciable; 0.1–0.3 pequeño; 0.3–0.5 mediano.

VariableMedia clase 0 → COr rank-biserialr punto-biserialr²Efecto
int_rate12.74 → 15.71 %0.3550.1994.0 %Mediano
fico_range_high704.0 → 691.9−0.217−0.1191.4 %Pequeño
dti18.64 → 20.170.1200.0350.1 %Pequeño
revol_util49.7 → 54.8 %0.1180.0660.4 %Pequeño
annual_inc79,016 → 70,401−0.103−0.025< 0.1 %Pequeño
installment443 → 4650.0620.027< 0.1 %Despreciable
loan_amnt14,977 → 15,5650.0530.021< 0.1 %Despreciable
pub_rec0.19 → 0.250.0400.0320.1 %Despreciable
open_acc11.57 → 11.900.0390.019< 0.1 %Despreciable
total_acc24.09 → 24.710.0300.017< 0.1 %Despreciable
revol_bal16,834 → 15,354−0.020−0.021< 0.1 %Despreciable
emp_length_num5.94 → 5.86−0.015−0.007< 0.1 %Despreciable

4. Interpretación. int_rate es la única variable con efecto mediano: al tomar al azar un Charged Off y un préstamo de la clase 0, el primero tiene una tasa mayor en el 68 % de los casos ((1 + 0.355) / 2). fico_range_high, dti, revol_util y annual_inc tienen efectos pequeños con la dirección esperada: menor puntaje, más endeudamiento, más uso del cupo y menor ingreso se asocian con más default. Las otras 7 variables tienen efectos despreciables.

5. Diferencia entre las dos medidas. En dti y annual_inc, la rank-biserial es claramente mayor que la punto-biserial (0.120 frente a 0.035 y −0.103 frente a −0.025). La correlación de Pearson se ve afectada por los valores extremos de la Parte 11, mientras que Mann-Whitney usa rangos y es robusta a ellos. Tratar los outliers o aplicar una transformación log (Parte 14) debería mejorar el aporte de estas variables en los modelos lineales.

6. Implicaciones para el modelado. La mejor variable explica solo el 4 % de la variabilidad de default: ninguna predice el default por sí sola. El modelo deberá combinar variables numéricas y categóricas (sub_grade, verification_status, term; Parte 26), y posiblemente captar interacciones, donde los modelos de árboles (RandomForest, GBT) suelen tener ventaja.

Extensión a todas las variables. De las 113 variables numéricas, 102 muestran diferencias significativas, 8 no son significativas y 3 son constantes. Las 4 de efecto grande son todas variables de fuga: last_fico_range_high y last_fico_range_low (r rank-biserial = −0.89), recoveries (0.69) y collection_recovery_fee (0.66). Estas variables describen lo que pasó después de otorgar el préstamo; por ejemplo, recoveries solo es mayor que 0 si el préstamo ya cayó en default. Usarlas daría un modelo artificialmente perfecto e inútil en la práctica, lo que confirma que deben excluirse.

Entre las 73 variables elegibles (sin fuga y con 70 % de nulos o menos), int_rate es la única con efecto mediano, 28 tienen efecto pequeño y 43 despreciable. Tras int_rate (0.199) y fico_range_high / fico_range_low (−0.119, redundantes entre sí), aparecen variables que no estaban entre las 12 clave: acc_open_past_24mths (0.093), inq_last_6mths (0.083), num_tl_op_past_12m (0.083), bc_open_to_buy (−0.082), open_rv_24m (0.079) y bc_util (0.074). El patrón indica que abrir muchas cuentas o hacer muchas consultas de crédito recientemente se asocia con más riesgo, y que tener más crédito disponible se asocia con menos. Estas variables se considerarán en la selección de características.

Parte 25. KDE Comparativos de las 6 variables más relevantes

Se eligen las 6 con mayor r punto biserial de la parte 24

<Figure size 1650x880 with 6 Axes>

Las 6 variables con mayor |r punto-biserial| son int_rate, fico_range_high, revol_util, dti, pub_rec e installment. En int_rate la curva de los Charged Off está desplazada unos 3 puntos a la derecha: es la separación más visible. En fico_range_high, los Charged Off tienen un pico más alto en los puntajes bajos (660–690) y una cola más delgada hacia los altos. En revol_util y dti hay un desplazamiento leve hacia valores mayores. En pub_rec e installment las curvas casi coinciden. En todas las variables las densidades se superponen en gran medida, lo que confirma que ninguna separa las clases por sí sola.

B. Variables categóricas vs. default

Parte 26. Tablas de contigencia, chi cuadrado y V de Cramer

La V de Cramér va de 0 a 1 y mide la fuerza de la asociación; el p-value, igual que antes, sale ≈ 0 por el tamaño de la muestra.

Variables evaluadas: 20  ·  tasa global de default: 11.88 %
Loading...
Loading...

hardship_type  [FUGA]
Loading...

term  [clave]
Loading...

pymnt_plan  [FUGA]
Loading...

initial_list_status
Loading...

application_type  [clave]
Loading...

hardship_flag  [FUGA]
Loading...

disbursement_method
Loading...

debt_settlement_flag  [FUGA]
Loading...

verification_status  [clave]
Loading...

verification_status_joint
Loading...

hardship_status  [FUGA]
Loading...

settlement_status  [FUGA]
Loading...

hardship_loan_status  [FUGA]
Loading...

home_ownership  [clave]
Loading...

grade  [clave]
Loading...

hardship_reason  [FUGA]
Loading...

emp_length  [clave]
Loading...

purpose  [clave]
Loading...

sub_grade
Loading...

addr_state  [clave]
Loading...

Análisis inferencial: asociación entre las variables categóricas y default

Población y variables. Se analizan las 20 variables categóricas (todas excepto loan_status, origen de la variable objetivo) sobre los 2,260,668 préstamos (1,992,109 sin Charged Off y 268,559 Charged Off; tasa global de default: 11.88 %). Los nulos se tratan como una categoría más, “(faltante)”.

1. Hipótesis. Para cada variable se aplica una prueba de independencia chi-cuadrado de Pearson:

  • H₀: la variable y default son independientes.

  • H₁: la tasa de default varía según la categoría.

  • Nivel de significancia: α = 0.05.

2. Supuestos. Las observaciones son independientes: cada préstamo aparece una vez. La regla de Cochran (menos del 20 % de celdas con frecuencia esperada < 5 y ninguna < 1) se cumple en las 20 variables; el caso límite es hardship_loan_status, con 8.3 % de celdas con E < 5, por la categoría “Issued” (15 préstamos).

3. Resultado general. Se rechaza H₀ en las 20 variables. Con 2.26 millones de datos la prueba detecta hasta asociaciones mínimas, así que la relevancia se mide con la V de Cramér (umbrales de Cohen para 2 clases: < 0.1 despreciable; 0.1–0.3 pequeña; 0.3–0.5 moderada).

4. Clasificación de las variables

A) Fuga de información (8): se excluyen del modelo

VariableVHallazgoPor qué es fuga
settlement_status0.327ACTIVE, BROKEN, COMPLETE: 93.5–99.9 % de defaultAcuerdo de pago; solo existe después del impago
debt_settlement_flag0.327“Y”: 97.2 % frente a “N”: 10.6 %Ídem
hardship_status0.077BROKEN: 84.0 %Plan de alivio solicitado durante la vida del préstamo
hardship_reason0.057Entre 26.1 % y 50.7 %Motivo del plan de alivio
hardship_loan_status0.057Late (16-30 days): 43.7 %Estado de pago al entrar al plan
hardship_type0.05437.2 %Tipo de plan de alivio
hardship_flag0.007“Y”: 0 % de default (832 préstamos, todos vigentes)Plan de alivio posterior
pymnt_plan0.006“y”: 0 % de default (620 préstamos)Plan de pago posterior

Las dos variables con mayor asociación de todo el análisis son de fuga: incluirlas daría un modelo artificialmente perfecto.

B) Relevantes (asociación pequeña)

Variableχ² (gl)VTasa mín. – máx.Post-hoc
sub_grade116,768 (34)0.2271.6 % – 39.6 %35 de 35 categorías significativas
grade112,789 (6)0.2233.3 % (A) – 37.5 % (G)7 de 7

grade y sub_grade son los predictores categóricos más fuertes, con un gradiente monótono (residuos ajustados de −194 en A a +172 en E). Son redundantes entre sí (V = 1.00, Parte 30): se usará solo sub_grade.

C) Débiles pero interpretables (V entre 0.03 y 0.1)

VariableVHallazgo
verification_status0.095Verified 15.9 % > Source Verified 12.3 % > Not Verified 8.0 %: Lending Club verifica a los solicitantes que considera más riesgosos
term0.08460 meses: 16.2 % frente a 36 meses: 10.1 %. La asociación está atenuada por el sesgo temporal: muchos préstamos a 60 meses siguen vigentes
home_ownership0.052RENT 13.9 % frente a MORTGAGE 10.3 %; OWN, OTHER y NONE no son significativas
purpose0.049small_business 18.6 % (el más alto); car 8.9 % y credit_card 9.7 % (los más bajos); 6 categorías sin diferencia significativa
addr_state0.038Tasas entre 5.6 % y 14.4 %; 34 de 51 estados difieren del esperado
emp_length0.027“(faltante)” tiene la tasa más alta (14.4 %); 5 de 12 niveles no difieren del esperado

D) Asociación influida por la fecha de emisión: usar con cautela

VariableVHallazgoInterpretación
initial_list_status0.070“f” 15.2 % frente a “w” 10.3 %“w” predomina en los préstamos recientes, que aún están vigentes
disbursement_method0.059DirectPay 1.8 % frente a Cash 12.2 %DirectPay existe desde 2017: casi todos sus préstamos siguen vigentes
application_type0.049Joint App 5.3 % frente a Individual 12.3 %Las solicitudes conjuntas empezaron en 2017
verification_status_joint0.046Todas sus categorías ≈ 5 %Solo existe en solicitudes conjuntas (95 % de nulos): se elimina

Estas asociaciones no reflejan un menor riesgo real, sino que los préstamos no han tenido tiempo de caer en default (Parte 21).

5. Conclusiones

  1. Las 20 variables son estadísticamente dependientes de default, pero con fuerza muy desigual.

  2. 8 variables son de fuga y se eliminan, incluidas las dos con mayor asociación.

  3. Los predictores categóricos útiles son sub_grade (o grade), seguidos por verification_status, term, home_ownership, purpose, addr_state y emp_length.

  4. Las asociaciones de disbursement_method, application_type e initial_list_status están influidas por la fecha de emisión, y verification_status_joint se elimina por nulos.

Parte 27. Barras apiladas y tasa de default por categoría

Cada variable se muestra en una figura con dos paneles: a la izquierda, la frecuencia apilada por clase; a la derecha, la tasa de default comparada con la global.

<Figure size 1540x330 with 2 Axes>
<Figure size 1540x330 with 2 Axes>
<Figure size 1540x330 with 2 Axes>
<Figure size 1540x330 with 2 Axes>
<Figure size 1540x330 with 2 Axes>
<Figure size 1540x330 with 2 Axes>
<Figure size 1540x330 with 2 Axes>
<Figure size 1540x363 with 2 Axes>
<Figure size 1540x528 with 2 Axes>
<Figure size 1540x594 with 2 Axes>
<Figure size 1540x1287 with 2 Axes>
<Figure size 1540x1815 with 2 Axes>

Análisis de las barras apiladas y de la tasa de default por categoría

Cómo leer cada figura. El panel izquierdo muestra el volumen (préstamos por categoría y cuántos son Charged Off). El derecho muestra el riesgo (tasa de default frente a la global, 11.88 %). Volumen y riesgo no coinciden: las categorías pequeñas casi no se ven a la izquierda, pero pueden tener las tasas más altas a la derecha.

1. grade (V = 0.223). La tasa crece de forma monótona: A 3.3 %, B 7.9 %, C 13.2 %, D 18.8 %, E 26.6 %, F 34.7 % y G 37.5 %. El volumen se concentra en B y C (29 % cada uno), mientras que F y G, las de mayor riesgo, son solo el 2.4 % de los préstamos. C es el primer grado sobre la tasa global. Es ordinal: se codificará como número (A = 1, …, G = 7).

2. sub_grade (V = 0.227). Refina el patrón con 35 niveles, desde 1.6 % hasta 39.6 %. Hay diferencias dentro de cada grado, lo que explica su V algo mayor que la de grade. En F y G hay pequeñas irregularidades por el bajo volumen. Contiene toda la información de grade: se conserva sub_grade.

3. verification_status (V = 0.095). Resultado contraintuitivo: Verified 15.9 %, Source Verified 12.3 % y Not Verified 8.0 %, con volúmenes similares (28 %, 39 % y 33 %). Lending Club exige verificar los ingresos a los solicitantes que considera más riesgosos, así que la verificación refleja el riesgo percibido. Se mantienen las 3 categorías.

4. term (V = 0.084). Los préstamos a 60 meses tienen más default (16.2 % frente a 10.1 %) y son el 29 % del volumen. La diferencia es menor de lo esperado porque muchos préstamos a 60 meses todavía están vigentes y cuentan como clase 0 (Parte 21). Se codificará como binaria.

5. home_ownership (V = 0.052). RENT 13.9 %, OWN 11.8 % y MORTGAGE 10.3 %: tener hipoteca se asocia con menos riesgo. ANY, OTHER y NONE tienen tasas inestables (5.6 %–14.8 %) por su bajo volumen (54 a 996 préstamos): se unifican en “OTHER”.

6. purpose (V = 0.049). El volumen está dominado por debt_consolidation (56.5 %) y credit_card (22.9 %), pero el riesgo tiene otra jerarquía: small_business 18.6 % (la más alta), renewable_energy 15.3 % y moving 14.4 %. Las de menor riesgo son car (8.9 %) y credit_card (9.7 %). Las categorías con menos del 1 % se agruparán en “Otros”.

7. addr_state (V = 0.038). Tasas entre 5.6 % y 14.4 %, con un patrón geográfico débil y 51 categorías. Conviene agruparla por región o usar codificación por tasa de default (target encoding), en lugar de 50 variables dummy.

8. emp_length (V = 0.027). Las tasas por antigüedad son casi planas (11.1 %–13.2 %). La categoría “(faltante)” tiene 14.4 %, la más alta: no declarar el empleo es la señal útil. Esto confirma la categoría “Missing” (Parte 38).

9. initial_list_status (V = 0.070), disbursement_method (V = 0.059), application_type (V = 0.049) y verification_status_joint (V = 0.046). Muestran diferencias grandes (por ejemplo, DirectPay 1.8 % y Joint App 5.3 %), pero se explican por la fecha de emisión: estas categorías corresponden sobre todo a préstamos de 2016–2018, que aún están vigentes (Parte 21). No deben interpretarse como menor riesgo.

Síntesis

GrupoVariablesDecisión
Relevantessub_grade (o grade)Conservar sub_grade, con codificación ordinal
Débiles pero útilesverification_status, term, home_ownership, purpose, addr_state, emp_lengthConservar, agrupando categorías raras y addr_state por región
Influidas por la fechainitial_list_status, application_type, disbursement_methodUsar con cautela, controlando por el año de emisión; disbursement_method se elimina (96.5 % Cash)
Eliminarverification_status_jointMás de 70 % de nulos

Observación general. Las categorías de mayor riesgo son las de menor volumen (F, G, small_business, “(faltante)”). Esto refuerza la necesidad de tratar el desbalance de clases (7.4 : 1) y de usar métricas distintas de la exactitud.

Parte 28. Categoría de alto riesgo

Se marcan las categorías cuya tasa de default supera 1.25 veces la global (> 14.85 %) y que tienen al menos 1,000 préstamos, para que la tasa sea confiable.

Categorías de alto riesgo (tasa > 14.85 % y ≥ 1,000 préstamos): 30
Loading...

Análisis de las categorías de alto riesgo

Criterio. Tasa de default > 14.85 % (1.25 × 11.88 %) y al menos 1,000 préstamos. Se evaluaron todas las variables sin fuga de información.

Resultado: 30 categorías de alto riesgo, en cinco grupos:

1. Calidad crediticia baja (25 de 30): los grados D (18.8 %), E (26.6 %), F (34.7 %) y G (37.5 %) y los subgrados de C5 (15.1 %) a G5, con un máximo en G3 (39.6 %). Desde C5 hasta G5 hay 630,757 préstamos (27.9 % de la cartera). El umbral se cruza dentro del grado C: C5 es de alto riesgo aunque el grado C en conjunto (13.2 %) no lo sea.

2. Plazo largo: term = 60 months (16.2 %; 650,914 préstamos). Es la categoría de alto riesgo con mayor volumen.

3. Propósito: small_business (18.6 %; 24,689 préstamos) y renewable_energy (15.3 %; 1,445 préstamos).

4. Verificación del ingreso: verification_status = Verified (15.9 %; 629,631 préstamos). Lending Club verifica a los solicitantes que considera más riesgosos.

5. Tipo de listado: initial_list_status = f (15.2 %). Esta asociación está influida por la fecha: los préstamos “f” son sobre todo antiguos y ya terminaron (Parte 21).

Conclusiones

  1. El riesgo está concentrado en el grado del préstamo: 25 de las 30 categorías son grados o subgrados bajos.

  2. Las categorías de alto riesgo coinciden con las variables de mayor V de Cramér (sub_grade y grade, Parte 26).

  3. term, purpose y verification_status, aunque tienen una asociación global débil, contienen segmentos concretos de alto riesgo que el modelo puede aprovechar.

C. Multicolinealidad

Parte 29. Correlación de Pearson: todas las variables numéricas y heatmap

Se calcula con los 2.26 M préstamos y las 110 variables numéricas que no son constantes.

Pares con |r| > 0.7: 76 de 5,995
Loading...

Entre las variables clave (NUM_VARS):
Loading...
<Figure size 1100x935 with 2 Axes>
Bloques de variables con |r| > 0.7: 22 (60 variables en total)
Loading...
<Figure size 2640x2310 with 2 Axes>

Lo que sale: hay 76 pares con |r| > 0.7. Casi todos son copias o sumas de la misma cantidad:

loan_amnt, funded_amnt y funded_amnt_inv (r = 1.00) fico_range_low y fico_range_high (1.00) open_acc y num_sats (0.999) loan_amnt e installment (0.95) tot_cur_bal y tot_hi_cred_lim (0.98)

Fuga de información: varios pares de la lista (total_pymnt, total_rec_prncp, recoveries, out_prncp, last_pymnt_amnt, hardship_, settlement_) son datos que solo se conocen después de que el préstamo termina. Además de ser redundantes, habría que excluirlos del modelo porque le “revelan” la respuesta.

El heatmap agrupa las variables por similitud y enmarca 22 bloques con |r| > 0.7. Dentro de cada bloque, las variables miden prácticamente lo mismo; por ejemplo:

Bloque 1: loan_amnt, funded_amnt, funded_amnt_inv, installment (r ≈ 0.95–1.00) Bloque 11: fico_range_low y fico_range_high (r = 1.00) Bloque 8: open_acc, num_sats, num_op_rev_tl (r ≈ 0.84–1.00) Para el modelado basta con conservar una variable por bloque. Además, varios bloques contienen información posterior al otorgamiento del préstamo (total_pymnt, recoveries, out_prncp, last_fico_range_, hardship_, settlement_*); esas variables se excluirán por fuga de información, independientemente de su correlación.

Parte 30. V de Cramér entre pares de variables categóricas

Loading...
<Figure size 935x770 with 2 Axes>

La única asociación fuerte entre variables categóricas es grade ↔ sub_grade (V = 1.00): una contiene a la otra, así que se conservará solo sub_grade. Le siguen term con sub_grade (V = 0.40) y con grade (V = 0.39), porque los préstamos a 60 meses suelen tener grados peores, y verification_status con el grado (V ≈ 0.18–0.19). El resto de pares tiene asociaciones débiles (V < 0.12), así que no hay más redundancias entre las variables categóricas.

Parte 31. Redundancia entre tipos de variables y decisión de exclusión

Para una variable categórica frente a una numérica se usa la razón de correlación η: va de 0 a 1 y mide cuánto de la variable numérica explica la categoría.

Loading...
<Figure size 1100x495 with 1 Axes>

Se evaluaron los pares de variables potencialmente redundantes, incluidos los de tipo mixto, con la medida adecuada a cada caso: r de Pearson (numérica–numérica), razón de correlación η (categórica–numérica) y V de Cramér (categórica–categórica).

Redundancias que justifican excluir una variable: fico_range_low ↔ fico_range_high (r = 1.00) → se conserva fico_range_high. loan_amnt ↔ installment (r = 0.95) → se conserva loan_amnt. grade ↔ sub_grade (V = 1.00) → se conserva sub_grade, que tiene más detalle. La redundancia más relevante es entre tipos distintos: sub_grade explica casi toda la variación de int_rate (η = 0.977; grade, η = 0.953), porque Lending Club fija la tasa según el subgrado asignado. El boxplot lo confirma: cada grado ocupa un rango de tasas propio. Para los modelos lineales se evaluará conservar solo una de las dos (int_rate o sub_grade); para los modelos de árboles pueden mantenerse ambas. Pares que NO son redundantes: fico_range_high ↔ int_rate (r = −0.42), el ejemplo del enunciado, tiene una relación solo moderada: la tasa depende del FICO, pero también del grado, el plazo y otros factores. Ambas se conservan, porque cada una aporta información propia sobre default (Parte 24). Lo mismo ocurre con term → int_rate (η = 0.37) y con open_acc ↔ total_acc (r = 0.72), que miden conceptos distintos.

D. Visualizaciones avanzadas sugeridas por el profesor

Parte 32. Pairplot/matriz de dispersión

Se agrupan el pairplot y la matriz de dispersión, porque el pairplot es una matriz de dispersión coloreada por clase y con la densidad en la diagonal. Usa una muestra estratificada de unos 5,000 préstamos, tal como sugiere el enunciado.

<Figure size 1490.12x1320 with 20 Axes>

Lo que sale: no aparecen patrones no lineales marcados. Se ve que los Charged Off se concentran en int_rate alto y FICO bajo (esquina inferior derecha de ese panel), y que loan_amnt crece con el ingreso. loan_amnt forma franjas horizontales porque la gente pide montos redondos (10,000, 20,000, 35,000).

E. Valores faltantes (análisis detallado)

Parte 33. Número y porcentaje de nulos por columna

Se reporta el número y el porcentaje de nulos de cada una de las 151 columnas originales sobre los 2,260,668 préstamos. Con la regla del enunciado todo el dataset es la población de análisis, así que las columnas pct_nulos y pct_nulos_modelado coinciden.

Columnas analizadas: 151  ·  con al menos un nulo: 113
Loading...

Parte 34. Mapa de calor de nulos

Pintar 2.26 M de filas en una imagen no se puede leer. Por eso se agrupan por mes de emisión: cada celda es el % de nulos de esa columna en ese mes. Así se usan todas las filas y, además, se ve cuándo aparece cada variable.

<Figure size 2420x1210 with 2 Axes>

El mapa muestra que la mayoría de los nulos no son aleatorios, sino cronológicos: Lending Club agregó variables en distintas fechas, y los préstamos anteriores quedan vacíos en esas columnas.

Marzo y agosto de 2012: empiezan a registrarse bc_util, mort_acc, tot_cur_bal, num_tl, etc. Diciembre de 2015: aparece el bloque open_acc_6m, il_util, inq_fi, all_util, etc. (38 % de nulos). 2017: aparecen las variables del segundo solicitante (sec_app), que solo se llenan en solicitudes conjuntas. 2014: desc deja de usarse. Las variables hardship_* y settlement_* están casi vacías en todo el periodo.

Parte 35. ¿Se concentran en ciertas columnas?

Se buscan dos cosas: grupos de columnas que tienen exactamente las mismas filas vacías, y la correlación de nulidad, que mide si dos columnas tienden a quedar vacías a la vez.

Grupos de columnas con EXACTAMENTE las mismas filas vacías: 11 (73 columnas)
Loading...
<Figure size 1980x1760 with 2 Axes>

Los nulos se concentran en bloques de columnas: 73 columnas forman 11 grupos con exactamente las mismas filas vacías. Los más grandes son:

16 variables de historial crediticio (3.1 %) 13 hardship_* (99.5 %) 10 sec_app_* (95.2 %) 8 variables introducidas en 2015 (38.3 %) La correlación de nulidad lo confirma: dentro de cada bloque los indicadores de nulo tienen r ≈ 1. En consecuencia, en el preprocesamiento basta con un indicador de faltante por bloque, no uno por columna.

Parte 36. ¿Se concentran en ciertas filas?

Loading...
Loading...
% medio de nulos por fila según application_type:
application_type
Individual   32.5000
Joint App    19.0000
<Figure size 1650x528 with 2 Axes>

Ningún préstamo está completo: el mínimo es 3.3 % de columnas vacías y la mediana 29.1 %. El histograma tiene varios picos (≈ 28 %, ≈ 38 % y ≈ 60 %) que corresponden a las “generaciones” de préstamos: los de 2007–2011 tienen 60 % de columnas vacías, los de 2013–2015 el 37 % y los de 2016–2018 el 27 %.

Las solicitudes conjuntas tienen menos nulos (19.0 % frente a 32.5 %), porque llenan las columnas del segundo solicitante. Eliminar filas con muchos nulos no es viable: se perderían casi todos los préstamos antiguos y se sesgaría la muestra hacia los años recientes.

Parte 37. Relación con default (prueba de MCAR)

MCAR: el nulo aparece por puro azar. Si fuera así, la tasa de default debería ser la misma cuando el dato falta que cuando está presente. Para cada columna se hace una chi-cuadrado entre el indicador de nulo y default: si p < 0.05, se rechaza MCAR.

Se prefirió esta prueba por variable en lugar de la prueba global de Little, porque con 2.26 M de filas la de Little rechaza cualquier desviación mínima y no dice qué variable es la problemática.

Variables evaluadas: 112  ·  MCAR rechazado: 101
Loading...
<Figure size 1100x990 with 1 Axes>

Se evaluaron 112 variables y se rechaza MCAR en 101. Las 11 compatibles con MCAR tienen muy pocos nulos. Los datos no faltan al azar: el mecanismo es principalmente MAR, dependiente de la fecha de emisión (Parte 34).

Esto se ve en la dirección de las diferencias. Las variables que se empezaron a registrar después de 2012 o 2015 tienen más default cuando faltan: el bloque de historial crediticio de 2012 (tot_cur_bal y 19 más), +2.9 pp; open_acc_6m y su bloque de 2015, +8.5 pp; il_util, +6.5 pp. Los préstamos donde faltan son los antiguos, que ya terminaron y tuvieron tiempo de caer en default, mientras que los recientes (con el dato) siguen vigentes y cuentan como clase 0. Además, que falte emp_length aumenta el default en 2.7 pp (14.4 % frente a 11.7 %), una señal propia del solicitante.

Como el faltante es informativo, se conservará con indicadores de faltante (uno por bloque) y la categoría “Missing”, en lugar de imputar sin dejar rastro.

Parte 38. Plan de tratamiento por variable

Reglas, en orden de prioridad:

Eliminar: identificadores y texto libre; información posterior al otorgamiento (fuga); más de 70 % de nulos. Numéricas: mediana (por la asimetría vista en la Parte 14). Se agrega un indicador de faltante si hay más de 30 % de nulos o si el faltante cambia la tasa de default en ≥ 1 pp (con al menos 1,000 casos). Categóricas: categoría “Missing” si el faltante es informativo; si no, la moda.

Loading...
Loading...
Loading...
<Figure size 990x440 with 1 Axes>

Decisiones finales para el preprocesamiento

El plan anterior resume el tratamiento de los nulos (66 variables a eliminar; 53 con mediana + indicador; 11 con mediana; 1 con categoría “Missing”; 1 con moda; 19 sin tratamiento). A continuación se consolidan todas las decisiones del EDA.

#TemaDecisiónOrigen
1Variable objetivodefault según la regla del enunciado (Charged Off = 1). Desbalance de 7.4 : 1 → pesos por clase, partición estratificada y métricas AUC-ROC, AUC-PR, F1 y recall. Sin undersamplingParte 21
2Sesgo temporalCrear issue_year (año de emisión) como variable de control; interpretar con cautela initial_list_status y application_typePartes 21, 26 y 37
3Fuga de informaciónEliminar las 40 variables posteriores al otorgamiento (pagos, recuperaciones, last_fico_*, hardship_*, settlement_*, fechas de pago)Partes 24, 26 y 38
4Identificadores y textoEliminar id, member_id, url, desc, title, emp_title y zip_codePartes 19 y 38
5Más de 70 % de nulosEliminar las 16 variables del segundo solicitante (sec_app_*, *_joint)Parte 38
6mths_since_last_record, mths_since_recent_bc_dlq, mths_since_last_major_derog (74–84 % de nulos)No se eliminan: se convierten en indicadores binarios (“tuvo el evento: sí / no”), porque el nulo significa que el evento nunca ocurrióPartes 37 y 38
7Variables sin informaciónEliminar policy_code (constante) y disbursement_method (96.5 % Cash; asociación explicada por la fecha)Partes 7, 26 y 27
8RedundanciasEliminar funded_amnt, installment, fico_range_low y grade. En cada uno de los 22 bloques de la Parte 29 se conserva una sola variablePartes 29–31
9int_rate frente a sub_grade (η = 0.98)Modelos lineales (LogisticRegression, LinearSVC, NaiveBayes): solo int_rate. Modelos de árboles: ambasParte 31
10Imputación de numéricasMediana; más un indicador por bloque si hay más de 30 % de nulos o si el faltante es informativo. En las mths_since_* restantes, el nulo se imputa con un valor alto (nunca ocurrió)Parte 38
11emp_lengthUsar emp_length_num (0–10) más un indicador emp_length_faltantePartes 9, 27 y 37
12Valores imposiblesdti < 0 o = 999 → nulo e imputar; annual_inc = 0 → nulo; revol_util > 150 % → acotarPartes 11 y 23
13Asimetríalog1p o Yeo-Johnson en las variables con asimetría fuerte, y escalado estándar, para los modelos lineales y NaiveBayes. Los árboles no lo requierenParte 14
14pub_recConvertir en binaria (tiene o no registros públicos)Parte 23
15Categóricashome_ownership: unificar ANY, NONE y OTHER. purpose: agrupar las categorías < 1 %. addr_state: agrupar por región o target encoding. sub_grade y grade: codificación ordinal. term: binaria. verification_status: one-hotPartes 17, 19, 26 y 27
16Fechasissue_d → issue_year; earliest_cr_line → antigüedad del historial crediticio en añosParte 16

Resumen ejecutivo

Resumen ejecutivo — Análisis exploratorio de Lending Club

Resumen ejecutivo — Análisis exploratorio de Lending Club (2007–2018)

Mensajes clave

  1. Una cartera grande y en crecimiento: 2.26 millones de préstamos y 34,000 millones de USD financiados. El volumen anual pasó de 603 préstamos en 2007 a casi medio millón en 2018.

  2. El 11.9 % de los préstamos terminó en default (Charged Off), con una pérdida neta de capital estimada en ≈ 1,950 millones de USD. Es un límite inferior: el 38.9 % de la cartera sigue vigente y parte de ella aún podría caer en default.

  3. El cliente típico pide 10,000–15,000 USD para refinanciar deudas (79 %), gana ≈ 65,000 USD al año, tiene un FICO cercano a 694 y lleva más de 10 años en su empleo.

  4. El riesgo está concentrado y es predecible: el grado del préstamo y la tasa de interés separan claramente a los buenos de los malos pagadores. En el grado G, el 37.5 % de los préstamos cae en default; en el grado A, solo el 3.3 %.

  5. El dataset requiere una depuración importante: de 151 variables, 66 se eliminan (40 por fuga de información, 19 por exceso de nulos y 7 identificadores) y 65 requieren imputación.


1. El dataset en cifras

IndicadorValor
Préstamos2,260,668 (tras eliminar 33 filas de totales del archivo)
Variables151 (113 numéricas, 37 categóricas o de texto, 1 identificador)
PeriodoJunio de 2007 – diciembre de 2018
Monto total financiado34,000 millones de USD
Estado de la cartera47.6 % pagados · 38.9 % vigentes · 11.9 % en default · 1.5 % en mora
Variable objetivodefault = 1 si Charged Off (268,559); 0 en otro caso (1,992,109)
Tasa de default11.88 % → desbalance de clases de 7.4 : 1

Crecimiento de la cartera

Año200920112013201520172018
Préstamos5,28121,721134,814421,095443,579495,242
Monto (millones de USD)522621,9836,4186,5857,936

2. ¿Quién pide los préstamos?

Montos: el más frecuente es 10,000 USD (8.3 %), seguido de 20,000, 15,000 y 12,000 USD. Mediana de 12,900 USD, media de 15,047 USD y máximo de 40,000 USD. El 63 % está entre 5,000 y 20,000 USD. El 71 % es a 36 meses.

Propósito: consolidación de deudas 56.5 %, tarjeta de crédito 22.9 %, mejoras del hogar 6.7 %, otros 6.2 %, compra importante 2.2 %. → 8 de cada 10 clientes refinancian deudas existentes.

Ocupación: las más frecuentes son docentes (48,460), gerentes (45,852), dueños de negocio (33,591), enfermeras (≈ 40,500) y conductores (≈ 35,000). La base es muy diversa: las 10 principales suman solo el 11.5 %. El 33 % lleva 10 años o más en su empleo; el 6.5 % no lo declara.

Situación financiera

IndicadorValor típico
Ingreso anualMediana de 65,000 USD; el 48 % gana entre 40,000 y 80,000
Vivienda49 % con hipoteca, 40 % alquila, 11 % propietario sin hipoteca
FICOMediana de 694
Deuda / ingreso (DTI)Mediana de 17.8 %
Uso del cupo de tarjetasMediana de 50 %
Historial crediticioMediana de 14.8 años
Antecedentes negativos18.6 % con moras en los últimos 2 años; 15.8 % con registros públicos; 12 % con alguna bancarrota

Ubicación: California (13.9 %), Nueva York (8.2 %), Texas (8.2 %) y Florida (7.2 %) concentran el 38 %.

Calidad crediticia: la mayoría está en los grados B y C (29 % cada uno). La tasa de interés media es 13.1 %: va de 7.2 % (A) a 28.2 % (G).


3. ¿Quién no paga? Segmentos de riesgo

SegmentoTasa de defaultFrente a la media (11.9 %)
Subgrado G3 / grado G39.6 % / 37.5 %× 3.2
Grado F34.7 %× 2.9
Grado E26.6 %× 2.2
Grado D18.8 %× 1.6
Propósito pequeño negocio18.6 %× 1.6
Plazo de 60 meses16.2 % (frente a 10.1 % a 36 meses)× 1.4
Ingreso verificado15.9 % (frente a 8.0 % no verificado)× 1.3
Vive en alquiler13.9 % (frente a 10.3 % con hipoteca)× 1.2
Grado A3.3 %× 0.3
  • Los subgrados de C5 a G5 reúnen el 28 % de la cartera, con tasas de 15 % a 40 %.

  • Hallazgo contraintuitivo: los ingresos verificados incumplen más, porque Lending Club verifica a quienes considera más riesgosos.

  • Pérdidas: los préstamos en default recibieron 4,178 millones de USD y recuperaron 2,227 millones (incluidos 325 millones de cobranza): una pérdida neta de ≈ 1,950 millones de USD.

  • Precaución con los años recientes: la tasa observada cae a 8.8 % en 2017 y 1.8 % en 2018, pero no porque los préstamos sean mejores: el 59 % y el 86 % de ellos siguen vigentes. Las tasas por año solo son comparables hasta 2015.


4. Variables más relevantes para predecir el default

TipoVariableFuerzaLectura de negocio
Numéricaint_rater = 0.20 (la mayor)La tasa ya refleja el riesgo asignado
Categóricasub_grade / gradeV = 0.23 / 0.22Gradiente de 1.6 % a 39.6 %
Numéricafico_range_highr = −0.12A menor puntaje, más riesgo
Categóricaverification_statusV = 0.10Los verificados tienen más riesgo
Numéricaacc_open_past_24mths, inq_last_6mths, num_tl_op_past_12mr ≈ 0.08–0.09Abrir cuentas o pedir crédito recientemente es una señal de alerta
Numéricabc_open_to_buy, revol_util, bc_utilr ≈ 0.07–0.08Menos crédito disponible y más uso del cupo → más riesgo
Categóricaterm, home_ownership, purpose, addr_state, emp_lengthV = 0.03–0.08Débiles, pero con segmentos de alto riesgo

Ninguna variable predice el default por sí sola (la mejor explica el 4 % de la variabilidad): el modelo deberá combinar varias.


5. Plan de depuración de variables

Se eliminan 66 variables:

  • 40 de fuga de información: pagos (total_pymnt, total_rec_prncp, last_pymnt_amnt…), recuperaciones (recoveries), puntajes posteriores (last_fico_*), acuerdos de pago (debt_settlement_flag, settlement_*) y planes de alivio (hardship_*). Por ejemplo, debt_settlement_flag = Y tiene 97 % de default, porque el acuerdo solo existe después del impago.

  • 19 con más de 70 % de nulos: 16 del segundo solicitante (sec_app_*, *_joint) y 3 de historial negativo, que se transformarán en indicadores binarios en lugar de perderse.

  • 7 identificadores o texto libre: id, member_id, url, desc, title, emp_title y zip_code.

Además, se descartan policy_code (constante) y disbursement_method, y las redundantes (funded_amnt, installment, fico_range_low, grade).

Se imputan 65 variables:

SituaciónVariablesTratamiento
Entre 30 % y 70 % de nulos16 (bloque de 2015 con 38 %, mths_since_last_delinq con 51 %, il_util…)Mediana + indicador
Menos de 30 %, con faltante informativo37 (bloque de historial de 2012, ≈ 3 %)Mediana + indicador (uno por bloque)
Menos de 0.1 % de nulos11 (dti, revol_util, annual_inc…)Mediana
emp_length (6.5 %)1Categoría “Missing” (o emp_length_num + indicador)
earliest_cr_line (29 nulos)1Moda

Los nulos no son aleatorios (se rechazó MCAR en 101 de 112 variables): dependen de la fecha de emisión, porque Lending Club incorporó variables en 2012, 2015 y 2017. Por eso no se eliminan filas.


6. Consideraciones para el modelado

AspectoAcción
Desbalance de 7.4 : 1Pesos por clase, partición estratificada y métricas AUC-ROC, AUC-PR, F1 y recall; no accuracy
Sesgo temporal de la etiquetaIncluir el año de emisión; interpretar con cautela las variables asociadas a préstamos recientes
Asimetría (84 variables)log1p o Yeo-Johnson para los modelos lineales y NaiveBayes
Valores imposiblesdti = −1 / 999, annual_inc = 0, revol_util = 892 % → tratamiento puntual
Variables ordinalessub_grade y grade con codificación ordinal
Alta cardinalidadaddr_state agrupada por región o con target encoding

7. Recomendaciones de negocio a un gerente

  1. Segmentar por grado: los grados D a G combinan tasas de default de 19 % a 38 %; conviene exigir más garantías o ajustar la tasa.

  2. El perfil ideal para promocionar: grados A–B, con hipoteca, ingreso no verificado por riesgo bajo, a 36 meses y para consolidar tarjetas de crédito. Estos perfiles tienen tasas de default por debajo del 8 %.

  3. Revisar la política para pequeños negocios: con 18.6 %, es el propósito de mayor riesgo.

  4. Hacer obligatorio el dato laboral: los clientes que no declaran su antigüedad incumplen más (14.4 % frente a 11.9 %).

  5. Vigilar la cartera reciente: los préstamos de 2016–2018 aún no muestran su default real; su riesgo está subestimado en las cifras actuales.

Preprocesamiento

9.10.4.2 Configuración común

Overwriting config_modelado.py

9.10.4.2 Importaciones y hardware

Loading...

9.10.4.2.1 Partición común de los datos

Loading...
Población: completa | filas: 2,260,668 | train: 1,808,534 | test: 452,134
Partición guardada en: E:\Documents\MachineLearningUN\results\split_assignment.parquet
⚠ El enunciado menciona ~260 mil observaciones de prueba; con POBLACION='completa' el conjunto de prueba tiene 452,134. Ver POBLACION en config_modelado.py.

9.10.4.2.2 scikit-learn

X_train: (1808534, 73) | X_test: (452134, 73)
Tasa de default — train: 0.1188 | test: 0.1188
Pesos por clase (calculados solo con train): {0: 0.5674056448976493, 1: 4.208888185545993}
Tiempo de preprocesamiento (carga + variables + matrices): 33.7 s
Validación cruzada: 3 pliegues (StratifiedKFold)

9.10.4.3 Modelos y espacios de búsquedas scikit-learn

Loading...
Loading...

9.10.4.4 Modelado con scikit-learn

[regresion_logistica] se cargan resultados existentes (FORZAR_REENTRENAMIENTO=False)
Loading...
[arbol_decision] se cargan resultados existentes (FORZAR_REENTRENAMIENTO=False)
Loading...
Fitting 3 folds for each of 9 candidates, totalling 27 fits
<Figure size 420x360 with 2 Axes>
Loading...
Fitting 3 folds for each of 4 candidates, totalling 12 fits
<Figure size 420x360 with 2 Axes>
Loading...
Fitting 3 folds for each of 3 candidates, totalling 9 fits
c:\miniconda3\envs\ml_venv\lib\site-packages\sklearn\svm\_base.py:1242: ConvergenceWarning: Liblinear failed to converge, increase the number of iterations.
  warnings.warn(
<Figure size 420x360 with 2 Axes>
Loading...
Fitting 3 folds for each of 1 candidates, totalling 3 fits
<Figure size 420x360 with 2 Axes>
Loading...
Loading...
<Figure size 2400x360 with 6 Axes>

9.10.4.7 Interpretabilidad con LIME scikit-learn

Modelo con mayor AUC (entre los que entregan probabilidades): Gradient boosting
Instancias mal clasificadas seleccionadas:
Loading...
<Figure size 640x480 with 1 Axes>
sklearn_gradient_boosting_falso_negativo_129517267 | score del modelo: 0.028726066433195904 | probabilidad LIME local: [-0.07620753]
Loading...
<Figure size 640x480 with 1 Axes>
sklearn_gradient_boosting_falso_positivo_93750575 | score del modelo: 0.9195012282936882 | probabilidad LIME local: [0.73615514]
Loading...
Tiempo LIME (scikit-learn): 19.2 s

9.10.4.2.3 PySpark sesión y configuración

Loading...

9.10.4.2.3 PySpark lectura, variables, partición común y caché

Train: 1,808,534 | Test: 452,134 | features: 101
Pesos por clase (calculados solo con train): {0: 0.5674056448976493, 1: 4.208888185545993}
Tiempo de preprocesamiento Spark (lectura + variables + ajuste + caché): 89.4 s

9.10.4.3 Modelos y espacios de búsqueda PySpark

Loading...

9.10.4.5 Modelado con PySpark

[regresion_logistica] se cargan resultados existentes (FORZAR_REENTRENAMIENTO=False). El modelo no queda en memoria para LIME.
Loading...
[arbol_decision] se cargan resultados existentes (FORZAR_REENTRENAMIENTO=False). El modelo no queda en memoria para LIME.
Loading...
<Figure size 420x360 with 2 Axes>
Loading...
<Figure size 420x360 with 2 Axes>
Loading...
<Figure size 420x360 with 2 Axes>
Loading...
<Figure size 420x360 with 2 Axes>
Loading...
Loading...
<Figure size 2400x360 with 6 Axes>

9.10.4.7 Interpretabilidad con LIME PySpark

Modelo con mayor AUC (entre los que entregan probabilidades): Gradient boosting
Filas de entrenamiento transferidas para LIME: 5,000 de 1,808,534 (6.7 s)
Instancias mal clasificadas seleccionadas:
Loading...
<Figure size 640x480 with 1 Axes>
spark_gradient_boosting_falso_negativo_139794277 | score del modelo: 0.02647736298060166 | probabilidad LIME local: [0.04338035]
Loading...
<Figure size 640x480 with 1 Axes>
spark_gradient_boosting_falso_positivo_93750575 | score del modelo: 0.9367038144555448 | probabilidad LIME local: [0.74484798]
Loading...
Tiempo LIME (PySpark, incluye transferencias pequeñas): 19.3 s
DataFrame[id: bigint, default: int, features: vector]

9.10.4.6 Comparación estadística de los modelos

Observaciones de prueba comunes: 452,134 | positivos: 53,712 | tasa: 0.1188

9.10.4.6.1 Prueba de DeLong

Loading...

library(pROC)
d <- read.csv("delong_validacion_datos.csv")
r1 <- roc(d$y, d$s_a, direction = "<", quiet = TRUE)
r2 <- roc(d$y, d$s_b, direction = "<", quiet = TRUE)
roc.test(r1, r2, method = "delong", paired = TRUE)

Resultado Python para comparar con pROC (caso 0): {'auc_1': 0.704841219907944, 'auc_2': 0.6834994536242922, 'z': 0.7844830928563521, 'p_valor': 0.43275669874417344}
Número de comparaciones por familia:
Loading...
Loading...
Loading...
<Figure size 1500x580 with 4 Axes>
<Figure size 1500x580 with 4 Axes>
<Figure size 1400x260 with 4 Axes>

9.10.4.6.2 Pruebas complementarias_ McNemar y bootstrap pareadp

Loading...
Par con mayor AUC por entorno: {'sklearn': ['Gradient boosting', 'Bosque aleatorio'], 'spark': ['Gradient boosting', 'Bosque aleatorio']}
statsmodels no está instalado: se usa la implementación manual (fórmula del enunciado).
Loading...
Diferencias máximas frente a scikit-learn con sample_weight: {'dif_auc': 1.1102230246251565e-16, 'dif_auc_pr': 0.0, 'dif_f1': 0.0}
Regresión logística: scikit-learn vs PySpark: 172.5 s
Árbol de decisión: scikit-learn vs PySpark: 66.6 s
Bosque aleatorio: scikit-learn vs PySpark: 159.9 s
Gradient boosting: scikit-learn vs PySpark: 157.2 s
SVM lineal: scikit-learn vs PySpark: 157.1 s
Naive Bayes: scikit-learn vs PySpark: 166.0 s
Gradient boosting vs Bosque aleatorio (scikit-learn): 173.4 s
Gradient boosting vs Bosque aleatorio (PySpark): 171.5 s
Loading...
Loading...
Comparaciones con discrepancia DeLong vs bootstrap (AUC): 0
Loading...

9.10.4.7 Interpretabilidad con LIME resultados de ambos entornos

Explicaciones LIME encontradas: 4
sklearn_gradient_boosting_falso_negativo_129517267.png
<IPython.core.display.Image object>
Loading...
sklearn_gradient_boosting_falso_positivo_93750575.png
<IPython.core.display.Image object>
Loading...
spark_gradient_boosting_falso_negativo_139794277.png
<IPython.core.display.Image object>
Loading...
spark_gradient_boosting_falso_positivo_93750575.png
<IPython.core.display.Image object>
Loading...

9.10.4.8 Comparación de resultados

Loading...
Loading...
Loading...
<Figure size 750x650 with 1 Axes>
<Figure size 750x650 with 1 Axes>
<Figure size 1500x500 with 2 Axes>
Loading...
<Figure size 900x500 with 1 Axes>
<Figure size 2400x720 with 12 Axes>
<IPython.core.display.Image object>
<IPython.core.display.Image object>
<IPython.core.display.Image object>
Loading...

CONCLUSIONES

Análisis general de los modelos

El mejor modelo en los dos entornos fue gradient boosting: AUC de 0.7728 en scikit-learn y 0.7735 en PySpark, con un AUC-PR cercano a 0.30. Le siguen el bosque aleatorio (≈0.763), el árbol de decisión (≈0.757), la regresión logística y el SVM lineal, que quedaron prácticamente empatados (≈0.7455), y al final Naive Bayes (0.681). Ningún modelo pasa de 0.78, y creemos que eso refleja lo difícil del problema más que una mala configuración: con la información disponible al momento de la solicitud, predecir el incumplimiento tiene un techo.

Con el umbral fijo de 0.5 y los pesos por clase, todos los modelos tienen un recall alto (0.66–0.76) pero una precisión baja (≈0.22). Es decir, detectan la mayoría de los incumplimientos a costa de muchos falsos positivos.

Cada modelo tiene su uso:

Gradient boosting es el más útil para ordenar solicitudes por riesgo (scoring). Bosque aleatorio tiene la mejor exactitud y el mejor F1 con el umbral de 0.5, así que sirve si lo que importa es la decisión de aprobar o rechazar. Regresión logística queda solo 0.027 de AUC por debajo del gradient boosting, es la más rápida de entrenar y es interpretable, lo que en crédito pesa mucho por temas regulatorios. Árbol de decisión da reglas fáciles de explicar, pero es inestable: con profundidad 15 se sobreajustó en ambos entornos (AUC en validación cruzada de 0.72). SVM lineal no aporta nada sobre la logística: rinde igual, tarda más y no entrega probabilidades. Naive Bayes sirve como línea base. Queda muy abajo porque su supuesto de independencia no se cumple: variables como int_rate y sub_grade están muy correlacionadas. Problemas durante la ejecución. En scikit-learn, el GridSearchCV del bosque aleatorio con n_jobs=-1 se detuvo por MemoryError. Cada proceso en paralelo trabaja con su propia copia de los datos de entrenamiento (≈1.8 M de filas), y con 16 procesos la RAM de 32 GB no alcanzó, así que se bajó a n_jobs=3.

En PySpark también hubo problemas de memoria. El árbol de decisión tumbó la JVM y el bosque aleatorio falló con java.lang.OutOfMemoryError: Java heap space al deserializar el modelo (100 árboles de profundidad 15). En modo local todo corre en el proceso del driver, así que spark.executor.memory no tiene ningún efecto. Por eso:

se subió spark.driver.memory de 8 GB a 14 GB y luego a 18 GB; se limitó el paralelismo a local[8]; se bajó maxMemoryInMB de los árboles a 256. La regresión logística y el árbol se entrenaron antes de ese último cambio, así que sus tiempos no se midieron exactamente con la misma configuración que los demás.

Para futuras iteraciones el grupo usará computadores disponibles en el bloque K piso 3 con mayor potencia.

¿Qué entorno fue más rápido y por qué?

scikit-learn fue claramente más rápido. La validación cruzada de todos los modelos tomó unas 3.0 horas (10,816 s), contra 10.8 horas (38,873 s) en PySpark, o sea unas 3.6 veces más. También fue más rápido en el preprocesamiento (34 s contra 89 s) y en la predicción (12 s contra 538 s).

La razón principal es que PySpark corrió en una sola máquina y no había ningún clúster sobre el cual repartir el trabajo. Así, solo pagamos los costos de Spark: el arranque de la JVM, la comunicación entre Python y Java (Py4J), la serialización de datos y de modelos, y la planificación de tareas. Además, las 400 particiones que exige la configuración son muchas para 8 núcleos: quedan tareas muy pequeñas y cada iteración paga el costo de planificarlas. Por eso la regresión logística, que recorre los datos muchas veces, fue 8.8 veces más lenta en Spark.

El caché ayudó: sin él, cada pliegue y cada iteración habría vuelto a leer y transformar el CSV. Aun así, no alcanzó para compensar lo demás.

El CrossValidator multiplica el costo, porque cada combinación de la grilla se entrena 3 veces y al final se reentrena el mejor modelo. Con 9 combinaciones, el bosque aleatorio necesitó 28 entrenamientos. Por eso fue el más lento en Spark (5 horas), y en la predicción se sumaron otros 526 s por el tamaño del modelo.

La brecha más pequeña se dio en gradient boosting (2.2 veces) y SVM (2.4 veces). El GradientBoostingClassifier de scikit-learn construye cada árbol con un solo núcleo, mientras que Spark reparte el cálculo de los histogramas entre los 8 núcleos. Cuando scikit-learn no puede paralelizar, Spark se acerca bastante.

¿Cuál fue más preciso?

En la práctica ninguno. Las AUC de los dos entornos coinciden hasta la tercera o cuarta cifra decimal en todos los modelos. PySpark quedó levemente arriba en gradient boosting (+0.0007) y en el bosque (+0.0004), y scikit-learn en el árbol de decisión (+0.0018).

Donde sí hay diferencias es entre modelos: gradient boosting es el más preciso en ambos entornos para el AUC y el AUC-PR, y el bosque aleatorio lo es para el F1 y la exactitud con el umbral de 0.5.

¿Qué diferencias en el AUC son significativas y cuáles son relevantes?

Entre entornos. Después de la corrección de Holm, DeLong encontró diferencias significativas en el árbol, el bosque, gradient boosting y Naive Bayes. La logística y el SVM no fueron significativas. Pero ninguna de estas diferencias es relevante en la práctica: la más grande es la del árbol (0.0018), y el margen que fijamos es de 0.005. La de Naive Bayes es de 0.0000012 y aun así salió significativa, lo que muestra que con 452 mil observaciones de prueba casi cualquier diferencia termina siendo “significativa”.

Entre modelos. Casi todas las diferencias son significativas y relevantes. Las excepciones son:

logística contra SVM (0.0002): significativa pero sin importancia práctica; árbol contra bosque en scikit-learn (0.0049): queda justo por debajo del margen, aunque en Spark sí lo supera (0.0071). La diferencia entre gradient boosting y el bosque (≈0.010) es significativa y relevante en los dos entornos.

¿Qué diferencias de implementación explican las discrepancias?

Árboles. Spark discretiza las variables continuas en maxBins=32 intervalos antes de buscar los cortes, mientras que scikit-learn evalúa todos los cortes posibles. Eso explica que el árbol individual de scikit-learn sea algo mejor (+0.0018 de AUC y +0.008 de AUC-PR). También explica que, aunque el AUC casi no cambie, los dos árboles clasifiquen distinto 27,506 préstamos (6 % del conjunto de prueba).

En los ensambles pasa lo contrario: Spark queda un poco arriba. Creemos que al promediar muchos árboles, la pérdida por discretizar se compensa e incluso funciona como una regularización. También hay diferencias de fondo:

en el bosque, Spark hace el muestreo bootstrap con una distribución de Poisson; en el boosting, scikit-learn ajusta el valor de cada hoja con un paso de Newton, que Spark no hace. LinearSVC. scikit-learn usa por defecto la pérdida hinge al cuadrado con liblinear, y además penaliza el intercepto. Spark usa la pérdida hinge estándar con el optimizador OWL-QN. Aun así, las AUC quedaron iguales (diferencia de 0.000002) y McNemar no encontró diferencias, así que en este caso la función de pérdida no se tradujo en ninguna discrepancia. En scikit-learn además apareció un ConvergenceWarning con max_iter=1000.

Regularización. Pusimos las dos grillas en la misma escala con la equivalencia C = 1/(λ·n), y los dos entornos eligieron valores equivalentes: C = 0.553 ↔ λ = 10⁻⁶ en la logística, y C = 0.055 ↔ λ = 10⁻⁵ en el SVM. Las grillas salieron casi planas: con 1.8 millones de filas, la regularización casi no cambia nada.

Naive Bayes. Dio predicciones idénticas en los dos entornos, sin ningún préstamo discordante. La diferencia mínima en el AUC es solo numérica.

Limitaciones de DeLong y complemento con McNemar y bootstrap

DeLong tiene varias limitaciones en este diseño:

Solo mide la variabilidad del conjunto de prueba, que es uno solo. No incluye la variabilidad del entrenamiento (la semilla, la partición o los pliegues), así que subestima la incertidumbre real al comparar dos entornos. Con n = 452 mil tiene tanta potencia que diferencias de 10⁻⁶ salen significativas. Por eso fue necesario el margen de relevancia. Solo compara el AUC, es decir, la capacidad de ordenar, y no dice nada de las decisiones con un umbral. Con tantas comparaciones, hay que corregir por multiplicidad. McNemar complementa lo último, porque compara los aciertos y errores con el umbral fijo. Ahí apareció algo que el AUC no muestra: entre gradient boosting y el bosque, el bosque acierta en 27,921 casos donde gradient boosting falla, y lo contrario ocurre en solo 10,143. Así, gradient boosting ordena mejor el riesgo, pero con el umbral de 0.5 el bosque clasifica mejor. El bootstrap pareado lo confirma con el F1: la diferencia es negativa para gradient boosting.

El bootstrap además es no paramétrico y da intervalos para el AUC, el AUC-PR y el F1.

¿Coinciden las tres pruebas? Para el AUC, DeLong y bootstrap coincidieron en las 8 comparaciones. McNemar coincide en la significancia entre entornos, excepto en Naive Bayes (DeLong es significativo, pero no hay ni un solo préstamo discordante). Entre gradient boosting y el bosque, las tres pruebas dan significativo, pero no apuntan en la misma dirección.

¿A partir de qué volumen de datos PySpark supera a scikit-learn?

Con nuestro experimento no lo podemos determinar, porque solo medimos los tiempos con el volumen completo. Con 2.26 millones de préstamos en una sola máquina de 16 hilos y 32 GB, scikit-learn fue más rápido en todos los modelos.

En modo local, lo que decide no es tanto el número de filas sino la memoria. Spark empieza a tener ventaja cuando los datos (más las copias que hace cada proceso) ya no caben en la RAM de un equipo, o cuando hay un clúster real. El MemoryError con n_jobs=-1 muestra que ya estábamos cerca de ese límite para scikit-learn. La brecha más chica en gradient boosting también sugiere que la ventaja de Spark aparecería primero en los modelos que scikit-learn no puede paralelizar.

¿Qué aporta LIME y cuáles son sus limitaciones en entornos distribuidos?

LIME permitió revisar casos puntuales de gradient boosting y ver que los dos entornos se apoyan en las mismas variables. Para el mismo falso positivo (préstamo 93750575), en los dos pesan sobre todo sub_grade_ord > 15 y que el año de emisión esté entre 2015 y 2016.

También dejó ver algo importante: issue_year > 2017 empuja mucho hacia “no incumple”. Eso se debe a que muchos préstamos recientes seguían vigentes y la regla del target los cuenta como 0. Es un efecto de censura, no una relación real, y sin LIME no lo habríamos notado.

Sus limitaciones:

Es local e inestable: depende del muestreo aleatorio y del ancho del kernel. Perturba cada variable por separado, sin tener en cuenta que están correlacionadas. En Spark no es distribuido: tuvimos que traer al driver una muestra de 5,000 filas, y cada llamada a la función de predicción crea un DataFrame de Spark con las perturbaciones, lo que es lento. Explicar muchos casos en Spark no escala.

¿Qué efecto tuvo cada condición obligatoria en el rendimiento?

400 particiones de shuffle y paralelismo por defecto de 400. Para 8 núcleos generan muchas tareas pequeñas. Probablemente es la causa principal de la lentitud de la logística y el SVM, que recorren los datos muchas veces. No hicimos una prueba aislada de este efecto, así que es una inferencia. spark.executor.memory = 8 GB. No tuvo ningún efecto en modo local. Los 8 GB del driver no alcanzaron y hubo que subirlos a 18 GB para los árboles. memory.fraction 0.8 y storageFraction 0.3. Dejan la mayor parte de la memoria para ejecución, lo que ayuda a los árboles. Lo que no cabe en el caché se va a disco (MEMORY_AND_DISK). Caché después del VectorAssembler. Fue la condición que más ayudó, porque evitó releer y transformar el CSV en cada pliegue y en cada iteración. Validación cruzada con 3 pliegues y misma partición en ambos entornos. Multiplicaron el costo de entrenamiento entre 4 y 28 veces según la grilla, pero hicieron que las comparaciones fueran justas: los dos entornos se evaluaron sobre exactamente los mismos 452,134 préstamos de prueba. La población “completa” que pide la regla del target dejó más observaciones de prueba que las “unas 260 mil” del enunciado. Eso aumenta los tiempos y hace que las pruebas sean todavía más sensibles.