{"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":"# Lets connect the dots\n<!-- ![]() -->\n\n<img src=\"https://i.pinimg.com/736x/1a/f2/b5/1af2b59eb6002d59981b5fd81035a649.jpg\" width=\"400\" height=\"400\" />\n\n\nThis notebook shows how we can leverage some knowledge about the training data to post process our predictions and gain a LB boost.\n\nThe idea is:\n1. The training data `waypoint` locations are not precise measurements. Instead they are locations unique to the building structure, uniformly distributed throughout the available hallway space.\n2. We can use the waypoints from the training data as a \"grid\" and snap our predictions to these grid points.\n3. I set a minimum threshold before snapping predictions to this grid.\n\nI'm sure more ideas will follow.\n\nTo demonstrate this post processing I am using the submission file from a top scoring notebook.","metadata":{}},{"cell_type":"code","source":"# Helper Functions\nimport pandas as pd\nimport numpy as np\n\nimport json\nimport matplotlib.pylab as plt\n\nfrom pathlib import Path\n\ndef split_col(df):\n    df = pd.concat([\n        df['site_path_timestamp'].str.split('_', expand=True) \\\n        .rename(columns={0:'site',\n                         1:'path',\n                         2:'timestamp'}),\n        df\n    ], axis=1).copy()\n    return df\n\nfloor_map = {\"B2\":-2, \"B1\":-1, \"F1\":0, \"F2\": 1, \"F3\":2,\n             \"F4\":3, \"F5\":4, \"F6\":5, \"F7\":6,\"F8\":7,\"F9\":8,\n             \"1F\":0, \"2F\":1, \"3F\":2, \"4F\":3, \"5F\":4, \"6F\":5,\n             \"7F\":6, \"8F\": 7, \"9F\":8}\n\n\ndef plot_preds(\n    site,\n    floorNo,\n    sub=None,\n    true_locs=None,\n    base=\"../input/indoor-location-navigation\",\n    show_train=True,\n    show_preds=True,\n    fix_labels=True,\n    map_floor=None\n):\n    \"\"\"\n    Plots predictions on floorplan map.\n    \n    map_floor : use a different floor's map\n    \"\"\"\n    if map_floor is None:\n        map_floor = floorNo\n    # Prepare width_meter & height_meter (taken from the .json file)\n    floor_plan_filename = f\"{base}/metadata/{site}/{map_floor}/floor_image.png\"\n    json_plan_filename = f\"{base}/metadata/{site}/{map_floor}/floor_info.json\"\n    with open(json_plan_filename) as json_file:\n        json_data = json.load(json_file)\n\n    width_meter = json_data[\"map_info\"][\"width\"]\n    height_meter = json_data[\"map_info\"][\"height\"]\n\n    floor_img = plt.imread(f\"{base}/metadata/{site}/{map_floor}/floor_image.png\")\n\n    fig, ax = plt.subplots(figsize=(12, 12))\n    plt.imshow(floor_img)\n\n    if show_train:\n        true_locs = true_locs.query('site == @site and floorNo == @map_floor').copy()\n        true_locs[\"x_\"] = true_locs[\"x\"] * floor_img.shape[0] / height_meter\n        true_locs[\"y_\"] = (\n            true_locs[\"y\"] * -1 * floor_img.shape[1] / width_meter\n        ) + floor_img.shape[0]\n        true_locs.query(\"site == @site and floorNo == @map_floor\").groupby(\"path\").plot(\n            x=\"x_\",\n            y=\"y_\",\n            style=\"+\",\n            ax=ax,\n            label=\"train waypoint location\",\n            color=\"grey\",\n            alpha=0.5,\n        )\n\n    if show_preds:\n        sub = sub.query('site == @site and floorNo == @floorNo').copy()\n        sub[\"x_\"] = sub[\"x\"] * floor_img.shape[0] / height_meter\n        sub[\"y_\"] = (\n            sub[\"y\"] * -1 * floor_img.shape[1] / width_meter\n        ) + floor_img.shape[0]\n        for path, path_data in sub.query(\n            \"site == @site and floorNo == @floorNo\"\n        ).groupby(\"path\"):\n            path_data.plot(\n                x=\"x_\",\n                y=\"y_\",\n                style=\".-\",\n                ax=ax,\n                title=f\"{site} - floor - {floorNo}\",\n                alpha=1,\n                label=path,\n            )\n    if fix_labels:\n        handles, labels = ax.get_legend_handles_labels()\n        by_label = dict(zip(labels, handles))\n        plt.legend(\n            by_label.values(), by_label.keys(), loc=\"center left\", bbox_to_anchor=(1, 0.5)\n        )\n    return fig, ax\n\ndef sub_process(sub, train_waypoints):\n    train_waypoints['isTrainWaypoint'] = True\n    sub = split_col(sub[['site_path_timestamp','floor','x','y']]).copy()\n    sub = sub.merge(train_waypoints[['site','floorNo','floor']].drop_duplicates(), how='left')\n    sub = sub.merge(\n        train_waypoints[['x','y','site','floor','isTrainWaypoint']].drop_duplicates(),\n        how='left',\n        on=['site','x','y','floor']\n             )\n    sub['isTrainWaypoint'] = sub['isTrainWaypoint'].fillna(False)\n    return sub.copy()","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_buildings = [\n    '5a0546857ecc773753327266',\n    '5c3c44b80379370013e0fd2b',\n    '5d27075f03f801723c2e360f',\n    '5d27096c03f801723c31e5e0',\n    '5d27097f03f801723c320d97',\n    '5d27099f03f801723c32511d',\n    '5d2709a003f801723c3251bf',\n    '5d2709b303f801723c327472',\n    '5d2709bb03f801723c32852c',\n    '5d2709c303f801723c3299ee',\n    '5d2709d403f801723c32bd39',\n    '5d2709e003f801723c32d896',\n    '5da138274db8ce0c98bbd3d2',\n    '5da1382d4db8ce0c98bbe92e',\n    '5da138314db8ce0c98bbf3a0',\n    '5da138364db8ce0c98bc00f1',\n    '5da1383b4db8ce0c98bc11ab',\n    '5da138754db8ce0c98bca82f',\n    '5da138764db8ce0c98bcaa46',\n    '5da1389e4db8ce0c98bd0547',\n    '5da138b74db8ce0c98bd4774',\n    '5da958dd46f8266d0737457b',\n    '5dbc1d84c1eb61796cf7c010',\n    '5dc8cea7659e181adb076a3f'\n]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 1: Identify training waypoints\nAs an example I'll plot the training waypoints on the map for a given floor.","metadata":{}},{"cell_type":"code","source":"# Blend Subs\nsub1 = split_col(pd.read_csv('../input/indoor-location-train-waypoints/6.771LB_submission.csv'))\nsub2 = split_col(pd.read_csv('../input/indoor-location-train-waypoints/7.274LB_submission.csv'))\nsub3 = split_col(pd.read_csv('../input/indoor-location-train-waypoints/7.518LB_submission_LSTM.csv'))\nsub4 = split_col(pd.read_csv('../input/indoor-location-train-waypoints/7.661LB_LSTM_submission.csv'))\nsub5 = split_col(pd.read_csv('../input/indoor-location-train-waypoints/7.745LB_submission.csv'))\n\n# Blend\nsub = sub1.merge(sub2[['site_path_timestamp','floor','x','y']],\n           on=['site_path_timestamp','floor'],\n           how='left',\n           suffixes=('_s1','_s2')).copy()\nsub['x'] = sub1['x'] \nsub['y'] = sub1['y'] \n\nsub = sub.merge(sub3[['site_path_timestamp','floor','x','y']],\n           on=['site_path_timestamp','floor'],\n           how='left',\n           suffixes=('','_s3')).copy()\nsub = sub.merge(sub4[['site_path_timestamp','floor','x','y']],\n           on=['site_path_timestamp','floor'],\n           how='left',\n           suffixes=('','_s4')).copy()\nsub = sub.merge(sub5[['site_path_timestamp','floor','x','y']],\n           on=['site_path_timestamp','floor'],\n           how='left',\n           suffixes=('','_s5')).copy()\n\nsub['x'] = sub[['x_s1','x_s2','x_s3','x_s4','x_s5']].mean(axis=1)\nsub['y'] = sub[['y_s1','y_s2','y_s3','y_s4','y_s5']].mean(axis=1)","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_waypoints = pd.read_csv('../input/indoor-location-train-waypoints/train_waypoints.csv')\n# sub = sub_process(pd.read_csv('../input/indoor-location-train-waypoints/6.578LB_submission.csv'),\n#                  train_waypoints)\nsub = sub_process(pd.read_csv('../input/indoor-location-post-processing/submission.csv'), train_waypoints)\n# Plot the training Data For an example Floor\nexample_site = '5dbc1d84c1eb61796cf7c010'\nexample_floorNo = 'F3'\n\nplot_preds(example_site, example_floorNo, sub,\n           train_waypoints, show_preds=False)\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 2: Find the closest \"grid\" point for each prediction.\n\nWe can find the closest \"grid\" point to our predictions using the `cdist` function in scipy.","metadata":{}},{"cell_type":"code","source":"from scipy.spatial.distance import cdist\n\ndef add_xy(df):\n    df['xy'] = [(x, y) for x,y in zip(df['x'], df['y'])]\n    return df\n\ndef closest_point(point, points):\n    \"\"\" Find closest point from a list of points. \"\"\"\n    return points[cdist([point], points).argmin()]\n\nsub = add_xy(sub)\ntrain_waypoints = add_xy(train_waypoints)\n\nds = []\nfor (site, myfloor), d in sub.groupby(['site','floor']):\n    true_floor_locs = train_waypoints.loc[(train_waypoints['floor'] == myfloor) &\n                                          (train_waypoints['site'] == site)] \\\n        .reset_index(drop=True)\n    if len(true_floor_locs) == 0:\n        print(f'Skipping {site} {myfloor}')\n        continue\n    d['matched_point'] = [closest_point(x, list(true_floor_locs['xy'])) for x in d['xy']]\n    d['x_'] = d['matched_point'].apply(lambda x: x[0])\n    d['y_'] = d['matched_point'].apply(lambda x: x[1])\n    ds.append(d)\n\nsub = pd.concat(ds)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# for building in test_buildings:\n#     for floor_dir in Path(f'../input/indoor-location-navigation/train/{building}').iterdir():\n#         floor = str(floor_dir).split('/')[-1]\n#         plot_preds(building, floor, sub,\n#            train_waypoints, show_preds=True)\n#         plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Example of raw predictions\n# plot_preds(example_site, example_floorNo, sub,\n#            train_waypoints, show_preds=True)\n# plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Step 3: Apply a Threshold and \"Snap to Grid\"\n\nI've found a threshold of 3-8 works well on the LB. But this most likely will be a function of how good your predictions are to start with.","metadata":{}},{"cell_type":"code","source":"def snap_to_grid(sub, threshold):\n    \"\"\"\n    Snap to grid if within a threshold.\n    \n    x, y are the predicted points.\n    x_, y_ are the closest grid points.\n    _x_, _y_ are the new predictions after post processing.\n    \"\"\"\n    sub['_x_'] = sub['x']\n    sub['_y_'] = sub['y']\n    sub.loc[sub['dist'] < threshold, '_x_'] = sub.loc[sub['dist'] < threshold]['x_']\n    sub.loc[sub['dist'] < threshold, '_y_'] = sub.loc[sub['dist'] < threshold]['y_']\n    return sub.copy()\n\n# Calculate the distances\nsub['dist'] = np.sqrt( (sub.x-sub.x_)**2 + (sub.y-sub.y_)**2 )\n\nsub_pp = snap_to_grid(sub, threshold=5)\n\nsub_pp = sub_pp[['site_path_timestamp','floor','_x_','_y_','site','path','floorNo']] \\\n    .rename(columns={'_x_':'x', '_y_':'y'})","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Lets take a look at the predictions after post processing.","metadata":{}},{"cell_type":"code","source":"for building in test_buildings:\n    for floor_dir in Path(f'../input/indoor-location-navigation/train/{building}').iterdir():\n        floor = str(floor_dir).split('/')[-1]\n        plot_preds(building, floor, sub,\n           train_waypoints, show_preds=True)\n        plt.show()\n        plot_preds(building, floor, sub_pp,\n           train_waypoints, show_preds=True)\n        plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot example after post processing\n# plot_preds(example_site, example_floorNo, sub_pp,\n#            train_waypoints, show_preds=True)\n# plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Not perfect, but it looks much better!\n\n![](https://media3.giphy.com/media/Hkoamb0iCaQbZmM9ok/giphy.gif?cid=ecf05e47ra5yjuw0f93p7ti3gpmsyst6whhdexd06vyygyvw&rid=giphy.gif)","metadata":{}},{"cell_type":"markdown","source":"## Evaluate The Change in Predictions","metadata":{}},{"cell_type":"code","source":"sub['dist_pp_change'] = np.sqrt(((sub['x'] - sub['_x_']) ** 2) + ((sub['y'] - sub['_y_']) ** 2))\nfig, axs = plt.subplots(1, 2, figsize=(15, 5))\nsub['dist_pp_change'].plot(kind='hist', bins=30,\n                           ax=axs[0],\n                           title='Distance Changed by Post Processing')\nsub.query('dist_pp_change > 0.1')['dist_pp_change'] \\\n    .plot(kind='hist', bins=30, ax=axs[1],\n          title='Distance Changed (Excluding <0.1 Change)')\n\nplt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub.groupby(['site','floorNo'])['dist_pp_change'].mean() \\\n    .reset_index() \\\n    .sort_values('dist_pp_change') \\\n    .set_index(['site','floorNo']).head(20).plot(kind='barh')","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Final Step: Save Post Processed Submission.","metadata":{}},{"cell_type":"code","source":"sub_pp[['site_path_timestamp','floor','x','y']] \\\n    .to_csv('submission_snap_to_grid.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# df = pd.read_csv('../input/indoor-location-post-processing/submission.csv')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# sub_pp[sub_pp['x'] < 0]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# sub_pp = sub_pp.sort_index()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# sub_pp.loc[1697]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# i = 3626\n# sub_pp.loc[3628, 'x':'y'] = (sub_pp.loc[3625, 'x':'y'] + sub_pp.loc[3629, 'x':'y']) / 2","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# sub_pp.iloc[3620: 3630]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# FIN","metadata":{}}]}