Modelo de contactabilidad

Advanced Supervised Machine Learning - Trabajo final

Seguimiento a los experimentos de aprendizaje automático
Autor/a
Afiliación
Fecha de publicación

12 de junio de 2024

Introducción

Conceptos Previos

Leads:

“Leads” es un término comúnmente utilizado en el ámbito del marketing y ventas. Un “lead” se refiere a un individuo o entidad que ha mostrado interés en un producto o servicio, pero que aún no se ha convertido en cliente. Por lo general, un lead ha proporcionado cierta información de contacto (como un número de teléfono o una dirección de correo electrónico) que permite a una empresa seguir comunicándose con él o ella en un intento de convertir ese interés inicial en una venta.

Contacto Efectivo:

El término “Contacto Efectivo (CE)” generalmente se refiere a un contacto exitoso con un lead en el cual se cumple el objetivo previsto. Aquí algunos ejemplos de lo que podría considerarse un “contacto efectivo”:

  • Si el objetivo es simplemente verificar la validez de un número telefónico, entonces un “contacto efectivo” podría ser cuando el titular contesta la llamada.
  • Si el objetivo es vender un producto o servicio, entonces un “contacto efectivo” podría ser cuando el lead muestra un interés genuino o realiza una compra.
  • Si el objetivo es recopilar información o hacer una encuesta, un “contacto efectivo” podría ser cuando el lead responde satisfactoriamente a las preguntas.

En este proyecto se considera que el contacto fue efectivo si el titular contestó la llamada.

Planteamiento del Problema

Actualmente Interbank es uno de los bancos más grandes y reconocidos de Perú. Interbank ofrece una amplia gama de productos y servicios financieros, que incluyen cuentas de ahorro y corriente, créditos hipotecarios, préstamos personales, tarjetas de crédito, seguros, entre otros.

La contactabilidad es un componente esencial en la operación de un banco. El no tener contactos efectivos, en particular en una institución financiera como Interbank, puede generar una serie de inconvenientes y problemas tanto para la entidad como para sus clientes. Estos son algunos de los problemas que podrían surgir:

  1. Pérdida de Oportunidades de Negocio: No poder contactar eficazmente a los clientes significa perder oportunidades de ofrecer nuevos productos, servicios o promociones que podrían ser beneficiosos tanto para el banco como para el cliente.

  2. Dificultades en la Gestión de Créditos: Si un cliente ha solicitado un crédito o tiene pagos pendientes, la falta de comunicación podría resultar en morosidades o malentendidos que afecten la salud financiera del cliente y el portafolio de crédito del banco.

  3. Ineficiencia Operativa: Cada intento fallido de contacto implica un costo en términos de tiempo y recursos. Aumentar la eficiencia en la contactabilidad puede traducirse en ahorros significativos para la entidad.

  4. Insatisfacción del Cliente: Si un cliente espera ser contactado para resolver una duda, recibir una oferta o simplemente para confirmar algún dato y no recibe la llamada, esto puede generar insatisfacción y afectar la percepción de servicio.

  5. Limitación en Actualizaciones y Notificaciones: Muchas veces, los bancos necesitan comunicarse con los clientes para informar sobre cambios en los términos y condiciones, actualizaciones en políticas, o simplemente para notificar sobre movimientos importantes en sus cuentas.

  6. Problemas de Seguridad y Fraude: La comunicación efectiva es esencial para confirmar transacciones sospechosas o para verificar la identidad del titular. La falta de contacto efectivo puede exponer tanto al banco como al cliente a riesgos de fraude.

  7. Percepción de Mercado: En un mercado competitivo, la eficiencia en la comunicación y el servicio al cliente son factores clave de diferenciación. Una deficiente tasa de contactabilidad puede afectar negativamente la imagen del banco frente a sus competidores.

  8. Dificultades en Estrategias de Retención: La contactabilidad efectiva es esencial para ejecutar estrategias de retención. Si un cliente está considerando cerrar una cuenta o dejar un servicio, una comunicación efectiva podría hacer la diferencia para retenerlo.

Fin

Mejorar la eficiencia del proceso de contacto telefónico.

Objetivo

Predecir cuáles números telefónicos tienen la mayor probabilidad de resultar en un CE antes de intentar establecer contacto telefónico.

Importancia

Optimizar la capacidad de contactar a los clientes de manera efectiva puede traducirse en: * mejorar la experiencia del cliente, * mejoras operativas, * mayores ingresos, * reducción de riesgos y * una mejor relación con el cliente.

Importanción de paquetes

# Importación de bibliotecas básicas
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Configuración para visualizaciones
%matplotlib inline
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (10, 6)

Adquisición de la base de datos

Fuente de los datos

  • Los conjuntos de datos utilizados en este proyecto fueron proporcionados por “Interbank” (supuesto).
  • Los conjuntos de datos proporcionados fueron:
    • data_selec_entre: base de datos de entrenamiento
    • data_selec_test: base de datos de prueba
  • La base de datos incluye variables que describen tanto características del cliente como historiales de contacto.

Consideraciones

  • Los conjuntos de datos presentan 40 variables.
  • En ambos conjuntos de datos, los valores -999 representan nulos.

Diccionario de Variables

