{"cells":[{"metadata":{},"cell_type":"markdown","source":"* Notebook reads in sample of data due to memory limits","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport pandas as pd\n\nfrom random import randint\nfrom catboost import CatBoostClassifier\nfrom catboost import Pool, cv\nfrom catboost import CatBoost, Pool, MetricVisualizer\n\nfrom pprint import pprint\nfrom sklearn.model_selection import train_test_split, TimeSeriesSplit, GroupKFold, GroupShuffleSplit\nfrom pprint import pprint\nimport shap\nfrom catboost import cv\nshap.initjs()\nimport zipfile\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.model_selection import cross_val_score, TimeSeriesSplit, cross_validate\nfrom sklearn.metrics import precision_recall_fscore_support, roc_auc_score, classification_report\n\nfrom copy import deepcopy\n\n\npd.options.display.float_format = '{:,.3f}'.format\n\nimport os","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!unzip /kaggle/input/expedia-personalized-sort/data.zip \n### ZipFile can't read this proprietary format \nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_target(row):\n    \"\"\"\n    0=not clicked at all, 1=clicked but not booked, 5=booked\n    \"\"\"\n    if row.booking_bool>0:\n        return 1\n    if row.click_bool>0 :\n        return 0.2\n    return 0\n\n\ndef featurize_df(df:pd.DataFrame) ->pd.DataFrame:\n    \"\"\"\n    Extract more features\n    \"\"\"\n    df[\"weekday\"] = df[\"date_time\"].dt.weekday\n    df[\"week_of_year\"] = df[\"date_time\"].dt.week\n\n    df[\"hour\"] = df[\"date_time\"].dt.hour\n    df[\"minute\"] = df[\"date_time\"].dt.minute\n    ## total time elapsed - allows model to learn continous trend over time to a degree\n    df[\"time_epoch\"] = df[\"date_time\"].astype('int64')//1e9\n    ## if we were looking at fraud: df[\"seconds\"] = df.timestamp.dt.second\n    df[\"early_night\"] = ((df[\"hour\"]>19) | (df[\"hour\"]<3)) # no added value from feature\n    \n    df[\"nans_count\"] = df.isna().sum(axis=1)\n    \n    ## we won't make any time series features for now\n    ## We could add time series features per property/hotel. We'd need to check for unaries, and to add a shift/offset dependant on forecast horizon\n\n    return df","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# data = zipfile.ZipFile(\"/kaggle/input/expedia-personalized-sort/data.zip\") #  zipped file.\n# df = pd.read_csv(data.open('train.csv'))\ndf = pd.read_csv('train.csv',nrows=30123456) # memory error when reading all data in\nprint(df.shape)\n\n# df = df.dropna(how=\"all\")\n# float_cols = df.columns[df.dtypes.eq('float')]# float_cols = df.columns.drop(['date_time'])\n# for c in float_cols:\n#     df[c] = pd.to_numeric(df[c], errors=\"ignore\",downcast=\"integer\")  # parse columns back to integer\n\nprint(df.shape)\ndf[\"date_time\"] = pd.to_datetime(df[\"date_time\"],infer_datetime_format=True)\ndf[\"target\"] = df.apply(get_target,axis=1)\n# featurization must be after leaky cols are dropped, otherwise the nan feature will bea leak!\ndisplay(df.describe())\ndisplay(df.nunique())\n\ndisplay(df[\"date_time\"].describe())\ndisplay(df)\n\ndf","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# df_test = pd.read_csv(data.open('test.csv'))\ndf_test = pd.read_csv('test.csv')\nprint(df_test.shape)\ncols = df_test.columns.drop(['date_time'])\nfloat_cols = df_test.columns[df_test.dtypes.eq('float')]# float_cols = df.columns.drop(['date_time'])\nfor c in float_cols:\n    df_test[c] = pd.to_numeric(df_test[c], errors=\"ignore\",downcast=\"integer\") \ndf_test[\"date_time\"] = pd.to_datetime(df_test[\"date_time\"],infer_datetime_format=True)\n\ndf_test","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### EDA & drop bad cols","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"df.drop_duplicates(['click_bool','booking_bool','random_bool'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"drop_cols = []\n\n## we see many columns are unary - drop them, barring feature engineering\ndrop_unary_cols = [c for c\n             in list(df)\n             if df[c].nunique(dropna=False) <= 1]\ntarget_cols = [\"gross_bookings_usd\",\"click_bool\",\"booking_bool\"] # leaky column, and original target columns\ndrop_cols.extend(drop_unary_cols)\ndrop_cols.extend(target_cols) \n\n### we'll need to remove datetime from the model, but it may be useful for train/test split before that\n# drop_cols.append(\"date_time\")\n\ndf = df.drop(columns=drop_cols,errors=\"ignore\")\ndf_test = df_test.drop(columns=drop_cols,errors=\"ignore\")\nprint(df.shape)\ndf","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Add features","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"df = featurize_df(df)\ndf_test = featurize_df(df_test)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Naive feature importance - by rank / interest\n* We could also do by target class (booked vs clicked vs 0), +- p-values","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"## sort by high rank, regardless of booked or not (for easy comp)\ndf.drop(['comp3_rate',\n       'comp3_inv', 'comp3_rate_percent_diff', 'comp4_inv', 'comp5_rate',\n       'comp5_inv', 'comp5_rate_percent_diff', 'comp8_rate', 'comp8_inv',\n       'comp8_rate_percent_diff'],axis=1).groupby(df[\"target\"]>0).mean()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### train /test (\"eval\") split\nSplit by time, and groupwise (by queries). Depends what makes more sense.. Given very small time period covered, learning a model on a time split may cause us to lose out on features relevant to the testing data in this cases\n\nWe can also just use catboost built in Cross Validation (which supports groupwise splits), since I'm not doing any real hyperparameter tuning at this stage.\n\nWe'll split by the lasy ~10% of queries, i.e by group and time for evaluation.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"cutoff_id = df[\"srch_id\"].quantile(0.94) # 90/10 split\nX_train = df.loc[df.srch_id< cutoff_id].drop([\"target\"],axis=1)\nX_eval = df.loc[df.srch_id>= cutoff_id].drop([\"target\"],axis=1)\ny_train = df.loc[df.srch_id< cutoff_id][\"target\"]\ny_eval = df.loc[df.srch_id>= cutoff_id][\"target\"]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"mean relevancy train\",round(y_train.mean(),4))\nprint(\"mean relevancy eval\",round(y_eval.mean(),4))\nprint(y_eval.value_counts()) # check we have all 3 \"labels\" in subset","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df[\"target\"].value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Train (Ranking) models\n* explain top features using SHAP\n* Build a classification/AUC model then a ranking model (best fit for data)","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"categorical_cols = ['prop_id',\"srch_destination_id\", \"weekday\"] # ,\"week_of_year\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df.tail()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"## check for feature/column leaks\nset(X_train.columns).symmetric_difference(set(df_test.columns))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_pool = Pool(data=X_train,\n                  label = y_train,\n                  cat_features=categorical_cols,\n                  group_id=X_train[\"srch_id\"]\n                 )\n\neval_pool = Pool(data=X_eval,\n                  label = y_eval,\n                  cat_features=categorical_cols,\n                  group_id=X_eval[\"srch_id\"]\n                 )\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"## default_parameters  = {\n    'iterations': 2000,\n    'custom_metric': ['NDCG', \"AUC:type=Ranking\"], # , 'AverageGain:top=3'# 'QueryRMSE', \"YetiLoss\" (use with hints)\n    'verbose': False,\n    'random_seed': 42,\n#     \"task_type\":\"GPU\",\n    \"has_time\":True,\n    \"metric_period\":4,\n    \"save_snapshot\":False,\n    \"use_best_model\":True, # requires eval set to be set\n} \n\nparameters = {}\n\ndef fit_model(loss_function, additional_params=None, train_pool=train_pool, test_pool=eval_pool):\n    parameters = deepcopy(default_parameters)\n    parameters['loss_function'] = loss_function\n    parameters['train_dir'] = loss_function\n    \n    if additional_params is not None:\n        parameters.update(additional_params)\n        \n    model = CatBoost(parameters)\n    model.fit(train_pool, eval_set=test_pool, plot=True)\n    print(\"best results (train on train):\")\n    print(model.get_best_score()[\"learn\"])\n    print(\"best results (on validation set):\")\n    print(model.get_best_score()[\"validation\"])\n    \n    print(\"(Default) Feature importance (on train pool)\")\n    display(model.get_feature_importance(data=train_pool,prettified=True).head(15))\n    \n    try:\n        print(\"SHAP features importance, on all data:\")\n        explainer = shap.TreeExplainer(model)\n        shap_values = explainer.shap_values(pd.concat([X_train,X_eval]),\n                                            y=pd.concat([y_train,y_eval]))\n\n        # # summarize the effects of all the features\n        shap.summary_plot(shap_values, pd.concat([X_train,X_eval]))\n    finally:\n        return model","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}