Modelo predictivo que muestra la probabilidad de que un CLIENTE retire el 70% o más de su saldo en los siguientes 3 meses¶
-Primer paso Se realiza la lectura de los saldos y se busca estandarizar la información para un manejo más sencillo de los datos. Realizamos una agrupación a nivel ID del cliente para saber su saldo, qué contratos y que productos tiene activos, incluyendo el historial de su saldo mensual
import pyarrow.parquet as pq
import pandas as pd
import re
import numpy as np
from sklearn.linear_model import LinearRegression
import math
saldos = pq.read_table('0saldos.parquet').to_pandas()
saldos = saldos.loc[saldos['TipoDocum'] == 'C ']
saldos.info()
<class 'pandas.core.frame.DataFrame'> Index: 197217 entries, 0 to 201519 Data columns (total 26 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 TipoDocum 197217 non-null object 1 SALDO_202101 197217 non-null float64 2 SALDO_2021O2 197217 non-null float64 3 SALDO_2021O3 197217 non-null float64 4 SALDO_2021O4 197217 non-null float64 5 SALDO_2021O5 197217 non-null float64 6 SALDO_2021O6 197217 non-null float64 7 SALDO_2021O7 197217 non-null float64 8 SALDO_2021O8 197217 non-null float64 9 SALDO_2021O9 197217 non-null float64 10 SALDO_202110 197217 non-null float64 11 SALDO_202111 197217 non-null float64 12 SALDO_202112 197217 non-null float64 13 SALDO_202201 197217 non-null float64 14 SALDO_202202 197217 non-null float64 15 SALDO_202203 197217 non-null float64 16 SALDO_202204 197217 non-null float64 17 SALDO_202205 197217 non-null float64 18 SALDO_202206 197217 non-null float64 19 SALDO_202207 197217 non-null float64 20 SALDO_202208 197217 non-null float64 21 SALDO_202209 197217 non-null float64 22 SALDO_202210 197217 non-null float64 23 Contrato 197217 non-null int64 24 PlanProducto 197217 non-null int64 25 NroDocum 197217 non-null int64 dtypes: float64(22), int64(3), object(1) memory usage: 40.6+ MB
Detección de valores NaN
saldos.isna().sum()
TipoDocum 0 SALDO_202101 0 SALDO_2021O2 0 SALDO_2021O3 0 SALDO_2021O4 0 SALDO_2021O5 0 SALDO_2021O6 0 SALDO_2021O7 0 SALDO_2021O8 0 SALDO_2021O9 0 SALDO_202110 0 SALDO_202111 0 SALDO_202112 0 SALDO_202201 0 SALDO_202202 0 SALDO_202203 0 SALDO_202204 0 SALDO_202205 0 SALDO_202206 0 SALDO_202207 0 SALDO_202208 0 SALDO_202209 0 SALDO_202210 0 Contrato 0 PlanProducto 0 NroDocum 0 dtype: int64
# Agrupamos por MultiIndex los datos por cliente
saldos.set_index(['NroDocum', 'Contrato', 'PlanProducto'], inplace=True)
regex = re.compile(r'^SALDO')
columnas_a_descartar = [columna for columna in saldos.columns if regex.match(columna)]
def perdidas(matrix):
# Compare the last element with all other elements in the row (excluding the last one)
results = [ np.max(matrix[i][matrix[i] > 0][-4:] ) - np.max(matrix[i][matrix[i] > 0][-1] ) for i in range(matrix.shape[0])]
# Convert the list of results to a column vector
result_array = np.array(results).reshape(-1, 1)
return result_array
saldos['SALDO_202209'] = (saldos['SALDO_202208'] + saldos['SALDO_202210']) / 2
# Filtrar las columnas que coinciden con el regex
# Sumar las columnas
saldos['total'] = saldos.filter(regex=regex).sum(axis=1)
saldos['mediaSaldo'] = saldos.filter(regex=regex).mean(axis=1)
saldos = saldos.loc[saldos['total'] > 0]
saldos_array = []
for i in saldos.filter(regex=regex).columns:
if len(saldos_array) >= 1:
saldos_array[0] = np.hstack([saldos_array[0], np.array(saldos[i].values).reshape(len(saldos[i].values), 1)])
else:
saldos_array.append(np.array(saldos[i].values).reshape(len(saldos[i].values), 1))
saldos['posiblesPerdidas'] =perdidas(saldos_array[0])
saldos.drop(columns=saldos.filter(regex=regex).columns, axis=1, inplace=True)
saldos.info()
<class 'pandas.core.frame.DataFrame'> MultiIndex: 36131 entries, (1070499713, 6283724, 3376) to (1133162696, 7738006, 5172) Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 TipoDocum 36131 non-null object 1 total 36131 non-null float64 2 mediaSaldo 36131 non-null float64 3 posiblesPerdidas 36131 non-null float64 dtypes: float64(3), object(1) memory usage: 11.8+ MB
transferencias = pq.read_table('0transferencias.parquet').to_pandas()
transferencias.sort_values(by='FechaEfectiva', ascending=True, inplace=True)
transferencias.FechaEfectiva = pd.to_datetime(transferencias.FechaEfectiva, format='%Y-%m-%d')
# Agrupar las transacciones por mes y sumar los valores
transferencias = transferencias.groupby(
[transferencias['Contrato'], transferencias['FechaEfectiva'].dt.to_period('M')], as_index=True
).agg(
ValorTransaccion=('ValorNeto', 'sum'),
Productos=('PlanProducto', 'first'),
User=('Contrato', 'first'),
Tipo=('TipoOper', 'first'),
)
transferencias = transferencias.groupby(
[transferencias['User']], as_index=True
).agg(
ValorTransaccion=('ValorTransaccion', 'mean'),
NumberOfTransactions=('ValorTransaccion', 'count'),
)
print(transferencias.info())
transferencias['Users'] = transferencias.index
transferencias
<class 'pandas.core.frame.DataFrame'> Index: 23412 entries, 10359 to 9999898 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ValorTransaccion 23412 non-null float64 1 NumberOfTransactions 23412 non-null int64 dtypes: float64(1), int64(1) memory usage: 548.7 KB None
ValorTransaccion | NumberOfTransactions | Users | |
---|---|---|---|
User | |||
10359 | -2.400000e+04 | 5 | 10359 |
11135 | 1.514032e+08 | 1 | 11135 |
11486 | 4.206700e+06 | 1 | 11486 |
11595 | 5.200000e+06 | 23 | 11595 |
11989 | -5.500000e+07 | 1 | 11989 |
... | ... | ... | ... |
9998936 | 4.387500e+08 | 1 | 9998936 |
9999584 | 1.449000e+07 | 4 | 9999584 |
9999852 | -3.300900e+07 | 1 | 9999852 |
9999884 | -3.338484e+07 | 1 | 9999884 |
9999898 | 5.333333e+06 | 3 | 9999898 |
23412 rows × 3 columns
def check_last_less_than_others(matrix):
last_elements = matrix[:, -1]
# Compare the last element with all other elements in the row (excluding the last one)
results = [last_elements[i] < (np.max((matrix[i, -4:-3])) * 0.3) for i in range(matrix.shape[0])]
# Convert the list of results to a column vector
result_array = np.array(results).reshape(-1, 1)
return result_array
saldos['activeCases'] = check_last_less_than_others(saldos_array[0])
clientes = pq.read_table('0clientes.parquet').to_pandas()
clientes = clientes.loc[clientes['TIPODOCUM'] == 'C']
clientes.drop(
columns=['EnvioExtractos', 'TIPODOCUM'], inplace=True)
# saldos = saldos.merge(clientes, how="left", left_on=['NroDocum'], right_on=['NroDocum'])
saldos = saldos.join(clientes.set_index("NroDocum"), on="NroDocum")
saldos['edad'] = saldos['FecNacim']
saldos = saldos.astype({'edad': 'str'})
saldos['edad'] = saldos['edad'].str.slice(0, 4)
saldos = saldos.astype({'edad': 'int'})
saldos['edad'] = -(saldos['edad'] - 2022)
saldos
TipoDocum | total | mediaSaldo | posiblesPerdidas | activeCases | CIUDAD | FecNacim | edad | |||
---|---|---|---|---|---|---|---|---|---|---|
NroDocum | Contrato | PlanProducto | ||||||||
1070499713 | 6283724 | 3376 | C | 1.036830e+08 | 4.712866e+06 | 0.00 | False | BOGOTA D.C., BOGOTA | 1941-07-24 | 81 |
1067734816 | 4241304 | 3376 | C | 2.613527e+09 | 1.187967e+08 | 0.00 | False | BOGOTA D.C., BOGOTA | 1966-12-06 | 56 |
1018964721 | 9943050 | 3376 | C | 2.808434e+08 | 1.276561e+07 | 830075.86 | False | BOGOTA D.C., BOGOTA | 1966-04-11 | 56 |
1169386237 | 9995431 | 3376 | C | 1.732767e+08 | 7.876213e+06 | 0.00 | False | BOGOTA D.C., BOGOTA | 1962-05-26 | 60 |
1095392085 | 5555946 | 3376 | C | 1.256775e+09 | 5.712612e+07 | 0.00 | False | BOGOTA D.C., BOGOTA | 1962-09-25 | 60 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1081765591 | 7398048 | 5172 | C | 1.504645e+07 | 6.839297e+05 | 0.00 | False | BOGOTA D.C., BOGOTA | 1974-02-12 | 48 |
1155662224 | 8963958 | 5172 | C | 3.006538e+08 | 1.366608e+07 | 0.00 | False | BARRANQUILLA, ATLANTICO | 1956-07-08 | 66 |
1054358908 | 4117269 | 5172 | C | 4.520644e+07 | 2.054838e+06 | 0.00 | False | BOGOTA D.C., BOGOTA | 1980-04-28 | 42 |
1051426081 | 3737506 | 5172 | C | 4.491944e+07 | 2.041793e+06 | 0.00 | False | BOGOTA D.C., BOGOTA | 1948-08-17 | 74 |
1133162696 | 7738006 | 5172 | C | 3.002064e+06 | 1.364575e+05 | 0.00 | False | ENVIGADO, ANTIOQUIA | 1973-05-14 | 49 |
36131 rows × 8 columns
saldos['Contratos'] = saldos.index.get_level_values('Contrato')
saldos = saldos.join(transferencias.set_index("Users"), on="Contratos")
saldos
#
saldos.loc[saldos['activeCases'] == False].head(10)
saldos = saldos.drop(columns=['TipoDocum', 'FecNacim', 'NumberOfTransactions', 'CIUDAD', 'mediaSaldo', 'edad'])
# 1079454835
def predictions(matrix):
mask_array = []
for i in range(matrix.shape[0]):
# Aqui suve a 90 sin -3
serie_temporal = matrix[i, :-3]
try:
serie = pd.Series(serie_temporal)
# Calcula el promedio móvil con una ventana de tamaño window_size
serie_temporal = serie.rolling(window=3, min_periods=1).mean().dropna().values[-3:]
tiempo = np.arange(len(serie_temporal)).reshape(-1, 1)
# Crea y ajusta un modelo de regresión lineal
modelo = LinearRegression()
modelo.fit(tiempo, serie_temporal)
# Hacer predicciones para los siguientes tres valores
siguientes_tres_valores = np.array(
[[len(serie_temporal)], [len(serie_temporal) + 1], [len(serie_temporal) + 2]])
predicciones = list((modelo.predict(siguientes_tres_valores)))
predict_2 = (predicciones[-2]) < (serie_temporal[-1] * 0.3)
predict_3 = (predicciones[-3]) < (serie_temporal[-1] * 0.3)
predict_1 = (predicciones[-1]) < (serie_temporal[-1] * 0.3)
if predict_1 or predict_2 or predict_3:
mask_array.append(True)
else:
mask_array.append(False)
except:
mask_array.append(False)
return mask_array
def tendencia(matrix):
mask_array = []
for i in range(matrix.shape[0]):
serie_temporal = matrix[i, :-3]
tiempo = np.arange(len(serie_temporal)).reshape(-1, 1)
modelo = LinearRegression()
modelo.fit(tiempo, serie_temporal)
pendiente = modelo.coef_[0]
mask_array.append(pendiente)
return mask_array
def drop_percent(matrix):
drop_array = []
for i in range(matrix.shape[0]):
y = list(matrix[i])
y = [i for i in y if i > 0]
start = -2
try:
porcentaje_caida = ((y[-1]) / (y[start] * 0.01)) - 100
drop_array.append(porcentaje_caida)
except:
drop_array.append(0)
return drop_array
saldos['porcentajeCaida'] = drop_percent(saldos_array[0])
saldos['slope'] = tendencia(saldos_array[0])
saldos['prediction'] = predictions(saldos_array[0])
saldos
total | posiblesPerdidas | activeCases | Contratos | ValorTransaccion | porcentajeCaida | slope | prediction | |||
---|---|---|---|---|---|---|---|---|---|---|
NroDocum | Contrato | PlanProducto | ||||||||
1070499713 | 6283724 | 3376 | 1.036830e+08 | 0.00 | False | 6283724 | -5.000000e+06 | 0.718735 | -3.040252e+05 | False |
1067734816 | 4241304 | 3376 | 2.613527e+09 | 0.00 | False | 4241304 | NaN | 0.906037 | 9.221042e+04 | False |
1018964721 | 9943050 | 3376 | 2.808434e+08 | 830075.86 | False | 9943050 | -1.825385e+04 | -0.905232 | 1.173794e+05 | False |
1169386237 | 9995431 | 3376 | 1.732767e+08 | 0.00 | False | 9995431 | 1.443160e+06 | 0.471734 | 1.130727e+06 | False |
1095392085 | 5555946 | 3376 | 1.256775e+09 | 0.00 | False | 5555946 | NaN | 0.590664 | 1.434138e+04 | False |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1081765591 | 7398048 | 5172 | 1.504645e+07 | 0.00 | False | 7398048 | 1.000000e+07 | 100.000000 | 0.000000e+00 | False |
1155662224 | 8963958 | 5172 | 3.006538e+08 | 0.00 | False | 8963958 | 2.020000e+08 | 100.000000 | 0.000000e+00 | False |
1054358908 | 4117269 | 5172 | 4.520644e+07 | 0.00 | False | 4117269 | 3.000000e+07 | 100.000000 | 0.000000e+00 | False |
1051426081 | 3737506 | 5172 | 4.491944e+07 | 0.00 | False | 3737506 | 3.000000e+07 | 100.000000 | 0.000000e+00 | False |
1133162696 | 7738006 | 5172 | 3.002064e+06 | 0.00 | False | 7738006 | 2.000000e+06 | 100.000000 | 0.000000e+00 | False |
36131 rows × 8 columns
baja_true = saldos.loc[(saldos['activeCases'] == True) & (saldos['slope'] < 0)]
sube_true = saldos.loc[(saldos['activeCases'] == True) & (saldos['slope'] > 0)]
baja_false = saldos.loc[(saldos['activeCases'] == False) & (saldos['slope'] < 0)]
sube_false = saldos.loc[(saldos['activeCases'] == False) & (saldos['slope'] > 0)]
baja_true
total | posiblesPerdidas | activeCases | Contratos | ValorTransaccion | porcentajeCaida | slope | prediction | |||
---|---|---|---|---|---|---|---|---|---|---|
NroDocum | Contrato | PlanProducto | ||||||||
1105297798 | 2200275 | 4899 | 4.218034e+08 | 8.272368e+06 | True | 2200275 | -1.284862e+06 | -86.497742 | -1.248519e+06 | False |
1187108348 | 7044987 | 8404 | 3.471420e+09 | 8.442569e+07 | True | 7044987 | -1.399432e+08 | -37.094613 | -8.187250e+05 | False |
1108520330 | 3533499 | 8404 | 1.946047e+09 | 7.691772e+07 | True | 3533499 | -2.585378e+07 | -72.480132 | -3.550158e+05 | False |
1022097976 | 8721209 | 8404 | 5.373961e+07 | 1.121144e+05 | True | 8721209 | -2.601374e+05 | -1.312980 | -1.844221e+04 | False |
1165012646 | 5990227 | 3376 | 5.917253e+09 | 3.002709e+08 | True | 5990227 | -1.913979e+07 | -50.000000 | -3.332961e+06 | False |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1164992875 | 5654444 | 4899 | 1.107758e+08 | 6.093500e+06 | True | 5654444 | -6.386633e+06 | -82.716618 | -1.411481e+05 | False |
1058514762 | 7123959 | 4899 | 6.803875e+09 | 1.591713e+08 | True | 7123959 | 4.021410e+06 | -50.000000 | -2.150744e+06 | False |
1000118312 | 5648869 | 4899 | 1.150976e+09 | 3.823788e+07 | True | 5648869 | 1.571801e+05 | -50.000000 | -3.087818e+05 | False |
1080796902 | 408749 | 5172 | 1.537971e+08 | 1.779550e+06 | True | 408749 | 8.726171e+04 | -50.000000 | -1.513201e+05 | False |
1015365088 | 376257 | 4899 | 7.917705e+09 | 3.754148e+08 | True | 376257 | 7.915645e+05 | -96.460457 | -1.518964e+06 | False |
797 rows × 8 columns
Porcentaje de caida Tendencia si pronostica una caida de mas del 70%
a = len(baja_true.loc[baja_true.prediction == True].values)
b = len(sube_true.loc[sube_true.prediction == True].values)
verdaderos = len(saldos.loc[saldos['activeCases'] == True].index.values)
print('TOTAL DE CASOS ACTIVOS: ', verdaderos)
print('CASOS ACTIVOS CON PREDICCION -30%: ', (a + b))
a = len(baja_true.loc[baja_true.slope < 0].values)
verdaderos = len(saldos.loc[saldos['activeCases'] == True].index.values)
print('CASOS ACTIVOS SLOPE: ', (a))
TOTAL DE CASOS ACTIVOS: 1452 CASOS ACTIVOS CON PREDICCION -30%: 171 CASOS ACTIVOS SLOPE: 797
a = len(baja_false.loc[baja_false.prediction == True].values)
b = len(sube_false.loc[sube_false.prediction == True].values)
verdaderos = len(saldos.loc[saldos['activeCases'] == False].index.values)
print('TOTAL DE NO CASOS ACTIVOS: ', verdaderos)
print('CASOS NO ACTIVOS CON PREDICCION -30%: ', (a + b))
a = len(baja_false.loc[baja_false.slope < 0].values)
b = len(sube_false.loc[sube_false.slope < 0].values)
verdaderos = len(saldos.loc[saldos['activeCases'] == False].index.values)
print('CASOS NO ACTIVOS SLOPE: ', (a))
saldos['totalPercent'] = saldos['slope'] / saldos['total']
# Mostrar el DataFrame modificado
saldos['ValorTransaccion'] = saldos['ValorTransaccion'].fillna(saldos['total'] / 1000)
saldos['ValorTransaccion'].fillna(0, inplace=True)
saldos['totalPercent'] = saldos['totalPercent'].fillna(saldos['porcentajeCaida'] * 10)
saldos
TOTAL DE NO CASOS ACTIVOS: 34679 CASOS NO ACTIVOS CON PREDICCION -30%: 1884 CASOS NO ACTIVOS SLOPE: 21292
total | posiblesPerdidas | activeCases | Contratos | ValorTransaccion | porcentajeCaida | slope | prediction | totalPercent | |||
---|---|---|---|---|---|---|---|---|---|---|---|
NroDocum | Contrato | PlanProducto | |||||||||
1070499713 | 6283724 | 3376 | 1.036830e+08 | 0.00 | False | 6283724 | -5.000000e+06 | 0.718735 | -3.040252e+05 | False | -0.002932 |
1067734816 | 4241304 | 3376 | 2.613527e+09 | 0.00 | False | 4241304 | 2.613527e+06 | 0.906037 | 9.221042e+04 | False | 0.000035 |
1018964721 | 9943050 | 3376 | 2.808434e+08 | 830075.86 | False | 9943050 | -1.825385e+04 | -0.905232 | 1.173794e+05 | False | 0.000418 |
1169386237 | 9995431 | 3376 | 1.732767e+08 | 0.00 | False | 9995431 | 1.443160e+06 | 0.471734 | 1.130727e+06 | False | 0.006526 |
1095392085 | 5555946 | 3376 | 1.256775e+09 | 0.00 | False | 5555946 | 1.256775e+06 | 0.590664 | 1.434138e+04 | False | 0.000011 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1081765591 | 7398048 | 5172 | 1.504645e+07 | 0.00 | False | 7398048 | 1.000000e+07 | 100.000000 | 0.000000e+00 | False | 0.000000 |
1155662224 | 8963958 | 5172 | 3.006538e+08 | 0.00 | False | 8963958 | 2.020000e+08 | 100.000000 | 0.000000e+00 | False | 0.000000 |
1054358908 | 4117269 | 5172 | 4.520644e+07 | 0.00 | False | 4117269 | 3.000000e+07 | 100.000000 | 0.000000e+00 | False | 0.000000 |
1051426081 | 3737506 | 5172 | 4.491944e+07 | 0.00 | False | 3737506 | 3.000000e+07 | 100.000000 | 0.000000e+00 | False | 0.000000 |
1133162696 | 7738006 | 5172 | 3.002064e+06 | 0.00 | False | 7738006 | 2.000000e+06 | 100.000000 | 0.000000e+00 | False | 0.000000 |
36131 rows × 9 columns
Descripción del modelo y sus variables¶
El RandomForestClassifier es un método de aprendizaje supervisado que pertenece a la familia de los métodos de ensamble, específicamente a los métodos de bagging. Se construye utilizando múltiples árboles de decisión durante el entrenamiento y produce la clase que es la moda de las clases de los árboles individuales para clasificación, o el promedio de las predicciones para regresión.
Características principales:
Robustez: Al utilizar múltiples árboles, el clasificador es generalmente robusto frente al sobreajuste, especialmente en comparación con otros modelos como los árboles de decisión individuales.
Manejo de Datos: Puede manejar automáticamente tanto variables numéricas como categóricas y no requiere que las variables de entrada estén escaladas o normalizadas.
Importancia de las Características: Ofrece una excelente perspectiva sobre la importancia de las características, lo que puede ser útil para la selección de variables y entender qué factores son más importantes para la predicción.
Flexibilidad: Puede ser utilizado tanto para problemas de clasificación como de regresión, haciendo que sea una herramienta versátil en el arsenal de un científico de datos.
El clasificador trabaja construyendo un conjunto de árboles de decisión entrenados en diferentes subconjuntos del conjunto de datos original (seleccionados con reemplazo), con cada árbol dando un voto en la predicción final. Esta técnica mejora la precisión de la predicción y controla el sobreajuste.
Variable | Description |
---|---|
Predicción con regresion Lineal |
Valor que indica si se detectó una predicción que baje del 70% en los 3 meses siguientes |
Pendiente promedio de los saldos |
Indica cual es el ritmo de crecimiento de la serie temporal ( si es negativo o positivo ) |
Promedio de transacciones |
Promedio de transacciones del cliente ( si ha retirado mas de lo que ha ingresado a la cuenta ) |
Relación pendiente - Saldo Total |
Indica la relación que hay entre el saldo total y la pendiente |
RandomForestClassifier¶
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier # Importar RandomForest
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
from sklearn.preprocessing import LabelEncoder
from sklearn.impute import SimpleImputer
import pandas as pd
# Suponemos que 'data' es tu DataFrame original y que 'saldos' ya ha sido cargado correctamente
data = saldos.copy()
# Imputación de valores faltan
# (Asumiendo que necesitas transformar 'prediction' de categórica a numérica si aún no está hecho)
le = LabelEncoder()
data['prediction'] = le.fit_transform(data['prediction'])
# Separar las variables predictoras y el objetivo
X = data[['prediction', 'porcentajeCaida', 'totalPercent', 'ValorTransaccion']]
y = data['activeCases']
rd = 5
# Dividir los datos en conjuntos de entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=rd)
# Modelo de Random Forest
forest_model = RandomForestClassifier(criterion='log_loss', n_estimators=10, random_state=rd, n_jobs=3) # Usar 100 árboles
forest_model.fit(X_train, y_train)
# Predicciones y evaluación del modelo
predicciones_forest = forest_model.predict(X_test)
reporte_clasificacion_forest = classification_report(y_test, predicciones_forest)
matriz_confusion_forest = confusion_matrix(y_test, predicciones_forest)
accuracy_forest = accuracy_score(y_test, predicciones_forest)
# Imprimir los resultados
print("Reporte de clasificación (Random Forest):\n", reporte_clasificacion_forest)
print("Matriz de confusión (Random Forest):\n", matriz_confusion_forest)
print(f'Accuracy del modelo (Random Forest): {accuracy_forest:.2f}')
Reporte de clasificación (Random Forest): precision recall f1-score support False 0.99 1.00 0.99 6941 True 0.91 0.78 0.84 286 accuracy 0.99 7227 macro avg 0.95 0.89 0.92 7227 weighted avg 0.99 0.99 0.99 7227 Matriz de confusión (Random Forest): [[6919 22] [ 63 223]] Accuracy del modelo (Random Forest): 0.99
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint
# Definición del modelo
# Definición del espacio de parámetros a probar
param_dist = {
'n_estimators': randint(10, 50),
'criterion': ['gini', 'entropy', ],
'max_depth': [None, 10, 20, 30],
'min_samples_split': randint(2, 11)
}
# Crear el objeto RandomizedSearchCV
random_search = RandomizedSearchCV(estimator=forest_model, param_distributions=param_dist, n_iter=10, cv=5, scoring='accuracy', random_state=rd)
# Ejecutar la búsqueda aleatoria
random_search.fit(X_train, y_train)
# Mejores parámetros encontrados
print("Mejores parámetros:", random_search.best_params_)
# Mejor modelo encontrado
best_forest_random = random_search.best_estimator_
# Evaluación del mejor modelo
predicciones_best_forest_random = best_forest_random.predict(X_test)
reporte_clasificacion_forest = classification_report(y_test, predicciones_forest)
matriz_confusion_forest = confusion_matrix(y_test, predicciones_forest)
accuracy_forest = accuracy_score(y_test, predicciones_forest)
# Imprimir los resultados
print("Reporte de clasificación (Random Forest):\n", reporte_clasificacion_forest)
print("Matriz de confusión (Random Forest):\n", matriz_confusion_forest)
print(f'Accuracy del modelo (Random Forest): {accuracy_forest:.2f}')
print("Accuracy del mejor modelo (Randomized):", accuracy_score(y_test, predicciones_best_forest_random))
Mejores parámetros: {'criterion': 'gini', 'max_depth': 30, 'min_samples_split': 3, 'n_estimators': 40} Reporte de clasificación (Random Forest): precision recall f1-score support False 0.99 1.00 0.99 6941 True 0.91 0.78 0.84 286 accuracy 0.99 7227 macro avg 0.95 0.89 0.92 7227 weighted avg 0.99 0.99 0.99 7227 Matriz de confusión (Random Forest): [[6919 22] [ 63 223]] Accuracy del modelo (Random Forest): 0.99 Accuracy del mejor modelo (Randomized): 0.9876850698768507
print("Importancia de los predictores en el modelo")
print("-------------------------------------------")
importancia_predictores = pd.DataFrame(
{'predictor': forest_model.feature_names_in_,
'importancia': forest_model.feature_importances_}
)
importancia_predictores.sort_values('importancia', ascending=False)
Importancia de los predictores en el modelo -------------------------------------------
predictor | importancia | |
---|---|---|
1 | porcentajeCaida | 0.593222 |
2 | totalPercent | 0.220141 |
3 | ValorTransaccion | 0.172519 |
0 | prediction | 0.014118 |
Predecir si un cliente retirará el 70% o más de sus ahorros en los próximos meses puede ofrecer a un banco una serie de ventajas financieras y estratégicas significativas. Aquí hay algunas de ellas:¶
Ventajas | Description |
---|---|
Gestión de Riesgos |
Mejora la capacidad del banco para anticipar y prepararse para los grandes retiros, mejorando la estabilidad y la confianza en el banco. |
Planificación de Liquidez |
Permite al banco manejar su flujo de caja y reservas de efectivo de manera más eficiente, reduciendo la necesidad de obtener liquidez costosa en el último minuto. |
Retención de Clientes |
Ofrece al banco una oportunidad para implementar estrategias proactivas de retención de clientes, ajustando productos y servicios para evitar la salida de fondos. |
Optimización de Inversiones |
Ayuda al banco a equilibrar su cartera de inversiones para maximizar los rendimientos sin sacrificar la liquidez necesaria para responder a los retiros. |
Prevención del Fraude |
Permite identificar comportamientos de retiro inusuales que podrían indicar fraude, lo que protege al cliente y al banco. |
Capital de Reserva |
Asegura que el banco cumpla con las regulaciones de capital de reserva más eficientemente, alineando las necesidades de liquidez con los requisitos regulatorios. |
Estabilidad Financiera |
Previniendo retiros masivos, la institución mantiene su estabilidad y la del sistema financiero en su conjunto. |
Segmentación de Mercado |
Facilita la personalización de la oferta de productos y la comunicación de marketing al comprender la probabilidad de retiro de los clientes. |
Planificación Estratégica |
Informa decisiones estratégicas del banco en áreas como expansión operativa, inversiones y estrategias de retención de clientes. |
dinero = saldos.copy()
dinero = dinero.loc[dinero['activeCases'] == True]
dinero = dinero.join(clientes.set_index("NroDocum"), on="NroDocum")
dinero['posiblesPerdidas']
NroDocum Contrato PlanProducto 1097503608 2032693 3376 57521420.94 1105297798 2200275 4899 8272368.04 1113002343 9557745 8404 25719668.61 1187108348 7044987 8404 84425691.55 1108520330 3533499 8404 76917717.31 ... 1113253168 3292675 4899 29908682.79 1130196219 798784 4899 0.00 1074554783 6698162 4899 77726154.93 1146147828 1019994 5172 14263039.89 1043158374 4104137 5172 23870328.64 Name: posiblesPerdidas, Length: 1452, dtype: float64