miércoles, 1 de julio de 2020

Predicción de aprobaciones de tarjetas de Crédito

Untitled

Riesgo de crédito en tarjetas de crédito

Los bancos comerciales reciben muchas solicitudes de tarjetas de crédito. Muchos de ellos son rechazados por muchas razones, como saldos elevados de préstamos, bajos niveles de ingresos o demasiadas consultas sobre el informe crediticio de una persona, por ejemplo. El análisis manual de estas aplicaciones es mundano, propenso a errores y requiere mucho tiempo (¡y el tiempo es dinero!). Afortunadamente, esta tarea se puede automatizar con el poder del aprendizaje automático y casi todos los bancos comerciales lo hacen hoy en día. En este cuaderno, crearemos un predictor automático de aprobación de tarjetas de crédito utilizando técnicas de aprendizaje automático, tal como lo hacen los bancos reales.

In [1]:
import pandas as pd
cc_apps = pd.read_csv('datasets/cc_approvals.data', header=None)
cc_apps.head()
Out[1]:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 b 30.83 0.000 u g w v 1.25 t t 1 f g 00202 0 +
1 a 58.67 4.460 u g q h 3.04 t t 6 f g 00043 560 +
2 a 24.50 0.500 u g q h 1.50 t f 0 f g 00280 824 +
3 b 27.83 1.540 u g w v 3.75 t t 5 t g 00100 3 +
4 b 20.17 5.625 u g w v 1.71 t f 0 f s 00120 0 +

2. Inspección

El resultado puede parecer un poco confuso a primera vista, pero intentemos descubrir las características más importantes de una aplicación de tarjeta de crédito. Las funciones de este conjunto de datos se han anonimizado para proteger la privacidad, pero este blog nos brinda una bonita buena descripción de las características probables. Las características probables en una aplicación de tarjeta de crédito típica son Género , Edad , Debt , Married , BankCustomer , EducationLevel , Ethnicity , YearsEmployed , PriorDefault , Employed , CreditScore , DriversLicense , Citizen , ZipCode , Income y finalmente el ApprovalStatus . Esto nos da un buen punto de partida y podemos mapear estas características con respecto a las columnas en la salida.

Como podemos ver en nuestro primer vistazo a los datos, el conjunto de datos tiene una combinación de características numéricas y no numéricas. Esto se puede solucionar con un poco de procesamiento previo, pero antes de hacerlo, aprendamos un poco más sobre el conjunto de datos para ver si hay otros problemas del conjunto de datos que deban solucionarse.

In [2]:
# Print summary statistics
cc_apps_description = cc_apps.describe()
print(cc_apps_description)

print("\n")

# Print DataFrame information
cc_apps_info = cc_apps.info()
print(cc_apps_info)

print("\n")

# Inspect missing values in the dataset
cc_apps.tail(17)
               2           7          10             14
count  690.000000  690.000000  690.00000     690.000000
mean     4.758725    2.223406    2.40000    1017.385507
std      4.978163    3.346513    4.86294    5210.102598
min      0.000000    0.000000    0.00000       0.000000
25%      1.000000    0.165000    0.00000       0.000000
50%      2.750000    1.000000    0.00000       5.000000
75%      7.207500    2.625000    3.00000     395.500000
max     28.000000   28.500000   67.00000  100000.000000


