{"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":"markdown","source":"## Using Polars\n\nI took [this public notebook](https://www.kaggle.com/code/carnozhao/cpu-catboost-baseline-using-polars-inference) and only made small updates plus added many features. If you like this notebook, please upvote the original!\n\nUpdates:\n* 3 digit threshold\n* Compared with original from months ago: retrain with double data due to LB leak\n* Skip 6 questions. Inspired by https://www.kaggle.com/code/vadimkamaev/catboost-new\n* Add ~90 more features: time_future, log scaled time features, hover duration per hover event, room/screen click delta distance features, overall quantile features for time and distance. Finally, click distance = 0 features as well. Features were basically untested and left to the model to screen out unimportant or duplicate features.\n* Ended up sorting on elapsed time. Didn't have the chance to really test no sort vs sort index vs sort elapsed time.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport polars as pl\n\nfrom catboost import CatBoostClassifier\n\nBEST_THRESHOLD = 0.636 ## mistake in my submitted solution, the intended CV threshold was 0.629. Didn't have any impact on first 3 digits of private LB score.\n\nlevel_groups = [\"0-4\", \"5-12\", \"13-22\"]\nlevel_groups_reverse = {'0-4': 0, '5-12': 1, '13-22': 2}\nlevels = {'0-4': (0, 5), '5-12': (5, 13), '13-22': (13, 23)}\nquestions = {'0-4': [1,], '5-12': [4, 5, 6, 7, 8, 9, 10, 11], '13-22': [14, 15, 17]}\nEVENTS = ['checkpoint', 'cutscene_click', 'map_click', 'map_hover', 'navigate_click', 'notebook_click', 'notification_click', 'object_click', 'object_hover', 'observation_click', 'person_click']\nHOVER_EVENTS = ['map_hover', 'object_hover']\nCATS = ['event_name', 'name','fqid', 'room_fqid', 'text_fqid']\nNUMS = ['elapsed_time','level','page','room_coor_x', 'room_coor_y', 'screen_coor_x', 'screen_coor_y', 'hover_duration', \"time_past\", \"time_future\",\n        \"log_time_past\", \"log_hover_duration\", \"log_time_future\", \"room_distance\", \"screen_distance\"]\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-07-03T01:47:38.073118Z","iopub.execute_input":"2023-07-03T01:47:38.073701Z","iopub.status.idle":"2023-07-03T01:47:40.136346Z","shell.execute_reply.started":"2023-07-03T01:47:38.073675Z","shell.execute_reply":"2023-07-03T01:47:40.135669Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"columns = [\n    (\n        (pl.col(\"elapsed_time\") - pl.col(\"elapsed_time\").shift(1))\n         .fill_null(0)\n         .clip(0, 1e9)\n         .over([\"session_id\", \"level_group\"])\n         .alias(\"time_past\")\n    ),\n    (\n        (pl.col(\"elapsed_time\") - pl.col(\"elapsed_time\").shift(1))\n         .fill_null(1)\n         .clip(1, 1e9)\n         .log()\n         .over([\"session_id\", \"level_group\"])\n         .alias(\"log_time_past\")\n    ),\n    pl.col(\"hover_duration\").log().alias(\"log_hover_duration\"),\n\n    ## Distance features\n    (\n         ( ((pl.col(\"room_coor_x\") - pl.col(\"room_coor_x\").shift(1)) * (pl.col(\"room_coor_x\") - pl.col(\"room_coor_x\").shift(1))) +\n           ((pl.col(\"room_coor_y\") - pl.col(\"room_coor_y\").shift(1)) * (pl.col(\"room_coor_y\") - pl.col(\"room_coor_y\").shift(1)))\n         )\n         .sqrt()\n         .fill_null(0)\n         .clip(0, 1e9)\n         .over([\"session_id\", \"level_group\"])\n         .alias(\"room_distance\")\n    ),\n    (\n         ( ((pl.col(\"screen_coor_x\") - pl.col(\"screen_coor_x\").shift(1)) * (pl.col(\"screen_coor_x\") - pl.col(\"screen_coor_x\").shift(1))) +\n           ((pl.col(\"screen_coor_y\") - pl.col(\"screen_coor_y\").shift(1)) * (pl.col(\"screen_coor_y\") - pl.col(\"screen_coor_y\").shift(1)))\n         )\n         .sqrt()\n         .fill_null(0)\n         .clip(0, 1e9)\n         .over([\"session_id\", \"level_group\"])\n         .alias(\"screen_distance\")\n    ),\n]\nmore_columns = [\n    (\n         pl.col(\"time_past\").shift(1)\n         .fill_null(0)\n         .clip(0, 1e9)\n         .over([\"session_id\", \"level_group\"])\n         .alias(\"time_future\")\n    ),\n    (\n         pl.col(\"log_time_past\").shift(1)\n         .fill_null(0)\n         .over([\"session_id\", \"level_group\"])\n         .alias(\"log_time_future\")\n    ),\n]\n\naggs = [\n    pl.col('session_id'),\n    *[pl.col(c).drop_nulls().n_unique().cast(pl.Float32).alias(f\"{c}_unique\") for c in CATS],\n    *[pl.col(c).mean().cast(pl.Float32).alias(f\"{c}_mean\") for c in NUMS],\n    *[pl.col(c).std().cast(pl.Float32).alias(f\"{c}_std\") for c in NUMS],\n    *[(pl.col(\"event_name\") == c).sum().cast(pl.Float32).alias(f\"{c}_sum\") for c in EVENTS],\n    *[pl.col(\"time_past\").filter(pl.col(\"event_name\") == c).sum().cast(pl.Float32).alias(f\"{c}_time_past\") for c in EVENTS],\n    *[pl.col(\"hover_duration\").filter(pl.col(\"event_name\") == c).sum().cast(pl.Float32).alias(f\"{c}_hover\") for c in HOVER_EVENTS],\n    *[pl.col(\"time_future\").filter(pl.col(\"event_name\") == c).sum().cast(pl.Float32).alias(f\"{c}_time_future\") for c in EVENTS],\n    *[pl.col(\"log_time_past\").filter(pl.col(\"event_name\") == c).sum().cast(pl.Float32).alias(f\"{c}_log_time_past\") for c in EVENTS],\n    *[pl.col(\"log_hover_duration\").filter(pl.col(\"event_name\") == c).sum().cast(pl.Float32).alias(f\"{c}_log_hover\") for c in HOVER_EVENTS],\n    *[pl.col(\"log_time_future\").filter(pl.col(\"event_name\") == c).sum().cast(pl.Float32).alias(f\"{c}_log_time_future\") for c in EVENTS],\n    pl.col(\"time_past\").quantile(0.3).cast(pl.Float32).alias(\"time_past_q3\"),\n    pl.col(\"time_past\").quantile(0.5).cast(pl.Float32).alias(\"time_past_q5\"),\n    pl.col(\"time_past\").quantile(0.65).cast(pl.Float32).alias(\"time_past_q6_5\"),\n    pl.col(\"time_past\").quantile(0.8).cast(pl.Float32).alias(\"time_past_q8\"),\n    pl.col(\"time_past\").max().cast(pl.Float32).alias(\"time_past_max\"),\n    pl.col(\"room_distance\").quantile(0.1).cast(pl.Float32).alias(\"room_distance_q1\"),\n    pl.col(\"room_distance\").quantile(0.3).cast(pl.Float32).alias(\"room_distance_q3\"),\n    pl.col(\"room_distance\").quantile(0.5).cast(pl.Float32).alias(\"room_distance_q6_5\"),\n    pl.col(\"room_distance\").quantile(0.7).cast(pl.Float32).alias(\"room_distance_q7\"),\n    pl.col(\"room_distance\").sum().cast(pl.Float32).alias(\"room_distance_sum\"),\n    pl.col(\"screen_distance\").quantile(0.1).cast(pl.Float32).alias(\"screen_distance_q1\"),\n    pl.col(\"screen_distance\").quantile(0.3).cast(pl.Float32).alias(\"screen_distance_q3\"),\n    pl.col(\"screen_distance\").quantile(0.5).cast(pl.Float32).alias(\"screen_distance_q6_5\"),\n    pl.col(\"screen_distance\").quantile(0.7).cast(pl.Float32).alias(\"screen_distance_q7\"),\n    pl.col(\"screen_distance\").sum().cast(pl.Float32).alias(\"screen_distance_sum\"),\n    pl.col(\"elapsed_time\").filter(pl.col(\"screen_distance\") < 1).count().cast(pl.Float32).alias(f\"double_click_count\"),\n    pl.col(\"time_past\").filter(pl.col(\"screen_distance\") < 1).quantile(0.1).cast(pl.Float32).alias(f\"double_click_q1\"),\n    pl.col(\"time_past\").filter(pl.col(\"screen_distance\") < 1).quantile(0.25).cast(pl.Float32).alias(f\"double_click_q2_5\"),\n    pl.col(\"time_past\").filter(pl.col(\"screen_distance\") < 1).quantile(0.5).cast(pl.Float32).alias(f\"double_click_q5\"),\n]\nmodels_list = [[CatBoostClassifier().load_model(\n    f\"/kaggle/input/student-efficiency-catboost-polars-train/fold{fold}_q{q}.cbm\"\n) for fold in range(1)] for q in range(1, 19)]\n","metadata":{"execution":{"iopub.status.busy":"2023-07-03T01:47:40.139998Z","iopub.execute_input":"2023-07-03T01:47:40.141140Z","iopub.status.idle":"2023-07-03T01:47:40.336012Z","shell.execute_reply.started":"2023-07-03T01:47:40.141096Z","shell.execute_reply":"2023-07-03T01:47:40.334933Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def trans(test):\n    return (pl.from_pandas(test)\n            .sort(['session_id', 'elapsed_time'])\n            .with_columns(columns)\n            .with_columns(more_columns)\n            .select(aggs)\n            .with_columns(\n                pl.col('session_id').floordiv(1e15).cast(pl.Float32).alias('year'),\n                pl.col('session_id').floordiv(1e13).mod(100).cast(pl.Float32).alias('month'),\n                pl.col('session_id').floordiv(1e11).mod(100).cast(pl.Float32).alias('day'),\n                (\n                    pl.col('session_id').floordiv(1e9).mod(100).cast(pl.Float32) \n                    + (pl.col('session_id').floordiv(1e7).mod(100).cast(pl.Float32)/60)\n                ).alias('hour_minute')\n            )\n            .fill_null(-1)\n            .to_pandas()\n            )\n","metadata":{"execution":{"iopub.status.busy":"2023-07-03T01:47:40.338723Z","iopub.execute_input":"2023-07-03T01:47:40.339059Z","iopub.status.idle":"2023-07-03T01:47:40.349634Z","shell.execute_reply.started":"2023-07-03T01:47:40.339036Z","shell.execute_reply":"2023-07-03T01:47:40.348840Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import jo_wilder_310\nenv = jo_wilder_310.make_env()\niter_test = env.iter_test()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T01:47:40.353365Z","iopub.execute_input":"2023-07-03T01:47:40.355057Z","iopub.status.idle":"2023-07-03T01:47:40.396705Z","shell.execute_reply.started":"2023-07-03T01:47:40.355018Z","shell.execute_reply":"2023-07-03T01:47:40.396075Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fold = 0\nfor (test, sample_submission) in iter_test:\n    sample_submission['question'] = [int(label.split('_')[1][1:]) for label in sample_submission['session_id']]\n    sample_submission['correct'] = 1\n    sample_submission.loc[sample_submission.question.isin([13,]), 'correct'] = 0  \n    target_level_group = level_groups_reverse[test.level_group.iloc[0]]\n\n    df = trans(test)\n    df = df[models_list[0][0].feature_names_]\n    \n    for q in questions[level_groups[target_level_group]]:\n        model = models_list[q - 1][fold]\n        pred = model.predict_proba(df)[0,1]\n        pred = int(pred > BEST_THRESHOLD)\n        mask = sample_submission.question == q \n        sample_submission.loc[mask,'correct'] = pred      \n\n    sample_submission = sample_submission[['session_id', 'correct']]      \n    env.predict(sample_submission)","metadata":{"execution":{"iopub.status.busy":"2023-07-03T01:47:51.792544Z","iopub.execute_input":"2023-07-03T01:47:51.792849Z","iopub.status.idle":"2023-07-03T01:47:52.347058Z","shell.execute_reply.started":"2023-07-03T01:47:51.792827Z","shell.execute_reply":"2023-07-03T01:47:52.346125Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ## Just for debug in interactive mode\n# sub = pd.read_csv('submission.csv')\n# print(sub.shape, sub.correct.mean())\n# sub.head()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T01:47:52.348382Z","iopub.execute_input":"2023-07-03T01:47:52.348583Z","iopub.status.idle":"2023-07-03T01:47:52.355438Z","shell.execute_reply.started":"2023-07-03T01:47:52.348563Z","shell.execute_reply":"2023-07-03T01:47:52.354476Z"},"trusted":true},"execution_count":null,"outputs":[]}]}