# Variable Descripción
1 TOTGEST6 Cuantas gestiones se le realizo a la persona últimos 6 meses
2 TOTGEST12 Cuantas gestiones se le realizo a la persona últimos 12 meses
3 TARGET 1: contacto efectivo y 0: no contacto efectivo
4 SEGMENTO Segmento
5 RECENCIA_APP Hace cuanto tiempo has transaccionado con el APP
6 RANGO_INGRESOS Rango de ingreso
7 PROVINCIA Provincia del cliente
8 NUMPRIORIZACION De donde vino el teléfono (tienda, cajero,compra de datos), regla para definir que tan priorizado es tu teléfono, toma en cuenta al mejor canal
9 NT_DISTR6 Distribición de no tipificados (no se registra la tipificación) los últimos 6 meses
10 NT_DISTR12 Distribición de no tipificados (no se registra la tipificación) los últimos 12 meses
11 NT_DIAS6 Dias de la no tipificación los último 6 meses
12 NT_DIAS12 Dias de la no tipificación los último 12 meses
13 NT_CTD6 Cantidad de no tipificaciones los últimos 6 meses
14 NT_CTD12 Cantidad de no tipificaciones los últimos 12 meses
15 NC_DISTR12 % Distribución del no contacto en los 12 último meses
16 NC_DIAS6 Días de no contacto en los último 6 meses
17 NC_DIAS12 Días de no contacto en los último 12 meses
18 NC_CTD6 Cantidad de no contacto en los últimos 6 meses
19 NC_CTD12 Cantidad de no contacto en los últimos 12 meses
20 INGRESO_NETO_VIGENTE Ingreso neto
21 INGRESO_BRUTO Ingreso bruto
22 IDGRUPO Es similar al número de priorización, pero solo muestra la fuente por donde vino tu dato
23 FEC_LLAMADA Fecha de llamada
24 FBK_ULT6 Ultimo Feedback de lo que ocurrio en los útimos 6 meses
25 FBK_ULT12 Ultimo Feedback de lo que ocurrio en los útimos 12 meses
26 FBK_BEST6 El mejor resultado de los último 6 meses
27 FBK_BEST12 El mejor resultado de los último 12 meses
28 DIAS_ULT6 Dias del de último feedback de los útimo 6 meses
29 DIAS_ULT12 Dias del de último feedback de los útimo 12 meses
30 DIAS_BEST6 Dias del mejor resultado de los útimo 6 meses
31 DIAS_BEST12 Dias del mejor resultado de los útimo 12 meses
32 DIAS_ACT Cuanto ha sido la cantidad de día que ha sido activo el telefono, es como una resencia
33 DEPARTAMENTO Departamento del cliente
34 COD_SALA Codigo de la sala que se llama al teléfono
35 CNE_DISTR6 Distribución del contacto no efectivo los últimos 6 meses
36 CNE_DISTR12 Distribución del contacto no efectivo los últimos 12 meses
37 CNE_DIAS6 Días de contactos no efectivos útimos 6 meses
38 CNE_DIAS12 Días de contactos no efectivos útimos 12 meses
39 CNE_CTD6 Cantidad de contactos no efectivos útimos 6 meses
40 CNE_CTD12 Cantidad de contactos no efectivos útimos 12 meses

Descripción General

Variable Objetivo

TARGET: \[ TARGET = \begin{cases} 1 & \text{if } \text{el contacto fue efectivo} \\ 0 & \text{if } \text{el contacto no fue efectivo} \end{cases} \]

Recuerde que el término efectivo se refiere a la contestación por parte del titular del teléfono.

Variables Predictoras

Historial de Gestiones:

Tienes variables que indican cuántas veces se ha intentado contactar a un lead en distintos períodos de tiempo (TOTGEST6 y TOTGEST12).

Información Demográfica del Cliente:

La base de datos incluye información geográfica del cliente, como la PROVINCIA y el DEPARTAMENTO.

Información Financiera:

Se incluye información sobre los ingresos del cliente, tanto netos como brutos (RANGO_INGRESOS, INGRESO_NETO_VIGENTE, INGRESO_BRUTO).

Historial de Contactos y Resultados:

Tienes una serie de variables que rastrean no solo si se logró un contacto, sino también detalles sobre la naturaleza de esos contactos. Esto incluye datos sobre contactos que no fueron tipificados, contactos no exitosos y el feedback asociado a esos contactos.

Segmentación y Prioridad:

La base contiene información sobre la segmentación del cliente (SEGMENTO), la prioridad del número telefónico (NUMPRIORIZACION), y la fuente del número telefónico (IDGRUPO).

Interacción Digital:

Hay una variable (RECENCIA_APP) que parece indicar la reciente interacción del cliente con una aplicación, posiblemente una aplicación móvil o una plataforma digital.

Detalles Específicos del Contacto:

Se ha registrado información como la fecha de la llamada (FEC_LLAMADA) y el código de la sala desde donde se realizó la llamada (COD_SALA).