<class 'pandas.core.frame.DataFrame'>
RangeIndex: 690 entries, 0 to 689
Data columns (total 16 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   0       690 non-null    object 
 1   1       690 non-null    object 
 2   2       690 non-null    float64
 3   3       690 non-null    object 
 4   4       690 non-null    object 
 5   5       690 non-null    object 
 6   6       690 non-null    object 
 7   7       690 non-null    float64
 8   8       690 non-null    object 
 9   9       690 non-null    object 
 10  10      690 non-null    int64  
 11  11      690 non-null    object 
 12  12      690 non-null    object 
 13  13      690 non-null    object 
 14  14      690 non-null    int64  
 15  15      690 non-null    object 
dtypes: float64(2), int64(2), object(12)
memory usage: 86.4+ KB
None


Out[2]:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
673 ? 29.50 2.000 y p e h 2.000 f f 0 f g 00256 17 -
674 a 37.33 2.500 u g i h 0.210 f f 0 f g 00260 246 -
675 a 41.58 1.040 u g aa v 0.665 f f 0 f g 00240 237 -
676 a 30.58 10.665 u g q h 0.085 f t 12 t g 00129 3 -
677 b 19.42 7.250 u g m v 0.040 f t 1 f g 00100 1 -
678 a 17.92 10.210 u g ff ff 0.000 f f 0 f g 00000 50 -
679 a 20.08 1.250 u g c v 0.000 f f 0 f g 00000 0 -
680 b 19.50 0.290 u g k v 0.290 f f 0 f g 00280 364 -
681 b 27.83 1.000 y p d h 3.000 f f 0 f g 00176 537 -
682 b 17.08 3.290 u g i v 0.335 f f 0 t g 00140 2 -
683 b 36.42 0.750 y p d v 0.585 f f 0 f g 00240 3 -
684 b 40.58 3.290 u g m v 3.500 f f 0 t s 00400 0 -
685 b 21.08 10.085 y p e h 1.250 f f 0 f g 00260 0 -
686 a 22.67 0.750 u g c v 2.000 f t 2 t g 00200 394 -
687 a 25.25 13.500 y p ff ff 2.000 f t 1 t g 00200 1 -
688 b 17.92 0.205 u g aa v 0.040 f f 0 f g 00280 750 -
689 b 35.00 3.375 u g c h 8.290 f f 0 t g 00000 0 -

3. Manejo de los valores perdidos (parte i)

Hemos descubierto algunos problemas que afectarán el rendimiento de nuestros modelos de aprendizaje automático si no se modifican:

  • Nuestro conjunto de datos contiene datos numéricos y no numéricos (específicamente datos que son de los tipos float64 , int64 y object ). Específicamente, las características 2, 7, 10 y 14 contienen valores numéricos (de tipos float64, float64, int64 e int64 respectivamente) y todas las demás características contienen valores no numéricos.
  • El conjunto de datos también contiene valores de varios rangos. Algunas características tienen un rango de valores de 0 a 28, algunas tienen un rango de 2 a 67 y algunas tienen un rango de 1017 a 100000. Aparte de estas, podemos obtener información estadística útil (como mean , max y min ) sobre las características que tienen valores numéricos.
  • Finalmente, el conjunto de datos tiene valores faltantes, de los que nos ocuparemos en esta tarea. Los valores faltantes en el conjunto de datos están etiquetados con '?', Que se puede ver en la salida de la última celda.

Ahora, reemplacemos temporalmente estos signos de interrogación de valor faltante con NaN.

In [3]:
# Import numpy
import numpy as np
# Inspect missing values in the dataset
print(cc_apps.tail(17))

# Replace the '?'s with NaN
cc_apps = cc_apps.replace('?',np.nan)

# Inspect the missing values again
cc_apps.tail(17)
    0      1       2  3  4   5   6      7  8  9   10 11 12     13   14 15
673  ?  29.50   2.000  y  p   e   h  2.000  f  f   0  f  g  00256   17  -
674  a  37.33   2.500  u  g   i   h  0.210  f  f   0  f  g  00260  246  -
675  a  41.58   1.040  u  g  aa   v  0.665  f  f   0  f  g  00240  237  -
676  a  30.58  10.665  u  g   q   h  0.085  f  t  12  t  g  00129    3  -
677  b  19.42   7.250  u  g   m   v  0.040  f  t   1  f  g  00100    1  -
678  a  17.92  10.210  u  g  ff  ff  0.000  f  f   0  f  g  00000   50  -
679  a  20.08   1.250  u  g   c   v  0.000  f  f   0  f  g  00000    0  -
680  b  19.50   0.290  u  g   k   v  0.290  f  f   0  f  g  00280  364  -
681  b  27.83   1.000  y  p   d   h  3.000  f  f   0  f  g  00176  537  -
682  b  17.08   3.290  u  g   i   v  0.335  f  f   0  t  g  00140    2  -
683  b  36.42   0.750  y  p   d   v  0.585  f  f   0  f  g  00240    3  -
684  b  40.58   3.290  u  g   m   v  3.500  f  f   0  t  s  00400    0  -
685  b  21.08  10.085  y  p   e   h  1.250  f  f   0  f  g  00260    0  -
686  a  22.67   0.750  u  g   c   v  2.000  f  t   2  t  g  00200  394  -
687  a  25.25  13.500  y  p  ff  ff  2.000  f  t   1  t  g  00200    1  -
688  b  17.92   0.205  u  g  aa   v  0.040  f  f   0  f  g  00280  750  -
689  b  35.00   3.375  u  g   c   h  8.290  f  f   0  t  g  00000    0  -
Out[3]:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
673 NaN 29.50 2.000 y p e h 2.000 f f 0 f g 00256 17 -
674 a 37.33 2.500 u g i h 0.210 f f 0 f g 00260 246 -
675 a 41.58 1.040 u g aa v 0.665 f f 0 f g 00240 237 -
676 a 30.58 10.665 u g q h 0.085 f t 12 t g 00129 3 -
677 b 19.42 7.250 u g m v 0.040 f t 1 f g 00100 1 -
678 a 17.92 10.210 u g ff ff 0.000 f f 0 f g 00000 50 -
679 a 20.08 1.250 u g c v 0.000 f f 0 f g 00000 0 -
680 b 19.50 0.290 u g k v 0.290 f f 0 f g 00280 364 -
681 b 27.83 1.000 y p d h 3.000 f f 0 f g 00176 537 -
682 b 17.08 3.290 u g i v 0.335 f f 0 t g 00140 2 -
683 b 36.42 0.750 y p d v 0.585 f f 0 f g 00240 3 -
684 b 40.58 3.290 u g m v 3.500 f f 0 t s 00400 0 -
685 b 21.08 10.085 y p e h 1.250 f f 0 f g 00260 0 -
686 a 22.67 0.750 u g c v 2.000 f t 2 t g 00200 394 -
687 a 25.25 13.500 y p ff ff 2.000 f t 1 t g 00200 1 -
688 b 17.92 0.205 u g aa v 0.040 f f 0 f g 00280 750 -
689 b 35.00 3.375 u g c h 8.290 f f 0 t g 00000 0 -

4. Manejo de los valores perdidos (parte ii)

Reemplazamos todos los signos de interrogación con NaN. Esto nos ayudará en el próximo tratamiento de valor perdido que vamos a realizar.

Una pregunta importante que se plantea aquí es ¿por qué le damos tanta importancia a los valores perdidos ? ¿No se pueden simplemente ignorar? Ignorar los valores perdidos puede afectar en gran medida el rendimiento de un modelo de aprendizaje automático. Si bien ignora los valores faltantes, nuestro modelo de aprendizaje automático puede perder información sobre el conjunto de datos que puede ser útil para su entrenamiento. Entonces, hay muchos modelos que no pueden manejar valores perdidos implícitamente, como LDA.

Entonces, para evitar este problema, vamos a imputar los valores perdidos con una estrategia llamada imputación media.

In [4]:
# Impute the missing values with mean imputation
cc_apps.fillna(cc_apps.mean(), inplace=True)

# Count the number of NaNs in the dataset to verify
cc_apps.isnull().sum()
Out[4]:
0     12
1     12
2      0
3      6
4      6
5      9
6      9
7      0
8      0
9      0
10     0
11     0
12     0
13    13
14     0
15     0
dtype: int64

5. Manejo de los valores perdidos (parte iii)

Nos hemos ocupado con éxito de los valores faltantes presentes en las columnas numéricas. Todavía hay algunos valores faltantes para imputar para las columnas 0, 1, 3, 4, 5, 6 y 13. Todas estas columnas contienen datos no numéricos y por eso la estrategia de imputación media no funcionaría aquí. Esto necesita un tratamiento diferente.

Vamos a imputar estos valores perdidos con los valores más frecuentes presentes en las columnas respectivas. Esta es una buena práctica cuando se trata de imputar valores faltantes para datos categóricos en general.

In [5]:
# Iterate over each column of cc_apps
for col in cc_apps.columns:
    # Check if the column is of object type
    if cc_apps[col].dtypes == 'object':
        # Impute with the most frequent value
        cc_apps = cc_apps.fillna(cc_apps[col].value_counts().index[0])

# Count the number of NaNs in the dataset and print the counts to verify
print(cc_apps.isnull().sum())
0     0
1     0
2     0
3     0
4     0
5     0
6     0
7     0
8     0
9     0
10    0
11    0
12    0
13    0
14    0
15    0
dtype: int64

6. Preprocesamiento de los datos (parte i)

Los valores faltantes ahora se gestionan correctamente.

Todavía se necesita un preprocesamiento de datos menor pero esencial antes de continuar con la construcción de nuestro modelo de aprendizaje automático. Vamos a dividir estos pasos de preprocesamiento restantes en tres tareas principales:

  1. Convierta los datos no numéricos en numéricos.
  2. Divida los datos en conjuntos de prueba y de tren.
  3. Escale los valores de las funciones a un rango uniforme.

Primero, convertiremos todos los valores no numéricos en valores numéricos. Hacemos esto porque no solo da como resultado un cálculo más rápido, sino que también muchos modelos de aprendizaje automático (como XGBoost) (y especialmente los desarrollados con scikit-learn) requieren que los datos estén en un formato estrictamente numérico. Haremos esto usando una técnica llamada codificación de etiquetas .

In [6]:
# Import LabelEncoder
from sklearn.preprocessing import LabelEncoder

# Instantiate LabelEncoder
le=LabelEncoder()

# Iterate over all the values of each column and extract their dtypes
for col in cc_apps.columns.values:
    # Compare if the dtype is object
    if cc_apps[col].dtypes=='object':
    # Use LabelEncoder to do the numeric transformation
        cc_apps[col]=le.fit_transform(cc_apps[col])

7. Dividir el conjunto de datos en conjuntos de prueba y de entrenamiento

Hemos convertido correctamente todos los valores no numéricos en valores numéricos.

Ahora, dividiremos nuestros datos en un conjunto de trenes y un conjunto de pruebas para preparar nuestros datos para dos fases diferentes del modelado de aprendizaje automático: entrenamiento y prueba. Idealmente, no se debe usar ninguna información de los datos de prueba para escalar los datos de entrenamiento o se debe usar para dirigir el proceso de entrenamiento de un modelo de aprendizaje automático. Por lo tanto, primero dividimos los datos y luego aplicamos la escala.

Además, funciones como DriversLicense y ZipCode no son tan importantes como las otras funciones del conjunto de datos para predecir aprobaciones de tarjetas de crédito. Deberíamos descartarlos para diseñar nuestro modelo de aprendizaje automático con el mejor conjunto de características. En la literatura sobre ciencia de datos, esto a menudo se denomina selección de características .

In [7]:
# Import train_test_split
from sklearn.model_selection import train_test_split
# Drop the features 11 and 13 and convert the DataFrame to a NumPy array
cc_apps = cc_apps.drop([cc_apps.columns[11],cc_apps.columns[13]], axis=1)
cc_apps = cc_apps.values

# Segregate features and labels into separate variables
X,y = cc_apps[:,0:13], cc_apps[:,13]

# Split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X,
                                y,
                                test_size=0.33,
                                random_state=42)

