{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport xgboost as xgb\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nimport sklearn as sk\nimport multiprocessing\nfrom sklearn import tree\nfrom sklearn.utils.class_weight import compute_sample_weight\nfrom sklearn.svm import LinearSVC\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.metrics import confusion_matrix, accuracy_score, recall_score, f1_score\nfrom sklearn.model_selection import train_test_split, GridSearchCV\nfrom sklearn.ensemble import RandomForestClassifier, BaggingClassifier, VotingClassifier\nfrom sklearn.svm import SVC\nfrom sklearn.exceptions import ConvergenceWarning\nfrom keras.models import Sequential\nfrom keras.layers import Dense\nfrom keras.utils.vis_utils import plot_model\nfrom tensorflow import keras\nfrom warnings import simplefilter\nfrom pandas.core.common import SettingWithCopyWarning\nsimplefilter(\"ignore\", category=ConvergenceWarning)\nsimplefilter(\"ignore\", category=UserWarning)\nsimplefilter(\"ignore\", category=SettingWithCopyWarning)\nsns.set(rc = {'figure.figsize':(8,8)})\nN_CPU= multiprocessing.cpu_count()\ntf.config.threading.set_inter_op_parallelism_threads(N_CPU*2)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-08-08T22:23:30.666295Z","iopub.execute_input":"2022-08-08T22:23:30.667174Z","iopub.status.idle":"2022-08-08T22:23:39.055072Z","shell.execute_reply.started":"2022-08-08T22:23:30.667054Z","shell.execute_reply":"2022-08-08T22:23:39.053804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Carga de Datos:","metadata":{}},{"cell_type":"markdown","source":"## American Express - Default Prediction \n\n* [Link a la competencia](https://www.kaggle.com/competitions/amex-default-prediction)\n\nEl objetivo de este trabajo es predecir la probabilidad de que un cliente no pague el monto del saldo de su tarjeta de crédito (target = 1) en el futuro en función de su perfil de cliente mensual. La variable binaria target se calcula observando la ventana de rendimiento de 18 meses después del último extracto de la tarjeta de crédito, y si el cliente no paga el monto adeudado en 120 días después de la fecha del último extracto, se considera un evento de incumplimiento.\n\n## Sampleo del Dataset:\n\nComo este conjunto de datos de entrenamiento es muy grande (Posee un peso de alrededor de 16~17 GB), realizamos un sampleo para trabajar con un subconjunto del mismo tomando solo un 5% del total de los datos (Dejandonos con aproximadamente 900 Mb).\n\nPara realizar este sampleo, utilizaremos la funcón **sample_without_replacement()** y estableceremos su **random_state** a una seed, la cual se genera a partir del numero de nuestro grupo utilizando la siguiente formula:\n\n> (31416 × (número-grupo)) % 1000\n\n## Repositorio con el sampleo:\n\nYa que la memoria de la maquina de Kaggle no fue suficiente para levantar el set entero, realizamos el sampleo de forma local y generamos un nuevo dataset en Kaggle con el mismo.\n\n* [Repositorio con el codigo del sampleo.](https://github.com/stesta1/AMEX-DefaultPrediction/tree/sampleo)\n* [Enlace del nuevo dataset sampleado en Kaggle.](https://www.kaggle.com/datasets/santiagotesta/train-data-sampled-g14)\n\n### Seed Grupo:\n\n* Incluimos la seed del grupo a la notebook para utilizarla al balancear el dataset y generar redes neuronales mas adelante...","metadata":{}},{"cell_type":"code","source":"NUM_GRUPO = 14\nSEED_GRUPO = (31416 * NUM_GRUPO) % 1000","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:23:39.057507Z","iopub.execute_input":"2022-08-08T22:23:39.058139Z","iopub.status.idle":"2022-08-08T22:23:39.065166Z","shell.execute_reply.started":"2022-08-08T22:23:39.058102Z","shell.execute_reply":"2022-08-08T22:23:39.063282Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualización de los datos:","metadata":{}},{"cell_type":"code","source":"df_train_data = pd.read_csv(\"/kaggle/input/train-data-sampled-g14/train_sampled_G14.csv\")\ndf_train_labels = pd.read_csv(\"../input/amex-default-prediction/train_labels.csv\")","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:23:39.066588Z","iopub.execute_input":"2022-08-08T22:23:39.067116Z","iopub.status.idle":"2022-08-08T22:24:00.676754Z","shell.execute_reply.started":"2022-08-08T22:23:39.067082Z","shell.execute_reply":"2022-08-08T22:24:00.675573Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_train_data","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:00.680209Z","iopub.execute_input":"2022-08-08T22:24:00.680783Z","iopub.status.idle":"2022-08-08T22:24:00.808934Z","shell.execute_reply.started":"2022-08-08T22:24:00.680731Z","shell.execute_reply":"2022-08-08T22:24:00.807779Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Analizamos la distribución de la variable target dentro del dataset. Como se puede apreciar en el siguiente gráfico, **el conjunto se encuentra desbalanceado** (es lógico que la cantidad de clientes que paga el saldo de su cuenta sea mayor). ","metadata":{}},{"cell_type":"code","source":"g = sns.countplot(x = \"target\", data = df_train_labels)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:00.810370Z","iopub.execute_input":"2022-08-08T22:24:00.810757Z","iopub.status.idle":"2022-08-08T22:24:01.084208Z","shell.execute_reply.started":"2022-08-08T22:24:00.810722Z","shell.execute_reply":"2022-08-08T22:24:01.082835Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Acto seguido, visualizamos la distribución de los valores en los atributos categóricos del dataset. Para estas variables categoricas, se presentarán gráficos del tipo countplot, los cuales cuentan, para cada variable, la cantidad de repeticiones de cada valor posible que estas pueden tomar.","metadata":{}},{"cell_type":"code","source":"categoricas = ['B_30', 'B_38', 'D_114', 'D_116', 'D_117', 'D_120', 'D_126', 'D_63', 'D_64', 'D_66', 'D_68']\nfig, ax = plt.subplots(11, 1, figsize=(15, 70), sharex=True)\n\nfor i, col in enumerate(categoricas):\n    graph = sns.countplot(y=df_train_data[col], ax=ax[i])\n    ax[i].set_title(*[col])\n    graph.bar_label(graph.containers[0])","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:01.086452Z","iopub.execute_input":"2022-08-08T22:24:01.086956Z","iopub.status.idle":"2022-08-08T22:24:03.616161Z","shell.execute_reply.started":"2022-08-08T22:24:01.086906Z","shell.execute_reply":"2022-08-08T22:24:03.614794Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A partir de los gráficos expuestos, se puede observar que hay algunas variables que no están bien distribuidas en sus valores posibles, es decir, la mayoría del conteo recae sobre unos pocos valores de estas variables. Estas mismas son las variables B_30, D_116, D_120, D_126, D_63 y D_66, mientras que el resto de ellas se encuentran balanceadas.","metadata":{}},{"cell_type":"code","source":"sns.set(rc = {'figure.figsize':(8,8)})","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-08-08T22:24:03.618072Z","iopub.execute_input":"2022-08-08T22:24:03.618637Z","iopub.status.idle":"2022-08-08T22:24:03.626142Z","shell.execute_reply.started":"2022-08-08T22:24:03.618589Z","shell.execute_reply":"2022-08-08T22:24:03.624931Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Tambien tenemos una variable de tipo string que nos indica la fecha en la que se realizó el registro, por lo que vamos a cambiar su tipo y realizar un histograma para ver el rango de fechas que poseemos:\n","metadata":{}},{"cell_type":"code","source":"df_train_data[\"S_2\"] = pd.to_datetime(df_train_data[\"S_2\"])\nsns.histplot(x=\"S_2\", data = df_train_data)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:03.627702Z","iopub.execute_input":"2022-08-08T22:24:03.628738Z","iopub.status.idle":"2022-08-08T22:24:04.214211Z","shell.execute_reply.started":"2022-08-08T22:24:03.628688Z","shell.execute_reply":"2022-08-08T22:24:04.212964Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Dentro de nuestro sampleo tenemos registros tomados a lo largo de 1 año, comendando en Marzo del 2017 y finalizando en Marzo del 2018","metadata":{}},{"cell_type":"markdown","source":"# Ing. Caracteristicas:\n\n## Balanceo del set:\n\nComo vismo durante la visualizacion de datos, el dataset de train posee un gran desblance con respecto a los targets.\n\nAntes de entrenar nuestros modelos, vamos a realizar un balanceo utilizando el metodo de undersampling, ya que a pesar de el gran desbalance del set seguimos teniendo una buena cantidad de ejemplos para los registros que indican la falta de pago. (Alrededor de los 75.000 ejemplos).\n\nPara poder hacer este balanceo replicable, tambien utilizaremos la seed de grupo como random_state.","metadata":{}},{"cell_type":"code","source":"sns.countplot(x = \"target\", data = df_train_data)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:04.215755Z","iopub.execute_input":"2022-08-08T22:24:04.216144Z","iopub.status.idle":"2022-08-08T22:24:04.425722Z","shell.execute_reply.started":"2022-08-08T22:24:04.216108Z","shell.execute_reply":"2022-08-08T22:24:04.424484Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"min_n = df_train_data['target'].value_counts().min()\ndf_train_data = df_train_data.groupby('target', as_index=False, group_keys=False).apply(lambda x: x.sample(n=min_n, random_state = SEED_GRUPO))\nsns.countplot(x = \"target\", data = df_train_data)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:04.430716Z","iopub.execute_input":"2022-08-08T22:24:04.431740Z","iopub.status.idle":"2022-08-08T22:24:05.327153Z","shell.execute_reply.started":"2022-08-08T22:24:04.431698Z","shell.execute_reply":"2022-08-08T22:24:05.325920Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Una vez realizado el balanceo, confirmamos que nuestro set de entrenamiento posee casi 70.000 registros para cada una de las clasificaciones a realizar.","metadata":{}},{"cell_type":"markdown","source":"## Reduccion de Dimensionalidad - Columnas:\n\nEl dataset original posee 193 columnas, muchas de las cuales no tienen una gran correlación con la variable target, como se acaba de analizar. Por lo tanto, decidimos no tener en consideración aquellas columnas que no cumplan con un mínimo umbral de correlación (en módulo) con la variable target. ","metadata":{}},{"cell_type":"code","source":"correlaciones_columnas = (df_train_data.corrwith(df_train_data[\"target\"]).abs().sort_values(ascending=True)).drop('target')\ncorrelaciones_columnas.describe()","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:05.328913Z","iopub.execute_input":"2022-08-08T22:24:05.329288Z","iopub.status.idle":"2022-08-08T22:24:05.785737Z","shell.execute_reply.started":"2022-08-08T22:24:05.329251Z","shell.execute_reply":"2022-08-08T22:24:05.784239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A partir de la tabla anteriormente expuesta, se puede observar que el tercer cuartil de la correlación entre las columnas y target tiene un valor de 0.235168. Al usar este valor como umbral, estaríamos usando un cuarto de la cantidad de columnas que se tenían originalmente, siendo estas las que mayor correlación tienen con el target. Por lo tanto, establecemos este valor como piso.","metadata":{}},{"cell_type":"code","source":"df_filtrado = df_train_data.copy()\ncorrelaciones_columnas.dropna()\ncorrelaciones_columnas_dict = correlaciones_columnas.to_dict()\ncorrelaciones_columnas_dict.pop('D_87', None)\ncorrelaciones_columnas_dict\numbral = 0.235168\ncolumns_to_drop = ['D_87'] # Se elimina D_87 por ser NaN\nfor col, corr in correlaciones_columnas_dict.items():\n    if corr < umbral:\n        columns_to_drop.append(col)\ndf_filtrado.drop(columns_to_drop, axis=1, inplace=True)\ndf_filtrado","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:05.789605Z","iopub.execute_input":"2022-08-08T22:24:05.791101Z","iopub.status.idle":"2022-08-08T22:24:06.202645Z","shell.execute_reply.started":"2022-08-08T22:24:05.791042Z","shell.execute_reply":"2022-08-08T22:24:06.201441Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Antes de pasar a la reduccion de dimensionalidad para las filas, tenemos que realizar unas transformaciones en el set para las variables categoricas **D_63** y **D_64**","metadata":{}},{"cell_type":"markdown","source":"## Generacion de nuevas columnas / Modificacion de columnas:","metadata":{}},{"cell_type":"markdown","source":"Puesto que las variables 'D_63' y 'D_64' son categoricas y de tipo String, se utiliza la función get_dummies para reemplazarlos y así poder calcular el promedio de sus apariciones y no estas información. Estas dos variables, a partir de lo expuesto anteriormente, se encuentran altamente correlacionados con el target, por lo que, si se descartaran, se perdería mucha información.","metadata":{}},{"cell_type":"code","source":"dummies1 = pd.get_dummies(df_filtrado.D_64)\ndummies2 = pd.get_dummies(df_filtrado.D_63)\ndf_filtrado = pd.concat([df_filtrado, dummies1, dummies2], axis='columns')\ndf_filtrado.drop(['D_63', 'D_64'], axis='columns', inplace = True)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:06.204127Z","iopub.execute_input":"2022-08-08T22:24:06.205151Z","iopub.status.idle":"2022-08-08T22:24:06.331138Z","shell.execute_reply.started":"2022-08-08T22:24:06.205102Z","shell.execute_reply":"2022-08-08T22:24:06.329736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_filtrado","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:06.332605Z","iopub.execute_input":"2022-08-08T22:24:06.333512Z","iopub.status.idle":"2022-08-08T22:24:06.406019Z","shell.execute_reply.started":"2022-08-08T22:24:06.333473Z","shell.execute_reply":"2022-08-08T22:24:06.404362Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Reduccion de Dimensionalidad - Filas:","metadata":{}},{"cell_type":"code","source":"datos = df_filtrado.pivot_table(columns=['customer_ID'], aggfunc='size')\ndatos.describe()","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:06.408030Z","iopub.execute_input":"2022-08-08T22:24:06.408750Z","iopub.status.idle":"2022-08-08T22:24:06.695398Z","shell.execute_reply.started":"2022-08-08T22:24:06.408684Z","shell.execute_reply":"2022-08-08T22:24:06.694081Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"El hecho de que existan diferentes entradas para ciertos ID significa que el banco posee un historial de registros de ellos.\n\nAnte esto, podemos optar por distintos caminos: podríamos tomar el registro más reciente o el más antiguo, o bien hacer un promedio entre ambas y desestimar el resto; el que tenga el valor máximo o mínimo entre las variables más o menos correlacionadas con el target; o bien se podrían promediar.\n\nAl tener diversas variables de las cuales solo se conocen de qué tipo son -morosidad, gastos, pago, balance, riesgo- pero no conocer en específico qué es cada una de las columnas, elegir valores basado en una sola de ellas o elegir un solo registro sería un desaprovechamiento de los datos, ya que los demás datos desestimados no se terminan teniendo en cuenta. Por lo tanto, para aprovechar al máximo esta información extra de estos clientes, hemos optado por calcular un promedio de cada variable de todos los registros repetidos, para obtener un único registro con todas las variables balanceadas.\n\n* A partir de la tabla anterior, se observa que el valor máximo de repeticiones es 5.","metadata":{}},{"cell_type":"code","source":"datos_dict = datos.to_dict()\nvalues_dict = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}\nfor value in datos_dict.values():\n    values_dict[value] += 1\nvalues_dict","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:06.696911Z","iopub.execute_input":"2022-08-08T22:24:06.697519Z","iopub.status.idle":"2022-08-08T22:24:06.910369Z","shell.execute_reply.started":"2022-08-08T22:24:06.697478Z","shell.execute_reply":"2022-08-08T22:24:06.908953Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.rcParams['figure.figsize'] = (10,5)\nfreq = sns.countplot(x=datos)\nfreq.set_xlabel(\"Frecuencia\", fontsize=20)\nfreq.set_ylabel(\"Cantidad de registros\", fontsize=20)\nabs_values = values_dict.values()\nfreq.bar_label(container=freq.containers[0], labels=abs_values)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:06.912798Z","iopub.execute_input":"2022-08-08T22:24:06.914160Z","iopub.status.idle":"2022-08-08T22:24:07.271427Z","shell.execute_reply.started":"2022-08-08T22:24:06.914107Z","shell.execute_reply":"2022-08-08T22:24:07.269659Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Para verificar que la cantidad de registros eliminados haya sido la correcta, iteramos sobre el diccionario values_dict, que nos indica la cantidad de clientes que poseen N registros repetidos. Si un cliente posee N registros, se eliminarán N-1 registros suyos, ya que los N registros originales quedan reemplazados por el registro del promedio entre ellos. Por lo tanto, calculamos la cantidad de filas a eliminar:","metadata":{}},{"cell_type":"code","source":"cant_eliminadas = 0\nfor cant_repeticiones, cant_repetidos in values_dict.items():\n    cant_eliminadas += cant_repetidos * (cant_repeticiones - 1)\ncant_eliminadas","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:07.273146Z","iopub.execute_input":"2022-08-08T22:24:07.273771Z","iopub.status.idle":"2022-08-08T22:24:07.285292Z","shell.execute_reply.started":"2022-08-08T22:24:07.273720Z","shell.execute_reply":"2022-08-08T22:24:07.283825Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(df_filtrado) - cant_eliminadas","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:07.287501Z","iopub.execute_input":"2022-08-08T22:24:07.288680Z","iopub.status.idle":"2022-08-08T22:24:07.299250Z","shell.execute_reply.started":"2022-08-08T22:24:07.288627Z","shell.execute_reply":"2022-08-08T22:24:07.297338Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_filtrado = df_filtrado.groupby('customer_ID').mean().reset_index()\ndf_filtrado","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:07.301325Z","iopub.execute_input":"2022-08-08T22:24:07.302474Z","iopub.status.idle":"2022-08-08T22:24:08.916623Z","shell.execute_reply.started":"2022-08-08T22:24:07.302419Z","shell.execute_reply":"2022-08-08T22:24:08.915473Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Analisis de Outliers:\n\nPara realizar el analisis de outliers de nuestro set, vamos a realizar un analisis de boxplots:","metadata":{}},{"cell_type":"code","source":"df_filtrado_2 = df_filtrado.drop(\"customer_ID\", axis=1, inplace=False)\nsns.set(rc = {'figure.figsize':(35,35)})\ndf = pd.DataFrame(data = df_filtrado_2, columns = df_filtrado_2.columns)\nsns.boxplot(x=\"variable\", y=\"value\", data=pd.melt(df, \"target\"))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:08.918024Z","iopub.execute_input":"2022-08-08T22:24:08.918503Z","iopub.status.idle":"2022-08-08T22:24:18.776941Z","shell.execute_reply.started":"2022-08-08T22:24:08.918467Z","shell.execute_reply":"2022-08-08T22:24:18.775517Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.boxplot(x=\"variable\", y=\"value\",hue = \"target\", data=pd.melt(df, \"target\"))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:18.778434Z","iopub.execute_input":"2022-08-08T22:24:18.778792Z","iopub.status.idle":"2022-08-08T22:24:28.104106Z","shell.execute_reply.started":"2022-08-08T22:24:18.778753Z","shell.execute_reply":"2022-08-08T22:24:28.102934Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Luego de graficar los valores existentes en el conjunto de entrenamiento, podemos visualizar la aparición de outliers para la mayoría de las columnas del dataset. A simple vista nos resaltan mucho algunos registros que toman valores extremadamente altos y dificultan la legibilidad de los outlayers mas cercanos a la media.\nPara poder realizar una mejor visualizacion, vamos a fijar un umbral inferior y superior al cual se transladaran los que lo superen.\n- **Umbral inferior = -1**\n- **Umbral superior = 8**\n\nUna vez establecidos estos, volvemos a realizar los graficos:\n","metadata":{}},{"cell_type":"code","source":"df.clip(-1, 8, inplace = True)\nsns.boxplot(x=\"variable\", y=\"value\", data=pd.melt(df, \"target\"))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:28.105460Z","iopub.execute_input":"2022-08-08T22:24:28.106835Z","iopub.status.idle":"2022-08-08T22:24:34.621139Z","shell.execute_reply.started":"2022-08-08T22:24:28.106792Z","shell.execute_reply":"2022-08-08T22:24:34.619883Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.boxplot(x=\"variable\", y=\"value\",hue = \"target\",data=pd.melt(df, \"target\"))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:34.622673Z","iopub.execute_input":"2022-08-08T22:24:34.623140Z","iopub.status.idle":"2022-08-08T22:24:43.939498Z","shell.execute_reply.started":"2022-08-08T22:24:34.623103Z","shell.execute_reply":"2022-08-08T22:24:43.938316Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"En estos ultimos boxplots, podemos ver como los registros que poseen **target = 1** (Representados en los plots naranjas) mayoritariamente ocupan la porcion de outliers que existen para los registros de **target = 0** (Representados en los plots azules). \n\n*Por ejemplo:* En los registros de las variables **B_37** se ve claramente como la porcion naranja ocupa casi la totalidad de los outliers de la porcion azul, comparando con el boxplot de esta misma variable que no esta dividido por targets, identificamos que casi toda esta porcion naranja esta ubicada entre sus outlayers.\n\nPor esta razon, entendemos que estos valores atipicos son en su mayoria los registros con **target = 1** y la tarea que deberían realizar nuestros modelos son identificarlos, por lo que decidimos no modificarlos ni eliminarlos (Exceptuando los que posean valores mucho mas elevados en coparación que los demas que son reemplazados por el umbral correspondiente)","metadata":{}},{"cell_type":"code","source":"sns.set(rc = {'figure.figsize':(8,8)})\ndf_filtrado.drop(\"customer_ID\", axis=1, inplace=True)\ndf_filtrado.clip(-1, 8, inplace = True)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:43.940960Z","iopub.execute_input":"2022-08-08T22:24:43.941432Z","iopub.status.idle":"2022-08-08T22:24:44.172590Z","shell.execute_reply.started":"2022-08-08T22:24:43.941384Z","shell.execute_reply":"2022-08-08T22:24:44.171455Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Datos Faltantes:","metadata":{}},{"cell_type":"code","source":"plt.rcParams['figure.figsize'] = (20,25)\ndf_filtrado.isna().sum().sort_values(ascending = True)[df_filtrado.isna().sum()>0].plot(kind='barh')","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:44.174196Z","iopub.execute_input":"2022-08-08T22:24:44.174691Z","iopub.status.idle":"2022-08-08T22:24:44.663953Z","shell.execute_reply.started":"2022-08-08T22:24:44.174644Z","shell.execute_reply":"2022-08-08T22:24:44.662805Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Dentro del dataframe que utilizaremos para entrenar, poseemos varias registros que poseen valores nulos en algunas de sus columnas.\n\nTeniendo en cuenta el cálculo anterior del promedio en la reduccion de filas, podemos identificar que estos faltantes son el resultado de que para todas las entradas de un customer_ID, los valores pertenecientes a la columna eran nulos. Por esto, tomamos la decision de tomar que estos promedios son iguales a 0.","metadata":{}},{"cell_type":"code","source":"df_filtrado = df_filtrado.fillna(0)\ndf_filtrado.isna().sum()","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:44.667368Z","iopub.execute_input":"2022-08-08T22:24:44.667767Z","iopub.status.idle":"2022-08-08T22:24:44.715109Z","shell.execute_reply.started":"2022-08-08T22:24:44.667734Z","shell.execute_reply":"2022-08-08T22:24:44.713814Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.set(rc = {'figure.figsize':(8,8)})","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-08-08T22:24:44.722831Z","iopub.execute_input":"2022-08-08T22:24:44.723251Z","iopub.status.idle":"2022-08-08T22:24:44.730613Z","shell.execute_reply.started":"2022-08-08T22:24:44.723218Z","shell.execute_reply":"2022-08-08T22:24:44.729091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Normalizacion y unificacion de valores en columnas:","metadata":{}},{"cell_type":"markdown","source":"## Descripcion de los datos:\n\nSabemos por la información brindada en la competencia (https://www.kaggle.com/competitions/amex-default-prediction/data) que el conjunto de datos originalmente contiene características de perfil agregadas para cada cliente en cada fecha de estado de cuenta. Los atributos vienen anonimizados (probablemente por una cuestión de sensibilidad de datos, véase las normas sobre este tema en Argentina: http://servicios.infoleg.gob.ar/infolegInternet/anexos/60000-64999/64790/norma.htm) como también normalizados. A la vez sabemos que se clasifican en las siguientes categorías generales:\n\nD* = Variables de morosidad\n\nS* = Variables de gasto\n\nP* = Variables de pago\n\nB* = Variables de balance\n\nR_* = Variables de riesgo\n\nY que los siguientes atributos son categóricos:\n\n['B_30', 'B_38', 'D_114', 'D_116', 'D_117', 'D_120', 'D_126', 'D_63', 'D_64', 'D_66', 'D_68']\n\nA partir de la limpieza basada en la correlación de los features y el target descrita anteriormente, se pudo reducir notoriamente la cantidad de columnas del dataset.  Cabe decir que las variables pertenecientes a los grupos (D) y (B) fueron aquellas que en general demostraron tener mayor correlatividad al target.\n\nAdicionalmente, al ser posible la existencia de múltiples registros por cada cliente, agrupamos las filas correspondientes a el mismo cliente según el promedio de sus atributos numéricos. También se generaron nuevas columnas \"dummies\" para los casos de las columnas categóricas aún existentes en nuestro conjunto. ","metadata":{}},{"cell_type":"markdown","source":"# Modelos:\n\nPara la elección de un modelo certero a la hora de predecir el target de los clientes, haremos uso de las métricas precisión (precision/accuracy), exhaustividad (recall) y f1-score. Estas métricas se calculan de la siguiente manera:\n\nPrecisión: $\\frac{TP}{TP + FP}$\n\nExhaustividad: $\\frac{TP}{TP + FN}$\n\nF1 Score: $\\frac{2*Precisión*Exhaustividad}{Precisión + Exhaustividad}$\n\ndonde TP = True Positive; FP = False Positive; FN = False Negative.\n\nUn True Positive es una predicción que se predijo como positiva y el resultado real fue positivo; un False Positive, cuando se predijo positivamente y resultó estar errada la predicción. Un True Negative, análogamente, es un cuando se predice negativamente y este resulta serlo; un False Negative, cuando resultó ser positivo con una predicción de resultado desfavorable.\n\nDebido a que la métrica f1-score involucra las otras dos métricas mencionadas de una manera equilibrada, es decir, su valor depende de un balance de ellas, esta es la que utilizaremos como principal indicador cuando queramos comparar distintos modelos de predictores.\n\n","metadata":{}},{"cell_type":"code","source":"def heatmap(modelo):\n    y_pred = modelo.predict(x_test)\n    ds_resultados = pd.DataFrame(zip(y_test,y_pred),columns=['test','pred'])\n    tabla = confusion_matrix(ds_resultados['test'], ds_resultados['pred'])\n    sns.heatmap(tabla,annot=True, fmt='g')\n    return ds_resultados\n\ndef metricas(df):\n    print(\"Accuracy: \" + str(accuracy_score(df['test'], df['pred'], normalize=True)))\n    print(\"Recall: \" + str(recall_score(df['test'], df['pred'])))\n    print(\"F1: \" + str(f1_score(df['test'], df['pred'])))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:44.732366Z","iopub.execute_input":"2022-08-08T22:24:44.732871Z","iopub.status.idle":"2022-08-08T22:24:44.742860Z","shell.execute_reply.started":"2022-08-08T22:24:44.732826Z","shell.execute_reply":"2022-08-08T22:24:44.741723Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Tipado y reducción de memoria de los atributos:","metadata":{}},{"cell_type":"code","source":"le = LabelEncoder()\n\ndf_filtrado.target = df_filtrado.target.astype('int8')\n\nfor columna in df_filtrado.columns:\n    if df_filtrado[columna].dtype == object:\n        df_filtrado[columna] = le.fit_transform(df_filtrado[columna].astype(str))\n    elif df_filtrado[columna].dtype == 'float64':\n        df_filtrado[columna] = df_filtrado[columna].astype('float32')\n    else:\n        pass","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:44.744233Z","iopub.execute_input":"2022-08-08T22:24:44.744664Z","iopub.status.idle":"2022-08-08T22:24:45.142921Z","shell.execute_reply.started":"2022-08-08T22:24:44.744629Z","shell.execute_reply":"2022-08-08T22:24:45.141942Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n## Split de los datos para testing:","metadata":{}},{"cell_type":"code","source":"df_train_data_x = df_filtrado.drop(columns = ['target'], axis='columns', inplace=False)\ndf_train_data_y = df_filtrado['target'].copy()\nx_train, x_test, y_train, y_test = train_test_split(df_train_data_x, df_train_data_y, test_size=0.2, random_state=12, stratify=df_filtrado['target'].values)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:45.144212Z","iopub.execute_input":"2022-08-08T22:24:45.145135Z","iopub.status.idle":"2022-08-08T22:24:45.247303Z","shell.execute_reply.started":"2022-08-08T22:24:45.145098Z","shell.execute_reply":"2022-08-08T22:24:45.246112Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Random Forest:","metadata":{}},{"cell_type":"code","source":"n_estimators = 10\nrandom_state = 10\n\nparam_grid = { \n    'max_features': ['auto', 'sqrt', 'log2'],\n    'criterion' :['gini', 'entropy']\n}\n\narbol = RandomForestClassifier(random_state=random_state,\n                               class_weight = \"balanced\",\n                               n_estimators = n_estimators,\n                               max_depth = 10)\nrf_random = GridSearchCV(estimator = arbol, param_grid = param_grid, cv = 3, verbose=0, n_jobs = -1)\nrf_random.fit(x_train,y_train)\n\nrandom_forest_modelo = rf_random.best_estimator_\nds_resultados_rf = heatmap(random_forest_modelo)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:24:45.248622Z","iopub.execute_input":"2022-08-08T22:24:45.249032Z","iopub.status.idle":"2022-08-08T22:25:18.822909Z","shell.execute_reply.started":"2022-08-08T22:24:45.248998Z","shell.execute_reply":"2022-08-08T22:25:18.821540Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"metricas(ds_resultados_rf)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:25:18.825065Z","iopub.execute_input":"2022-08-08T22:25:18.826097Z","iopub.status.idle":"2022-08-08T22:25:18.859029Z","shell.execute_reply.started":"2022-08-08T22:25:18.826038Z","shell.execute_reply":"2022-08-08T22:25:18.857600Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## XGBoost:","metadata":{}},{"cell_type":"code","source":"xgb_modelo = xgb.XGBClassifier(\n    random_state=random_state, \n    n_estimators=n_estimators**2, \n    objective=\"binary:logistic\")\n\nxgb_modelo.fit(x_train, y_train, sample_weight=compute_sample_weight(\"balanced\", y_train))\n\nds_resultados_xgb = heatmap(xgb_modelo)\ny_pred_testing = xgb_modelo.predict_proba(x_test)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:25:18.861054Z","iopub.execute_input":"2022-08-08T22:25:18.861575Z","iopub.status.idle":"2022-08-08T22:26:07.641038Z","shell.execute_reply.started":"2022-08-08T22:25:18.861527Z","shell.execute_reply":"2022-08-08T22:26:07.639741Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"metricas(ds_resultados_xgb)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:26:07.642738Z","iopub.execute_input":"2022-08-08T22:26:07.643983Z","iopub.status.idle":"2022-08-08T22:26:07.686612Z","shell.execute_reply.started":"2022-08-08T22:26:07.643929Z","shell.execute_reply":"2022-08-08T22:26:07.685327Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"xgb_modelo_maxdepth = xgb.XGBClassifier(\n    random_state=random_state, \n    n_estimators=n_estimators, \n    objective=\"binary:logistic\",\n    max_depth = 10)\nxgb_modelo_maxdepth.fit(x_train, y_train, sample_weight=compute_sample_weight(\"balanced\", y_train))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:26:07.688340Z","iopub.execute_input":"2022-08-08T22:26:07.689111Z","iopub.status.idle":"2022-08-08T22:26:16.626382Z","shell.execute_reply.started":"2022-08-08T22:26:07.689056Z","shell.execute_reply":"2022-08-08T22:26:16.625146Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds_resultados_xgb = heatmap(xgb_modelo_maxdepth)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:26:16.627950Z","iopub.execute_input":"2022-08-08T22:26:16.628669Z","iopub.status.idle":"2022-08-08T22:26:16.910655Z","shell.execute_reply.started":"2022-08-08T22:26:16.628623Z","shell.execute_reply":"2022-08-08T22:26:16.909214Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"metricas(ds_resultados_xgb)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:26:16.912371Z","iopub.execute_input":"2022-08-08T22:26:16.912835Z","iopub.status.idle":"2022-08-08T22:26:16.945773Z","shell.execute_reply.started":"2022-08-08T22:26:16.912796Z","shell.execute_reply":"2022-08-08T22:26:16.944467Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Como podemos observar, el primer modelo resultó tener mejores métricas, así que nos quedamos con ese.","metadata":{}},{"cell_type":"code","source":"del xgb_modelo_maxdepth","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:26:16.947634Z","iopub.execute_input":"2022-08-08T22:26:16.948012Z","iopub.status.idle":"2022-08-08T22:26:16.953455Z","shell.execute_reply.started":"2022-08-08T22:26:16.947977Z","shell.execute_reply":"2022-08-08T22:26:16.952189Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## SVM:","metadata":{}},{"cell_type":"code","source":"stand_scaler = StandardScaler()\nx_train_norm = stand_scaler.fit_transform(x_train)\nx_test_norm = stand_scaler.fit_transform(x_test)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:26:16.955130Z","iopub.execute_input":"2022-08-08T22:26:16.955539Z","iopub.status.idle":"2022-08-08T22:26:17.058412Z","shell.execute_reply.started":"2022-08-08T22:26:16.955503Z","shell.execute_reply":"2022-08-08T22:26:17.056981Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"param_grid = { \n    'C': [6,7,8],\n}\nclf = LinearSVC(class_weight='balanced', dual=False, penalty = \"l1\", random_state = random_state)\nsvc_random = GridSearchCV(estimator = clf, param_grid = param_grid, cv=2, verbose=0, n_jobs = -1)\nsvc_random.fit(x_train_norm,y_train)\nsvc_modelo = svc_random.best_estimator_","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:26:17.060334Z","iopub.execute_input":"2022-08-08T22:26:17.061134Z","iopub.status.idle":"2022-08-08T22:28:13.973189Z","shell.execute_reply.started":"2022-08-08T22:26:17.061082Z","shell.execute_reply":"2022-08-08T22:28:13.971746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_pred_norm = svc_modelo.predict(x_test_norm)\ncm = confusion_matrix(y_test,y_pred_norm)\nsns.heatmap(cm, annot=True, fmt='g')\nplt.xlabel('Predicted')\nplt.ylabel('True')","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:28:13.974682Z","iopub.execute_input":"2022-08-08T22:28:13.975037Z","iopub.status.idle":"2022-08-08T22:28:14.257438Z","shell.execute_reply.started":"2022-08-08T22:28:13.975004Z","shell.execute_reply":"2022-08-08T22:28:14.256052Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds_resultados_svm = pd.DataFrame(zip(y_test,y_pred_norm),columns=['test','pred'])\nmetricas(ds_resultados_svm)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:28:14.259184Z","iopub.execute_input":"2022-08-08T22:28:14.259683Z","iopub.status.idle":"2022-08-08T22:28:14.316687Z","shell.execute_reply.started":"2022-08-08T22:28:14.259631Z","shell.execute_reply":"2022-08-08T22:28:14.315212Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Ensamble Voting:","metadata":{}},{"cell_type":"code","source":"ensamble_voting_modelo = VotingClassifier(estimators = [('rf', random_forest_modelo), ('xgb', xgb_modelo), ('svm', svc_modelo)], voting = 'hard')\nensamble_voting_modelo.fit(x_train, y_train)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:28:14.318373Z","iopub.execute_input":"2022-08-08T22:28:14.318784Z","iopub.status.idle":"2022-08-08T22:29:52.787886Z","shell.execute_reply.started":"2022-08-08T22:28:14.318748Z","shell.execute_reply":"2022-08-08T22:29:52.786505Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds_resultados_ensamble_voting = heatmap(ensamble_voting_modelo)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:29:52.789507Z","iopub.execute_input":"2022-08-08T22:29:52.789897Z","iopub.status.idle":"2022-08-08T22:29:53.335485Z","shell.execute_reply.started":"2022-08-08T22:29:52.789865Z","shell.execute_reply":"2022-08-08T22:29:53.334175Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"metricas(ds_resultados_ensamble_voting)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:29:53.337457Z","iopub.execute_input":"2022-08-08T22:29:53.337990Z","iopub.status.idle":"2022-08-08T22:29:53.372752Z","shell.execute_reply.started":"2022-08-08T22:29:53.337944Z","shell.execute_reply":"2022-08-08T22:29:53.371581Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Red Neuronal","metadata":{}},{"cell_type":"markdown","source":"Para poder reproducir los resultados de nuestra red neuronal en todas las ejecuciones, primero tenemos que setear una seed, para esta, utilizamos la misma seed que usamos para el sampleo de los datos:","metadata":{}},{"cell_type":"code","source":"## Reproducibilidad del seteo: ##\n## https://deeplizard.com/learn/video/HcW0DeWRggs ##\nimport os\nimport random as rn\nimport tensorflow as tf\nos.environ['PYTHONHASHSEED'] = '0'\nos.environ['CUDA_VISIBLE_DEVICES'] = ''\nnp.random.seed(SEED_GRUPO)\nrn.seed(SEED_GRUPO)\ntf.random.set_seed(SEED_GRUPO)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:29:53.374244Z","iopub.execute_input":"2022-08-08T22:29:53.374616Z","iopub.status.idle":"2022-08-08T22:29:53.382225Z","shell.execute_reply.started":"2022-08-08T22:29:53.374582Z","shell.execute_reply":"2022-08-08T22:29:53.381049Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Una vez hecho esto, podemos pasar a la creacion de nuestra red:","metadata":{}},{"cell_type":"code","source":"n_columnas = len(x_test.columns)\nclasses=int(df_filtrado.loc[:,'target'].max()+1)\n\nred_modelo = Sequential([\n    # input_shape solo en la primer capa\n    Dense(n_columnas, input_shape = (n_columnas,), activation='relu'),\n    Dense(n_columnas, activation='relu'),\n    Dense(n_columnas*2, activation='relu'),\n    Dense(1, activation='sigmoid')\n    ])\n\nplot_model(red_modelo)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:29:53.383325Z","iopub.execute_input":"2022-08-08T22:29:53.383782Z","iopub.status.idle":"2022-08-08T22:29:54.801548Z","shell.execute_reply.started":"2022-08-08T22:29:53.383745Z","shell.execute_reply":"2022-08-08T22:29:54.800008Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"red_modelo.compile(loss='binary_crossentropy', optimizer=\"Adam\", metrics=['binary_accuracy'])\nhistory = red_modelo.fit(x_train,y_train,epochs= 32, batch_size= 256,verbose=0)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:29:54.803262Z","iopub.execute_input":"2022-08-08T22:29:54.804493Z","iopub.status.idle":"2022-08-08T22:30:32.561083Z","shell.execute_reply.started":"2022-08-08T22:29:54.804441Z","shell.execute_reply":"2022-08-08T22:30:32.559992Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_pred = red_modelo.predict(x_test)\na = pd.DataFrame(data = y_pred)\nb = a[0].values\nc = []\nfor valor in b:\n    if valor > 0.5:\n        c.append(1)\n    else:\n        c.append(0)\nd = pd.DataFrame(data = c)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:32.563002Z","iopub.execute_input":"2022-08-08T22:30:32.563753Z","iopub.status.idle":"2022-08-08T22:30:33.559579Z","shell.execute_reply.started":"2022-08-08T22:30:32.563712Z","shell.execute_reply":"2022-08-08T22:30:33.558288Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tabla = confusion_matrix(y_test, d)\nsns.heatmap(tabla,annot=True, fmt='g')\nplt.xlabel('Predicted')\nplt.ylabel('True')","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:33.561145Z","iopub.execute_input":"2022-08-08T22:30:33.561574Z","iopub.status.idle":"2022-08-08T22:30:33.823990Z","shell.execute_reply.started":"2022-08-08T22:30:33.561534Z","shell.execute_reply":"2022-08-08T22:30:33.822637Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"  Accuracy : %.5f\" % accuracy_score(y_test, d))\nprint(\"     Recall: %.5f\" % recall_score(y_test, d))\nprint(\"  f-measure: %.5f\" % f1_score(y_test, d))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:33.827935Z","iopub.execute_input":"2022-08-08T22:30:33.828687Z","iopub.status.idle":"2022-08-08T22:30:33.861123Z","shell.execute_reply.started":"2022-08-08T22:30:33.828625Z","shell.execute_reply":"2022-08-08T22:30:33.859666Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Ensamble Cascading:","metadata":{}},{"cell_type":"markdown","source":"Ya que no existe libería que nos permita realizar este ensamble, tenemos que generar nuestras propias funciones:","metadata":{}},{"cell_type":"code","source":"class Cascading:\n    def __init__(self, dict_modelos, umbral):\n        self.cantidad_modelos = len(dict_modelos)\n        self.modelo_final = list(dict_modelos)[-1]\n        self.modelos = dict_modelos\n        self.umbral = umbral\n\n        \n    def predict(self, x_test, y_test, total = False):\n        # Genero variable salida y remanentes del predict.\n        out = pd.DataFrame(columns = [\"target\", \"predict\"])\n        remanentes_x = x_test.copy()\n        remanentes_y = y_test.copy()\n        \n        # Itero todos los modelos.\n        for modelo in self.modelos:\n            if modelo == self.modelo_final and total == True:\n                # Si llega al ultimo modelo del ensamble, obtiene clasificaciones exactas para los remanentes.\n                modelo_pred = self.modelos[modelo][\"fun_clas\"](self.modelos[modelo][\"modelo\"], remanentes_x)\n                prediccion = pd.DataFrame(data = remanentes_y)\n                prediccion[\"predict\"] = modelo_pred\n                # Se añaden estas a el dataset de predecidas.\n                out = pd.concat([out, prediccion], axis = 0)\n                return out, None\n            else:\n                # Se obtienen los % de pertenencia a clase 1.\n                modelo_pred = self.modelos[modelo][\"fun_prob\"](self.modelos[modelo][\"modelo\"], remanentes_x)\n                prediccion = pd.DataFrame(data = remanentes_y)\n                prediccion[\"predict\"] = modelo_pred\n                # Los que superan el umbral, quedan definidos como 1\n                definidas = prediccion[prediccion[\"predict\"] > self.umbral]\n                #definidas[\"predict\"] = 1\n                definidas.loc[:, \"predict\"] = 1\n                # Se añaden estas a el dataset de predecidas y se dropean de la tabla de filas a probar\n                out = pd.concat([out, definidas], axis = 0)\n                remanentes_x.drop(definidas.index, inplace = True)\n                remanentes_y.drop(definidas.index, inplace = True)\n                \n        out[\"target\"] = out[\"target\"].astype(int)\n        out[\"predict\"] = out[\"predict\"].astype(int)\n        return out, remanentes_x","metadata":{"_kg_hide-input":false,"execution":{"iopub.status.busy":"2022-08-08T22:30:33.863126Z","iopub.execute_input":"2022-08-08T22:30:33.864850Z","iopub.status.idle":"2022-08-08T22:30:33.878639Z","shell.execute_reply.started":"2022-08-08T22:30:33.864794Z","shell.execute_reply":"2022-08-08T22:30:33.877492Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Documentacion de Cascading():\n\n* Cascading(dict_modelos: dict, umbral: float): Esta clase genera un ensable Cascading.\n   - dict_modelos: Diccionario de diccionarios que contiene los modelos que se van a utilizar (con orden sensible), este diccionario debe tener la siguiente estructura:  \n  {\n    \"Nombre\": {\n        \"modelo\" : #Modelo entrenado#,\n        \"fun_prob\" : #Funcion de probabilidades#,\n        \"fun_clas\" : #Funcion de clasificacion#\n    }\n  }\n\n  @param Funcion de probabilidades: Funcion que recibe (modelo, x_test) y devuelve un np.array con las probabilides de pertenencia a clase 1.\n\n  @param Funcion de clasificacion: Funcion que recibe (modelo, x_test) y devuelve un np.array con una clasificacion (clase 0 / clase 1).\n\n  - umbral: valor mínimo de probabilidad con el que el ensamble puede asegurar que, según este, puede estar clasificado correctamente como tipo 1\n\n\n* Cascading.predict(self: Cascading, x_test: DataFrame, y_test: DataFrame, total: bool) -> tuple:\n\n  @param x_test: Datos a ser evaluados por los modelos.\n  \n  @param y_test: Datos desde los cuales se verifica el si el modelo predijo bien.\n  \n  @param total: por default True, clasifica con no_class si está en True; si es False, clasifica todo.\n  \n  @return (out, no_class): out es el DataFrame de los datos clasificados; no_class es el DataFrame de los datos que no pudo clasificar. Si total está en True, no_class es None.","metadata":{}},{"cell_type":"markdown","source":"Una vez creado nuestro modelo de cascading, procedemos a realizar las predicciones con los siguientes ensambles creados previamente:\n\n1. Red Neuronal.\n2. Random Forest.\n3. XGBoost.","metadata":{}},{"cell_type":"code","source":"def fun_prob_red(modelo, x_test):\n    return modelo.predict(x_test)\n\ndef fun_clas_rf_xgb(modelo, x_test):\n    out = []\n    predicciones = modelo.predict(x_test)\n    for prediccion in predicciones:\n        out.append([prediccion])\n    return np.array(out)\n\ndef fun_prob_rf_xgb(modelo, x_test):\n    probabilidades = modelo.predict_proba(x_test)\n    probabilidades = np.delete(probabilidades,0,axis = 1)\n    return probabilidades\n\ndict_modelos = {\n    \"Red\":{\n        \"modelo\":red_modelo,\n        \"fun_prob\":fun_prob_red,\n        \"fun_clas\":None\n    },\n    \"Random_Forest\":{\n        \"modelo\":random_forest_modelo,\n        \"fun_prob\":fun_prob_rf_xgb,\n        \"fun_clas\":fun_clas_rf_xgb\n    },\n    \"XGBoost\":{\n        \"modelo\":xgb_modelo,\n        \"fun_prob\":fun_prob_rf_xgb,\n        \"fun_clas\":fun_clas_rf_xgb\n    }\n}","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:33.880474Z","iopub.execute_input":"2022-08-08T22:30:33.881913Z","iopub.status.idle":"2022-08-08T22:30:33.897382Z","shell.execute_reply.started":"2022-08-08T22:30:33.881857Z","shell.execute_reply":"2022-08-08T22:30:33.896299Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Prueba con umbral de 0.99:","metadata":{}},{"cell_type":"code","source":"modelo_cascading_1 = Cascading(dict_modelos, 0.99)\nresultados_cascading = modelo_cascading_1.predict(x_test, y_test, total = False)\nout, no_clas = resultados_cascading","metadata":{"_kg_hide-input":false,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-08-08T22:30:33.899498Z","iopub.execute_input":"2022-08-08T22:30:33.900689Z","iopub.status.idle":"2022-08-08T22:30:34.933705Z","shell.execute_reply.started":"2022-08-08T22:30:33.900634Z","shell.execute_reply":"2022-08-08T22:30:34.932371Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"El ensamble fue capaz de predecir los siguientes indices con una alta exactitud.","metadata":{}},{"cell_type":"code","source":"out.sort_values(by = \"predict\", ascending = True)","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:34.935593Z","iopub.execute_input":"2022-08-08T22:30:34.936384Z","iopub.status.idle":"2022-08-08T22:30:34.949862Z","shell.execute_reply.started":"2022-08-08T22:30:34.936334Z","shell.execute_reply":"2022-08-08T22:30:34.948917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Y esta dejando los siguientes para realizar una clasificacion manual:","metadata":{}},{"cell_type":"code","source":"no_clas","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:34.951004Z","iopub.execute_input":"2022-08-08T22:30:34.951840Z","iopub.status.idle":"2022-08-08T22:30:34.992058Z","shell.execute_reply.started":"2022-08-08T22:30:34.951803Z","shell.execute_reply":"2022-08-08T22:30:34.990905Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Para los predecidos con alta exactitud, procedemos a ver las metricas para verificar que tan buena fue la clasificacion.","metadata":{}},{"cell_type":"code","source":"tabla = confusion_matrix(out['target'], out['predict'])\nsns.heatmap(tabla,annot=True, fmt='g')\nplt.xlabel('Predicted')\nplt.ylabel('True')","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:34.993518Z","iopub.execute_input":"2022-08-08T22:30:34.993975Z","iopub.status.idle":"2022-08-08T22:30:35.260194Z","shell.execute_reply.started":"2022-08-08T22:30:34.993941Z","shell.execute_reply":"2022-08-08T22:30:35.258119Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Accuracy: \" + str(accuracy_score(out['target'], out['predict'], normalize=True)))\nprint(\"Recall: \" + str(recall_score(out['target'], out['predict'])))\nprint(\"F1: \" + str(f1_score(out['target'], out['predict'])))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:35.262608Z","iopub.execute_input":"2022-08-08T22:30:35.263615Z","iopub.status.idle":"2022-08-08T22:30:35.277863Z","shell.execute_reply.started":"2022-08-08T22:30:35.263554Z","shell.execute_reply":"2022-08-08T22:30:35.276494Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Prueba con umbral de 0.95:","metadata":{}},{"cell_type":"code","source":"modelo_cascading_2 = Cascading(dict_modelos, 0.95)\nresultados_cascading2 = modelo_cascading_2.predict(x_test, y_test, total = False)\nout2, no_clas2 = resultados_cascading2","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:35.279887Z","iopub.execute_input":"2022-08-08T22:30:35.280300Z","iopub.status.idle":"2022-08-08T22:30:36.348769Z","shell.execute_reply.started":"2022-08-08T22:30:35.280264Z","shell.execute_reply":"2022-08-08T22:30:36.346869Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"El ensamble fue capaz de predecir los siguientes indices con una alta exactitud.","metadata":{}},{"cell_type":"code","source":"out2","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:36.350196Z","iopub.execute_input":"2022-08-08T22:30:36.350566Z","iopub.status.idle":"2022-08-08T22:30:36.365075Z","shell.execute_reply.started":"2022-08-08T22:30:36.350532Z","shell.execute_reply":"2022-08-08T22:30:36.363854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Y esta dejando los siguientes para realizar una clasificacion manual:","metadata":{}},{"cell_type":"code","source":"no_clas2","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:36.366665Z","iopub.execute_input":"2022-08-08T22:30:36.367501Z","iopub.status.idle":"2022-08-08T22:30:36.407164Z","shell.execute_reply.started":"2022-08-08T22:30:36.367453Z","shell.execute_reply":"2022-08-08T22:30:36.405794Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Para los predecidos con alta exactitud, procedemos a ver las metricas para verificar que tan buena fue la clasificacion.","metadata":{}},{"cell_type":"code","source":"tabla = confusion_matrix(out2['target'], out2['predict'])\nsns.heatmap(tabla,annot=True, fmt='g')\nplt.xlabel('Predicted')\nplt.ylabel('True')","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:30:36.409614Z","iopub.execute_input":"2022-08-08T22:30:36.410796Z","iopub.status.idle":"2022-08-08T22:30:36.668402Z","shell.execute_reply.started":"2022-08-08T22:30:36.410743Z","shell.execute_reply":"2022-08-08T22:30:36.667193Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Accuracy: \" + str(accuracy_score(out2['target'], out2['predict'], normalize=True)))\nprint(\"Recall: \" + str(recall_score(out2['target'], out2['predict'])))\nprint(\"F1: \" + str(f1_score(out2['target'], out2['predict'])))","metadata":{"execution":{"iopub.status.busy":"2022-08-08T22:45:57.757015Z","iopub.execute_input":"2022-08-08T22:45:57.758448Z","iopub.status.idle":"2022-08-08T22:45:57.779642Z","shell.execute_reply.started":"2022-08-08T22:45:57.758368Z","shell.execute_reply":"2022-08-08T22:45:57.778547Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Conclusiones:\n\nObservando los resultados obtenidos a partir de los distintos modelos, se puede decir que, si bien no se puede predecir con total certeza si un cliente dejará de pagar, sí se puede hacer con una alta probabilidad.\n\nTambien es muy dificil definir en que casos es posible realizar esta prediccion, ya que si bien los modelos hacen una buena prediccion, realmente no sabemos cuales son los valores exactos que provocan las decisiones de clasificacion. Lo que si podemos asegurar, luego de ver el dataframe filtrado resultante, es que las variables de Delincuencia y Balance son las más correlacionadas al target. Es por ello que podemos afirmar que, tomando en cuenta estos valores, podriamos hacer una prediccion bastante acorde.\n\nEn línea con lo expuesto anteriormente, al estar anonimizados los datos, no se sabe cuáles son los aportados. Creemos que, de no estarlo, variables como la categoría del clientes, su antigüedad o la deuda a saldar podrían ser de utilidad si se tuvieran de manera explícita.\n\nSi el sistema entrase en producción y existiera intervención humana esto podria generar pesos dependiendo en que areas se realice esta intervencion:\n\n* **Si la intervencion se realiza en el proceso de carga/generacion de datos**:    \n\nEsto podría causar outliers, inconsistencias en los datos, o valores nulos generados por el error humano que cometemos al realizar cargas manuales. Esto haría que los modelos tengan una probabilidad de acierto más baja.\n\n* **Si la intervencion se realiza en modelos de clasificacion de tipo cascading**:\n\nComo demostramos en el ultimo ensamble, los modelos cascading, requieren de una verificacion humana para los registros que el modelo no puede clasificar con una certeza mayor a un umbral establecido. Esto conllevaría principalmente a una subida en los tiempos requeridos para las clasificaciones, ya que se revisarian manualmente una cantidad n (Dependiendo de que tan bueno sea el ensamble) de entradas. Con respecto a la certeza de esa clasificacion, estaría sujeta a la aptitud del personal que revisara los datos.\n\nCon respecto al dataset, si este quisiera ser actualizado a lo largo del tiempo, se deberia tener en cuenta tanto como el agregado como la eliminacion de datos. Si estos son analizados de forma exhaustiva, se puede identificar cuales de estos son realmente importantes y deben ser conservados e introducir los nuevos de forma periodica. Esto evitaría la gran acumulacion de datos de entrenamiento, amortiguando el overfitting.","metadata":{}}]}