- Caso de uso de data science en banca: detección de fraude
- Preparación del dataset
- Cálculo del fraude en el dataset
- Uso de SMOTE para reequilibrar los datos
- Aplicación de la regresión logística
- Conclusión
La banca es uno de esos sectores afortunados en los que históricamente se ha recopilado gran cantidad de datos estructurados, y también uno de los primeros en adoptar tecnologías de ciencia de datos.
¿Cómo se usa la ciencia de datos en banca? Hoy en día, los datos se han convertido en el activo más valioso en este ámbito. La ciencia de datos es imprescindible para que los bancos mantengan el ritmo de sus competidores, atraigan a más clientes, fidelicen a los existentes, tomen decisiones más eficientes basadas en datos, impulsen el negocio, mejoren la eficiencia operativa, optimicen los servicios y productos actuales e introduzcan otros nuevos, refuercen la seguridad y, como resultado de todo ello, aumenten los ingresos. No es de extrañar que gran parte de la demanda de perfiles de data science provenga del sector bancario.
La ciencia de datos permite al sector bancario realizar con éxito numerosas tareas, entre ellas:
- análisis de riesgo de inversión
- predicción del valor del cliente a largo plazo
- segmentación de clientes
- predicción de la tasa de abandono
- marketing personalizado
- análisis de sentimiento del cliente
- asistentes virtuales y chatbots
A continuación, vamos a profundizar en uno de los casos de uso más habituales de la ciencia de datos en banca.
Caso de uso de data science en banca: detección de fraude
Las actividades fraudulentas suponen un problema complejo no solo en banca, sino también en muchos otros ámbitos, como la administración pública, los seguros, el sector público, las ventas y la sanidad. Cualquier negocio que gestione un gran volumen de transacciones online asume un riesgo significativo de fraude. Los delitos financieros adoptan múltiples formas: transacciones fraudulentas con tarjeta de crédito, cheques bancarios falsificados, evasión fiscal, blanqueo de capitales, ciberataques, robo de cuentas de clientes, identidades sintéticas, solicitudes falsas y estafas.
La detección de fraude es un conjunto de medidas proactivas para identificar y prevenir actividades fraudulentas y pérdidas económicas. Sus principales técnicas analíticas pueden dividirse en dos grupos:
- Estadísticas: cálculo de parámetros estadísticos, regresión, distribuciones de probabilidad, concordancia de datos
- Inteligencia artificial (IA): minería de datos, aprendizaje automático, deep learning
El aprendizaje automático es un pilar esencial de la detección de fraude. Su caja de herramientas ofrece dos enfoques:
- Métodos supervisados: k-vecinos más cercanos, regresión logística, máquinas de vectores de soporte, árbol de decisión, random forest, análisis de series temporales, redes neuronales, etc.
- Métodos no supervisados: análisis de clústeres, análisis de vínculos, mapas autoorganizados, análisis de componentes principales, detección de anomalías, etc.
No existe un algoritmo de aprendizaje automático universal y fiable para detectar fraude. En casos reales, se prueban varias técnicas o combinaciones de ellas, se calcula la precisión predictiva del modelo y se selecciona el enfoque óptimo.
El principal reto de los sistemas de detección de fraude es adaptarse con rapidez a los patrones cambiantes y a las tácticas de los defraudadores, y destapar con agilidad esquemas nuevos y cada vez más elaborados. Los casos de fraude siempre son minoría y quedan bien ocultos entre las transacciones reales.
Preparación del dataset
Vamos a explorar una implementación de machine learning para detectar fraude con tarjetas de crédito usando el lenguaje de programación Python. Trabajaremos con el dataset creditcard_data, una muestra modificada de un dataset de Kaggle sobre Credit Card Fraud Detection. Los datos originales recogen transacciones realizadas con tarjetas de crédito de titulares europeos durante dos días de septiembre de 2013.
Importemos los datos y echemos un vistazo rápido:
import pandas as pd
creditcard_data = pd.read_csv('creditcard_data.csv', index_col=0)
print(creditcard_data.info())
print('\n')
pd.options.display.max_columns = len(creditcard_data)
print(creditcard_data.head(3))
<class 'pandas.core.frame.DataFrame'>
Int64Index: 5050 entries, 0 to 5049
Data columns (total 30 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 V1 5050 non-null float64
1 V2 5050 non-null float64
2 V3 5050 non-null float64
3 V4 5050 non-null float64
4 V5 5050 non-null float64
5 V6 5050 non-null float64
6 V7 5050 non-null float64
7 V8 5050 non-null float64
8 V9 5050 non-null float64
9 V10 5050 non-null float64
10 V11 5050 non-null float64
11 V12 5050 non-null float64
12 V13 5050 non-null float64
13 V14 5050 non-null float64
14 V15 5050 non-null float64
15 V16 5050 non-null float64
16 V17 5050 non-null float64
17 V18 5050 non-null float64
18 V19 5050 non-null float64
19 V20 5050 non-null float64
20 V21 5050 non-null float64
21 V22 5050 non-null float64
22 V23 5050 non-null float64
23 V24 5050 non-null float64
24 V25 5050 non-null float64
25 V26 5050 non-null float64
26 V27 5050 non-null float64
27 V28 5050 non-null float64
28 Amount 5050 non-null float64
29 Class 5050 non-null int64
dtypes: float64(29), int64(1)
memory usage: 1.2 MB
V1 V2 V3 V4 V5 V6 V7 \
0 1.725265 -1.337256 -1.012687 -0.361656 -1.431611 -1.098681 -0.842274
1 0.683254 -1.681875 0.533349 -0.326064 -1.455603 0.101832 -0.520590
2 1.067973 -0.656667 1.029738 0.253899 -1.172715 0.073232 -0.745771
V8 V9 V10 V11 V12 V13 V14 \
0 -0.026594 -0.032409 0.215113 1.618952 -0.654046 -1.442665 -1.546538
1 0.114036 -0.601760 0.444011 1.521570 0.499202 -0.127849 -0.237253
2 0.249803 1.383057 -0.483771 -0.782780 0.005242 -1.273288 -0.269260
V15 V16 V17 V18 V19 V20 V21 \
0 -0.230008 1.785539 1.419793 0.071666 0.233031 0.275911 0.414524
1 -0.752351 0.667190 0.724785 -1.736615 0.702088 0.638186 0.116898
2 0.091287 -0.347973 0.495328 -0.925949 0.099138 -0.083859 -0.189315
V22 V23 V24 V25 V26 V27 V28 \
0 0.793434 0.028887 0.419421 -0.367529 -0.155634 -0.015768 0.010790
1 -0.304605 -0.125547 0.244848 0.069163 -0.460712 -0.017068 0.063542
2 -0.426743 0.079539 0.129692 0.002778 0.970498 -0.035056 0.017313
Amount Class
0 189.00 0
1 315.17 0
2 59.98 0
El dataset contiene las siguientes variables:
- Variables codificadas numéricamente de V1 a V28, que son los componentes principales obtenidos mediante una transformación PCA. Por motivos de confidencialidad, no se proporciona información sobre las características originales.
- La variable Amount representa el importe de la transacción.
- La variable Class indica si la transacción fue fraudulenta (1) o no (0).
Por su propia naturaleza, los casos de fraude son, por suerte, una pequeña minoría en cualquier lista de transacciones. Sin embargo, los algoritmos de aprendizaje automático suelen funcionar mejor cuando las distintas clases del dataset están más o menos equilibradas. De lo contrario, hay pocos datos de los que aprender. A este problema se le llama desequilibrio de clases.
Cálculo del fraude en el dataset
Calculemos el porcentaje de transacciones fraudulentas sobre el total de transacciones en nuestro dataset:
round(creditcard_data['Class'].value_counts()*100/len(creditcard_data)).convert_dtypes()
0 99
1 1
Name: Class, dtype: Int64
y creemos un gráfico para visualizar los puntos de datos de fraude frente a no fraude:
import matplotlib.pyplot as plt
import numpy as np
def prep_data(df):
X = df.iloc[:, 1:28]
X = np.array(X).astype(float)
y = df.iloc[:, 29]
y = np.array(y).astype(float)
return X, y
def plot_data(X, y):
plt.scatter(X[y==0, 0], X[y==0, 1], label='Class #0', alpha=0.5, linewidth=0.15)
plt.scatter(X[y==1, 0], X[y==1, 1], label='Class #1', alpha=0.5, linewidth=0.15, c='r')
plt.legend()
return plt.show()
X, y = prep_data(creditcard_data)
plot_data(X, y)

Uso de SMOTE para reequilibrar los datos
Podemos confirmar que la proporción de transacciones fraudulentas es muy baja y que estamos ante un caso de desequilibrio de clases. Para solucionarlo, podemos reequilibrar los datos con la técnica de sobremuestreo de la minoría sintética (SMOTE). A diferencia del sobremuestreo aleatorio, SMOTE es algo más sofisticado porque no crea copias exactas de las observaciones. En su lugar, utiliza las características de los vecinos más cercanos de los casos de fraude para generar nuevas muestras sintéticas similares a las observaciones existentes de la clase minoritaria. Apliquemos SMOTE a nuestros datos de tarjeta de crédito:
from imblearn.over_sampling import SMOTE
method = SMOTE()
X_resampled, y_resampled = method.fit_resample(X, y)
plot_data(X_resampled, y_resampled)

Como vemos, con SMOTE obtenemos de repente más observaciones de la clase minoritaria. Para apreciar aún mejor los resultados de este enfoque, vamos a compararlos con los datos originales:
def compare_plot(X, y, X_resampled, y_resampled, method):
f, (ax1, ax2) = plt.subplots(1, 2)
c0 = ax1.scatter(X[y==0, 0], X[y==0, 1], label='Class #0',alpha=0.5)
c1 = ax1.scatter(X[y==1, 0], X[y==1, 1], label='Class #1',alpha=0.5, c='r')
ax1.set_title('Original set')
ax2.scatter(X_resampled[y_resampled==0, 0], X_resampled[y_resampled==0, 1], label='Class #0', alpha=.5)
ax2.scatter(X_resampled[y_resampled==1, 0], X_resampled[y_resampled==1, 1], label='Class #1', alpha=.5,c='r')
ax2.set_title(method)
plt.figlegend((c0, c1), ('Class #0', 'Class #1'), loc='lower center', ncol=2, labelspacing=0.)
plt.tight_layout(pad=3)
return plt.show()
print(f'Original set:\n'
f'{pd.value_counts(pd.Series(y))}\n\n'
f'SMOTE:\n'
f'{pd.value_counts(pd.Series(y_resampled))}\n')
compare_plot(X, y, X_resampled, y_resampled, method='SMOTE')
Original set:
0.0 5000
1.0 50
dtype: int64
SMOTE:
0.0 5000
1.0 5000
dtype: int64

Así, el método SMOTE ha equilibrado por completo nuestros datos, y la clase minoritaria ahora tiene el mismo tamaño que la mayoritaria.
Volveremos en breve a la aplicación práctica de SMOTE, pero antes retomemos los datos originales e intentemos detectar los casos de fraude. Siguiendo el enfoque "clásico", tenemos que crear reglas para cazar el fraude. Estas reglas pueden referirse, por ejemplo, a ubicaciones inusuales de las transacciones o a transacciones sospechosamente frecuentes. La idea es definir valores umbral basados en estadísticas comunes, a menudo en las medias de las observaciones, y aplicar esos umbrales a nuestras variables para detectar fraude.
print(creditcard_data.groupby('Class').mean().round(3)[['V1', 'V3']])
V1 V3
Class
0 0.035 0.037
1 -4.985 -7.294
En nuestro caso, apliquemos estas condiciones: V1 < -3 y V3 < -5. Después, para estimar el rendimiento de este enfoque, compararemos los casos marcados como fraude con los reales:
creditcard_data['flag_as_fraud'] = np.where(np.logical_and(creditcard_data['V1']<-3, creditcard_data['V3']<-5), 1, 0)
print(pd.crosstab(creditcard_data['Class'], creditcard_data['flag_as_fraud'], rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud 0 1
Actual Fraud
0 4984 16
1 28 22
Aplicación de la regresión logística
Detectamos 22 de los 50 casos de fraude, pero no conseguimos identificar los otros 28, y además tuvimos 16 falsos positivos. Veamos si las técnicas de aprendizaje automático pueden mejorar esos resultados.
Ahora vamos a implementar un algoritmo sencillo de clasificación por regresión logística sobre nuestros datos de tarjeta de crédito para identificar casos fraudulentos y, después, visualizar los resultados en una matriz de confusión:
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
lr = LogisticRegression()
lr.fit(X_train, y_train)
predictions = lr.predict(X_test)
print(pd.crosstab(y_test, predictions, rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud 0.0 1.0
Actual Fraud
0.0 1504 1
1.0 1 9
Es importante señalar que aquí tenemos menos observaciones en la matriz de confusión porque solo usamos el conjunto de test para calcular los resultados del modelo, es decir, un 30% del dataset.
Detectamos un porcentaje mayor de fraudes: 90% (9 de 10), frente al 44% anterior (22 de 50). Además, ahora tenemos muchos menos falsos positivos, así que es una mejora.
Volvamos ahora al problema del desequilibrio de clases y comprobemos si podemos mejorar aún más las predicciones combinando la regresión logística con el remuestreo SMOTE. Para hacerlo de forma eficiente y en un solo paso, definiremos un pipeline y lo ejecutaremos sobre nuestros datos:
from imblearn.pipeline import Pipeline
# Defining which resampling method and which ML model to use in the pipeline
resampling = SMOTE()
lr = LogisticRegression()
pipeline = Pipeline([('SMOTE', resampling), ('Logistic Regression', lr)])
pipeline.fit(X_train, y_train)
predictions = pipeline.predict(X_test)
print(pd.crosstab(y_test, predictions, rownames=['Actual Fraud'], colnames=['Flagged Fraud']))
Flagged Fraud 0.0 1.0
Actual Fraud
0.0 1496 9
1.0 1 9
Como vemos, en nuestro caso SMOTE no ha aportado mejoras: seguimos detectando el 90% de los fraudes y, además, tenemos un número ligeramente mayor de falsos positivos. La explicación es que el remuestreo no conduce necesariamente a mejores resultados en todos los casos. Cuando los fraudes están muy dispersos en los datos, sus vecinos más cercanos no tienen por qué ser también fraudes, así que SMOTE puede introducir sesgos.
Conclusión
Como posibles siguientes pasos para aumentar la precisión del modelo de regresión logística, podemos ajustar algunos parámetros del algoritmo. También se puede considerar la validación cruzada K-fold en lugar de dividir el dataset en dos partes. Por último, podemos probar otros algoritmos de aprendizaje automático (por ejemplo, árbol de decisión o random forest) y ver si ofrecen mejores resultados.
Si quieres profundizar en los aspectos teóricos y técnicos de la implementación de modelos de detección de fraude, echa un vistazo a los materiales del curso Fraud Detection in Python.