8. Preprocesamiento de los datos (parte ii)

Los datos ahora se dividen en dos conjuntos separados: conjuntos de entrenamiento y de prueba, respectivamente. Solo nos queda un paso final de preprocesamiento de escalado antes de que podamos ajustar un modelo de aprendizaje automático a los datos.

Ahora, intentemos comprender qué significan estos valores escalados en el mundo real. Usemos CreditScore como ejemplo. La puntuación de crédito de una persona es su solvencia basada en su historial crediticio. Cuanto mayor sea este número, más confiable financieramente se considera que es una persona. Por lo tanto, un CreditScore de 1 es el más alto, ya que estamos cambiando la escala de todos los valores al rango de 0-1.

Send feedback History Saved Contribute

In [8]:
# Import MinMaxScaler
from sklearn.preprocessing import MinMaxScaler

# Instantiate MinMaxScaler and use it to rescale X_train and X_test
scaler = MinMaxScaler(feature_range=(0, 1))
rescaledX_train = scaler.fit_transform(X_train)
rescaledX_test = scaler.transform(X_test)

9. Ajuste de un modelo de regresión logística al tren

Básicamente, predecir si una solicitud de tarjeta de crédito será aprobada o no es una tarea de clasificación . Según UCI , nuestro conjunto de datos contiene más instancias que corresponden a Estado "Denegado" que las instancias correspondientes al estado "Aprobado". Específicamente, de 690 casos, hay 383 (55.5%) solicitudes que fueron denegadas y 307 (44.5%) solicitudes que fueron aprobadas.