Categoría # Variable Descripción
Historial de Gestiones 1 TOTGEST6 Cuantas gestiones se le realizo a la persona últimos 6 meses
Historial de Gestiones 2 TOTGEST12 Cuantas gestiones se le realizo a la persona últimos 12 meses
Información Demográfica 7 PROVINCIA Provincia del cliente
Información Demográfica 33 DEPARTAMENTO Departamento del cliente
Información Financiera 6 RANGO_INGRESOS Rango de ingreso
Información Financiera 20 INGRESO_NETO_VIGENTE Ingreso neto
Información Financiera 21 INGRESO_BRUTO Ingreso bruto
Historial de Contactos 9 NT_DISTR6 Distribución de no tipificados (no se registra la tipificación) los últimos 6 meses
Historial de Contactos 10 NT_DISTR12 Distribición de no tipificados (no se registra la tipificación) los últimos 12 meses
Historial de Contactos 11 NT_DIAS6 Dias de la no tipificación los último 6 meses
Historial de Contactos 12 NT_DIAS12 Dias de la no tipificación los último 12 meses
Historial de Contactos 13 NT_CTD6 Cantidad de no tipificaciones los últimos 6 meses
Historial de Contactos 14 NT_CTD12 Cantidad de no tipificaciones los últimos 12 meses
Historial de Contactos 15 NC_DISTR12 % Distribución del no contacto en los 12 último meses
Historial de Contactos 16 NC_DIAS6 Días de no contacto en los último 6 meses
Historial de Contactos 17 NC_DIAS12 Días de no contacto en los último 12 meses
Historial de Contactos 18 NC_CTD6 Cantidad de no contacto en los últimos 6 meses
Historial de Contactos 19 NC_CTD12 Cantidad de no contacto en los últimos 12 meses
Historial de Contactos 24 FBK_ULT6 Ultimo Feedback de lo que ocurrio en los útimos 6 meses
Historial de Contactos 25 FBK_ULT12 Ultimo Feedback de lo que ocurrio en los útimos 12 meses
Historial de Contactos 26 FBK_BEST6 El mejor resultado de los último 6 meses
Historial de Contactos 27 FBK_BEST12 El mejor resultado de los último 12 meses
Historial de Contactos 28 DIAS_ULT6 Dias del de último feedback de los útimo 6 meses
Historial de Contactos 29 DIAS_ULT12 Dias del de último feedback de los útimo 12 meses
Historial de Contactos 30 DIAS_BEST6 Dias del mejor resultado de los útimo 6 meses
Historial de Contactos 31 DIAS_BEST12 Dias del mejor resultado de los útimo 12 meses
Historial de Contactos 35 CNE_DISTR6 Distribución del contacto no efectivo los últimos 6 meses
Historial de Contactos 36 CNE_DISTR12 Distribución del contacto no efectivo los últimos 12 meses
Historial de Contactos 37 CNE_DIAS6 Días de contactos no efectivos útimos 6 meses
Historial de Contactos 38 CNE_DIAS12 Días de contactos no efectivos útimos 12 meses
Historial de Contactos 39 CNE_CTD6 Cantidad de contactos no efectivos útimos 6 meses
Historial de Contactos 40 CNE_CTD12 Cantidad de contactos no efectivos útimos 12 meses
Segmentación y Prioridad 4 SEGMENTO Segmento
Segmentación y Prioridad 8 NUMPRIORIZACION De donde vino el teléfono (tienda, cajero, compra de datos), regla para definir que tan priorizado es tu teléfono, toma en cuenta al mejor canal
Segmentación y Prioridad 22 IDGRUPO Es similar al número de priorización, pero solo muestra la fuente por donde vino tu dato
Interacción Digital 5 RECENCIA_APP Hace cuanto tiempo has transaccionado con el APP
Detalles Específicos del Contacto 23 FEC_LLAMADA Fecha de llamada
Detalles Específicos del Contacto 34 COD_SALA Código de la sala que se llama al teléfono
Detalles Específicos del Contacto 32 DIAS_ACT Cuanto ha sido la cantidad de día que ha sido activo el telefono, es como una recencia

Importación de los datos

data_train = pd.read_csv("../data/raw/data_selec_entre.csv", na_values = [-999])
data_test = pd.read_csv("../data/raw/data_selec_test.csv", na_values = [-999])

data_train.head()
NUMPRIORIZACION NC_DISTR12 TOTGEST6 TOTGEST12 IDGRUPO DIAS_ACT FBK_ULT6 FBK_ULT12 FBK_BEST6 DIAS_BEST6 ... NT_CTD6 NT_DISTR6 NT_DIAS6 PROVINCIA DEPARTAMENTO INGRESO_NETO_VIGENTE INGRESO_BRUTO SEGMENTO RANGO_INGRESOS TARGET
0 1 0.333333 6.0 6.0 3 118 TLV TLV TLV 8.0 ... NaN NaN NaN TACNA TACNA 7136.0 9389.0 2 Entre S/.4000-10000 1
1 1 0.461538 13.0 13.0 40 94 TLV TLV TLV 46.0 ... NaN NaN NaN LIMA LIMA 6920.0 9105.0 1BC Entre S/.4000-10000 1
2 1 0.666667 2.0 6.0 6 223 TLV TLV TLV 127.0 ... NaN NaN NaN LIMA LIMA 1473.0 1655.0 2 Entre S/.1000-4000 1
3 1 NaN 4.0 4.0 95 96 TLV TLV TLV 49.0 ... NaN NaN NaN LIMA LIMA 2293.0 2729.0 2 Entre S/.1000-4000 1
4 1 0.187500 10.0 16.0 4 91 TLV TLV TLV 27.0 ... NaN NaN NaN CUSCO CUSCO 6470.0 8513.0 3 Entre S/.4000-10000 1

