Exploración y preparación de datos de préstamo
Explorando los datos de crédito
# Libreria pandas
import pandas as pd
# Importación de los datos crediticios desde un archivo csv
cr_loan = pd.read_csv("DatosCrediticios.csv")
# Tabla interactiva de los datos de crédito en deepnote
cr_loan
# Libreria pyplot de matplotlib
import matplotlib.pyplot as plt
# Distribución de los montos de préstamo
patches = plt.hist(x=cr_loan['loan_amnt'], bins='auto', color='blue',alpha=0.7, rwidth=0.85)
plt.xlabel("Montos de préstamo")
plt.show()
# Diagrama de dispersión de ingresos vs edad
plt.scatter(cr_loan['person_income'], cr_loan['person_age'],c='blue', alpha=0.5)
plt.xlabel('Ingreso personal')
plt.ylabel('Edad')
plt.show()
Tabulaciones cruzadas y tablas dinámicas
# Tabla cruzada de la intención del préstamo y el estado del préstamo
print(pd.crosstab(cr_loan['loan_intent'], cr_loan['loan_status'], margins = True))
# Tabla cruzada de la propiedad de la vivienda, el estado del préstamo y el grado
print(pd.crosstab(cr_loan['person_home_ownership'],[cr_loan['loan_status'],cr_loan['loan_grade']]))
# Tabla cruzada de la propiedad de la vivienda, el estado del préstamo y el porcentaje de ingreso promedio
print(pd.crosstab(cr_loan['person_home_ownership'], cr_loan['loan_status'], values=cr_loan['loan_percent_income'], aggfunc='mean'))
# Create a box plot of percentage income by loan status
cr_loan.boxplot(column = ['loan_percent_income'], by = 'loan_status')
plt.title('Porcentaje del ingreso promedio por estado del préstamo')
plt.suptitle('')
plt.show()
Encontrar valores atípicos con tablas cruzadas
# Tabla cruzada para el estado del préstamo, la propiedad de la vivienda y la duración máxima del empleo
print(pd.crosstab(cr_loan['loan_status'],cr_loan['person_home_ownership'],
values=cr_loan['person_emp_length'], aggfunc='max'))
# Matriz de índices donde la duración del empleo sea superior a 60
indices = cr_loan[cr_loan['person_emp_length'] > 60].index
# Nuevo marco de datos sin registros atípicos
cr_loan_new = cr_loan.drop(indices)
# Tabla cruzada del nuevo marco de datos incluyendo la duración mínima del empleo
print(pd.crosstab(cr_loan_new['loan_status'],cr_loan_new['person_home_ownership'],values=cr_loan_new['person_emp_length'],aggfunc=('min','max')))
Visualización de valores atípicos de crédito
# Diagrama de dispersión para la edad y la cantidad
plt.scatter(cr_loan['person_age'], cr_loan['loan_amnt'], c='blue', alpha=0.5)
plt.xlabel("Person Age")
plt.ylabel("Loan Amount")
plt.show()
import matplotlib
# Marco de datos con personas de menos de 100 años de edad
cr_loan_new = cr_loan.drop(cr_loan[cr_loan['person_age'] > 100].index)
# Diagrama de dispersión de la edad y la tasa de interés
colors = ["blue","red"]
plt.scatter(cr_loan_new['person_age'], cr_loan_new['loan_int_rate'],
c = cr_loan_new['loan_status'],
cmap = matplotlib.colors.ListedColormap(colors),
alpha=0.5)
plt.xlabel("Person Age")
plt.ylabel("Loan Interest Rate")
plt.show()
Reemplazo de datos de crédito faltantes
# Matriz de columnas con valores nulos
print(cr_loan.columns[cr_loan.isnull().any()])
# Cinco filas superiores con valores nulos para la duración del empleo
print(cr_loan[cr_loan['person_emp_length'].isnull()].head())
# Reemplazo de los valores nulos con la media para la variable duraciones de empleo
cr_loan['person_emp_length'].fillna((cr_loan['person_emp_length'].median()), inplace=True)
# Histograma de la duración del empleo
n, bins, patches = plt.hist(cr_loan['person_emp_length'], bins='auto', color='blue')
plt.xlabel("Duración del empleo")
plt.show()
Eliminación de los datos faltantes
# Número de vacios
print(cr_loan['loan_int_rate'].isnull().sum())
# Índices nulos
indices = cr_loan[cr_loan['loan_int_rate'].isnull()].index
# Nueva base de datos sin valores nulos
cr_loan_clean = cr_loan.drop(indices)
Regresión logística para probabilidad de incumplimiento o default
Modelo de regresión logística
from sklearn.linear_model import LogisticRegression
import numpy as np
# Importación de los datos crediticios sin vacios desde un archivo csv
cr_loan_clean = pd.read_csv("DatosCrediticios_SinVaciosNiOutliers.csv")
# Variables independiente y dependiente
X = cr_loan_clean[['loan_int_rate']]
y = cr_loan_clean[['loan_status']]
# Modelo de regresión logística y su ajuste
clf_logistic_single = LogisticRegression()
clf_logistic_single.fit(X, np.ravel(y))
# Parametros del modelo
print(clf_logistic_single.get_params())
# Interceptos del modelo
print(clf_logistic_single.intercept_)
Regresión logística multivariada
# Variables independientes
X_multi = cr_loan_clean[['loan_int_rate','person_emp_length']]
# Estatus del préstamo como variable a predecir
y = cr_loan_clean[['loan_status']]
# Modelo de regresión logística multivariada
clf_logistic_multi = LogisticRegression(solver='lbfgs').fit(X_multi, np.ravel(y))
# Print the intercept of the model
print(clf_logistic_multi.intercept_)
Creación de conjuntos de entrenamiento y prueba
from sklearn.model_selection import train_test_split
# Variables independientes y dependiente
# X = cr_loan_clean[['loan_int_rate','person_emp_length','person_income']]
X = cr_loan_clean.loc[:,['loan_int_rate','person_emp_length','person_income']]
y = cr_loan_clean[['loan_status']]
# Conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=123)
# Ajuste del modelo de regresión logística
clf_logistic = LogisticRegression(solver='lbfgs').fit(X_train, np.ravel(y_train))
# Coeficientes del modelo
print(clf_logistic.coef_)
Codificación dummie
# Conjutos de datos numéricos y no numéricos
cred_num = cr_loan_clean.select_dtypes(exclude=['object'])
cred_str = cr_loan_clean.select_dtypes(include=['object'])
# Codificación dummie al conjunto de variables categóricas
cred_str_onehot = pd.get_dummies(cred_str)
# Unión de las variables numéricas con las codificadas
cr_loan_prep = pd.concat([cred_num, cred_str_onehot], axis=1)
# Lista de columnas del nuevo cnjunto de datos
print(cr_loan_prep.columns)
Predicción de probabilidades de incumplimiento
# Variables independientes y dependiente
X = cr_loan_prep.loc[:,cr_loan_prep.columns != 'loan_status']
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=123)
# Entrenamiento del modelo de regresión logística
clf_logistic = LogisticRegression(solver='lbfgs').fit(X_train, np.ravel(y_train))
# Predicciones de probabilidad para el estado del préstamo usando datos de prueba
preds = clf_logistic.predict_proba(X_test)
# Marcos de datos de las primeras cinco predicciones y las primeras cinco etiquetas verdaderas
preds_df = pd.DataFrame(preds[:,1][0:20], columns = ['prob_default'])
true_df = y_test.head(20)
# Comparación de los dos marcos de datos
print(pd.concat([true_df.reset_index(drop = True), preds_df], axis = 1))
Informe de clasificación de default
# Dataframe de las probabilidades de incumplimiento
preds_df = pd.DataFrame(preds[:,1], columns = ['prob_default'])
# Estado del préstamo según la probabilidad asignada
preds_df['loan_status'] = preds_df['prob_default'].apply(lambda X: 1 if X > .5 else 0)
# Recuento de filas para cada estado de préstamo
print(preds_df['loan_status'].value_counts())
from sklearn.metrics import classification_report
# Reporte de clasificación
target_names = ['Non-Default', 'Default']
print(classification_report(y_test, preds_df['loan_status'], target_names=target_names))
Selección de métricas del informe
from sklearn.metrics import precision_recall_fscore_support
# Valores no promedio del informe
print(precision_recall_fscore_support(y_test,preds_df['loan_status']))
# Dos primeros números del informe
print(precision_recall_fscore_support(y_test,preds_df['loan_status'])[0:2])
Gráfico del modelo de puntaje crediticio
# Predicciones para la probabilidad de incumplimiento
preds = clf_logistic.predict_proba(X_test)
# Puntuación de precisión del modelo
print(clf_logistic.score(X_test, y_test))
from sklearn import metrics
# Gáfico de la curva ROC de las probabilidades de incumplimiento
prob_default = preds[:, 1]
fallout, sensitivity, thresholds = metrics.roc_curve(y_test, prob_default)
plt.plot(fallout, sensitivity, color = 'darkorange')
plt.plot([0, 1], [0, 1], linestyle='--')
plt.show()
from sklearn.metrics import roc_auc_score
# Área debajo de la curva
auc = roc_auc_score(y_test, prob_default)
auc
Umbrales y matrices de confusión
# Estado del préstamo según el umbral 0.4
preds_df['loan_status'] = preds_df['prob_default'].apply(lambda x: 1 if x > 0.4 else 0)
from sklearn.metrics import confusion_matrix
# Matriz de confusión
tn, fp, fn, tp = confusion_matrix(y_test,preds_df['loan_status']).ravel()
default_recall = tp / (tp + fn)
default_recall
# Estado del prestamo según el umbral 0.5
preds_df['loan_status'] = preds_df['prob_default'].apply(lambda x: 1 if x > 0.5 else 0)
# Matriz de confusión
tn, fp, fn, tp = confusion_matrix(y_test,preds_df['loan_status']).ravel()
default_recall = tp / (tp + fn)
default_recall
Performance según el umbral elegido
avg_loan_amnt = cr_loan_clean['loan_amnt'].mean()
# Valores del estado del préstamo en función del nuevo umbral
preds_df['loan_status'] = preds_df['prob_default'].apply(lambda x: 1 if x > 0.4 else 0)
# Número de impagos de préstamos a partir de los datos de predicción
num_defaults = preds_df['loan_status'].value_counts()[1]
# Default recall del reporte de clasificación
default_recall = precision_recall_fscore_support(y_test,preds_df['loan_status'])[1][1]
# Impacto estimado de la nueva tasa de recuperación predeterminada
print(avg_loan_amnt * num_defaults * (1 - default_recall))
Selección del umbral
# Conjuntos de umbrales y respectivas métricas
i = 0.2
# Umbrales
thresh = []
# recall de incumplimiento
def_recalls = []
# recall de no incumplimiento
nondef_recalls = []
# accuracy
accs = []
while i < 0.65 :
thresh.append(i)
preds_df['loan_status'] = preds_df['prob_default'].apply(lambda x: 1 if x > i else 0)
def_recalls.append(precision_recall_fscore_support(y_test,preds_df['loan_status'])[1][1])
nondef_recalls.append(precision_recall_fscore_support(y_test,preds_df['loan_status'])[1][0])
accs.append(precision_recall_fscore_support(y_test,preds_df['loan_status'])[0][1])
i = i + 0.025
plt.plot(thresh,def_recalls)
plt.plot(thresh,nondef_recalls)
plt.plot(thresh,accs)
plt.xlabel("Probability Threshold")
plt.legend(["Default Recall","Non-default Recall","Model Accuracy"])
plt.show()
Gradient Boosted Trees usando XGBoost
Árboles para el incumplimiento de pago de la deuda
# Importación de los datos crediticios con variables dummies listo para modelar csv
cr_loan_prep = pd.read_csv("DatosCrediticios_ListoParaModelar.csv")
# Variables independientes y dependiente
X = cr_loan_prep.loc[:,cr_loan_prep.columns != 'loan_status']
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=123)
# Ajuste del modelo
import xgboost as xgb
clf_gbt = xgb.XGBClassifier().fit(X_train, np.ravel(y_train))
# Predicciones de la probabilidad de default con el modelo
gbt_preds = clf_gbt.predict_proba(X_test)
# Dataframes con probabilidades de dafault predicha y datos reales
preds_df = pd.DataFrame(gbt_preds[:,1][0:20], columns = ['prob_default'])
true_df = y_test.head(20)
# Comparación de los dos marcos de datos
print(pd.concat([true_df.reset_index(drop = True), preds_df], axis = 1))
Desempeño de la cartera impulsado por el gradiente
# Marco de datos llamado cartera para combinar las probabilidades de incumplimiento para
# ambos modelos (XGBoost y regresión logistica), la pérdida en caso de incumplimiento (20%
# por ahora) y el préstamo que se asumirá como la exposición en caso de incumplimiento.
portfolio = pd.DataFrame({'gbt_prob_default': gbt_preds[:,1], 'lr_prob_default': preds[:,1], 'lgd': [0.2] * 11784, 'loan_amnt': X_test['loan_amnt']})
portfolio
# Creación de dos columnas usando la fórmula de la perdida esperada para cada modelo
portfolio['gbt_expected_loss'] = portfolio['gbt_prob_default'] * portfolio['lgd'] * portfolio['loan_amnt']
portfolio['lr_expected_loss'] = portfolio['lr_prob_default'] * portfolio['lgd'] * portfolio['loan_amnt']
# Suma de la perdida esperada para el modelo de regresión lineal
print('LR perdida esperada: ', np.sum(portfolio['lr_expected_loss']))
# Suma de la perdida esperada para el modelo de XGBoost
print('GBT perdida esperada: ', np.sum(portfolio['gbt_expected_loss']))
Evaluación de árboles potenciados por gradiente
# Predicción del estado de préstamo
gbt_preds = clf_gbt.predict(X_test)
# Cadena de valores de los resulta
print(gbt_preds)
# Reporte de clasificación del modelo
target_names = ['Non-Default', 'Default']
print(classification_report(y_test, gbt_preds, target_names=target_names))
Importancia de seleccionar variables en la predicción de incumplimiento
# Selección de algunas variables independientes de ejemplo
X = cr_loan_prep[['person_income','loan_int_rate','loan_percent_income','loan_amnt','person_home_ownership_MORTGAGE','loan_grade_F']]
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=123)
# Entrenamiento del modelo
clf_gbt = xgb.XGBClassifier().fit(X_train,np.ravel(y_train))
# Importancias de las variables en el modelo
print(clf_gbt.get_booster().get_score(importance_type = 'weight'))
Visualización de la importancia de la variable
# Selección de algunas variables independientes de ejemplo
X2 = cr_loan_prep[['loan_int_rate','person_emp_length']]
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X2_train, X2_test, y_train, y_test = train_test_split(X2, y, test_size=.4, random_state=123)
# Entrenamiento de un modelo con las 2 variables seleccionadas
clf_gbt2 = xgb.XGBClassifier().fit(X2_train,np.ravel(y_train))
# Importancia de las variables para este modelo
xgb.plot_importance(clf_gbt2, importance_type = 'weight')
plt.show()
X3 = cr_loan_prep[['person_income','loan_int_rate','loan_percent_income']]
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X3_train, X3_test, y_train, y_test = train_test_split(X3, y, test_size=.4, random_state=123)
# Entrenamiento de un modelo con las 3 variables seleccionadas
clf_gbt3 = xgb.XGBClassifier().fit(X3_train,np.ravel(y_train))
# Importancia de las variables para este modelo
xgb.plot_importance(clf_gbt3, importance_type = 'weight')
plt.show()
Selección de variables y rendimiento del modelo
# Selección de algunas variables independientes de ejemplo
X = cr_loan_prep[['person_income','loan_int_rate','loan_percent_income','loan_amnt','person_home_ownership_MORTGAGE']]
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=123)
# Modelo XGBoost 1
gbt = xgb.XGBClassifier().fit(X_train,np.ravel(y_train))
# Predicción del estado del prestamo usando el modelo 1
gbt_preds = gbt.predict(X_test)
# Reporte de clasificación del primer modelo
target_names = ['Non-Default', 'Default']
print(classification_report(y_test, gbt_preds, target_names=target_names))
# Selección de algunas variables independientes de ejemplo
X2 = cr_loan_prep[['person_income','loan_int_rate','loan_percent_income','person_emp_length', 'person_home_ownership_RENT']]
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X2_train, X2_test, y_train, y_test = train_test_split(X2, y, test_size=.4, random_state=123)
# Modelo XGBoost 2
gbt2 = xgb.XGBClassifier().fit(X2_train,np.ravel(y_train))
# Predicción del estado del prestamo usando el modelo 2
gbt2_preds = gbt2.predict(X2_test)
# Reporte de clasificación del segundo modelo
print(classification_report(y_test, gbt2_preds, target_names=target_names))
Validación cruzada
# Variables independientes y dependiente
X = cr_loan_prep.loc[:,cr_loan_prep.columns != 'loan_status']
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=123)
# Ajuste del modelo
gbt = xgb.XGBClassifier().fit(X_train, np.ravel(y_train))
# Parametros
params = {'eval_metric': 'auc', 'objective': 'binary:logistic', 'seed': 123}
# Número de pliegues y interacciones
n_folds = 5
early_stopping = 10
# Matriz de entrenamiento para XGBoost
DTrain = xgb.DMatrix(X_train, label = y_train)
# Dataframe de la validación cruzada
cv_df = xgb.cv(params, DTrain, num_boost_round = 5, nfold=n_folds, early_stopping_rounds=early_stopping)
cv_df
Límites de las pruebas de validación cruzada
# Validación cruzada con más pliegues e interacciones
cv_results_big = xgb.cv(params, DTrain, num_boost_round = 600, nfold=10, shuffle = True)
cv_results_big
# Media de los puntajes AUC del test
print(np.mean(cv_results_big['test-auc-mean']).round(2))
# Gráfico de las puntuaciones AUC del test para cada iteración
plt.plot(cv_results_big['test-auc-mean'])
plt.title('Puntaje Test AUC sobre 600 iteraciones')
plt.xlabel('Iteration Number')
plt.ylabel('Test AUC Score')
plt.show()
Puntuación de validación cruzada
# Modelo de árbol potenciado por gradiente usando dos hiperparámetros
gbt = xgb.XGBClassifier(learning_rate = 0.1, max_depth = 7)
from sklearn.model_selection import cross_val_score
# Puntajes de validación cruzada para 4 pliegues
cv_scores = cross_val_score(gbt, X_train, np.ravel(y_train), cv = 4)
# Puntuaciones de validación cruzada
print(cv_scores)
# Precisión promedio y la desviación estándar de las puntuaciones
print("Precisión promedio: %0.2f (+/- %0.2f)" % (cv_scores.mean(), cv_scores.std() * 2))
Submuestreo de datos de entrenamiento
# Concatenación de las muestras de entrenamiento
X_y_train = pd.concat([X_train.reset_index(drop = True), y_train.reset_index(drop = True)], axis = 1)
# Cuenta de incumplidos y no incumplidos
count_nondefault, count_default = X_y_train['loan_status'].value_counts()
# Conjunto de datos para defaults and non-defaults
nondefaults = X_y_train[X_y_train['loan_status'] == 0]
defaults = X_y_train[X_y_train['loan_status'] == 1]
# Submuestreo de non-defaults
nondefaults_under = nondefaults.sample(count_default)
# Concatenación de submuestreo de nondefaults with defaults
X_y_train_under = pd.concat([nondefaults_under.reset_index(drop = True), defaults.reset_index(drop = True)], axis = 0)
# Conteo del estado del préstamo
print(X_y_train_under['loan_status'].value_counts())
Rendimiento del árbol submuestreado
# Variables independientes y dependiente
X = cr_loan_prep.loc[:,cr_loan_prep.columns != 'loan_status']
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=123)
# Ajuste del modelo
clf_gbt = xgb.XGBClassifier().fit(X_train, np.ravel(y_train))
# Predicciones de la probabilidad de default con el modelo
gbt_preds = clf_gbt.predict(X_test)
# Variables independientes y dependiente
X_train_under = X_y_train_under.loc[:,X_y_train_under.columns != 'loan_status']
y_train_under = X_y_train_under[['loan_status']]
# Ajuste del modelo
clf_gbt2 = xgb.XGBClassifier().fit(X_train_under, np.ravel(y_train_under))
# Predicciones de la probabilidad de default con el modelo
gbt2_preds = clf_gbt2.predict(X_test)
# Reportes de clasificación
target_names = ['Non-Default', 'Default']
print(classification_report(y_test, gbt_preds, target_names=target_names))
print(classification_report(y_test, gbt2_preds, target_names=target_names))
# Matriz de confusión de ambos modelos
print(confusion_matrix(y_test,gbt_preds))
print(confusion_matrix(y_test,gbt2_preds))
# Comparación de los puntajes AUC de los modelos
print(roc_auc_score(y_test, gbt_preds))
print(roc_auc_score(y_test, gbt2_preds))
Evaluación e Implementación del Modelo
Comparación de reportes de modelos
# Variables independientes y dependiente
X = cr_loan_prep.loc[:,cr_loan_prep.columns != 'loan_status']
y = cr_loan_prep[['loan_status']]
# Conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.4, random_state=123)
# Ajuste del modelo de regresión logística
clf_logistic = LogisticRegression(solver='lbfgs').fit(X_train, np.ravel(y_train))
# Predicciones de probabilidad para el estado del préstamo usando datos de prueba
preds_df_lr = clf_logistic.predict_proba(X_test)
preds_df_lr
# Dataframe de las probabilidades de incumplimiento
preds_df_lr = pd.DataFrame(preds_df_lr[:,1], columns = ['prob_default'])
# Estado del préstamo según la probabilidad asignada
preds_df_lr['loan_status'] = preds_df_lr['prob_default'].apply(lambda X: 1 if X > .4 else 0)
preds_df_lr
# Ajuste del modelo XGBoost
clf_gbt = xgb.XGBClassifier(base_score=0.5, colsample_bylevel=1, colsample_bytree=1, gamma=0, learning_rate=0.1, max_delta_step=0, max_depth=7, min_child_weight=1, min_split_loss=1.0, n_estimators=100, n_jobs=1, random_state=0, reg_alpha=0, reg_lambda=1.2, scale_pos_weight=1, seed=123, subsample=1).fit(X_train, np.ravel(y_train))
# Predicciones de la probabilidad de default con el modelo
preds_df_gbt = clf_gbt.predict_proba(X_test)
# Dataframe de las probabilidades de incumplimiento
preds_df_gbt = pd.DataFrame(preds_df_gbt[:,1], columns = ['prob_default'])
# Estado del préstamo según la probabilidad asignada
preds_df_gbt['loan_status'] = preds_df_gbt['prob_default'].apply(lambda X: 1 if X > .4 else 0)
preds_df_gbt
# Reporte de de clasificación de regresión logística
target_names = ['Non-Default', 'Default']
print(classification_report(y_test, preds_df_lr['loan_status'], target_names=target_names))
# Reporte de clasificación de árboles potenciados por gradiente
print(classification_report(y_test, preds_df_gbt['loan_status'], target_names=target_names))
# Puntuaciones F-1 de incumplimiento para la regresión logística
print(precision_recall_fscore_support(y_test,preds_df_lr['loan_status'], average = 'macro')[2])
# Puntuaciones F-1 de incumplimiento para el modelo de arboles poteciados por gradiente
print(precision_recall_fscore_support(y_test,preds_df_gbt['loan_status'], average = 'macro')[2])
Comparación de ROCs
# Predicciones de probabilidad para el estado del préstamo usando datos de prueba
clf_logistic_preds = np.array(preds_df_lr['prob_default'])
clf_gbt_preds = np.array(preds_df_gbt['prob_default'])
# Componentes del gráfico ROC
fallout_lr, sensitivity_lr, thresholds_lr = metrics.roc_curve(y_test, clf_logistic_preds)
fallout_gbt, sensitivity_gbt, thresholds_gbt = metrics.roc_curve(y_test, clf_gbt_preds)
# Gráfico ROC con ambos componentes
plt.plot(fallout_lr, sensitivity_lr, color = 'blue', label='%s' % 'Logistic Regression')
plt.plot(fallout_gbt, sensitivity_gbt, color = 'green', label='%s' % 'Gradient Boosted Tree')
plt.plot([0, 1], [0, 1], linestyle='--', label='%s' % 'Random Prediction')
plt.title("Gráfico ROC para LR y GBT sobre la probabilidad de incumplimiento")
plt.xlabel('Fall-out')
plt.ylabel('Sensitivity')
plt.legend()
plt.show()
# Puntaje AUC de la regresión logística
print("Logistic Regression AUC Score: %0.2f" % roc_auc_score(y_test, clf_logistic_preds))
# Puntaje AUC de los arboles potenciados por gradiente
print("Gradient Boosted Tree AUC Score: %0.2f" % roc_auc_score(y_test, clf_gbt_preds))
Curvas de calibración
from sklearn.calibration import calibration_curve
# Fracción de positivos
frac_of_pos_lr = calibration_curve(y_test, clf_logistic_preds, n_bins = 20)[0]
frac_of_pos_lr
# Probabilidad media
mean_pred_val_lr = calibration_curve(y_test, clf_logistic_preds, n_bins = 20)[1]
mean_pred_val_lr
# Fracción de positivos
frac_of_pos_gbt = calibration_curve(y_test, clf_gbt_preds, n_bins = 20)[0]
frac_of_pos_gbt
# Probabilidad media
mean_pred_val_gbt = calibration_curve(y_test, clf_gbt_preds, n_bins = 20)[1]
mean_pred_val_gbt
# Curva de calibración
plt.plot([0, 1], [0, 1], 'k:', label='Perfectly calibrated')
plt.plot(mean_pred_val_lr, frac_of_pos_lr, 's-', label='%s' % 'Logistic Regression')
plt.plot(mean_pred_val_gbt, frac_of_pos_gbt, 's-', label='%s' % 'Gradient Boosted tree')
plt.ylabel('Fraction of positives')
plt.xlabel('Average Predicted Probability')
plt.legend()
plt.title('Calibration Curve')
plt.show()
Tasas de aceptación
# Estadísticas de las probabilidades de incumplimiento
true_list = pd.DataFrame(y_test, columns = ['loan_status']).rename(columns={'loan_status':'true_loan_status'}, inplace=False)
test_pred_df = pd.concat([true_list.reset_index(drop = True), preds_df_gbt['prob_default']], axis = 1)
print(test_pred_df['prob_default'].describe())
# Umbral para una tasa de aceptación del 85 %
threshold_85 = np.quantile(test_pred_df['prob_default'], 0.85)
# Umbral de tasa de aceptación
test_pred_df['pred_loan_status'] = test_pred_df['prob_default'].apply(lambda x: 1 if x > threshold_85 else 0)
# Recuentos del estado del préstamo después del umbral
print(test_pred_df['pred_loan_status'].value_counts())
Visualización de cuantiles de aceptación
# Histograma de las probabilidades previstas de incumplimiento
plt.hist(clf_gbt_preds, color = 'blue', bins = 40)
# Línea de referencia al gráfico para el umbral
plt.axvline(x = threshold_85, color = 'red')
plt.show()
Bajas calificaciones
test_pred_df
# Subconjunto de solo préstamos aceptados
accepted_loans = test_pred_df[test_pred_df['pred_loan_status'] == 0]
# Cálculo de la baja tasa
print(np.sum(accepted_loans['true_loan_status']) / accepted_loans['true_loan_status'].count())
Impacto en la tasa de aceptación
test_pred_df = test_pred_df.rename(columns={'pred_loan_status':'pred_loan_status_15'}, inplace=False)
test_pred_df['loan_amnt'] = X_test['loan_amnt'].reset_index(drop = True)
test_pred_df
# Estadísticas de la variable monto del préstamo
print(test_pred_df['loan_amnt'].describe())
# Almacenar el monto promedio del préstamo
avg_loan = np.mean(test_pred_df['loan_amnt'])
# Formato de moneda y tabulación cruzada
pd.options.display.float_format = '${:,.2f}'.format
print(pd.crosstab(test_pred_df['true_loan_status'], test_pred_df['pred_loan_status_15']).apply(lambda x: x * avg_loan, axis = 0))
Tabla de la estrategia comercial
# Matriz de tazas de aceptación
accept_rates = [1.0,
0.95,
0.9,
0.85,
0.8,
0.75,
0.7,
0.65,
0.6,
0.55,
0.5,
0.45,
0.4,
0.35,
0.3,
0.25,
0.2,
0.15,
0.1,
0.05]
# Matriz de umbrales
thresholds = []
# Matriz de bajas tazas de aceptación
bad_rates = []
test_pred_df = test_pred_df.rename(columns={'pred_loan_status_15':'pred_loan_status'}, inplace=False)
test_pred_df
# Prestamos aceptados
accepted_loans = accepted_loans[0:0]
# Número de prestamos aceptados
num_accepted_loans = []
# Matrices de la tabla de estrategia con un bucle for
for rate in accept_rates:
# Umbral para la tasa de aceptación
thresh = np.quantile(preds_df_gbt['prob_default'], rate).round(3)
# Agregación del valor del umbral a la lista de umbrales
thresholds.append(np.quantile(preds_df_gbt['prob_default'], rate).round(3))
# Reasignación del valor del estado del préstamo usando el umbral
test_pred_df['pred_loan_status'] = \
test_pred_df['prob_default'].apply(lambda x: 1 if x > thresh else 0)
# Conjunto de préstamos aceptados usando esta tasa de aceptación
accepted_loans = test_pred_df[test_pred_df['pred_loan_status'] == 0]
# Calculo y asignación de la tasa baja usando la tasa de aceptación
bad_rates.append(np.sum((accepted_loans['true_loan_status'])
/ accepted_loans['true_loan_status'].count()).round(3))
# Numero de prestamos aceptados
num_accepted_loans.append(len(test_pred_df[test_pred_df['prob_default']<np.quantile(test_pred_df['prob_default'], rate).round(3)]))
# Marco de datos de la tabla de estrategia comercial
strat_df = pd.DataFrame(zip(accept_rates, thresholds, bad_rates), columns = ['Acceptance Rate','Threshold','Bad Rate'])
strat_df
Visualizando la estrategia comercial
# Diagrama de caja de la tabla de estrategia comercial
strat_df.boxplot()
plt.show()
# Gráfico de la curva de estrategia
plt.plot(strat_df['Acceptance Rate'], strat_df['Bad Rate'])
plt.xlabel('Acceptance Rate')
plt.ylabel('Bad Rate')
plt.title('Acceptance and Bad Rates')
plt.show()
Valor estimado del perfil
# Monto promedio del prestamo
mean_loan_amnt = np.mean(test_pred_df['loan_amnt']).repeat(20)
# Marco de datos de la tabla de estrategia comercial con el monto promedio del prestamo
strat_df = pd.DataFrame(zip(accept_rates, thresholds, bad_rates, num_accepted_loans, mean_loan_amnt), columns = ['Acceptance Rate','Threshold','Bad Rate', 'Num Accepted Loans', 'Avg Loan Amnt'])
strat_df
# El valor neto estimado de los no impagos menos los impagos
estimated_value = ((strat_df['Num Accepted Loans'] * (1 - strat_df['Bad Rate'])) * strat_df['Avg Loan Amnt']) - (strat_df['Num Accepted Loans'] * strat_df['Bad Rate'] * strat_df['Avg Loan Amnt'])
# Marco de datos de la tabla de estrategia comercial con el valor neto
strat_df = pd.DataFrame(zip(accept_rates, thresholds, bad_rates, num_accepted_loans, mean_loan_amnt, estimated_value), columns = ['Acceptance Rate','Threshold','Bad Rate', 'Num Accepted Loans', 'Avg Loan Amnt', 'Estimated Value'])
strat_df
# Gráfico con los valores estimados
plt.plot(strat_df['Acceptance Rate'],strat_df['Estimated Value'])
plt.title('Estimated Value by Acceptance Rate')
plt.xlabel('Acceptance Rate')
plt.ylabel('Estimated Value')
plt.show()
# Fila con el valor máximo estimado
print(strat_df.loc[strat_df['Estimated Value'] == np.max(strat_df['Estimated Value'])])
Pérdida total esperada
# Suposición de que la exposición es el valor total del préstamo y que la pérdida en caso de incumplimiento es del 100 %.
test_pred_df['pred_loan_status'] = 1
test_pred_df = test_pred_df.rename(columns={'pred_loan_status':'loss_given_default'}, inplace=False)
test_pred_df
# Pérdida esperada del banco
test_pred_df['expected_loss'] = test_pred_df['prob_default'] * test_pred_df['loss_given_default'] * test_pred_df['loan_amnt']
# Pérdida total esperada con dos decimales
tot_exp_loss = round(np.sum(test_pred_df['expected_loss']),2)
# Pérdida total esperada
print('Total expected loss: ', '${:,.2f}'.format(tot_exp_loss))