Esto nos da una referencia. Un buen modelo de aprendizaje automático debería poder predecir con precisión el estado de las aplicaciones con respecto a estas estadísticas.

¿Qué modelo deberíamos elegir? Una pregunta que debe hacerse es: ¿las características que afectan el proceso de decisión de aprobación de la tarjeta de crédito están correlacionadas entre sí? Aunque podemos medir la correlación, está fuera del alcance de este portátil, por lo que confiaremos en nuestra intuición de que de hecho están correlacionados por ahora. Debido a esta correlación, aprovecharemos el hecho de que los modelos lineales generalizados funcionan bien en estos casos. Comencemos nuestro modelado de aprendizaje automático con un modelo de regresión logística (un modelo lineal generalizado).

In [9]:
# Import LogisticRegression
from sklearn.linear_model import LogisticRegression

# Instantiate a LogisticRegression classifier with default parameter values
logreg = LogisticRegression()

# Fit logreg to the train set
logreg.fit(rescaledX_train,y_train)
Out[9]:
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=100,
                   multi_class='auto', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)

10. Hacer predicciones y evaluar el desempeño

Pero, ¿qué tan bien funciona nuestro modelo?

Ahora evaluaremos nuestro modelo en el conjunto de prueba con respecto a la precisión de clasificación . Pero también echaremos un vistazo a la matriz de confusión del modelo. En el caso de predecir aplicaciones de tarjetas de crédito, es igualmente importante ver si nuestro modelo de aprendizaje automático es capaz de predecir el estado de aprobación de las aplicaciones como denegadas que originalmente fueron denegadas. Si nuestro modelo no está funcionando bien en este aspecto, entonces podría terminar aprobando la aplicación que debería haber sido aprobada. La matriz de confusión nos ayuda a ver el desempeño de nuestro modelo desde estos aspectos.

