🎯 Proyecto de limpieza y análisis exploratorio de datos con Python

Objetivo principal: Identificar los factores clave que predicen el incumplimiento de pago mediante el análisis de datos financieros y sociodemográficos.
Acción propuesta: Desarrollar un modelo de machine learning para predecir la probabilidad de que un préstamo caiga en morosidad.
Propósito: Automatizar la evaluación del riesgo crediticio para instituciones financieras, permitiéndoles tomar decisiones más informadas y reducir su cartera vencida.
En este proyecto realicé la limpieza y preparación de un dataset de análisis de riesgo crediticio de clientes bancarios. El objetivo fue dejar los datos listos para futuros modelos de predicción de riesgo, corrigiendo valores nulos, duplicados y formatos inconsistentes. Entre los principales hallazgos destacan variables con alta cantidad de datos faltantes y patrones relevantes en el comportamiento crediticio.

1. Carga de Datos

- Se importaron librerías esenciales para manipulación, visualización y modelado: pandas, numpy, matplotlib y scikit-learn.
- Se cargó el conjunto de datos desde Google Drive utilizando Google Colab.

Librerías usadas

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

# Conexión con  la base de drive
from google.colab import drive
drive.mount('/content/drive')

# Creación de data frame 
datos_respaldo = pd.read_csv('drive/MyDrive/Colab Notebooks/Análisis de riesgo en banco/lendingclub.csv')
datos = datos_respaldo.copy()
pd.options.display.max_columns = None
datos.tail(2)

datos.info()

2. Limpieza y transformación de Datos

- Se eliminaron columnas con valores nulos y aquellas irrelevantes para el análisis.
- Se transformaron columnas categóricas como 'estado_prestamo'.
- Se limpia columna 'plazo_meses' y se transforma de string a int.

Tomar del df datos solo las columnas esenciales para el modelo

# Se omiten las columnas vacías y aquellas donde faltantes son mas del 60%

df_predict = datos[["loan_status", "loan_amnt", "funded_amnt", "int_rate", "installment", "term",
             "emp_length", "home_ownership", "annual_inc", "dti", "open_acc", "revol_bal",
             "revol_util", "delinq_2yrs", "inq_last_6mths"]]
df_predict.head(4)

df_predict = df_predict.rename(columns={
    "loan_status": "estados_prestamo",
    "loan_amnt": "monto_prestamo",
    "funded_amnt": "monto_financiado",
    "int_rate": "tasa_interes",
    "installment": "pago_mensual",
    "term": "plazo_meses",
    "emp_length": "anios_experiencia",
    "home_ownership": "tipo_vivienda",
    "annual_inc": "ingreso_anual",
    "dti": "relacion_deuda_ingreso",
    "open_acc": "cuentas_abiertas",
    "revol_bal": "saldo_tarjetas",
    "revol_util": "uso_credito",
    "delinq_2yrs": "moras_ultimos_2_anios",
    "inq_last_6mths": "consultas_credito_6_meses"
})

# Verificar cambios
df_predict.head(3)
df_predict.info()

df_predict["estados_prestamo"].unique()
df_predict["estado_prestamo"] = df_predict["estados_prestamo"].map({
    "Fully Paid": 1,
    "Does not meet the credit policy. Status:Fully Paid": 1,
    "Current": 1,
    "Charged Off": 0,
    "Default": 0,
    "Late (31-120 days)": 0,
    'In Grace Period': 0,
    "Late (16-30 days)": 0,
    "Does not meet the credit policy. Status:Charged Off": 0
})
df_predict["estado_prestamo"].unique()

# Limpieza del campo
df_predict['plazo_meses'] = df_predict['plazo_meses'].str.replace('months', '').astype(int)

# Ver los valores únicos
df_predict['plazo_meses'].unique()
df_predict['anios_experiencia'].unique()
# Quitar textos innecesarios
df_predict['anios_experiencia'] = df_predict['anios_experiencia'].str.replace('+ years', '') \
                                                             .str.replace('years', '') \
                                                             .str.replace('< 1 year', '0') \
                                                             .str.replace('year', '')

