Para esta segunda práctica nos encontrábamos frente a un problema de riesgo de crédito, el cual permite predecir mediante probabilidad la posibilidad de incurrir en una pérdida debido a un incumplimiento de un futuro crédito que se desee brindar.
El objetivo principal de la práctica fue el de realizar un modelo de probabilidad el cual permitiese predecir la probabilidad de que un individuo incumpla sus obligaciones financieras en los siguientes 12 meses desde que se genere el crédito.
También se debía representar este mismo modelo con un Scorecard. De igual forma se debía analizar qué variables hacen más riesgosa a una persona. Y finalmente, se debía desarrollar una aplicación web que le permitiera a los usuario ver su calificación de scorecard, de acuerdo a sus características, y cómo se encuentra respecto al resto de la población.
import pandas as pd
import numpy as np
import seaborn as sns
from scipy.stats import chi2_contingency
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import plotly.express as px
from sklearn.naive_bayes import GaussianNB
from tqdm import tqdm
from plotly.subplots import make_subplots
import plotly.graph_objects as go
from sklearn.linear_model import LogisticRegression
mes_name=['Dec', 'Nov', 'Oct', 'Sep', 'Aug', 'Jul', 'Jun', 'May', 'Apr', 'Mar', 'Feb', 'Jan']
mes_num=list(pd.Series(list((13-np.arange(1,13)))).astype("str"))
def format_replace(string_): # ajust formato de fecha
for i in range(0,12):
string_=str(string_).replace(mes_name[i],mes_num[i] )
return string_
def dummy_creation(df_, columns_list): # creacion de variables dummy
df_dummies = []
for col in columns_list:
df_dummies.append(pd.get_dummies(df_[col], prefix = col, prefix_sep = ':',drop_first=True))
df_dummies = pd.concat(df_dummies, axis = 1)
df_ = pd.concat([df_, df_dummies], axis = 1)
df_=df_.drop(labels=columns_list,axis=1)
return df_
def plot_barplot(df_temp, x, y,nrow,ncol,fig_temp,show_leg):
df1 = df_temp.groupby(x)[y].value_counts(normalize=True)
df1 = df1.mul(100)
df1 = df1.rename('percent').reset_index()
df_temp=df1[df1[y]==1].copy()
fig_temp.add_trace(go.Bar(x=df_temp[x],
y=df_temp["percent"],
name="1",
marker_color='rgb(55, 83, 109)',
hovertext =(x,"percent" ),
showlegend=show_leg
), row=nrow,col=ncol)
df_temp=df1[df1[y]==0].copy()
fig_temp.add_trace(go.Bar(x=df_temp[x],
y=df_temp["percent"],
name="0",
marker_color='rgb(26, 118, 255)',
hovertext =(x,"percent" ),
showlegend=show_leg
),row=nrow,col=ncol)
fig_temp.update_yaxes(range = [0,100])
return fig_temp
# function to calculate WoE and IV of categorical features
# The function takes 3 arguments: a dataframe (X_train_prepr), a string (column name), and a dataframe (y_train_prepr).
def woe_discrete(df, cat_variabe_name, y_df):
df = pd.concat([df[cat_variabe_name], y_df], axis = 1)
df = pd.concat([df.groupby(df.columns.values[0], as_index = False)[df.columns.values[1]].count(),
df.groupby(df.columns.values[0], as_index = False)[df.columns.values[1]].mean()], axis = 1)
df = df.iloc[:, [0, 1, 3]]
df.columns = [df.columns.values[0], 'n_obs', 'prop_good']
df['prop_n_obs'] = df['n_obs'] / df['n_obs'].sum()
df['n_good'] = df['prop_good'] * df['n_obs']
df['n_bad'] = (1 - df['prop_good']) * df['n_obs']
df['prop_n_good'] = df['n_good'] / df['n_good'].sum()
df['prop_n_bad'] = df['n_bad'] / df['n_bad'].sum()
df['WoE'] = np.log(df['prop_n_good'] / df['prop_n_bad'])
df = df.sort_values(['WoE'])
df = df.reset_index(drop = True)
df['diff_prop_good'] = df['prop_good'].diff().abs()
df['diff_WoE'] = df['WoE'].diff().abs()
df['IV'] = (df['prop_n_good'] - df['prop_n_bad']) * df['WoE']
df['IV'] = df['IV'].sum()
return df
'''
function to calculate WoE & IV of continuous variables
This is same as the function we defined earlier for discrete variables
The only difference are the 2 commented lines of code in the function that results in the df
being sorted by continuous variable values
'''
## '\nfunction to calculate WoE & IV of continuous variables\nThis is same as the function we defined earlier for discrete variables\nThe only difference are the 2 commented lines of code in the function that results in the df\nbeing sorted by continuous variable values\n'
def continua_categorica(df_,variable,y_df ):
quantiles_=list(df_[variable].quantile([0.15,0.3,0.45,0.6,0.75,0.9]))
new_var=[variable+str(quantiles_[0])]
df_[variable+str(quantiles_[0])]=np.where(df_[variable]<quantiles_[0],1,0 )
for i in range(1,len(quantiles_)-1):
new_var.append(variable+str(quantiles_[i]))
df_[variable+str(quantiles_[i]) ]=np.where((df_[variable]<=quantiles_[i]) & (df_[variable]>quantiles_[i-1]),1,0 )
df_[variable+str(quantiles_[5])]=np.where(df_[variable]>=quantiles_[5],1,0)
new_var.append(variable+str(quantiles_[5]) )
woes=[]
for vars_ in new_var:
woe_temp=[*list(woe_ordered_continuous(df_,vars_,y_df )["WoE"] ),vars_]
woes.append(woe_temp)
return woes
def woe_ordered_continuous(df, continuous_variabe_name, y_df):
df = pd.concat([df[continuous_variabe_name], y_df], axis = 1)
df = pd.concat([df.groupby(df.columns.values[0], as_index = False)[df.columns.values[1]].count(),
df.groupby(df.columns.values[0], as_index = False)[df.columns.values[1]].mean()], axis = 1)
df = df.iloc[:, [0, 1, 3]]
df.columns = [df.columns.values[0], 'n_obs', 'prop_good']
df['prop_n_obs'] = df['n_obs'] / df['n_obs'].sum()
df['n_good'] = df['prop_good'] * df['n_obs']
df['n_bad'] = (1 - df['prop_good']) * df['n_obs']
df['prop_n_good'] = df['n_good'] / df['n_good'].sum()
df['prop_n_bad'] = df['n_bad'] / df['n_bad'].sum()
df['WoE'] = np.log(df['prop_n_good'] / df['prop_n_bad'])
#df = df.sort_values(['WoE'])
#df = df.reset_index(drop = True)
df['diff_prop_good'] = df['prop_good'].diff().abs()
df['diff_WoE'] = df['WoE'].diff().abs()
df['IV'] = (df['prop_n_good'] - df['prop_n_bad']) * df['WoE']
df['IV'] = df['IV'].sum()
return df
Para este ejercicio, se contó con la base de datos loan_data_2007_2014.csv obtenida a través de kaggle. Esta contiene información perteneciente a usuarios entre los años 2007 y 2014 de lendingclub, una empresa que realiza préstamos digitales en Estados Unidos ( lendingclub ).
df=pd.read_csv("loan_data_2007_2014.csv")
## <string>:1: DtypeWarning:
##
## Columns (19) have mixed types. Specify dtype option on import or set low_memory=False.
La base de datos loan_data_2007_2014.csv cuenta con 74 columnas y 466285 registros.
Para la creación del modelo se tienen:
issue_d: El mes en que se financió el préstamo (mes-año).
last_pymnt_d: El último mes de pago fue recibido.
loan_satus: Esta sera la variable objetivo, cuenta con 9 categorías que clasifican el ultimo estado registrado.
Como el objetivo es crear un modelo para predecir si al cabo de 12 meses que se origina el credíto (issue_d) el usuario incumple sus obligaciones financieras, luego de analizar las variables fecha registradas se crea month_last: meses que han pasado desde el ultimo pago, que es la diferencia (last_pymnt_d-issue_d ) esto nos dará informción del tiempo que pago el usuario y con la variable loan_status se podrá saber si el usuario incumple entre el tiempo de interés (antes de 12 meses).
Se crea las variables.
#results="asis"
table_frec=pd.DataFrame(df["loan_status"].value_counts())
status_mora=['Charged Off', 'Default', 'Late (31-120 days)','Does not meet the credit policy. Status:Charged Off']
table_frec["good_status"]=1
filtro=pd.Series(table_frec.index).isin( status_mora)
table_frec.loc[list(filtro), "good_status"]=0
table_frec=table_frec.reset_index()
table_frec.columns=["loan_status", "Frec", "good_status" ]
df_temp=table_frec[["loan_status", "good_status","Frec" ]]
df["good_status"]=1
df.loc[df["loan_status"].isin(status_mora),"good_status"]=0
| loan_status | good_status | Frec |
|---|---|---|
| Current | 1 | 224226 |
| Fully Paid | 1 | 184739 |
| Charged Off | 0 | 42475 |
| Late (31-120 days) | 0 | 6900 |
| In Grace Period | 1 | 3146 |
| Does not meet the credit policy. Status:Fully Paid | 1 | 1988 |
| Late (16-30 days) | 1 | 1218 |
| Default | 0 | 832 |
| Does not meet the credit policy. Status:Charged Off | 0 | 761 |
df[ 'issue_d']=pd.to_datetime(df['issue_d'], format = "%m-%y")
df['last_pymnt_d']=pd.to_datetime(df['last_pymnt_d'], format = "%m-%y")
df["month_last"]= ((df.last_pymnt_d - df.issue_d)/np.timedelta64(1, 'M'))
df['target_time']=0
df.loc[df["month_last"]<=12,'target_time']=1
El modelo general tendra la estructura:
\[ P(\text{good_status=1} )= f(\text{month_last}, {X },\theta ) \] Donde month_last define el tiempo en que queremos predecir, \(X\) es un vector de variables que puedan afectar la probabilidad y \(\theta\) son los parámetros que puede contener el modelo.
Asumiendo que se tolera al menos un 20 % de valores NA en los datos de las y omitiendo las columnas de identificación se cuenta con:
drop_columns=["id", "member_id", "url", "title"]
df=df.drop(labels=drop_columns,axis=1 )
total_na=df.isna().sum()
filtro=total_na< df.shape[0]*0.2
total_na=total_na[filtro]
result=pd.DataFrame({"Variables":["Menos del 20% NA"," Mas del 20% NA"],
"Total variables":[total_na.shape[0], 70-total_na.shape[0] ]} )
| Variables | Total variables |
|---|---|
| Menos del 20% NA | 51 |
| Mas del 20% NA | 19 |
Se omiten 22 variables por su alto porcentaje de valores faltantes, aunque 20% de valores faltantes es una cantidad alta, existen variables importantes que contienen alta cantidad de valores faltantes que se muestran a continuación.
vars_=total_na.sort_values(ascending=False).head()
result=pd.DataFrame({"Variables":vars_.index,"Descripción":[" Total crédito rotativo alto entre límite de crédito. ","Saldo corriente en todas las cuentas ", " Montos totales de cobro adeudados. ", "Tipo de trabajo.","Años en el trabajo " ], "Total NA":vars_ })
result=result.reset_index()
| index | Variables | Descripción | Total NA |
|---|---|---|---|
| total_rev_hi_lim | total_rev_hi_lim | Total crédito rotativo alto entre límite de crédito. | 70276 |
| tot_cur_bal | tot_cur_bal | Saldo corriente en todas las cuentas | 70276 |
| tot_coll_amt | tot_coll_amt | Montos totales de cobro adeudados. | 70276 |
| emp_title | emp_title | Tipo de trabajo. | 27588 |
| emp_length | emp_length | Años en el trabajo | 21008 |
De estas variables puede ser dificil que el usuario obtenga total_rev_hi_lim, emp_title tiene muchas categorías.
Es importante identificar que variables puede dar un usuario al momento del registro, pues existen variables donde se obtienen la información al pasar el tiempo o un usuario no puede identificar.
| funded_amnt_inv | grade | sub_grade | emp_title |
| verification_status | zip_code | addr_state | dti |
| delinq_2yrs | inq_last_6mths | revol_bal | revol_util |
| total_acc | out_prncp | out_prncp_inv | out_prncp_inv |
| total_pymnt | total_rec_prncp | total_rec_int | total_rec_late_fee |
| recoveries | collection_recovery_fee | last_pymnt_amnt | last_credit_pull_d |
| collections_12_mths_ex_med | policy_code | tot_coll_amt | total_rev_hi_lim |
Las variables en la tabla no se tienen en cuenta porque son medidas que son proporcionadas por LC, información al pasar el tiempo después del prestamo o son extraidas de un externo, por ende, las variables a considerar como influyentes en el incumplimiento de las finanzas son:
| Variables | Descripción |
|---|---|
| loan_amnt | El monto indicado del préstamo solicitado por el prestatario. Si en algún momento, el departamento de crédito reduce el monto del préstamo, entonces se reflejará en este valor. |
| funded_amnt | El monto total comprometido con ese préstamo en ese momento. |
| term | El número de pagos del préstamo. Los valores son en meses y pueden ser 36 o 60. |
| int_rate | tasa de interés del préstamo. |
| installment | cuota El pago mensual adeudado por el prestatario si el préstamo se origina. |
| home_ownership | El estado de propiedad de la vivienda proporcionado por el prestatario durante el registro. Nuestros valores son |
| annual_inc | Los ingresos anuales autoinformados proporcionados por el prestatario durante el registro. |
| earliest_cr_line | El mes en que se abrió la primera línea de crédito reportada del prestatario. |
| open_acc | El número de líneas de crédito abiertas en el archivo de crédito del prestatario. |
| pub_rec | numero de derogatory public records. |
| acc_now_delinq | El número de cuentas en las que el prestatario está ahora en mora. |
| purpose | Razón por la que se hace el prestamo. |
| tot_cur_bal | Saldo corriente total de todas las cuentas |
| emp_length | años trabajo |
| initial_list_status | El estado inicial de listado del préstamo. Los valores posibles son – W, F |
| pymnt_plan | indica si se a establecido un plan de pago. |
En esta tabla se tienen las posibles variables para el modelo, con good_status y target_time.
variables_=pd.Series(variables_).apply(lambda x: x.replace("__","" ))
df["month_earliest_cr_line"]=((df.issue_d-df.earliest_cr_line )/np.timedelta64(1, 'M'))
df=df[ [*variables_, "good_status","target_time","month_earliest_cr_line" ]]
df=df[~df.isna().any(axis=1)]
result=pd.DataFrame({"":["Filas", "Columnas"] ,"Cantidad":df.shape})
| Cantidad | |
|---|---|
| Filas | 377062 |
| Columnas | 19 |
Como resultado para el modelo se usara un data frame con estas dimesiones.
¿Cuál es la distribución de good_estatus?
tabla_1=df.good_status.value_counts(normalize=True)*100
tabla_2=df.good_status.value_counts()
tabla_3=df.target_time.value_counts(normalize=True)*100
tabla_4=df.target_time.value_counts()
table_final=pd.DataFrame({"good status":[1,0],"Frecuencia":tabla_2,
"Frec %":tabla_1, "target time":[0,1],
"Frecuencia ":tabla_4, "Frec % ":tabla_3 } )
| good status | Frecuencia | Frec % | target time | Frecuencia | Frec % |
|---|---|---|---|---|---|
| 1 | 37709 | 10 | 0 | 305925 | 81.13 |
| 0 | 339353 | 90 | 1 | 71137 | 18.87 |
La variable acc_now_delinq se puede transformar en 1 si tiene al menos una cuenta en mora, 0 sino tiene cunetas en mora. También la variable pub_rec se transforma 1 si tiene al menos un derogatory public records, 0 sino. Se tomo la decisión ya que son variables conteos donde no parece ser necesario. Como esta es la fecha en que se hizo su primer prestamo earliest_cr_line, se debe calcular los meses que han pasado desde que solicito el prestamo month_earliest_cr_line
df=df.drop(labels="earliest_cr_line",axis=1)
df["acc_now_delinq"]=np.where(df["acc_now_delinq"]>0, 1,0)
df["pub_rec"]=np.where(df["pub_rec"]>0,1,0)
df_temp=df.copy()
df_temp["acc_now_delinq"]=df_temp["acc_now_delinq"].astype("str")
df_temp["target_time"]=df_temp["target_time"].astype("str")
df_temp["pub_rec"]=df_temp["pub_rec"].astype("str")
X=df_temp.drop(labels="good_status",axis=1)
Y=df_temp["good_status"]
X_train_cat = X.select_dtypes(include = 'object').copy()
X_train_num = X.select_dtypes(include = 'number').copy()
# define an empty dictionary to store chi-squared test results
chi2_check = {}
# loop over each column in the training set to calculate chi-statistic with the target variable
for column in X_train_cat:
chi, p, dof, ex = chi2_contingency(pd.crosstab(Y, X_train_cat[column]))
chi2_check.setdefault('Feature',[]).append(column)
chi2_check.setdefault('p-value',[]).append(round(p, 10))
# convert the dictionary to a DF
chi2_result = pd.DataFrame(data = chi2_check)
chi2_result.sort_values(by = ['p-value'], ascending = True, ignore_index = True, inplace = True)
| Feature | p-value |
|---|---|
| term | 0.00 |
| home_ownership | 0.00 |
| purpose | 0.00 |
| emp_length | 0.00 |
| initial_list_status | 0.00 |
| target_time | 0.00 |
| pub_rec | 0.00 |
| pymnt_plan | 0.14 |
| acc_now_delinq | 0.51 |
Se realiza pruebas \(\chi^2\) para las variables categoricas en contraste con la variable good_status y se observa 8 variables con un p-valor pequeño, esto significa que estas variables pueden influir en el incumplimiento de las finanzas. Se excluye pymnt_plan debido a un p-valor > 0.05.
corrmat = X_train_num.corr()
sns.heatmap(corrmat)
Correlación entre variables númericas.
En la figura 1 se observa que hay 3 variables con una alta correlación entre si (\(\approx\) 1) que son: loan_amnt, funded_amnt, installment y según la table - 5 se opta por loan_amnt por ser el prestamo definitivo que dio LC.
vars_=['loan_amnt', 'int_rate', 'annual_inc',
'open_acc', 'tot_cur_bal',"month_earliest_cr_line"]
fig, axs = plt.subplots(ncols=3,nrows=2,figsize=(18, 7))
sns.boxplot(data=df, x="good_status", y=vars_[0], ax=axs[0, 0],showfliers = False)
sns.boxplot(data=df, x="good_status", y=vars_[1], ax=axs[0, 1],showfliers = False)
sns.boxplot(data=df, x="good_status", y=vars_[2], ax=axs[0, 2],showfliers = False)
sns.boxplot(data=df, x="good_status", y=vars_[3], ax=axs[1, 1],showfliers = False)
sns.boxplot(data=df, x="good_status", y=vars_[4], ax=axs[1, 2],showfliers = False)
sns.boxplot(data=df, x="good_status", y=vars_[5], ax=axs[1, 0],showfliers = False)
Box plot comparativos dado good_status
En la figura se observa que las variables con una tendencia de influir en good_status son int_rate, annual_inc pues aunque no se observa gran diferencia, parece existir una influencia.
Las variables a usar para crear el modelo son:
variables_final=list(chi2_result["Feature"].iloc[0:8])
variables_final=[*variables_final,*vars_,"good_status"]
result=np.array([*variables_final,""])
result.shape=(4,4)
result=pd.DataFrame(result)
result.columns=[""]*4
| term | home_ownership | purpose | emp_length |
| initial_list_status | target_time | pub_rec | pymnt_plan |
| loan_amnt | int_rate | annual_inc | open_acc |
| tot_cur_bal | month_earliest_cr_line | good_status |
Estas son las covariables que se tendrán en el modelo.
df.loc[df["home_ownership"] == "ANY","home_ownership"] = 'NONE'
var_cat=['term','pub_rec','home_ownership','initial_list_status','purpose','emp_length']
fig_ = make_subplots(rows=4, cols=2,subplot_titles =var_cat)
fig_ =plot_barplot(df, var_cat[0],"good_status", 1,1,fig_,True)
fig_ =plot_barplot(df, var_cat[1],"good_status", 1,2,fig_, True)
fig_ =plot_barplot(df, var_cat[2],"good_status", 2,1,fig_, True)
fig_ =plot_barplot(df, var_cat[3],"good_status", 2,2,fig_, True)
fig_ =plot_barplot(df, var_cat[4],"good_status", 3,1,fig_, True)
fig_ =plot_barplot(df, var_cat[5],"good_status", 3,2,fig_, True)
# fig_ =plot_barplot(df, var_cat[6],"good_status", 4,1,fig_, True)
fig_.update_layout(
title="Good status",
xaxis_tickfont_size=14,
yaxis=dict(
title='Distribution percent ',
titlefont_size=16,
tickfont_size=14,
),
legend=dict(
x=1,
y=1.0,
bgcolor='rgba(255, 255, 255, 0)',
bordercolor='rgba(255, 255, 255, 0)'
),
barmode='group',
bargap=0.15, # gap between bars of adjacent location coordinates.
bargroupgap=0.1, # gap between bars of the same location coordinate.
height=1000, width=900
)
Cuando el número de pagos es \(>\) 36 meses la probabilidad de incumplimiento es mayor, es decir, un usuario puede inclumir si tiene mas número de pagos al inicio del prestamo.
Si el usuario marca NONE o OTHER o RENT influye negativamente, es decir, aumenta la probabilidad de que incumpla sus obligaciones financieras.
Si el proposito del prestamo es small_business, house, weddlng aumenta la probabilidad de que incumpla sus obligaciones financieras.
A medida que el usuario tiene menor tiempo de trabajo, aumenta la probabilidad de que incumpla sus obligaciones financieras.
Se plantean diferentes modelos aplicando validación cruzada 80% prueba 20% test.
Luego de ensayar modelos se encontro que el valor de probabilidad de cambio mas adecuado es 0.8, es decir, si la probabilidad >=0.8 good_status 1, por el contrario 0.
df_modelo=df[variables_final].copy()
df_modelo.loc[df_modelo["home_ownership"] == "ANY","home_ownership"] = 'NONE'
X = df_modelo.drop('good_status', axis = 1).copy()
X=dummy_creation(X, ["term","home_ownership", "purpose",'initial_list_status','emp_length' ,"pymnt_plan"])
y = df_modelo['good_status'].copy()
# y= np.where(y==1,0,1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 421)#, stratify = y)
X_train, X_test = X_train.copy(), X_test.copy()
result=pd.DataFrame({"":["Filas", "Columnas"] ,"Trian":X_train.shape,"Test":X_test.shape })
| Trian | Test | |
|---|---|---|
| Filas | 301649 | 75413 |
| Columnas | 37 | 37 |
De las variables que se escogieron se probo eliminando variables y para el modelo las variables selecionadas son: target_time, pub_rec, loan_amnt, int_rate, open_acc, home_ownership.
var_=[
'target_time',
'pub_rec',
'loan_amnt',
'int_rate',
'open_acc',
'home_ownership:NONE', 'home_ownership:OTHER', 'home_ownership:OWN',
'home_ownership:RENT',
]
X_train_pca=X_train[var_]
# X_train_pca=PCA_5.transform(X_train)
X_test_pca=X_test[var_]
# X_test_pca=PCA_5.transform(X_test)
clf = GaussianNB()
clf.fit(X_train_pca, y_train)
## GaussianNB()
prop_=0.8
predict_train_nb= clf.predict_proba(X_train_pca)[:,1]
predict_test_nb= clf.predict_proba(X_test_pca)[:,1]
predict_train_nb=np.where(predict_train_nb>=prop_,1,0 )
predict_test_nb=np.where(predict_test_nb>=prop_,1,0 )
tasa_acierto_1=accuracy_score(y_train, predict_train_nb)*100
tasa_acierto_2=accuracy_score(y_test,predict_test_nb )*100
result=pd.DataFrame({"Población":["Train", "Test"], "Aciertos": [tasa_acierto_1,tasa_acierto_2 ] })
errores_bayes=pd.concat([pd.crosstab(y_train,predict_train_nb ,margins=False, normalize=True, colnames=["predicción"], rownames=['Real']),
pd.crosstab(y_test,predict_test_nb , margins=False, normalize=True, colnames=["predicción"], rownames=['Real'])
],axis=1,keys=["Train", "Test"])
| Población | Aciertos |
|---|---|
| Train | 85.11 |
| Test | 85.20 |
Se observa una tasa de acierto homogenea entre los datos de prueba y entrenamiento.
Para este modelo se encontraron las variables apropiadas: target_time, pub_rec, loan_amnt, int_rate, open_acc, home_ownership, emp_length.
var_=[
'target_time',
'pub_rec',
'loan_amnt',
'int_rate',
'open_acc',
'home_ownership:NONE', 'home_ownership:OTHER', 'home_ownership:OWN',
'home_ownership:RENT',
'emp_length:10+ years', 'emp_length:2 years',
'emp_length:3 years', 'emp_length:4 years', 'emp_length:5 years',
'emp_length:6 years', 'emp_length:7 years', 'emp_length:8 years',
'emp_length:9 years', 'emp_length:< 1 year'
]
X_train_pca=X_train[var_]
# X_train_pca=PCA_5.transform(X_train)
X_test_pca=X_test[var_]
# X_test_pca=PCA_5.transform(X_test)
Modelo = LogisticRegression()#C=1e-09,class_weight="balanced",solver="sag")
Modelo.fit(X_train_pca, y_train)
## LogisticRegression()
prop_=0.8
predict_train_log= Modelo.predict_proba(X_train_pca)[:,1]
predict_test_log= Modelo.predict_proba(X_test_pca)[:,1]
predict_train_log=np.where(predict_train_log>=prop_,1,0 )
predict_test_log=np.where(predict_test_log>=prop_,1,0 )
tasa_acierto_1=accuracy_score(y_train, predict_train_log)*100
tasa_acierto_2=accuracy_score(y_test,predict_test_log )*100
result=pd.DataFrame({"Población":["Train", "Test"], "Aciertos": [tasa_acierto_1,tasa_acierto_2 ] })
errores_logistico=pd.concat([pd.crosstab(y_train,predict_train_log ,margins=False, normalize=True, colnames=["predicción"], rownames=['Real']),
pd.crosstab(y_test,predict_test_log , margins=False, normalize=True, colnames=["predicción"], rownames=['Real'])
],axis=1,keys=["Train", "Test"])
| Población | Aciertos |
|---|---|
| Train | 82.23 |
| Test | 82.45 |
Esto es menor al modelo de bayes, luego se deben analizar los tipos de errores en cada modelo.
| Train 0 | 1 | Test 0 | 1 | |
|---|---|---|---|---|
| 0 | 4.34 | 5.71 | 4.36 | 5.48 |
| 1 | 12.06 | 77.90 | 12.07 | 78.09 |
| Train 0 | 1 | Test 0 | 1 | |
|---|---|---|---|---|
| 0 | 4.26 | 5.78 | 4.18 | 5.66 |
| 1 | 9.11 | 80.85 | 9.14 | 81.02 |
Se observa que el modelo logístico tiene mayor tasa de acierto cuando good_status es 0, se tiene en cuenta las variables para el modelo.
Con las variables seleccionadas se obta por crear un modelo con todos los datos, las variables numericas se dividen en 4 grupos (open_acc en 2 grupos) con pd.cut, se define un score de 300 a 850.
Para las variables el nivel de refencia son:
loan_amnt: (9500.0, 18000.0]
int_rate: (5.98, 11.015]
open_acc: (-0.084, 42.0]
home_ownership: MORTGAGE
emp_length: 1 year
X_model=X[var_ ].copy()
X_model=X_model.reset_index()
X_model=X_model.drop(labels="index", axis=1)
# varibles continuas
X_model["loan_amnt"]=pd.cut(X_model["loan_amnt"],4)
X_model["int_rate"]=pd.cut(X_model["int_rate"],4)
X_model["open_acc"]=pd.cut(X_model["open_acc"],2)
# dummy de variables
X_model=dummy_creation(X_model, ["loan_amnt","int_rate", "open_acc"])
# ajuiste modelo logistico
modelo_final = LogisticRegression()
modelo_final.fit(X_model, Y)
## LogisticRegression()
df_scorecard=pd.DataFrame({"variables_modelo":["(intercept)", *list(X_model.columns)],
"Coefficients":[modelo_final.intercept_[0],*modelo_final.coef_.tolist()[0]] })
df_scorecard["var_origen"]=df_scorecard["variables_modelo"].apply(lambda x: x.split(":")[0] )
min_score = 300
max_score = 850
def min_temp(vector):
result=vector.min()
if result>0 :
result=0
return result
def max_temp(vector):
result=vector.max()
if result<0:
result=0
return result
# calculate the sum of the minimum coefficients of each category within the original feature name
min_sum_coef = df_scorecard.groupby('var_origen')['Coefficients'].apply(min_temp).sum()+df_scorecard.loc[0,'Coefficients']
# calculate the sum of the maximum coefficients of each category within the original feature name
max_sum_coef = df_scorecard.groupby('var_origen')['Coefficients'].apply(max_temp).sum()#+df_scorecard.loc[0,'Coefficients']
df_scorecard['Score - Calculation'] = df_scorecard['Coefficients'] * (max_score - min_score) / (max_sum_coef - min_sum_coef)
# min_sum_coef = df_scorecard.groupby('var_origen')['Coefficients'].apply(min).sum()
# update the calculated score of the Intercept
df_scorecard.loc[0, 'Score - Calculation'] = ((df_scorecard.loc[0,'Coefficients'] - min_sum_coef) /
(max_sum_coef - min_sum_coef
)) * (max_score - min_score) + min_score
# # round the values of the 'Score - Calculation' column and store them in a new column
df_scorecard['Score - Final'] = df_scorecard['Score - Calculation'].round()
# check the min and max possible scores of our scorecard
min_sum_score_prel = df_scorecard.groupby('var_origen')['Score - Final'].apply(min_temp).sum()+df_scorecard.loc[0, 'Score - Final']
max_sum_score_prel = df_scorecard.groupby('var_origen')['Score - Final'].apply(max_temp).sum()
| variables_modelo | Coefficients | var_origen | Score - Calculation | Score - Final |
|---|---|---|---|---|
| (intercept) | 3.90 | (intercept) | 773.17 | 773 |
| target_time | -1.67 | target_time | -170.63 | -171 |
| pub_rec | 0.20 | pub_rec | 20.24 | 20 |
| home_ownership:NONE | -0.49 | home_ownership | -50.15 | -50 |
| home_ownership:OTHER | -0.81 | home_ownership | -82.93 | -83 |
| home_ownership:OWN | -0.08 | home_ownership | -8.40 | -8 |
| home_ownership:RENT | -0.25 | home_ownership | -25.69 | -26 |
| emp_length:10+ years | 0.08 | emp_length | 7.68 | 8 |
| emp_length:2 years | 0.00 | emp_length | -0.12 | 0 |
| emp_length:3 years | 0.00 | emp_length | -0.35 | 0 |
| emp_length:4 years | 0.02 | emp_length | 2.38 | 2 |
| emp_length:5 years | -0.06 | emp_length | -5.71 | -6 |
| emp_length:6 years | -0.10 | emp_length | -9.79 | -10 |
| emp_length:7 years | -0.02 | emp_length | -1.97 | -2 |
| emp_length:8 years | -0.04 | emp_length | -3.84 | -4 |
| emp_length:9 years | -0.06 | emp_length | -6.22 | -6 |
| emp_length:< 1 year | -0.07 | emp_length | -6.72 | -7 |
| loan_amnt:(9500.0, 18000.0] | -0.17 | loan_amnt | -17.06 | -17 |
| loan_amnt:(18000.0, 26500.0] | -0.19 | loan_amnt | -19.34 | -19 |
| loan_amnt:(26500.0, 35000.0] | -0.10 | loan_amnt | -9.82 | -10 |
| int_rate:(11.015, 16.03] | -0.89 | int_rate | -90.80 | -91 |
| int_rate:(16.03, 21.045] | -1.48 | int_rate | -151.28 | -151 |
| int_rate:(21.045, 26.06] | -1.86 | int_rate | -190.47 | -190 |
| open_acc:(42.0, 84.0] | 0.48 | open_acc | 48.91 | 49 |
En esta tabla se puede observar que la población que desde el inicio del crédito al final del crédito con \(\leq 12\) meses afecta negativamente al score con 171 puntos menos, si se tiene el estado de la vivienda diferente a hipoteca, afecta negativamente, si se tiene varias lineas de crédito (\(\geq\) 42) afecta de manera positia, si la tasa de interes es alta afecta de manera negativa al score, si se pide un prestamo superior a 9.500 dolares afecta el score.
scores=np.array(df_scorecard["Score - Final"])
scores.shape=(24,1)
X_model_temp=pd.concat([ pd.DataFrame({"(intercept)" :list(np.repeat(1,X_model.shape[0]))} ),X_model],axis=1,ignore_index=True ).copy()
score_poblacional=np.matmul(np.array(X_model_temp),scores)
score_poblacional=pd.DataFrame(np.transpose(score_poblacional).tolist()[0])
Y=Y.reset_index()
Y=Y.drop(labels="index",axis=1 )
score_poblacional["good_status"]=Y
fig = px.histogram(score_poblacional, x=0 ,color="good_status",
marginal="box", #or violin, rug
hover_data=score_poblacional.columns)
fig.show()
score_poblacional.to_csv("score_poblacional.csv")
Esta es la distrubución del score para toda la población y se discrimina por good_status, se observa que aqullos usuarios con incumpliminetos financieros en promedio tienen un score menor a los que no tienen incumpliminetos financieros.
Hay varios factores, unos más determinantes que otros, en todo el problema de riesgo de crédito.
Dentro de los factores que más influyen se encuentran: la estabilidad laboral, conocida también como el tiempo que lleva cada individuo trabajando para la misma empresa. El número de líneas de crédito que tiene, que se puede interpretar como el nivel de endeudamiento del individuo. El si ha incumplido obligaciones financieras en el pasado. El número y tipo de bienes raíces que posee. También es vital la cantidad del préstamo solicitado, ya que si la cantidad es pequeña se puede ser más indulgente que en caso contrario. Otro factor importante es el interés al cual se presta, pues, a mayor interés es más poco probable que el crédito sea otorgado.