In [10]:
# Import confusion_matrix
from sklearn.metrics import confusion_matrix

# Use logreg to predict instances from the test set and store it
y_pred = logreg.predict(rescaledX_test)

# Get the accuracy score of logreg model and print it
print("Accuracy of logistic regression classifier: ", logreg.score(rescaledX_test,y_test))

# Print the confusion matrix of the logreg model
confusion_matrix(y_test,y_pred)
Accuracy of logistic regression classifier:  0.8421052631578947
Out[10]:
array([[94,  9],
       [27, 98]], dtype=int64)

11. Búsqueda de cuadrículas y mejora del rendimiento del modelo

¡Nuestro modelo era bastante bueno! Pudo producir una puntuación de precisión de casi el 84%.

Para la matriz de confusión, el primer elemento de la primera fila de la matriz de confusión denota los verdaderos negativos, es decir, el número de instancias negativas (aplicaciones denegadas) predichas correctamente por el modelo. Y el último elemento de la segunda fila de la matriz de confusión denota los verdaderos positivos, es decir, el número de instancias positivas (aplicaciones aprobadas) predichas correctamente por el modelo.

Veamos si podemos hacerlo mejor. Podemos realizar una búsqueda de cuadrícula de los parámetros del modelo para mejorar la capacidad del modelo. para predecir las aprobaciones de tarjetas de crédito.

la implementación de scikit-learn de la regresión logística consta de diferentes hiperparámetros, pero nosotros buscará en cuadrícula los dos siguientes:

  • tol
  • max_iter
In [11]:
# Import GridSearchCV
from sklearn.model_selection import GridSearchCV

# Define the grid of values for tol and max_iter
tol = [0.01, 0.001 ,0.0001]
max_iter = [100, 150, 200]

# Create a dictionary where tol and max_iter are keys and the lists of their values are the corresponding values
param_grid = dict(tol=tol, max_iter=max_iter)

12. Encontrar el modelo con mejor rendimiento

Hemos definido la cuadrícula de valores de hiperparámetros y los hemos convertido en un formato de diccionario único que GridSearchCV () espera como uno de sus parámetros. Ahora, comenzaremos la búsqueda en la cuadrícula para ver qué valores funcionan mejor.

Crearemos una instancia de GridSearchCV () con nuestro modelo anterior logreg con todos los datos que tenemos. En lugar de pasar el tren y los conjuntos de prueba por separado, proporcionaremos X (versión escalada) e y . También le indicaremos a GridSearchCV () que realice una validación cruzada de cinco pliegues.

Finalizaremos el cuaderno almacenando la puntuación mejor obtenida y los mejores parámetros respectivos.

Al crear este predictor de tarjetas de crédito, abordamos algunos de los pasos de procesamiento previo más conocidos, como escalado , codificación de etiquetas y imputación de valor faltante . Terminamos con algo de aprendizaje automático para predecir si la solicitud de una persona para una tarjeta de crédito se aprobaría o no se proporcionaría información sobre esa persona.

In [12]:
# Instantiate GridSearchCV with the required parameters
grid_model = GridSearchCV(estimator=logreg, param_grid=param_grid, cv=5)

# Use scaler to rescale X and assign it to rescaledX
rescaledX = scaler.fit_transform(X)

# Fit grid_model to the data
grid_model_result = grid_model.fit(rescaledX, y)

# Summarize results
best_score, best_params = grid_model_result.best_score_, grid_model_result.best_params_
print("Best: %f using %s" % (best_score, best_params))
Best: 0.850725 using {'max_iter': 100, 'tol': 0.01}

No hay comentarios:

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.