# Rellenar nulos con 0
df_predict['anios_experiencia'] = df_predict['anios_experiencia'].fillna(0).astype(float)

# Ver los valores únicos
df_predict['anios_experiencia'].unique()
df_predict.dropna(inplace=True)
- Se eliminaron columnas con valores nulos y aquellas irrelevantes para el análisis.
- Se transformaron columnas categóricas como 'estado_prestamo'.
- Se limpia columna  'plazo_meses' y se transforma de string a int.

3. Análisis Exploratorio (EDA)

- Se realizaron visualizaciones con seaborn y matplotlib:

Boxplots segmentados por estado del préstamo.

Gráficos de dispersión para observar relación entre deuda, ingresos y moras.

Histogramas de distribución de variables numéricas.
- Se detectaron outliers y sesgos en los datos.
- Se observó que factores como la relación deuda-ingreso, experiencia y propósito del crédito son relevantes.
df_predict.columns

Distribución de Montos y Pagos (Histograma); Relación Monto del Préstamo vs Ingreso Anual (Scatter Plot)

## Crear gráfico
fig, axs = plt.subplots(1, 2, figsize=(16, 6))

# Tema del gráfico
sns.set_theme(style="whitegrid", palette="pastel")

# Histogramas de prestamos, financiados y pago mensual en axs[0]
sns.histplot(df_predict['monto_prestamo'], color='blue', label='Monto Préstamo', alpha=0.6, bins=50, ax=axs[0])
sns.histplot(df_predict['monto_financiado'], color='green', label='Monto Financiado', alpha=0.4, bins=60, ax=axs[0])
sns.histplot(df_predict['pago_mensual'], color='red', label='Pago anual', alpha=0.4, bins=50, ax=axs[0])

# Configuración de ejes y título
axs[0].set_xlabel('Monto')
axs[0].set_ylabel('Frecuencia')
axs[0].set_title("Distribución de Montos y Pagos")

# Crear el scatterplot en axs[1]
sns.scatterplot(data = df_predict, x='monto_prestamo', y= 'ingreso_anual',
                ax=axs[1], hue= 'tasa_interes', palette="viridis",  size='pago_mensual',
                alpha=0.4, sizes=(20, 800))

# Ajustar los valores para que no muestre notación
axs[1].yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: '%1.0f' % x))

# Configuración de ejes y título para el gráfico de dispersión
axs[1].set_xlabel('Monto Préstamo')
axs[1].set_ylabel('Ingreso anual')
axs[1].set_title('Relación entre Monto de Préstamo e Ingreso Anual')

# Agregar la leyenda al subplot correspondiente
axs[0].legend()
axs[1].legend(loc='upper right')

# Mostrar gráfico
plt.show()

Distribución de Montos y Pagos (Histograma)

Distribución de Montos y Pagos (Histograma)

Observaciones:
– Los montos de préstamo y monto financiado presentan una distribución multimodal, es decir, hay varios picos o concentraciones de datos.
– Los valores más frecuentes de préstamos solicitados se encuentran en rangos alrededor de los $10,000 y $15,000.
– El monto del pago anual está concentrado en valores muy bajos (menos de $1,000), lo cual puede indicar que una gran parte de los créditos corresponde a pagos bajos o plazos muy largos.


Interpretación:
– Los clientes suelen solicitar préstamos en montos específicos posiblemente por políticas internas, montos máximos permitidos o preferencias del mercado.
– La mayor concentración de pagos bajos puede indicar que la mayoría de los clientes prefiere mensualidades accesibles.

Relación Monto del Préstamo vs Ingreso Anual (Scatter Plot)

Observaciones:
– Existe una concentración importante de puntos en ingresos anuales menores a $500,000 y montos de préstamo menores a $15,000.
– A mayor monto de préstamo solicitado, tiende a aumentar el ingreso anual del cliente, aunque no de manera proporcional.
– La tasa de interés no parece tener una relación directa con el ingreso anual o el monto del préstamo (hay colores de diferentes tasas distribuidos aleatoriamente).
– El tamaño de las burbujas (pago mensual) muestra que aunque algunos clientes tienen ingresos altos, prefieren mantener pagos mensuales bajos.


