# 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)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:
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.
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.
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.
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.
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.
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.
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.
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
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
-999representan 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_predLa siguiente función permite obtener probabilidades de las predicciones.
def predict_proba(model, X):
y_pred_proba = model.predict_proba(X)
return y_pred_probaModelado
Importando librerías:
import mlflow
import mlflow.sklearnConectando 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.
La figura Figura 3 muestra el rendimiento de estos modelos basados primero en ROC_AUC, segundo en recall y por último en precisión.
Los parámetros usados en los modelos se muestra en la figura Figura 4.
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.

