from datetime import datetime, date from time import time from google.colab import drive import pandas as pd import numpy as np from shapely.geometry import Point import geopandas as gpd import matplotlib.pyplot as plt import matplotlib.pylab as pylab import seaborn as sns from geopandas import GeoDataFrame from sklearn.metrics import confusion_matrix, roc_curve, auc from sklearn.model_selection import train_test_split, RandomizedSearchCV from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score, classification_report, precision_recall_curve import xgboost as xgb from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.tree import DecisionTreeClassifier drive.mount('/content/drive') data = pd.read_csv("/content/drive/MyDrive/datasets/fraud_test.csv", index_col=[0]) data.info() data.head() data_fraudes = data.groupby('is_fraud').size() print(data_fraudes) data.describe() def day_of_week(date_str): day = { 0: 'Lunes', 1: 'Martes', 2: 'Miercoles', 3: 'Jueves', 4: 'Viernes', 5: 'Sabado', 6: 'Domingo' } day_num = day[date_str.weekday()] return day_num def calculate_age(row): born = datetime.strptime(row['dob'], "%d/%m/%Y").date() trans = row['fecha_trans'] return trans.year - born.year - ((trans.month, trans.day) < (born.month, born.day)) def preprocesar_data(data): # Separar fecha y hora de transaccion data['fecha_trans'] = data['trans_date_trans_time'].apply(lambda x: datetime.strptime(x, "%d/%m/%Y %H:%M").date()) data['hora_trans'] = data['trans_date_trans_time'].apply(lambda x: datetime.strptime(x, "%d/%m/%Y %H:%M").hour) # Obtener dia de semana data['dia_semana'] = data['fecha_trans'].apply(day_of_week) # Calcular edad data['edad'] = data.apply(calculate_age, axis=1) # Pasamos todos los datos a lower case data['job'] = data['job'].str.lower() # En caso de contener una coma, extraemos solo la primera parte data['job'] = data['job'].apply(lambda x: x.split(',')[0] if ',' in x else x) return data data = preprocesar_data(data) data[['fecha_trans', 'hora_trans', 'dia_semana', 'edad', 'job']].head() cat_sin_fraude = data[data['is_fraud']==0].groupby('category').size().sort_values() cat_con_fraude = data[data['is_fraud']==1].groupby('category').size().sort_values() fig, ax = plt.subplots(2, 1, figsize=(6,8)) ax[0].barh(y=cat_sin_fraude.index, width=cat_sin_fraude, color='green') ax[0].set_title('Compras no fraudulentas') ax[0].set_ylabel('Categoría') ax[1].barh(y=cat_con_fraude.index, width=cat_con_fraude, color='pink') ax[1].set_title('Compras fraudulentas') ax[1].set_ylabel('Categoría') plt.show() gen_sin_fraude = data[data['is_fraud']==0].groupby('gender').size().sort_values() gen_con_fraude = data[data['is_fraud']==1].groupby('gender').size().sort_values() fig, ax = plt.subplots(1, 2, figsize=(8,6)) ax[0].pie(gen_sin_fraude, labels=gen_sin_fraude.index, autopct='%1.1f%%') ax[0].set_title('Compras no fraudulentas') ax[1].pie(gen_con_fraude, labels=gen_con_fraude.index, autopct='%1.1f%%') ax[1].set_title('Compras fraudulentas') plt.show() edad_sin_fraude = data[data['is_fraud']==0].groupby('edad').size().sort_index() edad_con_fraude = data[data['is_fraud']==1].groupby('edad').size().sort_index() fig, ax = plt.subplots(1, 2, figsize=(12,4)) ax[0].plot(edad_sin_fraude.index, edad_sin_fraude, color='green') ax[0].set_title('Compras no fraudulentas') ax[0].set_xlabel('Edad') ax[1].plot(edad_con_fraude.index, edad_con_fraude, color='pink') ax[1].set_title('Compras fraudulentas') ax[1].set_xlabel('Edad') plt.show() sns.boxplot(data=data, y='edad', x='is_fraud'); cat_sin_fraude = data[data['is_fraud']==0].groupby('fecha_trans').size().sort_index() cat_con_fraude = data[data['is_fraud']==1].groupby('fecha_trans').size().sort_index() fig, ax = plt.subplots(2, 1, figsize=(10,10)) ax[0].plot(cat_sin_fraude.index, cat_sin_fraude, color='green') ax[0].set_title('Compras no fraudulentas') ax[0].set_xlabel('Fecha') ax[1].plot(cat_con_fraude.index, cat_con_fraude, color='pink') ax[1].set_title('Compras fraudulentas') ax[1].set_xlabel('Fecha') plt.show() cat_sin_fraude = data[data['is_fraud']==0].groupby('fecha_trans').size().sort_index() cat_con_fraude = data[data['is_fraud']==1].groupby('fecha_trans').size().sort_index() fig, ax = plt.subplots(1, 1, figsize=(10,3)) ax.plot(cat_sin_fraude.index, cat_sin_fraude, color='green') ax.axvline(datetime.strptime('2020-09-07', '%Y-%m-%d'), color = 'b', linestyle='--') ax.axvline(datetime.strptime('2020-09-14', '%Y-%m-%d'), color = 'b', linestyle='--') ax.axvline(datetime.strptime('2020-09-21', '%Y-%m-%d'), color = 'b', linestyle='--') ax.set_title('Compras no fraudulentas - ciclos') ax.set_xlabel('Fecha') ax.set_xlim(date(2020, 9, 1), date(2020, 9, 22)) plt.show() dia_sin_fraude = data[data['is_fraud']==0].groupby('dia_semana').size() dia_con_fraude = data[data['is_fraud']==1].groupby('dia_semana').size() fig, ax = plt.subplots(2, 1, figsize=(6,8)) ax[0].barh(y=dia_sin_fraude.index, width=dia_sin_fraude, color='green') ax[0].set_title('Compras no fraudulentas') ax[0].set_ylabel('Dia de semana') ax[1].barh(y=dia_con_fraude.index, width=dia_con_fraude, color='pink') ax[1].set_title('Compras fraudulentas') ax[1].set_ylabel('Dia de semana') plt.show() cat_sin_fraude = data[data['is_fraud']==0].groupby('hora_trans').size().sort_index() cat_con_fraude = data[data['is_fraud']==1].groupby('hora_trans').size().sort_index() fig, ax = plt.subplots(1, 2, figsize=(12,4)) ax[0].bar(x=cat_sin_fraude.index, height=cat_sin_fraude, color='green') ax[0].set_title('Compras no fraudulentas') ax[0].set_xlabel('Hora') ax[1].bar(x=cat_con_fraude.index, height=cat_con_fraude, color='pink') ax[1].set_title('Compras fraudulentas') ax[1].set_xlabel('Hora') plt.show() cat_sin_fraude = data[data['is_fraud']==0].groupby('amt').size().sort_index() cat_con_fraude = data[data['is_fraud']==1].groupby('amt').size().sort_index() fig, ax = plt.subplots(2, 1, figsize=(8,10)) ax[0].plot(cat_sin_fraude.index, cat_sin_fraude, color='green') ax[0].set_title('Compras no fraudulentas') ax[0].set_xlim(-100, 1500) ax[0].set_xlabel('Monto') ax[1].plot(cat_con_fraude.index, cat_con_fraude, color='pink') ax[1].set_title('Compras fraudulentas') ax[1].set_xlim(-100, 1500) ax[1].set_xlabel('Monto') plt.show() # Mapa de compras no fraudulentas data_no_fraud = data[data['is_fraud']==0] geometry = [Point(xy) for xy in zip(data_no_fraud['long'], data_no_fraud['lat'])] gdf = GeoDataFrame(data_no_fraud, geometry=geometry) world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres')); ax = world.plot(figsize=(10, 6)) gdf.plot(ax=ax, marker='o', color='lightgreen', markersize=15) ax.set_xlim(-130, -60) ax.set_ylim(25, 50) ax.set_title('Compras no fraudulentas') # Mapa de compras fraudulentas data_fraud = data[data['is_fraud']==1] geometry = [Point(xy) for xy in zip(data_fraud['long'], data_fraud['lat'])] gdf = GeoDataFrame(data_fraud, geometry=geometry) world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres')) ax = world.plot(figsize=(10, 6)) gdf.plot(ax=ax, marker='o', color='red', markersize=15); ax.set_xlim(-130, -60) ax.set_ylim(25, 50) ax.set_title('Compras fraudulentas'); cat_sin_fraude = data[data['is_fraud']==0].groupby('state').size().sort_values() cat_con_fraude = data[data['is_fraud']==1].groupby('state').size().sort_values() fig, ax = plt.subplots(2, 1, figsize=(15,8)) ax[0].bar(x=cat_sin_fraude.index, height=cat_sin_fraude, color='green') ax[0].set_title('Compras no fraudulentas por estado') ax[1].bar(x=cat_con_fraude.index, height=cat_con_fraude, color='pink') ax[1].set_title('Compras fraudulentas por estado') plt.show() cat_sin_fraude = data[data['is_fraud']==0].groupby('job').size().nlargest(n=10, keep='first').sort_values() cat_con_fraude = data[data['is_fraud']==1].groupby('job').size().nlargest(n=10, keep='first').sort_values() fig, ax = plt.subplots(2, 1, figsize=(6,8)) ax[0].barh(y=cat_sin_fraude.index, width=cat_sin_fraude, color='green') ax[0].set_title('Compras no fraudulentas por trabajo (TOP 10)') ax[1].barh(y=cat_con_fraude.index, width=cat_con_fraude, color='pink') ax[1].set_title('Compras fraudulentas por trabajo (TOP 10)') plt.show() def feature_engineering(df): ################### # ONE-HOT ENCODING ################### # Columna género df["gender"] = df["gender"].apply(lambda x: 1 if x == "M" else 0) # Columna categorias de compras top_10_categories = df['category'].value_counts().nlargest(10).index df['category'] = df['category'].apply(lambda x: x if x in top_10_categories else 'other') df = pd.get_dummies(df, columns=["category"]) # Columna Ocupación top_10_jobs = df['job'].value_counts().nlargest(n=10, keep='first').index df['job'] = df['job'].apply(lambda x: x if x in top_10_jobs else 'other') df = pd.get_dummies(df, columns=["job"]) # Reemplazar todos los True por 1 y False por 0 en las columnas que agregamos con get_dummies df.replace({True: 1, False: 0}, inplace=True) ################### # LABEL ENCODING ################### # Columna trimestre df['trimestre'] = df['fecha_trans'].apply(lambda x: (x.month - 1) // 3 + 1) ################### # CYCLIC ENCODING ################### # Columna día df['dia_sin'] = np.sin(pd.to_datetime(df['fecha_trans']).dt.dayofweek*2*np.pi/7) df['dia_cos'] = np.cos(pd.to_datetime(df['fecha_trans']).dt.dayofweek*2*np.pi/7) # Columna hora df['hora_sin'] = np.sin(df['hora_trans']*2*np.pi/24) df['hora_cos'] = np.cos(df['hora_trans']*2*np.pi/24) ########################### # ELIMINACIÓN DE COLUMNAS ########################### # Drop de columnas que no se usarán en el modelo df = df.drop(["state", "fecha_trans", 'hora_trans', 'dia_semana', 'cc_num', 'trans_num', 'first', 'last', 'merchant', 'street', 'zip', 'city', 'unix_time', 'trans_date_trans_time', 'dob'], axis=1) return df data_fe = feature_engineering(data) data_fe.head() data_fe.info() # División del conjunto de entrenamiento y test df_train, df_test = train_test_split(data_fe, test_size=0.2, random_state=7, stratify=data_fe['is_fraud']) # Sobremuestreo para equiparar la desigualdad df_train_con_fraude = df_train[df_train["is_fraud"]==1] df_train_sin_fraude = df_train[df_train["is_fraud"]==0] df_train_con_fraude_sobremuestreado = df_train_con_fraude.sample(n=len(df_train_sin_fraude), replace=True, random_state=7); df_train_sobremuestreado = pd.concat([df_train_sin_fraude, df_train_con_fraude_sobremuestreado]) # División X, y (X son todas las columnas que no son la objetivo, y es la objetivo) X_train_sobremuestreo = df_train_sobremuestreado.drop('is_fraud', axis=1) y_train_sobremuestreo = df_train_sobremuestreado['is_fraud'] X_test = df_test.drop('is_fraud', axis=1) y_test = df_test['is_fraud'] modelos = { 'Logistic Regression': LogisticRegression(random_state=7), 'Gradient Boosting': GradientBoostingClassifier(random_state=7), 'Decision Tree': DecisionTreeClassifier(random_state=7), 'Random Forest': RandomForestClassifier(random_state=7), 'XGBoost': xgb.XGBClassifier(random_state=7) } def entrenar_modelo(modelo, X_train, y_train, X_test, y_test): # Inicio tiempo entrenamiento start = time() # Ajustar el modelo modelo.fit(X_train, y_train) y_pred = modelo.predict(X_test) # Predecir probabilidades y_pred_proba = modelo.predict_proba(X_test)[:, 1] # Calcular precision-recall curve precisions, recalls, thresholds = precision_recall_curve(y_test, y_pred_proba) # Fin tiempo entrenamiento end = time() tiempo_entrenamiento = end - start # Resultados resultado = { 'modelo': modelo, 'y_pred': y_pred, 'y_pred_proba': y_pred_proba, 'precisions': precisions, 'recalls': recalls, 'thresholds': thresholds, 'tiempo_entrenamiento': tiempo_entrenamiento } return resultado resultados = {} for modelo in modelos: resultados[modelo] = entrenar_modelo(modelos[modelo], X_train_sobremuestreo, y_train_sobremuestreo, X_test, y_test) fig, ax = plt.subplots(1, 2, figsize=(18, 5)) fig.suptitle('Resultados de Entrenamiento', fontweight='bold') # Gráfico de precision-recall curve for modelo in resultados: ax[0].plot(resultados[modelo]['recalls'], resultados[modelo]['precisions'], lw=2, label=modelo) ax[0].plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') ax[0].set_xlim([0.0, 1.0]) ax[0].set_ylim([0.0, 1.05]) ax[0].set_xlabel('Recall') ax[0].set_ylabel('Precision') ax[0].set_title('Precision - Recall curve') ax[0].legend(loc='lower left') # Gráfico de tiempos de entrenamiento tiempos = pd.DataFrame({ 'modelo': list(resultados.keys()), 'tiempo_entrenamiento': [resultados[modelo]['tiempo_entrenamiento'] for modelo in resultados] }).sort_values('tiempo_entrenamiento', ascending=False) ax[1].set_title('Tiempos de entrenamiento') ax[1].bar(tiempos['modelo'], tiempos['tiempo_entrenamiento']) ax[1].set_title('Tiempos de entrenamiento') ax[1].set_xlabel('Modelo') ax[1].set_ylabel('Tiempo (s)'); fig, ax = plt.subplots(1, 1, figsize=(5, 5)) ax.plot(resultados['XGBoost']['thresholds'], resultados['XGBoost']['precisions'][1:], label='Precision') ax.plot(resultados['XGBoost']['thresholds'], resultados['XGBoost']['recalls'][1:], label='Recall') ax.axvline(x=0.89, color='g', linestyle='--', label='Umbral=0.89') ax.axvline(x=0.2, color='purple', linestyle='--', label='Umbral=0.2') ax.set_title('Precision y Recall por umbral') ax.set_xlabel('Umbral') ax.set_ylabel('Valor') ax.legend(); umbrales = [0.89, 0.2] fig, ax = plt.subplots(1, 2, figsize=(10, 5)) fig.suptitle('Matriz de Confusión', fontweight='bold') for i, umbral in enumerate(umbrales): y_pred = (resultados['XGBoost']['y_pred_proba'] > umbral).astype(int) cm = confusion_matrix(y_test, y_pred) sns.heatmap(cm, annot=True, linewidths=.5, square=True, cmap='coolwarm', cbar=False, fmt='g', ax=ax[i]) ax[i].set_title(f'Umbral={umbral}') ax[i].set_xlabel('Predicción') ax[i].set_ylabel('Real') ax[i].set_xticklabels(['No Fraude', 'Fraude']) ax[i].set_yticklabels(['No Fraude', 'Fraude'])