Interpretación:
– La mayoría de los clientes que solicitan créditos pertenecen a un perfil de ingresos medios o bajos.
– No todos los clientes con mayor ingreso solicitan los préstamos más altos, lo que puede deberse a estrategias de financiamiento o ahorro.
– La tasa de interés aplicada parece ser uniforme en los diferentes rangos de ingreso y préstamo, posiblemente definida por políticas estandarizadas.

# Crear gráfico
fig, axs = plt.subplots(1, 2, figsize=(16, 6))

# Tema del gráfico
sns.set_theme()

# Crear el scatterplot en axs[0]
sns.scatterplot(data = df_predict, x='monto_financiado', y= 'moras_ultimos_2_anios',
                hue = 'relacion_deuda_ingreso', size ='anios_experiencia', ax=axs[0],
                palette="viridis", alpha=0.3, sizes=(5, 400))

# Configuración de ejes y título
axs[0].set_xlabel('Monto financiado')
axs[0].set_ylabel('Moras en los ultimos 2 años')
axs[0].set_title("Distribución de uso de credito y saldos")

# Ajustar los valores para que no muestre notación
axs[0].yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: '%1.0f' % x))
#Rotar etiquetas del eje x
axs[0].set_xticklabels(axs[0].get_xticklabels(), rotation=45, ha='right')
# Agregar leyenda a la gráfico
axs[0].legend(loc='upper right')

# Crear el boxplot en axs[1]
sns.boxplot(data=df_predict, x= pd.cut(df_predict["anios_experiencia"], bins=[0, 3, 6, 10],
             labels=["0-3", "4-6", "7-10"]), linewidth=2,
               y='relacion_deuda_ingreso', hue='estado_prestamo', palette="viridis", ax=axs[1])

# Configuración de ejes y título
axs[1].set_xlabel('Años de experiencia')
axs[1].set_ylabel('Relación de deuda e ingreso')

# Obtener handles y labels después del boxplot
handles, labels = axs[1].get_legend_handles_labels()
axs[1].legend(handles, ["Deudor", "Al corriente"], title="Estado de prestamo", loc='lower right')

# Mostrar gráfico
plt.show()

Distribución de uso de crédito y moras (Gráfico de dispersión)

Distribución de uso de crédito y moras (Gráfico de dispersión)

Observaciones:
La mayoría de los clientes presentan pocas moras (entre 0 y 5) en los últimos 2 años.
– Los clientes con mayor número de moras (más de 10) suelen tener montos financiados más bajos.
– Los círculos de mayor tamaño (relación deuda/ingreso alta) se presentan principalmente en clientes con menos moras, lo que podría indicar que aunque el monto financiado es alto, mantienen buen comportamiento de pago.
– El color muestra los años de experiencia; no se observa una tendencia clara entre experiencia laboral y número de moras o monto financiado.


Interpretación:
– Los clientes con menor experiencia laboral o recién ingresados al mercado tienen montos de crédito más bajos.
– La morosidad elevada es más frecuente en clientes con montos de crédito reducidos.
– La experiencia laboral no garantiza menor número de moras.

Relación deuda-ingreso por años de experiencia (Boxplot)

Observaciones:
– La mediana de la relación deuda/ingreso es mayor en los clientes catalogados como deudores, sin importar los años de experiencia.
– Los clientes al corriente presentan menor relación deuda/ingreso.
– No hay una reducción significativa de la relación deuda/ingreso conforme aumentan los años de experiencia.
– Los valores atípicos (outliers) están presentes en todos los grupos de experiencia.


Interpretación:
– La experiencia laboral no garantiza un mejor manejo de la relación deuda-ingreso.
– Los clientes que se mantienen al corriente en sus pagos generalmente destinan una menor proporción de sus ingresos al pago de deuda.
– El riesgo crediticio no depende exclusivamente de la experiencia, sino del nivel de endeudamiento y comportamiento de pago.

df_predict.to_csv('drive/MyDrive/Colab Notebooks/Análisis de riesgo en banco/df_lendingclub_limpio.csv', index=False)