5 rows × 40 columns

Análisis exploratorio de datos


Nuestra base de datos presenta las siguientes variables:

# Obtén una serie con los tipos de datos de cada columna
data_types_series = data_train.dtypes

# Construye una lista de listas con los nombres de las columnas y los tipos de datos
data_types_list = [[col, data_types_series[col]] for col in data_types_series.index]

# Construye la tabla Markdown como una string
markdown_table = "| Variable | Tipo de Dato Actual |\n|----------|--------------|\n"
for row in data_types_list:
    markdown_table += f"| {row[0]} | {row[1]} |\n"

# Imprime la tabla Markdown
print(markdown_table)
| Variable | Tipo de Dato Actual |
|----------|--------------|
| NUMPRIORIZACION | category |
| NC_DISTR12 | float64 |
| TOTGEST6 | float64 |
| TOTGEST12 | float64 |
| DIAS_ACT | float64 |
| FBK_ULT6 | category |
| FBK_ULT12 | category |
| FBK_BEST6 | category |
| DIAS_BEST6 | float64 |
| DIAS_ULT6 | float64 |
| FBK_BEST12 | category |
| DIAS_BEST12 | float64 |
| DIAS_ULT12 | float64 |
| CNE_CTD12 | float64 |
| CNE_CTD6 | float64 |
| CNE_DIAS6 | float64 |
| CNE_DISTR6 | float64 |
| CNE_DISTR12 | float64 |
| CNE_DIAS12 | float64 |
| NC_CTD12 | float64 |
| NC_DIAS6 | float64 |
| NC_DIAS12 | float64 |
| NC_CTD6 | float64 |
| RECENCIA_APP | float64 |
| COD_SALA | category |
| NT_CTD12 | float64 |
| NT_DISTR12 | float64 |
| NT_DIAS12 | float64 |
| FEC_LLAMADA | datetime64[ns] |
| NT_CTD6 | float64 |
| NT_DISTR6 | float64 |
| NT_DIAS6 | float64 |
| PROVINCIA | category |
| DEPARTAMENTO | category |
| INGRESO_NETO_VIGENTE | float64 |
| INGRESO_BRUTO | float64 |
| SEGMENTO | category |
| RANGO_INGRESOS | category |
| TARGET | int64 |
| YEAR | category |
| MONTH | category |
| WEEKDAY | category |

Análisis univariado

De variables numéricas

for column in data_train.select_dtypes(include=['float64', 'int64']).columns:
    plt.figure(figsize=(10, 5))
    
    # Histograma
    plt.subplot(1, 2, 1)
    sns.histplot(data_train[column], bins=50, kde=True)
    plt.title(f'Distribución de {column}')
    
    # Boxplot
    plt.subplot(1, 2, 2)
    sns.boxplot(y=data_train[column])
    plt.title(f'Boxplot de {column}')
    
    plt.tight_layout()
    plt.show()

De variables categóricas

categorical_columns = data_train.select_dtypes(include=['category', 'object']).columns

for column in categorical_columns:
    print(f"Frecuencias de {column}:")
    print(data_train[column].value_counts(normalize=True) * 100)
    print("\n")
Frecuencias de NUMPRIORIZACION:
NUMPRIORIZACION
1     61.105404
2     18.662165
3      8.916302
4      4.824476
5      2.758560
6      1.625455
7      0.951956
8      0.567741
9      0.362298
10     0.225644
Name: proportion, dtype: float64


Frecuencias de FBK_ULT6:
FBK_ULT6
TLV     67.406138
NULO    25.343681
IVR      6.558461
COB      0.691719
Name: proportion, dtype: float64


Frecuencias de FBK_ULT12:
FBK_ULT12
TLV     70.015633
NULO    22.380339
IVR      6.798233
COB      0.805796
Name: proportion, dtype: float64


Frecuencias de FBK_BEST6:
FBK_BEST6
TLV     70.026195
NULO    25.343681
IVR      4.001516
COB      0.628608
Name: proportion, dtype: float64


Frecuencias de FBK_BEST12:
FBK_BEST12
TLV     73.348271
NULO    22.380339
IVR      3.600796
COB      0.670594
Name: proportion, dtype: float64


Frecuencias de COD_SALA:
COD_SALA
NC     31.636493
EC     18.479036
C      11.607129
PP     11.198223
CD      7.117620
PA      6.336779
CON     4.209863
IL      2.707067
UPG     2.256175
PRR     1.842517
2DA     1.770559
DIL     0.418808
BPE     0.414979
DEF     0.004093
TC      0.000660
Name: proportion, dtype: float64


Frecuencias de PROVINCIA:
PROVINCIA
LIMA                       57.819110
AREQUIPA                    5.092634
PROV. CONST. DEL CALLAO     3.522237
TRUJILLO                    3.444866
INF. NO DISPONIBLE          3.291708
                             ...    
VICTOR FAJARDO              0.000528
GRAN CHIMU                  0.000528
LAURICOCHA                  0.000396
OCROS                       0.000396
PURUS                       0.000132
Name: proportion, Length: 192, dtype: float64


