{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Training Set usando ventanas de tiempo"},{"metadata":{},"cell_type":"markdown","source":"La generación de este training set parte de la hipótesis de que la cantidad de data points no es suficiente para realizar una buena predicción. Si bien hay una cantidad significativa de eventos, gran parte de los mismos son prácticamente irrelevantes para la tarea de predicción.\nOtra hipótesis importante es que los eventos más relevantes para determinar si un usuario va a convertir son los más cercanos al intervalo en el que se espera predecir."},{"metadata":{},"cell_type":"markdown","source":"La idea principal es modelar el comportamiento de los usuarios en distintas ventanas de tiempo. Por ejemplo, si un usuario tuvo eventos en todos los meses de Enero a Septiembre, y se define una ventana de tres meses, entonces el training set va a tener 9/3 = 3 filas que corresponden al mismo."},{"metadata":{},"cell_type":"markdown","source":"## Features utilizadas"},{"metadata":{},"cell_type":"markdown","source":"Se definió el siguiente conjunto de features como relevantes para el modelo, a partir de una prueba sencilla de feature selection. Esta decisión es para optimizar el uso de memoria y tiempo de entrenamiento."},{"metadata":{"trusted":true},"cell_type":"code","source":"features = ['PAGE_41', 'PAGE_110', 'PAGE_109', 'PAGE_108',\n       'CONTENT_CATEGORY_16', 'CONTENT_CATEGORY_BOTTOM_16', 'PAGE_321',\n       'PAGE_285', 'SITE_ID_4', 'PAGE_296', 'PAGE_360',\n       'CONTENT_CATEGORY_34', 'CONTENT_CATEGORY_BOTTOM_34',\n       'CONTENT_CATEGORY_TOP_1', 'CONTENT_CATEGORY_BOTTOM_1',\n       'CONTENT_CATEGORY_1', 'SITE_ID_1', 'PAGE_286', 'PAGE_111',\n       'PAGE_1259', 'PAGE_287', 'PAGE_39', 'PAGE_57', 'PAGE_85',\n       'PAGE_203', 'PAGE_455', 'PAGE_345', 'PAGE_153', 'PAGE_1',\n       'PAGE_123', 'SITE_ID_3', 'PAGE_202', 'PAGE_611', 'PAGE_40',\n       'PAGE_612', 'PAGE_89', 'PAGE_1227', 'PAGE_190', 'PAGE_660',\n       'PAGE_457', 'PAGE_456', 'PAGE_11', 'PAGE_516',\n       'CONTENT_CATEGORY_18', 'CONTENT_CATEGORY_BOTTOM_18', 'PAGE_426',\n       'PAGE_2', 'PAGE_4', 'PAGE_1248', 'PAGE_1247', 'PAGE_573',\n       'PAGE_901', 'PAGE_902', 'PAGE_1254', 'CONTENT_CATEGORY_TOP_3',\n       'PAGE_138', 'CONTENT_CATEGORY_20', 'CONTENT_CATEGORY_BOTTOM_20',\n       'PAGE_289', 'PAGE_9', 'PAGE_163', 'PAGE_121', 'PAGE_1249',\n       'CONTENT_CATEGORY_TOP_2', 'PAGE_20', 'PAGE_212', 'PAGE_197',\n       'PAGE_135', 'CONTENT_CATEGORY_BOTTOM_2', 'CONTENT_CATEGORY_2',\n       'PAGE_254', 'PAGE_690', 'PAGE_211', 'PAGE_903', 'PAGE_164',\n       'PAGE_346', 'PAGE_307', 'PAGE_1251', 'PAGE_165', 'PAGE_195',\n       'PAGE_210', 'PAGE_1250', 'PAGE_15', 'PAGE_243', 'PAGE_50',\n       'PAGE_49', 'PAGE_26', 'PAGE_479', 'PAGE_154', 'PAGE_69', 'PAGE_10',\n       'PAGE_284', 'PAGE_831', 'CONTENT_CATEGORY_BOTTOM_19',\n       'CONTENT_CATEGORY_19', 'PAGE_25', 'PAGE_604', 'PAGE_102',\n       'CONTENT_CATEGORY_31', 'CONTENT_CATEGORY_BOTTOM_31']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom matplotlib import pyplot as plt\nfrom tqdm import tqdm_notebook","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Lectura de los datos"},{"metadata":{"trusted":true},"cell_type":"code","source":"original = pd.read_csv(\"../input/banco-galicia-dataton-2019/pageviews/pageviews.csv\",parse_dates=[\"FEC_EVENT\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"complemento = pd.read_csv(\"../input/banco-galicia-dataton-2019/pageviews_complemento/pageviews_complemento.csv\", parse_dates=[\"FEC_EVENT\"])[original.columns]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"data = pd.concat([original,complemento])\ndata.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"conversiones = pd.read_csv(\"../input/banco-galicia-dataton-2019/conversiones/conversiones.csv\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Definición de funciones"},{"metadata":{},"cell_type":"markdown","source":"month_range devuelve una lista de listas de meses en un intervalo determinado y para un tamaño de ventana."},{"metadata":{"trusted":true},"cell_type":"code","source":"def month_range(window,start,end):\n    out = []\n    for i in range(window+(start-1),end+1):\n        out.append([i-j for j in range(window)])\n    return out","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"month_range(2,4,6)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"get_col_distribution es la función de Benchmark que obtiene la cuenta de eventos para cada usuario, se descartó ON_SITE_TERM por el feature selection previo"},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_col_distribution(df):\n    out = []\n    for c in tqdm_notebook(df.drop([\"USER_ID\", \"FEC_EVENT\", \"ON_SITE_SEARCH_TERM\"], axis=1).columns,leave=False):\n        temp = pd.crosstab(df.USER_ID, df[c])\n        temp.columns = [c + \"_\" + str(v) for v in temp.columns]\n#         temp = temp.apply(lambda x: x / x.sum(), axis=1)\n        out.append(temp)\n    return pd.concat(out, axis=1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"set_conversions agrega una variable con la cantidad de conversiones que tuvo cada usuario en los meses indicados"},{"metadata":{"trusted":true},"cell_type":"code","source":"def set_conversions(df,conversions,months):\n    user_conversions = conversions[conversions.mes.isin(months)].groupby(\"USER_ID\").size()\n    user_conversions.name = \"conversions\"\n    df = df.join(user_conversions, how=\"left\")\n    df[\"conversions\"] = df[\"conversions\"].fillna(0)\n    return df","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"get_y devuelve la variable que se intenta predecir: si el usuario convirtió en los siguientes tres meses. Aclaración: si el mes máximo del intervalo es 12, entonces \"y\" van a ser todos 0."},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_y(df,conversiones,months):\n    y = pd.Series(0, index=df.index)\n    months = [max(months) + i for i in range(1,4)]\n    idx = set(conversiones[conversiones.mes.isin(months)].USER_ID.unique()).intersection(set(df.index))\n    y.loc[list(idx)] = 1    \n    return y","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"normalize_df divide todos los features por la cantidad de conversiones en el intervalo de tiempo."},{"metadata":{"trusted":true},"cell_type":"code","source":"def normalize_df(df):\n    c = df.conversions.sum()\n    for col in df.drop(columns=\"conversions\"):\n        df[col] /= c\n    return df","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"get_dataset devuelve el training_set entero para los meses indicados"},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_dataset(data,conversiones,months):\n    X = data[data.FEC_EVENT.dt.month.isin(months)]\n    X = get_col_distribution(X)\n    X = set_conversions(X,conversiones,months)\n    X = normalize_df(X)\n    y = get_y(X,conversiones,months)\n    return X,y","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"unify_columns fue definida porque no en todas las ventanas hay eventos asocidados a todas las variables, entonces se utiliza la intersección."},{"metadata":{"trusted":true},"cell_type":"code","source":"def unify_columns(dfs):\n    l = [df.columns for df in dfs]\n    new_cols = set(l[0]).intersection(*l).intersection(features + [\"conversions\"])\n    return [df[sorted(new_cols)] for df in dfs]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Generación del training set"},{"metadata":{},"cell_type":"markdown","source":"se utilizan ventanas de 6,7 y 8 meses porque fueron las que mejores resultados dieron para las distintas combinaciones posibles (no se probaron todas igualmente)"},{"metadata":{"trusted":true},"cell_type":"code","source":"Xs = []\nys = []\nfor r in tqdm_notebook(month_range(6,1,9) + month_range(7,1,9) + month_range(8,1,9)):\n    X, y = get_dataset(data, conversiones, r)\n    Xs.append(X)\n    ys.append(y)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"Xs = unify_columns(Xs)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X = pd.concat(Xs)\ny = pd.concat(ys)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(X.columns)\nprint(len(X.columns))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Una vez más, el intervalo elegido para el set de predicción es de 7 meses, por evidencia empírica. Aclaración: no todos los usuarios tienen eventos en los últimos 7 meses, por lo que hay que decidir que hacer con los usuarios restantes. La opción que mejores resultados dió fue asignarles probabilidad 0. "},{"metadata":{"trusted":true},"cell_type":"code","source":"X_test, y_test = get_dataset(data,conversiones,[6,7,8,9,10,11,12])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X, X_test = unify_columns([X,X_test])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Finalmente, se puede utilizar cualquier modelo para realizar la predicción. En mi caso, utilizando RandomForest obtuve un score final de 0.84933."},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.7"}},"nbformat":4,"nbformat_minor":1}