Frecuencias de DEPARTAMENTO:
DEPARTAMENTO
LIMA                  59.507412
AREQUIPA               5.256091
LA LIBERTAD            3.853507
CALLAO                 3.522237
LAMBAYEQUE             3.489493
INF. NO DISPONIBLE     3.291708
JUNIN                  2.837647
PIURA                  2.542686
ICA                    2.485516
CUSCO                  2.202438
ANCASH                 1.370764
CAJAMARCA              1.369575
PUNO                   1.359013
TACNA                  1.168490
HUANUCO                0.935716
TUMBES                 0.725652
SAN MARTIN             0.724464
LORETO                 0.681025
UCAYALI                0.631909
MOQUEGUA               0.568269
AYACUCHO               0.410886
PASCO                  0.384875
APURIMAC               0.202010
MADRE DE DIOS          0.178640
AMAZONAS               0.166625
HUANCAVELICA           0.133353
Name: proportion, dtype: float64


Frecuencias de SEGMENTO:
SEGMENTO
3       41.363476
2       27.868939
1BC     13.538635
1A       9.911697
4        5.945698
5        0.982984
6        0.202010
NULO     0.186562
Name: proportion, dtype: float64


Frecuencias de RANGO_INGRESOS:
RANGO_INGRESOS
Entre S/.1000-4000     55.438164
Entre S/.4000-10000    35.496205
Mayor a S/.10000        8.140081
Entre S/.600-1000       0.709808
Sin ingresos            0.203859
Entre S/.0-600          0.011751
NULO                    0.000132
Name: proportion, dtype: float64


Frecuencias de YEAR:
YEAR
2017    73.321204
2018    26.678796
Name: proportion, dtype: float64


Frecuencias de MONTH:
MONTH
2     26.678796
9     26.278077
10    24.910086
11    22.128156
7      0.003565
8      0.001320
Name: proportion, dtype: float64


Frecuencias de WEEKDAY:
WEEKDAY
2    20.263722
1    19.725293
3    19.668783
4    18.896523
0    18.757889
5     2.687790
Name: proportion, dtype: float64

for column in categorical_columns:
    plt.figure(figsize=(10, 5))
    sns.countplot(data=data_train, x=column, order=data_train[column].value_counts().index)
    plt.title(f'Distribución de {column}')
    plt.xticks(rotation=45)
    plt.show()

Análisis bivariado

Correlaciones para variables numéricas

numeric_cols = data_train.select_dtypes(include=['float64', 'int64']).columns

correlation_matrix = data_train[numeric_cols].corr()

# Visualizar la matriz de correlación con Seaborn
plt.figure(figsize=(15, 10))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', vmin=-1, vmax=1)
plt.title("Matriz de Correlación")
plt.show()

Los resultados de las correlaciones muestran que las variables DIAS_ACT, DIAS_BEST6, DIAS_BEST12, DIAS_ULT6, DIAS_ULT12, CNE_CTD6, CNE_CTD12, CNE_DIAS6, CNE_DIAS12, CNE_DISTR6, CNE_DISTR12, NC_CTD6, NC_CTD12, NC_DIAS6, NC_DIAS12, NC_DISTR12, TOTGEST6, TOTGEST12, INGRESO_NETO_VIGENTE, INGRESO_BRUTO tienen una correlación baja con la variable objetivo TARGET.

Por otro lado, las variables que presentan una alta correlación con la variable objetivo TARGET son NUMPRIORIZACION y SEGMENTO.

Análisis de la variable objetivo

# Frecuencia absoluta
frec_abs = data_train['TARGET'].value_counts()

# Frecuencia relativa (porcentual)
frec_rel = data_train['TARGET'].value_counts(normalize=True) * 100

# Consolidar ambas frecuencias en un solo DataFrame
desc_stats = pd.DataFrame({'Frecuencia Absoluta': frec_abs,
                           'Frecuencia Porcentual (%)': frec_rel})

print(desc_stats)
        Frecuencia Absoluta  Frecuencia Porcentual (%)
TARGET                                                
1                    492388                  65.011328
0                    265000                  34.988672
# Gráfico de barras
plt.figure(figsize=(8, 6))
sns.countplot(data=data_train, x='TARGET')
plt.title("Distribución de la variable objetivo 'TARGET'")
plt.ylabel("Cantidad")
plt.xlabel("Valor de TARGET (0 = No Contacto Efectivo, 1 = Contacto Efectivo)")
plt.show()

Mostramos la relación entre la variable objetivo y otras variables categóricas:

cat_vars = data_train.select_dtypes(include=['object', 'category']).columns.tolist()

# Si deseas excluir la variable objetivo de esta lista (en caso de que sea categórica)
if 'TARGET' in cat_vars:
    cat_vars.remove('TARGET')
# Configurar el tamaño de los gráficos
plt.figure(figsize=(15, 5 * len(cat_vars)))

for i, var in enumerate(cat_vars, 1):
    plt.subplot(len(cat_vars), 1, i)
    sns.countplot(data=data_train, x=var, hue='TARGET')
    plt.title(f"Relación entre TARGET y {var}")
    plt.ylabel("Cantidad")
    plt.legend(title="TARGET", labels=["No Contacto Efectivo", "Contacto Efectivo"])

plt.tight_layout()
plt.show()

Preparación de los datos

Eliminando variable temporal

for data in data_train, data_test:
    data.drop("FEC_LLAMADA", axis=1, inplace=True)

Codificando variables categóricas

# Selecciona las columnas categóricas
cat_columns = data_train.select_dtypes(include=['category']).columns.tolist()
# Aplicar get_dummies
data_train_dummies = pd.get_dummies(data_train[cat_columns])

# Unir los DataFrames
data_train = pd.concat([data_train, data_train_dummies], axis=1)

# Eliminar variables categóricas originales
data_train.drop(cat_columns, axis=1, inplace=True)
# Aplicar get_dummies
data_test_dummies = pd.get_dummies(data_test[cat_columns])

# Unir los DataFrames
data_test = pd.concat([data_test, data_test_dummies], axis=1)

# Eliminar variables categóricas originales
data_test.drop(cat_columns, axis=1, inplace=True)

Quitando variables no vistas en entrenamiento pero sí en el conjunto de prueba:

data_test.drop(['MONTH_12','PROVINCIA_JULCAN','PROVINCIA_SUCRE'], axis=1, inplace=True)

Separación de la base de datos

X_train = data_train.drop("TARGET", axis=1)
y_train = data_train[["TARGET"]]
X_test = data_test.drop("TARGET", axis=1)
y_test = data_test[["TARGET"]]

Guardando base de datos preparada para el modelado

X_train.to_pickle("../data/interm/X_train_preparada.pkl")
X_test.to_pickle("../data/interm/X_test_preparada.pkl")
y_train.to_pickle("../data/interm/y_train_preparada.pkl")
y_test.to_pickle("../data/interm/y_test_preparada.pkl")

Selección de variables

Cargando bases de datos preparadas para el modelado:

import pandas as pd
X_train_preparada = pd.read_pickle('../data/interm/X_train_preparada.pkl')
X_test_preparada = pd.read_pickle('../data/interm/X_test_preparada.pkl')
y_train_preparada = pd.read_pickle('../data/interm/y_train_preparada.pkl')
y_test_preparada = pd.read_pickle('../data/interm/y_test_preparada.pkl')
from sklearn.ensemble import RandomForestClassifier

# Entrenar el modelo de Random Forest
model = RandomForestClassifier(n_estimators=100, random_state=2024)
model.fit(X_train_preparada, y_train_preparada)

# Obtener la importancia de las características
importances = model.feature_importances_

# Crear un DataFrame con las características y sus importancias
feature_importances = pd.DataFrame({'feature': X_train_preparada.columns, 'importance': importances})

# Guardando características y sus importancias
feature_importances.to_pickle('../data/final/feature_importances.pkl')
# Cargar características y sus importancias
feature_importances = pd.read_pickle('../data/final/feature_importances.pkl')

# Ordenar las características por importancia
feature_importances = feature_importances.sort_values(by='importance', ascending=False)

# Mostrar las características más importantes
print(feature_importances)

# Seleccionar las características más importantes (por ejemplo, las 2 más importantes)
selected_features = feature_importances['feature'].head(10).tolist()
print("Características seleccionadas:", selected_features)
                      feature    importance
3                    DIAS_ACT  8.359303e-02
0                  NC_DISTR12  7.774602e-02
25       INGRESO_NETO_VIGENTE  5.917369e-02
26              INGRESO_BRUTO  5.891626e-02
27          NUMPRIORIZACION_1  5.141687e-02
..                        ...           ...
173        PROVINCIA_LA UNION  4.428344e-07
202        PROVINCIA_PALLASCA  3.354169e-07
251  PROVINCIA_VICTOR FAJARDO  2.629123e-07
299       RANGO_INGRESOS_NULO  1.205713e-07
66                COD_SALA_TC  4.331061e-08

[315 rows x 2 columns]
Características seleccionadas: ['DIAS_ACT', 'NC_DISTR12', 'INGRESO_NETO_VIGENTE', 'INGRESO_BRUTO', 'NUMPRIORIZACION_1', 'DIAS_BEST12', 'TOTGEST12', 'DIAS_BEST6', 'NC_DIAS12', 'DIAS_ULT12']

Manteniendo sólo variables relevantes:

X_train_preparada = X_train_preparada[selected_features]
X_test_preparada = X_test_preparada[selected_features]

Guardando base de datos preparada con variables seleccionadas para el modelado:

X_train_preparada.to_pickle("../data/final/X_train.pkl")
X_test_preparada.to_pickle("../data/final/X_test.pkl")
y_train_preparada.to_pickle("../data/final/y_train.pkl")
y_test_preparada.to_pickle("../data/final/y_test.pkl")

Preámbulo sobre la evaluación de modelos

Cargando bases de datos preparadas para el modelado:

import pandas as pd
X_train = pd.read_pickle('../data/final/X_train.pkl')
X_test = pd.read_pickle('../data/final/X_test.pkl')
y_train = pd.read_pickle('../data/final/y_train.pkl')
y_test = pd.read_pickle('../data/final/y_test.pkl')

Criterios de evaluación

Las métricas a evaluar son:

  • accuracy_score
  • precision_score
  • recall_score
  • entropy
  • ROC-AUC

Las métricas a evaluar las obtendremos con la siguiente función.

def get_metrics(y, y_pred, y_pred_proba):
    from sklearn.metrics import accuracy_score, precision_score, recall_score, log_loss, roc_auc_score
    accuracy = accuracy_score(y, y_pred)
    precision = precision_score(y, y_pred)
    recall = recall_score(y, y_pred)
    entropy = log_loss(y, y_pred_proba)
    roc_auc = roc_auc_score(y, y_pred_proba[:,1])
    return {'accuracy': round(accuracy, 2), 'precision': round(precision, 2), 'recall': round(recall, 2), 'entropy': round(entropy, 2), 'roc-auc': round(roc_auc, 2)}

Definiendo funciones sobre predicción

La siguiente función permite obtener predicciones.

def predict(model, X):
    y_pred = model.predict(X)
    return y_pred

La siguiente función permite obtener probabilidades de las predicciones.

def predict_proba(model, X):
    y_pred_proba = model.predict_proba(X)
    return y_pred_proba

Modelado

Importando librerías:

import mlflow
import mlflow.sklearn

Conectando la sesión de MLflow a Databricks CE:

mlflow.set_tracking_uri("databricks")

Regresión logística

print("_____ Experimento: Regresión logística _____")

exp = mlflow.set_experiment(experiment_name="/logisticRegression")

print(f'Nombre del experimento: {exp.name}')
print(f'ID del experimento: {exp.experiment_id}')
def track_model(run_name, penalty, class_weight):
    
    mlflow.start_run(run_name=run_name)

    run = mlflow.active_run()
    print(f'Nombre de la ejecución activa es: {run.info.run_name}')
    print(f'ID de la ejecución activa es: {run.info.run_id}')

    tags = {
        "Modelo": "Regresión Logística",
    }
    mlflow.set_tags(tags)

    # Entrenando el modelo:
    from sklearn.linear_model import LogisticRegression
    model = LogisticRegression(penalty=penalty, class_weight=class_weight)
    model.fit(X_train, y_train)

    mlflow.sklearn.log_model(model, "Regresión Logística")

    params = {
        'penalty': penalty,
        'class_weight': class_weight
    }
    mlflow.log_params(params)

    # Obteniendo predicciones:
    y_train_pred = predict(model, X_train)
    y_test_pred = predict(model, X_test)

    # Obteniendo probabilidades de las predicciones:
    y_train_pred_proba = predict_proba(model, X_train)
    y_test_pred_proba = predict_proba(model, X_test)

    # Obteniendo métricas:
    run_metrics_train = get_metrics(y_train, y_train_pred, y_train_pred_proba)
    run_metrics_test = get_metrics(y_test, y_test_pred, y_test_pred_proba)

    print(run_metrics_train)
    print(run_metrics_test)

    # Rastreando métricas sólo del conjunto de prueba
    for metric in run_metrics_test:
        mlflow.log_metric(metric, run_metrics_test[metric])

    mlflow.end_run()
track_model(run_name="Base", penalty='l2', class_weight=None)
track_model(run_name="Base", penalty='l2', class_weight='balanced')

Arbol de decisión

print("_____ Experimento: Arbol de decisión _____")

exp = mlflow.set_experiment(experiment_name="/treeDecision")

print(f'Nombre del experimento: {exp.name}')
print(f'ID del experimento: {exp.experiment_id}')
def track_model(run_name, max_depth, splitter):
    
    mlflow.start_run(run_name=run_name)

    run = mlflow.active_run()
    print(f'Nombre de la ejecución activa es: {run.info.run_name}')
    print(f'ID de la ejecución activa es: {run.info.run_id}')

    tags = {
        "Modelo": "Arbol de decisión",
    }
    mlflow.set_tags(tags)

    # Entrenando el modelo:
    from sklearn.tree import DecisionTreeClassifier
    model = DecisionTreeClassifier(max_depth=max_depth, splitter=splitter, random_state=2024)
    model.fit(X_train, y_train)

    mlflow.sklearn.log_model(model, "Arbol de decisión")

    params = {
        'max_depth': max_depth,
        'splitter': splitter
    }
    mlflow.log_params(params)

    # Obteniendo predicciones:
    y_train_pred = predict(model, X_train)
    y_test_pred = predict(model, X_test)

    # Obteniendo probabilidades de las predicciones:
    y_train_pred_proba = predict_proba(model, X_train)
    y_test_pred_proba = predict_proba(model, X_test)

    # Obteniendo métricas:
    run_metrics_train = get_metrics(y_train, y_train_pred, y_train_pred_proba)
    run_metrics_test = get_metrics(y_test, y_test_pred, y_test_pred_proba)

    print(run_metrics_train)
    print(run_metrics_test)

    # Rastreando métricas sólo del conjunto de prueba
    for metric in run_metrics_test:
        mlflow.log_metric(metric, run_metrics_test[metric])

    mlflow.end_run()
track_model(run_name="max_depth=3, splitter='best'", max_depth=3, splitter='best')
track_model(run_name="max_depth=5, splitter='best'", max_depth=5, splitter='best')
track_model(run_name="max_depth=7, splitter='best'", max_depth=7, splitter='best')
track_model(run_name="max_depth=3, splitter='random'", max_depth=3, splitter='random')
track_model(run_name="max_depth=5, splitter='random'", max_depth=5, splitter='random')
track_model(run_name="max_depth=7, splitter='random'", max_depth=7, splitter='random')

Red neuronal

print("_____ Experimento: Red neuronal _____")

exp = mlflow.set_experiment(experiment_name="/neuralNetwork")

print(f'Nombre del experimento: {exp.name}')
print(f'ID del experimento: {exp.experiment_id}')
def track_model(run_name, hidden_layer_sizes):
    
    mlflow.start_run(run_name=run_name)

    run = mlflow.active_run()
    print(f'Nombre de la ejecución activa es: {run.info.run_name}')
    print(f'ID de la ejecución activa es: {run.info.run_id}')

    tags = {
        "Modelo": "Red neuronal",
    }
    mlflow.set_tags(tags)

    # Entrenando el modelo:
    from sklearn.neural_network import MLPClassifier
    model = MLPClassifier(hidden_layer_sizes=hidden_layer_sizes, random_state=2024)
    model.fit(X_train, y_train)

    mlflow.sklearn.log_model(model, "Red neuronal")

    params = {
        'hidden_layer_sizes': hidden_layer_sizes,
    }
    mlflow.log_params(params)

    # Obteniendo predicciones:
    y_train_pred = predict(model, X_train)
    y_test_pred = predict(model, X_test)

    # Obteniendo probabilidades de las predicciones:
    y_train_pred_proba = predict_proba(model, X_train)
    y_test_pred_proba = predict_proba(model, X_test)

    # Obteniendo métricas:
    run_metrics_train = get_metrics(y_train, y_train_pred, y_train_pred_proba)
    run_metrics_test = get_metrics(y_test, y_test_pred, y_test_pred_proba)

    print(run_metrics_train)
    print(run_metrics_test)

    # Rastreando métricas sólo del conjunto de prueba
    for metric in run_metrics_test:
        mlflow.log_metric(metric, run_metrics_test[metric])

    mlflow.end_run()
track_model(run_name="hidden_layer_sizes=25", hidden_layer_sizes=25)
track_model(run_name="hidden_layer_sizes=50", hidden_layer_sizes=50)
track_model(run_name="hidden_layer_sizes=75", hidden_layer_sizes=75)
track_model(run_name="hidden_layer_sizes=100", hidden_layer_sizes=100)

Random forest

print("_____ Experimento: Random forest _____")

exp = mlflow.set_experiment(experiment_name="/randomForest")

print(f'Nombre del experimento: {exp.name}')
print(f'ID del experimento: {exp.experiment_id}')
def track_model(run_name, n_estimators, max_depth):
    
    mlflow.start_run(run_name=run_name)

    run = mlflow.active_run()
    print(f'Nombre de la ejecución activa es: {run.info.run_name}')
    print(f'ID de la ejecución activa es: {run.info.run_id}')

    tags = {
        "Modelo": "Random forest",
    }
    mlflow.set_tags(tags)

    # Entrenando el modelo:
    from sklearn.ensemble import RandomForestClassifier
    model = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth, random_state=2024)
    model.fit(X_train, y_train)

    mlflow.sklearn.log_model(model, "Random forest")

    params = {
        'n_estimators': n_estimators,
        'max_depth': max_depth
    }
    mlflow.log_params(params)

    # Obteniendo predicciones:
    y_train_pred = predict(model, X_train)
    y_test_pred = predict(model, X_test)

    # Obteniendo probabilidades de las predicciones:
    y_train_pred_proba = predict_proba(model, X_train)
    y_test_pred_proba = predict_proba(model, X_test)

    # Obteniendo métricas:
    run_metrics_train = get_metrics(y_train, y_train_pred, y_train_pred_proba)
    run_metrics_test = get_metrics(y_test, y_test_pred, y_test_pred_proba)

    print(run_metrics_train)
    print(run_metrics_test)

    # Rastreando métricas sólo del conjunto de prueba
    for metric in run_metrics_test:
        mlflow.log_metric(metric, run_metrics_test[metric])

    mlflow.end_run()
track_model(run_name="n_estimators=25 & max_depth=5", n_estimators=25, max_depth=5)
track_model(run_name="n_estimators=25 & max_depth=10", n_estimators=50, max_depth=10)
track_model(run_name="n_estimators=25 & max_depth=5", n_estimators=25, max_depth=5)
track_model(run_name="n_estimators=25 & max_depth=10", n_estimators=50, max_depth=10)

Eligiendo el mejor modelo

Se compara el rendimiento de los modelos en Databricks CE. En la figura Figura 1 se muestran todos los experimentos realizados. Estos cuatro experimentos contienen en total 16 modelos como se muestra en la figura Figura 2.

Figura 1: Experimentos realizados
Figura 2: Total de experimentos

La figura Figura 3 muestra el rendimiento de estos modelos basados primero en ROC_AUC, segundo en recall y por último en precisión.

Figura 3: Métricas de los modelos

Los parámetros usados en los modelos se muestra en la figura Figura 4.

Figura 4: Parámetros de los modelos

Conclusión

El mejor modelo resultó ser una red neuronal con un tamaño de capas ocultas igual a 50. Los mejores modelos fueron redes neuronales, seguido de random forest con n_estimators=25 y max_depth=5. Seguido de un árbol de decisión con max_depth=5 y splitter=‘best’. Por último, se encuentran el resto de modelos de árboles de decisión y regresión logística.