{"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 matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport math\nfrom math import sin, cos, sqrt, atan2\nimport geopy.distance\nimport pickle\n\nimport random\n\nfrom sklearn.metrics import f1_score\n\nfrom matplotlib.gridspec import GridSpec\nfrom matplotlib.colors import ListedColormap, LinearSegmentedColormap\nimport matplotlib.colors\nfrom matplotlib.offsetbox import AnchoredText\nimport matplotlib.dates as mdates\nfrom matplotlib.ticker import (AutoMinorLocator, MultipleLocator)\nfrom matplotlib.dates import HourLocator, MonthLocator, YearLocator\n\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nimport plotly.express as px\n\ndef hex_to_rgb(value):\n    '''\n    Converts hex to rgb colours\n    value: string of 6 characters representing a hex colour.\n    Returns: list length 3 of RGB values'''\n    value = value.strip(\"#\") # removes hash symbol if present\n    lv = len(value)\n    return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))\n\ndef rgb_to_dec(value):\n    '''\n    Converts rgb to decimal colours (i.e. divides each value by 256)\n    value: list (length 3) of RGB values\n    Returns: list (length 3) of decimal values'''\n    return [v/256 for v in value]\n\ndef get_continuous_cmap(hex_list, float_list=None):\n    ''' creates and returns a color map that can be used in heat map figures.\n        If float_list is not provided, colour map graduates linearly between each color in hex_list.\n        If float_list is provided, each color in hex_list is mapped to the respective location in float_list. \n        \n        Parameters\n        ----------\n        hex_list: list of hex code strings\n        float_list: list of floats between 0 and 1, same length as hex_list. Must start with 0 and end with 1.\n        \n        Returns\n        ----------\n        colour map'''\n    rgb_list = [rgb_to_dec(hex_to_rgb(i)) for i in hex_list]\n    if float_list:\n        pass\n    else:\n        float_list = list(np.linspace(0,1,len(rgb_list)))\n        \n    cdict = dict()\n    for num, col in enumerate(['red', 'green', 'blue']):\n        col_list = [[float_list[i], rgb_list[i][num], rgb_list[i][num]] for i in range(len(float_list))]\n        cdict[col] = col_list\n    cmp = LinearSegmentedColormap('my_cmp', segmentdata=cdict, N=256)\n    return cmp\n\nSTOCK_CMAP = ListedColormap([\"#B22222\", \"#228B22\"])\nBACK_COLOR = \"#f5f5f5\"\nGRID_COLOR = \"#ffffff\"\nLINE_COLOR = \"#000000\" # black\nDIRT_COLOR = \"#DAA520\" # gold\n\nTEMPLATE = 'simple_white'\n\nbg_cmap = get_continuous_cmap([LINE_COLOR, DIRT_COLOR])\n\n# !pip -q install utm\n# import utm\n# https://stackoverflow.com/questions/24617013/convert-latitude-and-longitude-to-x-and-y-grid-system-using-python","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:03.006922Z","iopub.execute_input":"2022-11-07T04:55:03.007439Z","iopub.status.idle":"2022-11-07T04:55:05.153564Z","shell.execute_reply.started":"2022-11-07T04:55:03.007321Z","shell.execute_reply":"2022-11-07T04:55:05.152449Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>0. <span style=\"color:#DAA520\">I</span>ntro</b>  ","metadata":{}},{"cell_type":"markdown","source":"  <img src=\"https://i.guim.co.uk/img/media/6d1d45a9d27e29ed6fe00e6e93201534bc4efcc0/0_160_5309_3186/master/5309.jpg?width=1200&height=1200&quality=85&auto=format&fit=crop&s=9298e5d306e172e41519da4c78a3b3fc\" alt=\"finger snap\" width=\"350\" height=\"250\" class=\"center\"/>","metadata":{"execution":{"iopub.status.busy":"2022-10-03T12:09:50.287051Z","iopub.execute_input":"2022-10-03T12:09:50.287801Z","iopub.status.idle":"2022-10-03T12:09:50.294846Z","shell.execute_reply.started":"2022-10-03T12:09:50.287760Z","shell.execute_reply":"2022-10-03T12:09:50.293156Z"}}},{"cell_type":"markdown","source":"As many horse races have been held in the meantime, betting has been made through past race records. Simply put, the more winning horse riders ride, the higher the chance of winning. But what would we expect if a horse that has never been played, a jockey, played his first game? Or if horses with similar winning careers compete?  \nLet's analyze how we can respond to these exceptional situations.","metadata":{}},{"cell_type":"markdown","source":"# <b>1. <span style=\"color:#DAA520\">B</span>asic</b>  \nI referred to the notebook below for data preprocessing.  \n- https://www.kaggle.com/code/xxxxyyyy80008/derby-2022-horse-analysis/notebook","metadata":{}},{"cell_type":"code","source":"# https://www.kaggle.com/code/xxxxyyyy80008/derby-2022-horse-analysis/notebook\n\nstart_table = pd.read_csv('/kaggle/input/big-data-derby-2022/nyra_start_table.csv', header=None)\nstart_table.columns=['track_id', 'race_date', 'race_number', 'program_number', 'weight_carried', 'jockey', 'odds', 'position_at_finish']\n\nhorse_names = pd.read_csv(r\"/kaggle/input/big-data-derby-2022-global-horse-ids-and-places/horse_names.csv\", index_col=0)\nhorse_ids = pd.read_csv(r\"/kaggle/input/big-data-derby-2022-global-horse-ids-and-places/horse_ids.csv\", index_col=0)\n\nhorse_ids = horse_ids.merge(horse_names, on='horse_id', how='inner')\nhorse_ids['program_number'] = horse_ids['program_number'].apply(lambda x: x.ljust(3, ' '))\n\nstart_table = start_table.merge(horse_ids, \n                                left_on=['track_id', 'race_date', 'race_number', 'program_number'],\n                                right_on=['track_id', 'race_date', 'race', 'program_number'],\n                                how='inner'\n                               )\nrace_table = pd.read_csv('/kaggle/input/big-data-derby-2022/nyra_race_table.csv')\nstart_table = start_table.merge(race_table, on=['track_id','race_date', 'race_number'], how='inner')\n\nrace_type_dict =   {  'STK': 'Stakes', \n                      'WCL': 'Waiver Claiming', \n                      'WMC': 'Waiver Maiden Claiming', \n                      'SST': 'Starter Stakes', \n                      'SHP': 'Starter Handicap',\n                      'CLM': 'Claiming', \n                      'STR': 'Starter Allowance', \n                      'AOC': 'Allowance Optionl Claimer', \n                      'SOC': 'Starter Optional Claimer', \n                      'MCL': 'Maiden Claiming', \n                      'ALW': 'Allowance', \n                      'MSW': 'Maiden Special Weight'\n                      }\n\ntrack_id_dict = dict(AQU = 'Aqueduct', BEL = 'Belmont', SAR = 'Saratoga')\ncourse_type_dict = dict(M = 'Hurdle', D = 'Dirt', O = 'Outer turf', I = 'Inner turf', T = 'Turf')\ntrack_condition_dict =  dict(YL = 'Yielding', FM ='Firm', SY = 'Sloppy', GD = 'Good', FT = 'Fast', MY = 'Muddy', SF = 'Soft')\n\nstart_table['race_type'] = start_table['race_type'].map(race_type_dict)\nstart_table['track_id'] = start_table['track_id'].map(track_id_dict)\nstart_table['course_type'] = start_table['course_type'].map(course_type_dict)\n\nstart_table['track_condition'] = start_table['track_condition'].apply(lambda x:x.replace(\" \", ''))\nstart_table['track_condition'] = start_table['track_condition'].map(track_condition_dict)\n\nstart_table['race_month'] = start_table['race_date'].apply(lambda x : x.split(\"-\")[1])\n\ntrack_table = pd.read_csv('/kaggle/input/big-data-derby-2022/nyra_tracking_table.csv')\ntrack_table['track_id'] = track_table['track_id'].map(track_id_dict)\n# track_table.head() # track_id\trace_date\trace_number\tprogram_number\ttrakus_index\tlatitude\tlongitude\n\ntarget_rankings = [1, 2]\ntemp_ = start_table.groupby(['track_id', 'course_type', 'track_condition', 'distance_id', 'race_number', 'race_date'])[['horse_name']].count().reset_index()\ntemp_ = temp_.iloc[:, :-1].reset_index(drop=True)\n\nrace_condition_table = temp_.copy()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:05.271718Z","iopub.execute_input":"2022-11-07T04:55:05.272122Z","iopub.status.idle":"2022-11-07T04:55:14.855704Z","shell.execute_reply.started":"2022-11-07T04:55:05.272089Z","shell.execute_reply":"2022-11-07T04:55:14.854357Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## <b>1.1. <span style=\"color:#DAA520\">D</span>irt & 6 furlong.</b>  \nThe most common horse race was Dirt's 6 furlong race. About 20 percent of the games corresponded to this.","metadata":{}},{"cell_type":"code","source":"temp_ = pd.DataFrame(race_condition_table.groupby(['course_type', 'distance_id']).size()).reset_index()\ntemp_['race_type'] = temp_['course_type'] + \"_\" + temp_['distance_id'].astype(str)\ntemp_ = temp_.sort_values(by=[0], ascending=False)\n\ntotal_race = temp_[0].sum()\ndirt_600 = temp_[temp_['race_type']=='Dirt_600'][0].sum()\n\nprint(f\"Total Race : {total_race}\")\nprint(f\"Dirt 6 furlong Race : {dirt_600}\")\nprint(f\"Ratio : {dirt_600 / total_race : .4f}\")\n\nfig = make_subplots(specs=[[{\"secondary_y\": True}]])\nfig.add_trace(go.Bar(x=temp_['race_type'], y=temp_[0], marker_color=[DIRT_COLOR]+[\"#000000\"], name='1st Place Count'))\n\nfig.update_layout(title_text=f'<b>Number of races by type</b>', template=TEMPLATE, height=400)\nfig.update_yaxes(title='Count')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:14.857361Z","iopub.execute_input":"2022-11-07T04:55:14.857743Z","iopub.status.idle":"2022-11-07T04:55:16.160015Z","shell.execute_reply.started":"2022-11-07T04:55:14.857711Z","shell.execute_reply":"2022-11-07T04:55:16.158892Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## <b>1.2. <span style=\"color:#DAA520\">J</span>ockey in Dirt 6 furlong</b>  \nThe two most ranked first in these games are **Manuel Franco** and **Jose Lezcano**.    \n\nThese two riders have almost the same number of first place, but **Jose Lezcano** has a higher percentage of first place per game. (Dirt 6 furlong)","metadata":{}},{"cell_type":"code","source":"temp_ = start_table[(start_table['course_type']=='Dirt') & (start_table['distance_id']==600)]\ntemp_ = pd.DataFrame(temp_.groupby(['jockey', 'position_at_finish']).size()).unstack().fillna(0)\ntemp_['total_race'] = temp_.sum(axis=1)\ntemp_ = temp_.iloc[:, [0, -1]]\ntemp_.columns = ['1st', 'total_race']\n\ntemp_['1st_rate'] = temp_['1st'] / temp_['total_race']\ntemp_ = temp_.sort_values(by=['1st'], ascending=False)\ntemp_ = temp_[temp_['total_race'] >= 50]\n# print(len(temp_))\ndisplay(temp_)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:16.161299Z","iopub.execute_input":"2022-11-07T04:55:16.162050Z","iopub.status.idle":"2022-11-07T04:55:16.210119Z","shell.execute_reply.started":"2022-11-07T04:55:16.162002Z","shell.execute_reply":"2022-11-07T04:55:16.208632Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"temp_ = start_table[(start_table['course_type']=='Dirt') & (start_table['distance_id']==600)]\ntemp_ = pd.DataFrame(temp_.groupby(['jockey', 'position_at_finish']).size()).unstack().fillna(0)\ntemp_['total_race'] = temp_.sum(axis=1)\ntemp_ = temp_.iloc[:, [0, -1]]\ntemp_.columns = ['1st', 'total_race']\n\ntemp_['1st_rate'] = temp_['1st'] / temp_['total_race']\ntemp_ = temp_.sort_values(by=['1st'], ascending=False).head(20)\n\ntemp_\n\nfig = make_subplots(specs=[[{\"secondary_y\": True}]])\nfig.add_trace(go.Bar(x=temp_.index, y=temp_['1st'], marker_color=[DIRT_COLOR, DIRT_COLOR]+[\"#000000\"]*18, name='1st Place Count'))\nfig.add_trace(go.Scatter(x=temp_.index, y=temp_['1st_rate'], marker_color='DarkGreen', name='1st Place Rate'), secondary_y=True)\n\nfig.update_layout(title_text=f'<b>A jockey who won 1st place a lot</b>', template=TEMPLATE)\nfig.update_yaxes(title='Total 1st Place')\nfig.update_yaxes(title='1st Win Rate', secondary_y=True)\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:16.214121Z","iopub.execute_input":"2022-11-07T04:55:16.214687Z","iopub.status.idle":"2022-11-07T04:55:16.314762Z","shell.execute_reply.started":"2022-11-07T04:55:16.214637Z","shell.execute_reply":"2022-11-07T04:55:16.313204Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Jose Lezcano** is slightly ahead in terms of their opponent's record. Of the 111 races in which the two participated together, **Jose Lezcano** won 24 times, and **Manuel Franco** won 20 times. Even in the race where they didn't win, **Jose Lezcano** was ahead of **Manuel Franco** 36 times. Conversely, it was 31 times when **Manuel Franco** was ahead of **Jose Lezcano**.    \n\nIn other words, **Jose Lezcano** prevails in the confrontation between the two.","metadata":{}},{"cell_type":"code","source":"jockeys_ = [\"Manuel Franco\", \"Jose Lezcano\"]\ntemp_ = start_table[\n                    (start_table['jockey'].isin(jockeys_)) &\n                    (start_table['course_type']=='Dirt') &\n                    (start_table['distance_id']==600)\n                ]\n\ntemp_ = temp_.groupby(['track_id', 'race_date', 'race_number', 'jockey'])[['position_at_finish']].min().unstack()#.fillna(-1)\ntemp_.columns = [c[1] for c in temp_.columns]\ntemp_ = temp_.reset_index().dropna()\n\ntemp_[\"1st Win Manuel Franco\"] = temp_[\"Manuel Franco\"] == 1\ntemp_[\"1st Win Jose Lezcano\"] = temp_[\"Jose Lezcano\"] == 1\n\ntemp_[\"Win Manuel Franco\"] = temp_[\"Manuel Franco\"] < temp_[\"Jose Lezcano\"]\ntemp_[\"Win Jose Lezcano\"] = temp_[\"Jose Lezcano\"] < temp_[\"Manuel Franco\"]\nboth_parti_race = len(temp_)\n\nw1mf = temp_[\"1st Win Manuel Franco\"].sum()\nwmf = temp_[\"Win Manuel Franco\"].sum() - w1mf\n\nw1jl = temp_[\"1st Win Jose Lezcano\"].sum()\nwjl = temp_[\"Win Jose Lezcano\"].sum() - w1jl\n\nfig = go.Figure()\nfig.add_trace(go.Bar(\n   y=['Manuel Franco', 'Jose Lezcano'],\n    x=[w1mf, w1jl],\n    name='1st Win',\n    orientation='h',\n    marker=dict(\n        color=DIRT_COLOR,\n        line=dict(color='black', width=1)\n    )\n))\n\nfig.add_trace(go.Bar(\n    y=['Manuel Franco', 'Jose Lezcano'],\n    x=[wmf, wjl],\n    name=\"get ahead of one's opponent\",\n    orientation='h',\n    marker=dict(\n        color='black', line=dict(color='black', width=1)\n    )\n))\n\nfig.update_layout(title_text=f'<b>A race in which both participated</b><br>: {both_parti_race} races', template=TEMPLATE, barmode='stack', height=300)\nfig.update_yaxes(title='')\nfig.update_xaxes(title='Count')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:16.316291Z","iopub.execute_input":"2022-11-07T04:55:16.316678Z","iopub.status.idle":"2022-11-07T04:55:16.396741Z","shell.execute_reply.started":"2022-11-07T04:55:16.316645Z","shell.execute_reply":"2022-11-07T04:55:16.394964Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In terms of horses, there weren't that many horses that topped the list more than twice. I don't think there's a horse that shows overwhelming ability.","metadata":{}},{"cell_type":"code","source":"temp_ = start_table[(start_table['course_type']=='Dirt') & (start_table['distance_id']==600)]\ntemp_ = pd.DataFrame(temp_.groupby(['horse_name', 'position_at_finish']).size()).unstack().fillna(0)\ntemp_['total_race'] = temp_.sum(axis=1)\ntemp_ = temp_.iloc[:, [0, -1]]\ntemp_.columns = ['1st', 'total_race']\n\ntemp_ = temp_[temp_['1st']>0]\ntemp_['1st_rate'] = temp_['1st'] / temp_['total_race']\ntemp_ = temp_.sort_values(by=['1st'], ascending=False)#.head(100)\n\ntemp_\n\nfig = make_subplots(specs=[[{\"secondary_y\": True}]])\nfig.add_trace(go.Bar(x=temp_.index, y=temp_['1st'], marker_color=[\"#000000\"]*18, name='1st Place Count'))\nfig.add_trace(go.Scatter(x=temp_.index, y=temp_['1st_rate'], marker_color='DarkGreen', name='1st Place Rate'), secondary_y=True)\n\nfig.update_layout(title_text=f'<b>A horse who won 1st place a lot</b>', template=TEMPLATE)\nfig.update_yaxes(title='Total 1st Place')\nfig.update_yaxes(title='1st Win Rate', secondary_y=True)\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:16.398531Z","iopub.execute_input":"2022-11-07T04:55:16.399004Z","iopub.status.idle":"2022-11-07T04:55:16.488695Z","shell.execute_reply.started":"2022-11-07T04:55:16.398963Z","shell.execute_reply":"2022-11-07T04:55:16.487306Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **NOTE** : In jockeys, certain jockeys seem to win a lot, but not in horses. I will focus on the horses that won first place, not the specific rider and the specific horse in the future analysis.","metadata":{}},{"cell_type":"markdown","source":"# <b>2. <span style=\"color:#DAA520\">T</span>rack information</b>  \nThe approximate appearance of the 6 Furlong Track is shown below. If we look at the reference picture [here](https://horseyhooves.com/what-is-a-furlong/), it's roughly half a turn. So the horses run in a straight line, a curve, and a straight line.  ","metadata":{}},{"cell_type":"code","source":"fig = make_subplots(\n    rows=1, cols=3,\n    specs=[[{\"type\": \"scatter\"}, {\"type\": \"scatter\"}, {\"type\": \"scatter\"}]],\n    subplot_titles=['< Aqueduct >', '< Belmont >', '< Saratoga >']\n)\n\n\ntemp_ = track_table[\n            (track_table['track_id'] == \"Aqueduct\") &\n            (track_table['race_date'] == \"2019-01-18\") &\n            (track_table['race_number'] == 1)\n        ]\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    fig.add_trace(go.Scatter(x=cur_['longitude'], y=cur_['latitude'], name=pn.strip(), mode='markers', marker_color='black', showlegend=False), row=1, col=1)\n    break\n\n\ntemp_ = track_table[\n            (track_table['track_id'] == \"Belmont\") &\n            (track_table['race_date'] == \"2019-05-27\") &\n            (track_table['race_number'] == 1)\n        ]\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    fig.add_trace(go.Scatter(x=cur_['longitude'], y=cur_['latitude'], name=pn.strip(), mode='markers', marker_color='black', showlegend=False), row=1, col=2)\n    break\n    \ntemp_ = track_table[\n            (track_table['track_id'] == \"Saratoga\") &\n            (track_table['race_date'] == \"2019-07-28\") &\n            (track_table['race_number'] == 1)\n        ]\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    fig.add_trace(go.Scatter(x=cur_['longitude'], y=cur_['latitude'], name=pn.strip(), mode='markers', marker_color='black', showlegend=False), row=1, col=3)\n    break\n\n\nfig.update_layout(title_text=f'<b>Racetrack</b>', template=TEMPLATE)\nfig.update_yaxes(title='latitude')\nfig.update_xaxes(title='longitude')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:16.491121Z","iopub.execute_input":"2022-11-07T04:55:16.491686Z","iopub.status.idle":"2022-11-07T04:55:18.655946Z","shell.execute_reply.started":"2022-11-07T04:55:16.491635Z","shell.execute_reply":"2022-11-07T04:55:18.654615Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## <b>2.1. <span style=\"color:#DAA520\">S</span>ection information</b>  ","metadata":{}},{"cell_type":"markdown","source":"So for later, I'm going to set a range of straight lines based on coordinates. Naturally, non-straight sections become curved sections.  ","metadata":{}},{"cell_type":"code","source":"# # manually check\n# TRACK_SECTION = {\n#     'Aqueduct' : {\n#         '1' : {\n#             'LONG' : [-73.829, -73.827],\n#             'LATI' : [40.66, 40.674]\n#         },\n#         '2' : {\n#             'LONG' : [-73.833, -73.83045],\n#             'LATI' : [40.67, 40.67471]\n#         }\n#     },\n#     'Belmont' : {\n#         '1' : {\n#             'LONG' : [-73.7249, -73.720],\n#             'LATI' : [40.716, 40.718]\n#         },\n#         '2' : {\n#             'LONG' : [-73.72642, -73.72],\n#             'LATI' : [40.7125, 40.71429]\n#         }\n#     },\n#     'Saratoga' : {\n#         '1' : {\n#             'LONG' : [-73.771, -73.764],\n#             'LATI' : [43.069, 43.07148]\n#         },\n#         '2' : {\n#             'LONG' : [-73.773, -73.76711],\n#             'LATI' : [43.071, 43.07369]\n#         }\n#     }\n# }\n\n# def get_section(track_id, latitude, longitude):\n#     straight_1 = TRACK_SECTION[track_id]['1']\n    \n#     for sec, key in zip(['First Straight', 'Last Straight'], ['1', '2']):\n#         cur_ = TRACK_SECTION[track_id][key]\n#         if (longitude >= cur_['LONG'][0]) & (longitude <= cur_['LONG'][1]) & (latitude >= cur_['LATI'][0]) & (latitude <= cur_['LATI'][1]):\n#             return sec\n#     return 'Curve'\n\n# # visualization\n# fig = make_subplots(\n#     rows=1, cols=3,\n#     specs=[[{\"type\": \"scatter\"}, {\"type\": \"scatter\"}, {\"type\": \"scatter\"}]],\n#     subplot_titles=['< Aqueduct >', '< Belmont >', '< Saratoga >']\n# )\n\n\n# temp_ = track_table[\n#             (track_table['track_id'] == \"Aqueduct\") &\n#             (track_table['race_date'] == \"2019-01-18\") &\n#             (track_table['race_number'] == 1)\n#         ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\n# for pn in temp_['program_number'].unique():\n#     cur_ = temp_[temp_['program_number']==pn]\n#     for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n#         cur_sec_ = cur_[cur_['section']==sec]\n#         fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=1)\n#     break\n\n\n# temp_ = track_table[\n#             (track_table['track_id'] == \"Belmont\") &\n#             (track_table['race_date'] == \"2019-05-27\") &\n#             (track_table['race_number'] == 1)\n#         ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\n# for pn in temp_['program_number'].unique():\n#     cur_ = temp_[temp_['program_number']==pn]\n#     for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n#         cur_sec_ = cur_[cur_['section']==sec]\n#         fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=2)\n#     break\n    \n# temp_ = track_table[\n#             (track_table['track_id'] == \"Saratoga\") &\n#             (track_table['race_date'] == \"2019-07-28\") &\n#             (track_table['race_number'] == 1)\n#         ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\n# for pn in temp_['program_number'].unique():\n#     cur_ = temp_[temp_['program_number']==pn]\n#     for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n#         cur_sec_ = cur_[cur_['section']==sec]\n#         fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=3)\n#     break\n\n# fig.update_layout(title_text=f'<b>Racetrack</b><br>: Gold - Curve, Others - First, Last Straight', template=TEMPLATE)\n# fig.update_yaxes(title='latitude')\n# fig.update_xaxes(title='longitude')\n# fig.show()\n\n# manually check\nCURVE_SECTION = {\n    'Aqueduct' : {\n       'LONG' : [-73.830844, -73.826647],\n       'LATI' : [40.674882, 40.673775]\n    },\n    'Belmont' : {\n        'LONG' : [-73.726309, -73.724534],\n        'LATI' : [40.713963, 40.717865]\n    },\n    'Saratoga' : {\n        'LONG' : [-73.767031, -73.765053],\n        'LATI' : [43.074006, 43.071277]\n    }\n}\n\ndef is_cross_curve(track_id, x11,y11, x12,y12):\n    # https://crazyj.tistory.com/140\n    x21 = CURVE_SECTION[track_id]['LONG'][0]\n    y21 = CURVE_SECTION[track_id]['LATI'][0]\n    x22 = CURVE_SECTION[track_id]['LONG'][1]\n    y22 = CURVE_SECTION[track_id]['LATI'][1]\n    \n    def is_divide_pt(x11,y11, x12,y12, x21,y21, x22,y22):\n        f1= (x12-x11)*(y21-y11) - (y12-y11)*(x21-x11)\n        f2= (x12-x11)*(y22-y11) - (y12-y11)*(x22-x11)\n        if f1*f2 < 0 :\n            return True\n        else:\n            return False\n\n    b1 = is_divide_pt(x11,y11, x12,y12, x21,y21, x22,y22)\n    b2 = is_divide_pt(x21,y21, x22,y22, x11,y11, x12,y12)\n    if b1 and b2:\n        return True\n    else:\n        return False\n\n# def get_section(track_id, latitude, longitude):\n#     straight_1 = TRACK_SECTION[track_id]['1']\n    \n#     for sec, key in zip(['First Straight', 'Last Straight'], ['1', '2']):\n#         cur_ = TRACK_SECTION[track_id][key]\n#         if (longitude >= cur_['LONG'][0]) & (longitude <= cur_['LONG'][1]) & (latitude >= cur_['LATI'][0]) & (latitude <= cur_['LATI'][1]):\n#             return sec\n#     return 'Curve'\n\n# visualization\nfig = make_subplots(\n    rows=1, cols=3,\n    specs=[[{\"type\": \"scatter\"}, {\"type\": \"scatter\"}, {\"type\": \"scatter\"}]],\n    subplot_titles=['< Aqueduct >', '< Belmont >', '< Saratoga >']\n)\n\n# finish line\nfig.add_trace(go.Scatter(x=CURVE_SECTION['Aqueduct']['LONG'], y=CURVE_SECTION['Aqueduct']['LATI'], name='curve', mode='markers+lines', marker_color='Red', showlegend=False), row=1, col=1)\nfig.add_trace(go.Scatter(x=CURVE_SECTION['Belmont']['LONG'], y=CURVE_SECTION['Belmont']['LATI'], name='curve', mode='markers+lines', marker_color='Red', showlegend=False), row=1, col=2)\nfig.add_trace(go.Scatter(x=CURVE_SECTION['Saratoga']['LONG'], y=CURVE_SECTION['Saratoga']['LATI'], name='curve', mode='markers+lines', marker_color='Red', showlegend=False), row=1, col=3)\n\n\ntemp_ = track_table[\n            (track_table['track_id'] == \"Aqueduct\") &\n            (track_table['race_date'] == \"2019-01-18\") &\n            (track_table['race_number'] == 1)\n        ].copy()\n\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn].copy()\n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)\n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=1)\n    break\n\n\ntemp_ = track_table[\n            (track_table['track_id'] == \"Belmont\") &\n            (track_table['race_date'] == \"2019-05-27\") &\n            (track_table['race_number'] == 1)\n        ].copy()\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn].copy()\n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)\n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=2)\n    break\n    \ntemp_ = track_table[\n            (track_table['track_id'] == \"Saratoga\") &\n            (track_table['race_date'] == \"2019-07-28\") &\n            (track_table['race_number'] == 1)\n        ].copy()\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn].copy()\n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)\n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=3)\n    break\n\nfig.update_layout(title_text=f'<b>Racetrack</b><br>: Gold - Curve, Others - First, Last Straight', template=TEMPLATE)\nfig.update_yaxes(title='latitude')\nfig.update_xaxes(title='longitude')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:18.658211Z","iopub.execute_input":"2022-11-07T04:55:18.658684Z","iopub.status.idle":"2022-11-07T04:55:20.889659Z","shell.execute_reply.started":"2022-11-07T04:55:18.658649Z","shell.execute_reply":"2022-11-07T04:55:20.888476Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **NOTE** : Since the separation of the zones was determined after checking them directly, there can be errors.  \n> For analysis results that contain zone information, the focus should be on identifying trends rather than accurate figures.","metadata":{}},{"cell_type":"markdown","source":"## <b>2.2. <span style=\"color:#DAA520\">A</span>ngle of rotation</b>  \nThe center coordinates are obtained by manually checking  \nWhen the center coordinates are selected appropriately, we can see which position leads further by the size of the angle, as shown in the example below.  \nIn the examples below, the angle of the yellow lines is greater than the angle of the gray lines because the yellow point is ahead of the gray point.  ","metadata":{}},{"cell_type":"code","source":"# temp_aqueduct_ = pd.merge(race_df[race_df['track_id']=='Aqueduct'], track_table, how='left', on=['track_id', 'race_date', 'race_number'])[['latitude', 'longitude']].mean()\n# temp_belmont_ = pd.merge(race_df[race_df['track_id']=='Belmont'], track_table, how='left', on=['track_id', 'race_date', 'race_number'])[['latitude', 'longitude']].mean()\n# temp_saratoga_ = pd.merge(race_df[race_df['track_id']=='Saratoga'], track_table, how='left', on=['track_id', 'race_date', 'race_number'])[['latitude', 'longitude']].mean()\n\nTRACK_CENTER = {\n    'Aqueduct' : {\n        'LONG' : -73.832, #temp_aqueduct_['longitude'],\n        'LATI' : 40.67 #temp_aqueduct_['latitude']\n    },\n    'Belmont' : {\n        'LONG' : -73.720, # temp_belmont_['longitude'],\n        'LATI' : 40.714 # temp_belmont_['latitude']\n    },\n    'Saratoga' : {\n        'LONG' : -73.772, # temp_saratoga_['longitude'],\n        'LATI' : 43.0705 # temp_saratoga_['latitude']\n    }\n}\n\ndef get_degrees(track_id, latitude, longitude):\n    x_base, y_base = TRACK_CENTER[track_id]['LONG'], TRACK_CENTER[track_id]['LATI']\n    d = math.degrees(math.atan2(latitude-y_base, longitude-x_base))\n    if (track_id == 'Belmont') & (d < 0):\n        d = 360 + d # counter colockwise\n    return d\n\n# visualization\nfig = make_subplots(\n    rows=1, cols=3,\n    specs=[[{\"type\": \"scatter\"}, {\"type\": \"scatter\"}, {\"type\": \"scatter\"}]],\n    subplot_titles=['< Aqueduct >', '< Belmont >', '< Saratoga >']\n)\n\n\ntemp_ = track_table[\n            (track_table['track_id'] == \"Aqueduct\") &\n            (track_table['race_date'] == \"2019-01-18\") &\n            (track_table['race_number'] == 1)\n        ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    \n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)\n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=1)\n    break\n\n# sample\nx_test, y_test = -73.83234, 40.67064 # ahead\nd1 = get_degrees('Aqueduct', y_test, x_test)\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Aqueduct']['LONG'], x_test], y=[TRACK_CENTER['Aqueduct']['LATI'], y_test],\n                         name=\"\", text=[\"\", \"\"], mode='lines+markers', marker_color='gold', showlegend=False), row=1, col=1)\nfig.add_annotation(x=x_test, y=y_test, text=f\"{d1:.2f} degree\", showarrow=False, row=1, col=1)\nx_test, y_test = -73.82876, 40.67021\nd2 = get_degrees('Aqueduct', y_test, x_test)\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Aqueduct']['LONG'], x_test], y=[TRACK_CENTER['Aqueduct']['LATI'], y_test],\n                         name=\"\", text=[\"\", \"\"], textposition=\"bottom center\", mode='lines+markers', marker_color='silver', showlegend=False), row=1, col=1)\nfig.add_annotation(x=x_test, y=y_test, text=f\"{d2:.2f} degree\", showarrow=False, row=1, col=1)\n## center\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Aqueduct']['LONG']], y=[TRACK_CENTER['Aqueduct']['LATI']], name='Center', mode='markers', marker_color='Black', showlegend=False), row=1, col=1)    \n    \n    \n\n\ntemp_ = track_table[\n            (track_table['track_id'] == \"Belmont\") &\n            (track_table['race_date'] == \"2019-05-27\") &\n            (track_table['race_number'] == 1)\n        ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    \n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)\n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=2)\n    break\n\n# sample\nx_test, y_test = -73.7212, 40.71286 # ahead\nd1 = get_degrees('Belmont', y_test, x_test)\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Belmont']['LONG'], x_test], y=[TRACK_CENTER['Belmont']['LATI'], y_test],\n                         name=\"\", text=[\"\", \"\"], mode='lines+markers', marker_color='gold', showlegend=False), row=1, col=2)\nfig.add_annotation(x=x_test, y=y_test, text=f\"{d1:.2f} degree\", showarrow=False, row=1, col=2)\nx_test, y_test = -73.72647, 40.7143\nd2 = get_degrees('Belmont', y_test, x_test)\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Belmont']['LONG'], x_test], y=[TRACK_CENTER['Belmont']['LATI'], y_test],\n                         name=\"\", text=[\"\", \"\"], textposition=\"bottom center\", mode='lines+markers', marker_color='silver', showlegend=False), row=1, col=2)\nfig.add_annotation(x=x_test, y=y_test, text=f\"{d2:.2f} degree\", showarrow=False, row=1, col=2)\n## center\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Belmont']['LONG']], y=[TRACK_CENTER['Belmont']['LATI']], name='Center', mode='markers', marker_color='Black', showlegend=False), row=1, col=2)    \n    \n    \n    \n    \n    \ntemp_ = track_table[\n            (track_table['track_id'] == \"Saratoga\") &\n            (track_table['race_date'] == \"2019-07-28\") &\n            (track_table['race_number'] == 1)\n        ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    \n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)\n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=3)\n    break\n\n# sample\nx_test, y_test = -73.76442, 43.07302 # ahead\nd1 = get_degrees('Saratoga', y_test, x_test)\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Saratoga']['LONG'], x_test], y=[TRACK_CENTER['Saratoga']['LATI'], y_test],\n                         name=\"\", text=[\"\", \"\"], mode='lines+markers', marker_color='gold', showlegend=False), row=1, col=3)\nfig.add_annotation(x=x_test, y=y_test, text=f\"{d1:.2f} degree\", showarrow=False, row=1, col=3)\nx_test, y_test = -73.76986, 43.0696\nd2 = get_degrees('Saratoga', y_test, x_test)\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Saratoga']['LONG'], x_test], y=[TRACK_CENTER['Saratoga']['LATI'], y_test],\n                         name=\"\", text=[\"\", \"\"], textposition=\"bottom center\", mode='lines+markers', marker_color='silver', showlegend=False), row=1, col=3)\nfig.add_annotation(x=x_test, y=y_test, text=f\"{d2:.2f} degree\", showarrow=False, row=1, col=3)\n## center\nfig.add_trace(go.Scatter(x=[TRACK_CENTER['Saratoga']['LONG']], y=[TRACK_CENTER['Saratoga']['LATI']], name='Center', mode='markers', marker_color='Black', showlegend=False), row=1, col=3)    \n    \n\n    \nfig.update_layout(title_text=f'<b>Racetrack</b>', template=TEMPLATE)\nfig.update_yaxes(title='latitude')\nfig.update_xaxes(title='longitude')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:20.891954Z","iopub.execute_input":"2022-11-07T04:55:20.892631Z","iopub.status.idle":"2022-11-07T04:55:23.154966Z","shell.execute_reply.started":"2022-11-07T04:55:20.892592Z","shell.execute_reply":"2022-11-07T04:55:23.153494Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## <b>2.3. <span style=\"color:#DAA520\">F</span>inish Line</b>  \nBased on track data ([Aqueduct](http://www.horseracing-tracks.com/tracks/ny/layoutAqu.html), [Belmont](https://gettingoutofthegate.com/belmont-park/), [Saratoga](https://www.offtrackbetting.com/racetracks/SAR/saratoga_race_course.html)), video data (e.g. [Aqueduct](https://www.youtube.com/watch?v=UL47e0BzTVk), [Belmont](https://www.youtube.com/watch?v=OOz24iiIqvI), [Saratoga](https://www.youtube.com/watch?v=J4hoVJ2DB7I)), and Google Earth, the approximate finish line was specified.  \nThe chart below shows the finish line (Red) defined, and the yellow point is the point when the horse crosses the finish line.","metadata":{}},{"cell_type":"code","source":"FINISH_LINE = {\n    'Aqueduct' : {\n        'LONG' : [-73.832176, -73.831104],\n        'LATI' : [40.671722, 40.671431]\n    },\n    'Belmont' : {\n        'LONG' : [-73.722744, -73.722348],\n        'LATI' : [40.713002, 40.713872]\n    },\n    'Saratoga' : {\n        'LONG' : [-73.770919, -73.770355],\n        'LATI' : [43.072531, 43.071781]\n    }\n}\ndef is_cross_pt(track_id, x11,y11, x12,y12):\n    # https://crazyj.tistory.com/140\n    x21 = FINISH_LINE[track_id]['LONG'][0]\n    y21 = FINISH_LINE[track_id]['LATI'][0]\n    x22 = FINISH_LINE[track_id]['LONG'][1]\n    y22 = FINISH_LINE[track_id]['LATI'][1]\n    \n    def is_divide_pt(x11,y11, x12,y12, x21,y21, x22,y22):\n        f1= (x12-x11)*(y21-y11) - (y12-y11)*(x21-x11)\n        f2= (x12-x11)*(y22-y11) - (y12-y11)*(x22-x11)\n        if f1*f2 < 0 :\n            return True\n        else:\n            return False\n\n    b1 = is_divide_pt(x11,y11, x12,y12, x21,y21, x22,y22)\n    b2 = is_divide_pt(x21,y21, x22,y22, x11,y11, x12,y12)\n    if b1 and b2:\n        return True\n    else:\n        return False\n    \n# visualization\nfig = make_subplots(\n    rows=1, cols=3,\n    specs=[[{\"type\": \"scatter\"}, {\"type\": \"scatter\"}, {\"type\": \"scatter\"}]],\n    subplot_titles=['< Aqueduct >', '< Belmont >', '< Saratoga >']\n)\n\n# finish line\nfig.add_trace(go.Scatter(x=FINISH_LINE['Aqueduct']['LONG'], y=FINISH_LINE['Aqueduct']['LATI'], name='finish', mode='markers+lines', marker_color='Red', showlegend=False), row=1, col=1)\nfig.add_trace(go.Scatter(x=FINISH_LINE['Belmont']['LONG'], y=FINISH_LINE['Belmont']['LATI'], name='finish', mode='markers+lines', marker_color='Red', showlegend=False), row=1, col=2)\nfig.add_trace(go.Scatter(x=FINISH_LINE['Saratoga']['LONG'], y=FINISH_LINE['Saratoga']['LATI'], name='finish', mode='markers+lines', marker_color='Red', showlegend=False), row=1, col=3)\n\n\ntemp_ = track_table[\n            (track_table['track_id'] == \"Aqueduct\") &\n            (track_table['race_date'] == \"2019-01-18\") &\n            (track_table['race_number'] == 1)\n        ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)    \n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    \n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    cur_['is_finish'] = cur_.apply(lambda x : is_cross_pt(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=1)\n    fin_ = cur_[cur_['is_finish']]\n    fig.add_trace(go.Scatter(x=[fin_['longitude'].values[0], fin_['longitude_lag1'].values[0]], y=[fin_['latitude'].values[0], fin_['latitude_lag1'].values[0]],\n                             marker_color='yellow', mode='markers+lines', showlegend=False), row=1, col=1) # finish\n    break\n\n\ntemp_ = track_table[\n            (track_table['track_id'] == \"Belmont\") &\n            (track_table['race_date'] == \"2019-05-27\") &\n            (track_table['race_number'] == 1)\n        ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)\n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    \n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    cur_['is_finish'] = cur_.apply(lambda x : is_cross_pt(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=2)\n    fin_ = cur_[cur_['is_finish']]\n    fig.add_trace(go.Scatter(x=[fin_['longitude'].values[0], fin_['longitude_lag1'].values[0]], y=[fin_['latitude'].values[0], fin_['latitude_lag1'].values[0]],\n                             marker_color='yellow', mode='markers+lines', showlegend=False), row=1, col=2) # finish\n    break\n    \ntemp_ = track_table[\n            (track_table['track_id'] == \"Saratoga\") &\n            (track_table['race_date'] == \"2019-07-28\") &\n            (track_table['race_number'] == 1)\n        ].copy()\n# temp_.loc[:, 'section'] = temp_.apply(lambda x : get_section(x['track_id'], x['latitude'], x['longitude']), axis=1)\nfor pn in temp_['program_number'].unique():\n    cur_ = temp_[temp_['program_number']==pn]\n    cur_ = cur_.sort_values(by=['trakus_index'], ascending=True)\n    cur_['latitude_lag1'] = cur_['latitude'].shift(1)\n    cur_['longitude_lag1'] = cur_['longitude'].shift(1)\n    \n    cur_.loc[:, 'section'] = cur_.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    stt_curve_index = cur_[cur_['section']]['trakus_index'].values[0]\n    end_curve_index = cur_[cur_['section']]['trakus_index'].values[1]\n    cur_.loc[cur_['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n    cur_.loc[(cur_['trakus_index']>=stt_curve_index)&(cur_['trakus_index']<end_curve_index), 'section'] = 'Curve'\n    cur_.loc[cur_['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n    \n    cur_['is_finish'] = cur_.apply(lambda x : is_cross_pt(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n    for sec, color in zip(['First Straight', 'Curve', 'Last Straight'], ['Black', DIRT_COLOR, 'Black']):\n        cur_sec_ = cur_[cur_['section']==sec]\n        fig.add_trace(go.Scatter(x=cur_sec_['longitude'], y=cur_sec_['latitude'], name=pn.strip(), mode='markers', marker_color=color, showlegend=False), row=1, col=3)\n    fin_ = cur_[cur_['is_finish']]\n    fig.add_trace(go.Scatter(x=[fin_['longitude'].values[0], fin_['longitude_lag1'].values[0]], y=[fin_['latitude'].values[0], fin_['latitude_lag1'].values[0]],\n                             marker_color='yellow', mode='markers+lines', showlegend=False), row=1, col=3) # finish\n    break\n    \n\n\n\nfig.update_layout(title_text=f'<b>Racetrack</b><br>: Red - Finish Line, Yellow - The moment the horse crosses the finish line', template=TEMPLATE)\nfig.update_yaxes(title='latitude')\nfig.update_xaxes(title='longitude')\nfig.show()\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:23.158945Z","iopub.execute_input":"2022-11-07T04:55:23.159438Z","iopub.status.idle":"2022-11-07T04:55:25.458294Z","shell.execute_reply.started":"2022-11-07T04:55:23.159374Z","shell.execute_reply":"2022-11-07T04:55:25.456780Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>3. <span style=\"color:#DAA520\">D</span>rive Analysis</b>  \nAs a result of the search ([An introduction to track conditions](https://edge.twinspires.com/racing/an-introduction-to-track-conditions-and-all-the-related-lingo/)), I thought that the driving data could be greatly affected by the track condition, so I only targeted the best conditions.  \nTherefore, only the Dirt 6 Furlong competitions with fast track status were selected. (293 races)  ","metadata":{}},{"cell_type":"code","source":"TRACK_LENGTH = 3960 # feets\n\nall_race_dict = {}\nrace_df = race_condition_table[(race_condition_table['course_type']=='Dirt') &\n                               (race_condition_table['distance_id']==600) & \n                               (race_condition_table['track_condition'].isin(['Fast']))\n                              ].reset_index(drop=True).copy()\n# for i, row in race_condition_table.iterrows():\nfor i, row in race_df.iterrows():\n#     print(row)\n    \n    # filtering\n    target_track_id = row['track_id']\n    target_course_type = row['course_type']\n    target_track_condition = row['track_condition']\n    target_distance_id = row['distance_id']\n    target_race_number = row['race_number']\n    target_race_date = row['race_date']\n    \n\n    cur_df = start_table[\n                        (start_table['track_id']==target_track_id) &\n                        (start_table['course_type']==target_course_type) &\n                        (start_table['track_condition']==target_track_condition) &\n                        (start_table['distance_id']==target_distance_id) &\n                        (start_table['race_number']==target_race_number) &\n                        (start_table['race_date']==target_race_date)\n                       ]\n    cur_df = cur_df.sort_values(by=['finishing_place'])\n    \n#     break\n    cur_race = pd.DataFrame()\n    for ranking in cur_df['finishing_place'].unique():\n        temp_ = cur_df[cur_df['finishing_place']==ranking]\n        \n        cur_track = pd.merge(temp_, track_table, how='left', on=['track_id', 'race_date', 'race_number', 'program_number'])\n        cur_track = cur_track.sort_values(by=['trakus_index']).reset_index(drop=True)\n        cur_track['latitude_lag1'] = cur_track['latitude'].shift(1)\n        cur_track['longitude_lag1'] = cur_track['longitude'].shift(1)\n\n        # speed\n        cur_track['distance'] = [geopy.distance.geodesic(vs[:2], vs[2:]).feet if sum(np.isnan(vs))==0 else 0 for vs in cur_track[['latitude', 'longitude', 'latitude_lag1', 'longitude_lag1']].values]\n        cur_track['speed'] = cur_track['distance'] / 0.25\n        cur_track['distance_cum'] = cur_track['distance'].cumsum() # feets : 6 furlongs = 3960 feets\n        \n        # angle btw two points\n        cur_track['latitude_diff'] = cur_track['latitude'].shift(-1) - cur_track['latitude']\n        cur_track['longitude_diff'] = cur_track['longitude'].shift(-1) - cur_track['longitude']\n        ## get angle with two points\n        cur_track['angle'] = cur_track.apply(lambda x : math.degrees(math.atan2(x['latitude_diff'], x['longitude_diff'])), axis=1)\n        cur_track['angle'] = (cur_track['angle'] + 360) % 360\n        ## get delta of angle\n        cur_track['steering_angle'] = cur_track['angle'].diff().shift(-1) # icludes NaN\n        \n        # angle btw static point and horse point\n        cur_track['rotation_angle'] = cur_track.apply(lambda x : get_degrees(target_track_id, x['latitude'], x['longitude']), axis=1)\n        \n        # section\n        cur_track.loc[:, 'section'] = cur_track.apply(lambda x : is_cross_curve(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n        if len(cur_track[cur_track['section']]['trakus_index'].values) == 0:\n            cur_track.loc[:, 'section'] = 'First Straight'\n        elif len(cur_track[cur_track['section']]['trakus_index'].values) == 1:\n            stt_curve_index = cur_track[cur_track['section']]['trakus_index'].values[0]\n            cur_track.loc[cur_track['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n            cur_track.loc[(cur_track['trakus_index']>=stt_curve_index), 'section'] = 'Curve'\n        else:            \n            stt_curve_index = cur_track[cur_track['section']]['trakus_index'].values[0]\n            end_curve_index = cur_track[cur_track['section']]['trakus_index'].values[1]\n            cur_track.loc[cur_track['trakus_index']<stt_curve_index, 'section'] = 'First Straight'\n            cur_track.loc[(cur_track['trakus_index']>=stt_curve_index)&(cur_track['trakus_index']<end_curve_index), 'section'] = 'Curve'\n            cur_track.loc[cur_track['trakus_index']>=end_curve_index, 'section'] = 'Last Straight'\n#         cur_track['section'] = cur_track.apply(lambda x : get_section(target_track_id, x['latitude'], x['longitude']), axis=1)\n        \n        # finish\n        cur_track['is_finish'] = cur_track.apply(lambda x : is_cross_pt(x['track_id'], x['longitude'], x['latitude'], x['longitude_lag1'], x['latitude_lag1']), axis=1)\n        \n        cur_track = cur_track.loc[:, 'trakus_index':]#.set_index('trakus_index')\n        cur_track['finishing_place'] = ranking            \n        cur_race = pd.concat([cur_race, cur_track])\n        \n    race_m = pd.merge(cur_df, cur_race, how='left', on=['finishing_place'])\n    all_race_dict[i] = race_m.copy()\n\n\nf = open(\"all_race.pkl\",\"wb\")\npickle.dump(all_race_dict, f)\n\n\n# # load\n# with open('../input/temp-race/all_race.pkl', 'rb') as f:\n#     all_race_dict = pickle.load(f)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:25.460569Z","iopub.execute_input":"2022-11-07T04:55:25.462122Z","iopub.status.idle":"2022-11-07T04:55:27.615595Z","shell.execute_reply.started":"2022-11-07T04:55:25.462052Z","shell.execute_reply":"2022-11-07T04:55:27.614329Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## <b>3.1. <span style=\"color:#DAA520\">F</span>irst Straight Line</b>  \nIt's the first acceleration section, and it's also the section where riders prepare to face the curve. So I think we need to figure out how fast the horse accelerates and runs consistently.  \nLet's look at the first straight section below.  ","metadata":{"execution":{"iopub.status.busy":"2022-10-06T05:01:24.406858Z","iopub.execute_input":"2022-10-06T05:01:24.407273Z","iopub.status.idle":"2022-10-06T05:01:28.313076Z","shell.execute_reply.started":"2022-10-06T05:01:24.407238Z","shell.execute_reply":"2022-10-06T05:01:28.311894Z"}}},{"cell_type":"markdown","source":"---\n### How fast does it accelerate?  \nThe 25th quartile for the first interval is 60 feet/s, and let's see when the horse first reaches (trakus_index) the 25th quartile speed.  \nAfter removing outliers based on IQR, I will calculate the ranking of 200 or more based on the final ranking.\n","metadata":{}},{"cell_type":"code","source":"def remove_iqr_out(series, q1=0.25, q2=0.75):\n    q3 = series.quantile(q2)\n    q1 = series.quantile(q1)\n    iqr = q3 - q1\n    l_, u_ = q1-1.5*iqr, q3+1.5*iqr\n    return (series * ((series >= l_) & (series <= u_))).replace(0, np.nan)\n\n\n# merge all race\nall_fs_df = pd.DataFrame()\nfor k, v in all_race_dict.items():\n    fs_df = v[v['section']=='First Straight']    \n    all_fs_df = pd.concat([all_fs_df, fs_df])\n    \n# median_v = int(all_fs_df['speed'].median())\nv_ = int(np.percentile(all_fs_df['speed'], q=25))\nprint(f\"First Straight 25th Quartile Velocity : {v_} feet/sec\")\n\ntest_df = all_fs_df.copy()\ntest_df = test_df[test_df['speed'] < v_]\n\ntest_df = test_df.groupby(['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place'])[['trakus_index']].max().reset_index()\ntest_df['trakus_index'] = test_df['trakus_index'] + 1\ntest_df['trakus_index'] = remove_iqr_out(test_df['trakus_index']) # remove outlier\ntest_df = test_df.dropna()\n\ntemp_ = test_df.groupby(['finishing_place'])[['trakus_index']].count().rename(columns={'trakus_index':'count'}).T\ndisplay(temp_)\ntarget_position = list((temp_.T[temp_.T >= 200]).dropna().index) # over 200\nprint(f\"I'll only consider ranks in the next order. : {target_position}\")\ntest_df = test_df[test_df['finishing_place'].isin(target_position)]\nprint(f\"Total Horse : {len(test_df)}\")\n\ntest_1_df = test_df[test_df['finishing_place']==1]\ntest_0_df = test_df[test_df['finishing_place']!=1]\n\nprint(\"\\nTime to reach the 25th quartile speed (trakus_index=1 is 0.25 seconds)\")\ntest_df = test_df.groupby(['finishing_place'])[['trakus_index']].mean()\ntest_df['time'] = 0.25 * test_df['trakus_index']\ntemp_cmap = get_continuous_cmap([DIRT_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR])\ndisplay(test_df.style.background_gradient(cmap=temp_cmap, axis=0))\n\nprint(f\"\\nAcceleration time for non-1st-place horses : {test_0_df['trakus_index'].mean() * 0.25}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:27.617077Z","iopub.execute_input":"2022-11-07T04:55:27.617444Z","iopub.status.idle":"2022-11-07T04:55:33.374691Z","shell.execute_reply.started":"2022-11-07T04:55:27.617411Z","shell.execute_reply":"2022-11-07T04:55:33.373495Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **INSIGHT**  \n> The horses that finished in first place had faster first acceleration time than other horses.  \n> The final ranking and average acceleration time are directly proportional.","metadata":{"execution":{"iopub.status.busy":"2022-10-07T13:58:26.140415Z","iopub.execute_input":"2022-10-07T13:58:26.141006Z","iopub.status.idle":"2022-10-07T13:58:26.152408Z","shell.execute_reply.started":"2022-10-07T13:58:26.140956Z","shell.execute_reply":"2022-10-07T13:58:26.150361Z"}}},{"cell_type":"markdown","source":"---\n### What is the average speed?  \nLet's see how fast the horses are overall in the first section. Since it is clear that the lowest speed is 0, I will cut the outliers to the upper boundaries based on the IQR.","metadata":{}},{"cell_type":"code","source":"def remove_iqr_out(series):\n    q3 = series.quantile(0.75)\n    q1 = series.quantile(0.25)\n    iqr = q3 - q1\n    l_, u_ = 0, q3+1.5*iqr\n    return (series * ((series >= l_) & (series <= u_))).replace(0, np.nan)\n\n# merge all race\nall_fs_df = pd.DataFrame()\nfor k, v in all_race_dict.items():\n    fs_df = v[v['section']=='First Straight']    \n    all_fs_df = pd.concat([all_fs_df, fs_df])\n    \nall_fs_df['speed'] = remove_iqr_out(all_fs_df['speed']) # remove outlier\nall_fs_df = all_fs_df.dropna()\n\ntemp_ = all_fs_df[['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']].drop_duplicates().\\\n        groupby(['finishing_place'])[['program_number']].count().rename(columns={'program_number':'count'}).T\ndisplay(temp_)\ntarget_position = list((temp_.T[temp_.T >= 200]).dropna().index) # over 200\nprint(f\"I'll only consider ranks in the next order. : {target_position}\")\n\nall_fs_df = all_fs_df[all_fs_df['finishing_place'].isin(target_position)]\nprint(f\"Total Horse : {len(all_fs_df[['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']].drop_duplicates())}\")\n\nprint(\"\\nAverage speed over the first straight section\")\ntemp_ = all_fs_df.groupby(['track_id', 'finishing_place'])[['speed']].mean().unstack()\n\ntemp_cmap = get_continuous_cmap([BACK_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR, DIRT_COLOR])\ndisplay(temp_.style.background_gradient(cmap=temp_cmap, axis=1))\n\n\n# visualization\ntemp_ = all_fs_df.groupby(['track_id', 'finishing_place', 'trakus_index'])[['speed']].mean().unstack()#.dropna(axis=1)\ntemp_.columns = [c[1] for c in temp_.columns]\ntemp_ = temp_.T\ntemp_.columns = [f\"{c[0]}_{c[1]}\" for c in temp_.columns] # track_rank\n\nfig = go.Figure()\nfor track_name, marker_sym in zip(['Aqueduct', 'Belmont', 'Saratoga'], ['circle', 'square', 'diamond']):\n    cur_cols = [f\"{track_name}_{r}\" for r in target_position]\n    cur_ = temp_[cur_cols].dropna().copy()\n    for i, rank in enumerate(cur_.columns):\n        color_idx = (len(cur_.columns) - i) / len(cur_.columns)\n        fig.add_trace(go.Scatter(x=cur_.index, y=cur_[rank], name=f\"finish rank {rank.split('_')[1]}\", legendgroup=track_name, legendgrouptitle_text=track_name,\n                                 mode='markers', marker_color=f\"rgb{bg_cmap(color_idx, bytes=True)}\", marker_symbol=marker_sym))\n\nfig.update_layout(title_text=f'<b>Average Velocity by Final Rank</b>', template=TEMPLATE, hovermode=\"x\", height=400)\nfig.update_yaxes(title='average velocity')\nfig.update_xaxes(title='trakus_index')\nfig.show()\n\n\nfig = go.Figure()\nfor track_name, marker_sym in zip(['Aqueduct', 'Belmont', 'Saratoga'], ['circle', 'square', 'diamond']):\n    cur_cols = [f\"{track_name}_{r}\" for r in target_position]\n    cur_ = temp_[cur_cols].dropna().copy()\n    for i, rank in enumerate(cur_.columns):\n        color_idx = (len(cur_.columns) - i) / len(cur_.columns)\n        fig.add_trace(go.Scatter(x=cur_.index, y=1.5**cur_[rank], name=f\"finish rank {rank.split('_')[1]}\", legendgroup=track_name, legendgrouptitle_text=track_name,\n                                 mode='markers', marker_color=f\"rgb{bg_cmap(color_idx, bytes=True)}\", marker_symbol=marker_sym))\n\nfig.update_layout(title_text=f'<b>Average Velocity by Final Rank (Scale)</b>', template=TEMPLATE, hovermode=\"x\", height=400)\nfig.update_yaxes(title='1.5^velocity')\nfig.update_xaxes(title='trakus_index')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:33.376140Z","iopub.execute_input":"2022-11-07T04:55:33.376538Z","iopub.status.idle":"2022-11-07T04:55:39.191636Z","shell.execute_reply.started":"2022-11-07T04:55:33.376465Z","shell.execute_reply":"2022-11-07T04:55:39.190429Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **INSIGHT**  \n> The average speed of the top horse in the first straight section is the fastest.  \n> The average speed and rank in this interval are directly proportional.  ","metadata":{"execution":{"iopub.status.busy":"2022-10-13T05:27:27.087991Z","iopub.execute_input":"2022-10-13T05:27:27.088399Z","iopub.status.idle":"2022-10-13T05:27:27.095774Z","shell.execute_reply.started":"2022-10-13T05:27:27.088366Z","shell.execute_reply":"2022-10-13T05:27:27.094213Z"}}},{"cell_type":"markdown","source":"> **NOTE**  \n> Each track has different trakus_index lengths for each section, so we have to distinguish this.  \n> On the other hand, in the case of acceleration check, it was okay because there were enough trakus_index for each track just up to the target speed.","metadata":{"execution":{"iopub.status.busy":"2022-10-16T23:48:36.072133Z","iopub.execute_input":"2022-10-16T23:48:36.072563Z","iopub.status.idle":"2022-10-16T23:48:36.079326Z","shell.execute_reply.started":"2022-10-16T23:48:36.072517Z","shell.execute_reply":"2022-10-16T23:48:36.078164Z"}}},{"cell_type":"markdown","source":"---\n### How about track angle?  \nYou will know that I calculated the angle for each position to know the relative position on each track. In other words, the larger the angle, the more ahead of other horses.  \n> Interpretation should be noted as it includes errors in position, angle, etc.  \n  \nBecause the angles are different for each track, I checked the angle at the last point of the straight line for each track.","metadata":{}},{"cell_type":"code","source":"# merge all race\nall_fs_df = pd.DataFrame()\nfor k, v in all_race_dict.items():\n    fs_df = v[v['section']=='First Straight']    \n    all_fs_df = pd.concat([all_fs_df, fs_df])\n    \nall_fs_df['speed'] = remove_iqr_out(all_fs_df['speed']) # remove outlier\nall_fs_df = all_fs_df.dropna()\nall_fs_df = all_fs_df[all_fs_df['finishing_place'].isin(target_position)]\nall_fs_df['jockey_1st'] = all_fs_df['jockey'].apply(lambda x : x if x in ['Jose Lezcano', 'Manuel Franco'] else 'others')\n\ntemp_ = all_fs_df.drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'finishing_place'], keep='last').copy()\ntemp_['angle'] =  temp_.apply(lambda x : get_degrees(x['track_id'], x['latitude'], x['longitude']), axis=1)\ntemp_ = temp_.groupby(['track_id', 'finishing_place'])[['angle']].mean().unstack()\n\ntemp_cmap = get_continuous_cmap([BACK_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR, DIRT_COLOR])\ndisplay(temp_.style.background_gradient(cmap=temp_cmap, axis=1))\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T05:13:10.892493Z","iopub.execute_input":"2022-11-07T05:13:10.893010Z","iopub.status.idle":"2022-11-07T05:13:16.659241Z","shell.execute_reply.started":"2022-11-07T05:13:10.892971Z","shell.execute_reply":"2022-11-07T05:13:16.657997Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **INSIGHT**  \n> On the Aqueduct and Belmont tracks, the top horse had a big angle. This means that in the last section of the straight line, they are on average in the leading position.  \n> For Saratoga tracks, the results are quite different. For the top horse, it is placed third on average.  ","metadata":{}},{"cell_type":"markdown","source":"## <b>3.2. <span style=\"color:#DAA520\">C</span>urve</b>  \nEarlier, we found that in the case of the No. 1 horse, it runs the fastest straight section and enters the curve ahead of the other horses. Then let's check if the driving characteristics are different for each horse in this curved section.  ","metadata":{}},{"cell_type":"markdown","source":"---\n### What is the average speed?  \nAs before, let's check the average speed in curve section first.  ","metadata":{}},{"cell_type":"code","source":"def remove_iqr_out(series):\n    q3 = series.quantile(0.75)\n    q1 = series.quantile(0.25)\n    iqr = q3 - q1\n    l_, u_ = 0, q3+1.5*iqr\n    return (series * ((series >= l_) & (series <= u_))).replace(0, np.nan)\n\n# merge all race\nall_fs_df = pd.DataFrame()\nfor k, v in all_race_dict.items():\n    fs_df = v[(v['section']=='Curve')&(v['trakus_index']<=250)]    \n#     fs_df = v[(v['section']=='First Straight')]  \n    all_fs_df = pd.concat([all_fs_df, fs_df])\n    \nall_fs_df['speed'] = remove_iqr_out(all_fs_df['speed']) # remove outlier\nall_fs_df = all_fs_df.dropna()\n\ntemp_ = all_fs_df[['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']].drop_duplicates().\\\n        groupby(['finishing_place'])[['program_number']].count().rename(columns={'program_number':'count'}).T\ndisplay(temp_)\ntarget_position = list((temp_.T[temp_.T >= 200]).dropna().index) # over 200\nprint(f\"I'll only consider ranks in the next order. : {target_position}\")\n\nall_fs_df = all_fs_df[all_fs_df['finishing_place'].isin(target_position)]\nprint(f\"Total Horse : {len(all_fs_df[['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']].drop_duplicates())}\")\n\nprint(\"\\nAverage speed over the first straight section\")\ntemp_ = all_fs_df.groupby(['track_id', 'finishing_place'])[['speed']].mean().unstack()\ntemp_cmap = get_continuous_cmap([BACK_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR, DIRT_COLOR])\ndisplay(temp_.style.background_gradient(cmap=temp_cmap, axis=1))\n\n\n# visualization\ntemp_ = all_fs_df.groupby(['track_id', 'finishing_place', 'trakus_index'])[['speed']].mean().unstack().dropna(axis=1)\ntemp_.columns = [c[1] for c in temp_.columns]\ntemp_ = temp_.T\ntemp_.columns = [f\"{c[0]}_{c[1]}\" for c in temp_.columns] # track_rank\n\nfig = go.Figure()\nfor track_name, marker_sym in zip(['Aqueduct', 'Belmont', 'Saratoga'], ['circle', 'square', 'diamond']):\n    cur_cols = [f\"{track_name}_{r}\" for r in target_position]\n    cur_ = temp_[cur_cols].dropna().copy()\n    for i, rank in enumerate(cur_.columns):\n        color_idx = (len(cur_.columns) - i) / len(cur_.columns)\n        fig.add_trace(go.Scatter(x=cur_.index, y=cur_[rank], name=f\"finish rank {rank.split('_')[1]}\", legendgroup=track_name, legendgrouptitle_text=track_name,\n                                 mode='markers', marker_color=f\"rgb{bg_cmap(color_idx, bytes=True)}\", marker_symbol=marker_sym))\n\n# fig = go.Figure()\n# for i, rank in enumerate(temp_.columns):\n#     color_idx = (len(temp_.columns) - i) / len(temp_.columns)\n#     fig.add_trace(go.Scatter(x=temp_.index, y=temp_[rank], name=f\"finish rank {rank}\",\n#                              mode='markers', marker_color=f\"rgb{bg_cmap(color_idx, bytes=True)}\"))\n\nfig.update_layout(title_text=f'<b>Average Velocity by Final Rank</b>', template=TEMPLATE, hovermode=\"x\", height=400)\nfig.update_yaxes(title='average velocity')\nfig.update_xaxes(title='trakus_index')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:44.730227Z","iopub.execute_input":"2022-11-07T04:55:44.730587Z","iopub.status.idle":"2022-11-07T04:55:51.068253Z","shell.execute_reply.started":"2022-11-07T04:55:44.730555Z","shell.execute_reply":"2022-11-07T04:55:51.066895Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **INSIGHT**  \n> The average speed of the first place horses is the fastest in curve section.  \n> In the curve, the horse's speed gradually decreases.  \n> For some reason, the average speed on Aqueduct track is the lowest.  ","metadata":{}},{"cell_type":"markdown","source":"---\n### How long did the horses turn the corner?  \nWe have to figure out how far the horse travels in its inner path. The more inward the horse is driven, the more advantageous it can be in terms of distance, time, and energy.  \nSince there is a trackus_index, we can find the distance between the two points. And we add these distances up for the curve.","metadata":{}},{"cell_type":"code","source":"def remove_iqr_out(series):\n    q3 = series.quantile(0.75)\n    q1 = series.quantile(0.25)\n    iqr = q3 - q1\n    l_, u_ = 0, q3+1.5*iqr\n    return (series * ((series >= l_) & (series <= u_))).replace(0, np.nan)\n\n# merge all race\nall_fs_df = pd.DataFrame()\nfor k, v in all_race_dict.items():\n    fs_df = v[(v['section']=='Curve')&(v['trakus_index']<=250)]\n    all_fs_df = pd.concat([all_fs_df, fs_df])\n    \nall_fs_df['speed'] = remove_iqr_out(all_fs_df['speed']) # remove outlier\nall_fs_df = all_fs_df.dropna()\n\n# # get center of curve by track\n# first_df = all_fs_df.drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'program_number'], keep='first')\n# curve_first = first_df.groupby(['track_id'])[['latitude', 'longitude']].mean()\n# last_df = all_fs_df.drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'program_number'], keep='last')\n# curve_last = last_df.groupby(['track_id'])[['latitude', 'longitude']].mean()\n# CURVE_CENTER = {}\n# for track_id in ['Aqueduct', 'Belmont', 'Saratoga']:\n#     CURVE_CENTER[track_id] = {}\n#     CURVE_CENTER[track_id]['LONG'] = (curve_first.loc[track_id, 'longitude'] + curve_last.loc[track_id, 'longitude']) / 2\n#     CURVE_CENTER[track_id]['LATI'] = (curve_first.loc[track_id, 'latitude'] + curve_last.loc[track_id, 'latitude']) / 2\n\n# # add radius\n# def get_dist(cx, cy, long, lati):\n#     return geopy.distance.geodesic([cy, cx], [lati, long]).feet\n# def get_radius(track_id, longitude, latitude):\n#     cur_cx, cur_cy = CURVE_CENTER[track_id]['LONG'], CURVE_CENTER[track_id]['LATI']\n#     return get_dist(cur_cx, cur_cy, longitude, latitude)\n# all_fs_df['radius'] = all_fs_df.apply(lambda x : get_radius(x['track_id'], x['longitude'], x['latitude']), axis=1)\n    \nall_fs_df = all_fs_df[all_fs_df['finishing_place'].isin(target_position)]\n\n\ntemp_ = all_fs_df.groupby(['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place'])[['distance']].sum().reset_index()\ntemp_ = temp_.groupby(['track_id', 'finishing_place'])[['distance']].mean().unstack()\n\nprint(f\"Average total travel distance over corner section (unit : ft)\")\ntemp_cmap = get_continuous_cmap([DIRT_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR])\ndisplay(temp_.style.background_gradient(cmap=temp_cmap, axis=1))\n\n\ntemp_ = all_fs_df.groupby(['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place'])[['trakus_index']].count()\ntemp_ = temp_.groupby(['track_id', 'finishing_place'])[['trakus_index']].mean()\ntemp_ = temp_ * 0.25\ntemp_.columns = ['time']\ntemp_ = temp_.unstack()\n\nprint(f\"\\n\\nAverage total time over corner section (unit : sec)\")\ntemp_cmap = get_continuous_cmap([DIRT_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR])\ndisplay(temp_.style.background_gradient(cmap=temp_cmap, axis=1))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:51.070345Z","iopub.execute_input":"2022-11-07T04:55:51.071082Z","iopub.status.idle":"2022-11-07T04:55:57.163709Z","shell.execute_reply.started":"2022-11-07T04:55:51.071041Z","shell.execute_reply":"2022-11-07T04:55:57.162491Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **INSIGHT**  \n> Unexpectedly, the 1st place horse ran a little longer on the curve. (1st table, Exclude Saratoga track)  \n> However, in terms of how long the horse stayed in the curve, the 1st horse passed the fastest. (2nd table)  \n> In other words, in the case of the 1st place horse, it passed quickly even if it lost money on the distance. (Exclude Saratoga track)  \n> On Saratoga track, however, it ideally passed the shortest section the fastest.","metadata":{}},{"cell_type":"markdown","source":"---\n### How stable is the cornering?\nI'll check it out from two main points of view.\n- The similarity between the first and second half of the cornering  \n  - When a horse is moving forward in a constant curve, its trajectory is symmetrical\n- The variability of the steering angle.  \n  - If the horse wobbles less in the curved section, the variability of the steering angle will be low.  \n  \n(To find the center in the curve, I averaged the first and last positions in the curve of all horses.)   \nBelow is the cornering information for a horse. First, if we look at the radius of each position in the curve, the radius is approximately symmetrical. And what we see as the center point of the curve is the smallest radius. I think the corner track is not exactly a semicircular shape. (Of course, there may be errors in the distance calculation, but the effect of track shape seems to be greater in that it shows symmetry.)  \nNext, if we look at the chart on the right, we can see the difference between the center point of the trakus_index and the minimum radius point. Ideally, if the horse passes through a corner at the same speed, these two points should be the same. However, since the minimum radius point comes further ahead, we can see that it takes longer to pass through the back section relative to the center of the corner. This can also be seen from the mean speed distribution over the corner section. (The average speed of the horses in the corner section gradually decreases.)","metadata":{}},{"cell_type":"code","source":"def remove_iqr_out(series):\n    q3 = series.quantile(0.75)\n    q1 = series.quantile(0.25)\n    iqr = q3 - q1\n    l_, u_ = 0, q3+1.5*iqr\n    return (series * ((series >= l_) & (series <= u_))).replace(0, np.nan)\n\n# merge all race\nall_fs_df = pd.DataFrame()\nfor k, v in all_race_dict.items():\n    fs_df = v[(v['section']=='Curve')&(v['trakus_index']<=250)]\n    all_fs_df = pd.concat([all_fs_df, fs_df])\n    \nall_fs_df['speed'] = remove_iqr_out(all_fs_df['speed']) # remove outlier\nall_fs_df = all_fs_df.dropna()\n\n# get center of curve by track\nfirst_df = all_fs_df.drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'program_number'], keep='first')\ncurve_first = first_df.groupby(['track_id'])[['latitude', 'longitude']].mean()\nlast_df = all_fs_df.drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'program_number'], keep='last')\ncurve_last = last_df.groupby(['track_id'])[['latitude', 'longitude']].mean()\nCURVE_CENTER = {}\nfor track_id in ['Aqueduct', 'Belmont', 'Saratoga']:\n    CURVE_CENTER[track_id] = {}\n    CURVE_CENTER[track_id]['LONG'] = (curve_first.loc[track_id, 'longitude'] + curve_last.loc[track_id, 'longitude']) / 2\n    CURVE_CENTER[track_id]['LATI'] = (curve_first.loc[track_id, 'latitude'] + curve_last.loc[track_id, 'latitude']) / 2\n\n# add radius\ndef get_dist(cx, cy, long, lati):\n    return geopy.distance.geodesic([cy, cx], [lati, long]).feet\ndef get_radius(track_id, longitude, latitude):\n    cur_cx, cur_cy = CURVE_CENTER[track_id]['LONG'], CURVE_CENTER[track_id]['LATI']\n    return get_dist(cur_cx, cur_cy, longitude, latitude)\n    \n    \nall_fs_df = all_fs_df[all_fs_df['finishing_place'].isin(target_position)]\nall_fs_df['radius'] = all_fs_df.apply(lambda x : get_radius(x['track_id'], x['longitude'], x['latitude']), axis=1)\n\n\n# # add steering angle\n# test_df = all_fs_df.copy()\n# test_df = test_df.set_index(['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']).sort_index()\n# result_df = pd.DataFrame()\n# for idxs in test_df.index:\n#     cur_df = test_df.loc[idxs, :].copy()    \n#     cur_df['latitude_diff'] = cur_df['latitude'].shift(-1) - cur_df['latitude']\n#     cur_df['longitude_diff'] = cur_df['longitude'].shift(-1) - cur_df['longitude']\n#     # get angle with two points\n#     cur_df['angle'] = cur_df.apply(lambda x : math.degrees(math.atan2(x['latitude_diff'], x['longitude_diff'])), axis=1)\n#     cur_df['angle'] = (cur_df['angle'] + 360) % 360\n#     # get delta of angle\n#     cur_df['steering_angle'] = cur_df['angle'].diff().shift(-1) # icludes NaN\n#     result_df = pd.concat([result_df, cur_df.reset_index()], ignore_index=True)    \n# all_fs_df = test_df.copy()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:55:57.165586Z","iopub.execute_input":"2022-11-07T04:55:57.166778Z","iopub.status.idle":"2022-11-07T04:56:45.916420Z","shell.execute_reply.started":"2022-11-07T04:55:57.166730Z","shell.execute_reply":"2022-11-07T04:56:45.915276Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = make_subplots(\n    rows=1, cols=2,\n    specs=[[{\"type\": \"scatter\"}, {\"type\": \"scatter\"}]],\n    subplot_titles=['< Points & Radius >', '< Radius >']\n)\n\n# sample visualization\n## potins & radius\nsample_df = all_fs_df[(all_fs_df['track_id']=='Aqueduct') & (all_fs_df['race_date']=='2019-01-18') & (all_fs_df['race_number']==1) & (all_fs_df['program_number']=='4  ')]\nfig.add_trace(go.Scatter(x=sample_df['longitude'], y=sample_df['latitude'], name=\"\", text=[\"\", \"\"], mode='lines+markers', marker_color=DIRT_COLOR, showlegend=False))\nfor tidx in [94, 116, 139, 162, 185]:\n    x_test, y_test = sample_df[sample_df['trakus_index']==tidx]['longitude'].values[0], sample_df[sample_df['trakus_index']==tidx]['latitude'].values[0]\n    cur_r = get_dist(CURVE_CENTER['Aqueduct']['LONG'], CURVE_CENTER['Aqueduct']['LATI'], x_test, y_test)\n    fig.add_trace(go.Scatter(x=[CURVE_CENTER['Aqueduct']['LONG'], x_test], y=[CURVE_CENTER['Aqueduct']['LATI'], y_test],\n                             name=\"\", text=[\"\", \"\"], mode='lines+markers', marker_color='gray', showlegend=False), row=1, col=1)\n    fig.add_annotation(x=x_test, y=y_test, text=f\"{cur_r:.1f} ft\", showarrow=False, yanchor='bottom', row=1, col=1)\n\n## curve center\nfig.add_trace(go.Scatter(x=[CURVE_CENTER['Aqueduct']['LONG']], y=[CURVE_CENTER['Aqueduct']['LATI']], name=\"\", text=[\"\", \"\"],\n                         mode='lines+markers', marker_color='darkred', showlegend=False), row=1, col=1)\nfig.update_yaxes(title='latitude', row=1, col=1)\nfig.update_xaxes(title='longitude', row=1, col=1)\n\n## radius distribution\nfig.add_trace(go.Scatter(x=sample_df['trakus_index'], y=sample_df['radius'],\n                         name=\"\", text=[\"\", \"\"], mode='lines+markers', marker_color=DIRT_COLOR, showlegend=False), row=1, col=2)\nfig.add_vline(x=int((sample_df['trakus_index'].iloc[0] + sample_df['trakus_index'].iloc[-1]) / 2), line_width=3, line_color=\"green\",\n              annotation_text=\"middle of a trakus_index\", annotation_position=\"right\", row=1, col=2)\nfig.add_vline(x=sample_df[sample_df['radius']==sample_df['radius'].min()]['trakus_index'].values[0], line_width=3, line_color=\"blue\",\n              annotation_text=\"point with a min radius\", annotation_position=\"left\", row=1, col=2)\nfig.update_yaxes(title='radius (ft)', range=[440, 490], row=1, col=2)\nfig.update_xaxes(title='trakus_index', row=1, col=2)\n\nfig.update_layout(title_text=f'<b>Sample Curve Informations in Aqueduct Track</b> : 2019-01-18, race_number=1, program_number=4', template=TEMPLATE)\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:56:45.917794Z","iopub.execute_input":"2022-11-07T04:56:45.918115Z","iopub.status.idle":"2022-11-07T04:56:46.118094Z","shell.execute_reply.started":"2022-11-07T04:56:45.918087Z","shell.execute_reply":"2022-11-07T04:56:46.116929Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Based on the previous discussion, I organized data related to cornering.  \n- steering angle : We can find the angle of change with two directly adjacent velocities. I calculated how much the angle changed counterclockwise. Therefore, the stability of the cornering can be determined by the standard deviation of the steering angle.  \n- area : To see how wide the cornering was, I calculated the area passed based on the cornering center. We can divide the circle into smaller pieces and get the approximate area as a subset. ([reference image](https://mathworld.wolfram.com/Circle.html)) We can calculate this by the distance and radius between the two points.  \n- mid trakus idx - min radius trakus idx : This corresponds to the right side of the previous chart. This is the difference between the index at the half-cornering point and the index at the point with the shortest radius in the cornering section. The greater the difference, the greater the speed change based on half the cornering.  ","metadata":{}},{"cell_type":"code","source":"test_df = all_fs_df.copy()\nkey_cols = ['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']\nmin_radius = all_fs_df.groupby(key_cols)[['radius']].min().reset_index()\nmin_radius = min_radius.rename(columns={'radius':'min_radius'})\ntest_df = pd.merge(test_df, min_radius, on=key_cols)\ntest_df['radius_gap'] = test_df['radius'] - test_df['min_radius']\n\nmin_radius_tidx = test_df[test_df['radius_gap']==0].drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place'], keep='first')[key_cols+['trakus_index']]\nmin_radius_tidx = min_radius_tidx.rename(columns={'trakus_index':'min_radius_tidx'})\ntest_df = pd.merge(test_df, min_radius_tidx, on=key_cols)\ntest_df['mr_tidx_gap'] = test_df['trakus_index'] - test_df['min_radius_tidx']\n\n\nmid_tidx = ((test_df.groupby(key_cols)[['trakus_index']].min() + test_df.groupby(key_cols)[['trakus_index']].max())/2).reset_index()\nmid_tidx = mid_tidx.rename(columns={'trakus_index':'mid_tidx'})\nmid_tidx['mid_tidx'] = mid_tidx['mid_tidx'].astype(int)\ntest_df = pd.merge(test_df, mid_tidx, on=key_cols)\ntest_df['mid_tidx_gap'] = test_df['trakus_index'] - test_df['mid_tidx']\n\n# area\ntest_df['area'] = test_df['distance'] * test_df['radius'] / 2\n\n# lr_radius_gap = (test_df[test_df['mid_tidx_gap']>0].groupby(key_cols)[['area']].sum() / test_df[test_df['mid_tidx_gap']<0].groupby(key_cols)[['area']].sum()).reset_index()\n# lr_tidx_gap = (test_df.groupby(key_cols)[['mid_tidx_gap']].max() + test_df.groupby(key_cols)[['mid_tidx_gap']].min()).reset_index() # right : +, left : -\n\n# gap_df = pd.merge(lr_radius_gap, lr_tidx_gap, on=key_cols).rename(columns={'area':'gap_mean_radius', 'mid_tidx_gap':'gap_tidx'})\n# gap_df['gap_mean_radius'] = np.abs(gap_df['gap_mean_radius'])\n# gap_df['gap_tidx'] = np.abs(gap_df['gap_tidx'])\n\n# result\n## remove outlier\nfor c in ['steering_angle', 'area']:\n    test_df[c] = remove_iqr_out(test_df[c])\n    \nsa_df = test_df.groupby(key_cols)[['steering_angle']].std().reset_index()\nsa_df = sa_df.rename(columns={'steering_angle' : 'standard deviation of steering angle'})\ntemp_sa_df = sa_df.groupby(['track_id', 'finishing_place'])[['standard deviation of steering angle']].mean().unstack()\ntemp_cmap = get_continuous_cmap([DIRT_COLOR, DIRT_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR])\ndisplay(temp_sa_df.style.background_gradient(cmap=temp_cmap, axis=1))\n# display(temp_)\n\nar_df = test_df.groupby(key_cols)[['area']].mean()\ntemp_ar_df = ar_df.groupby(['track_id', 'finishing_place'])[['area']].mean().unstack()\ntemp_cmap = get_continuous_cmap([DIRT_COLOR, DIRT_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR])\ndisplay(temp_ar_df.style.background_gradient(cmap=temp_cmap, axis=1))\n\ntid_df = test_df.groupby(key_cols)[['min_radius_tidx', 'mid_tidx']].mean().reset_index()\ntid_df['tidx_gap'] = tid_df['mid_tidx'] - tid_df['min_radius_tidx']\ntid_df = tid_df.rename(columns={'tidx_gap' : 'mid trakus idx - min radius trakus idx'})\ntemp_tid_df = tid_df.groupby(['track_id', 'finishing_place'])[['mid trakus idx - min radius trakus idx']].mean().unstack()\ntemp_cmap = get_continuous_cmap([DIRT_COLOR, DIRT_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR])\ndisplay(temp_tid_df.style.background_gradient(cmap=temp_cmap, axis=1))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:56:46.120023Z","iopub.execute_input":"2022-11-07T04:56:46.120920Z","iopub.status.idle":"2022-11-07T04:56:47.841491Z","shell.execute_reply.started":"2022-11-07T04:56:46.120873Z","shell.execute_reply":"2022-11-07T04:56:47.840341Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **INSIGHT**  \n> I think the strength of the first place horse is the steering angle deviation. It shows the low value on all tracks, and it means that it is cornering with a smaller shake than other horses.  \n> In terms of area, the cornering of the first horse was relatively wide. I think the horse in 6th place cornering the innermost.  \n> For the first horse, the change in cornering speed appears to be moderate. And since there is a difference in cornering average speed by rank in the previous result (What is the average speed?), this should be considered. In other words, in the case of the top horse, the average speed was high, but there was a deviation.","metadata":{}},{"cell_type":"markdown","source":"---\n### Centrifugal force applied to the rider during cornering\nCornering is part of centrifugal motion, so the rider receives centrifugal force. ([Wiki-Centrifugal_force](https://en.wikipedia.org/wiki/Centrifugal_force)) If we know the centrifugal force that horses and riders receive, we can prepare for it through training.  \n- Centrifugal force (kg·m/sec^2) : $F = ma = \\frac{mv^{2}}{r}$, $a = \\frac{v^{2}}{r}$  \n  - m (kg) : Mass, weight of horse, weight of rider, etc  \n  - v (m/sec) : Acceleration, using instantaneous acceleration at each position \n  - r (m) : Radius, distance from the center of rotation at each position  \n  \nIn fact, mass varies from person to person (or from horse to horse), so we only need to find acceleration and compare it to gravitational acceleration. Gravity acceleration is about *9.80665* m/s² on a 1G basis. If \"a\" calculated above is 1G, the gravity that a person receives during cornering becomes equal to his or her weight. (the same is true of horses)","metadata":{"execution":{"iopub.status.busy":"2022-10-27T01:46:30.023029Z","iopub.execute_input":"2022-10-27T01:46:30.023587Z","iopub.status.idle":"2022-10-27T01:46:30.031076Z","shell.execute_reply.started":"2022-10-27T01:46:30.023539Z","shell.execute_reply":"2022-10-27T01:46:30.029978Z"}}},{"cell_type":"code","source":"def get_cfa(v, r):\n    # calc a in centrifugal force\n    # currently, velocity is feets/sec and radius is feets : we need to change it to meter\n    adj = 3.2808\n    r_ = r / adj # m\n    v_ = v / adj # m/s\n    a = (v_**2/r_) # m/s^2\n#     f = m*(v_**2/r_) # kg m / s^2 # Centrifugal force\n    return a\n\ndef remove_iqr_out(series):\n    q3 = series.quantile(0.75)\n    q1 = series.quantile(0.25)\n    iqr = q3 - q1\n    l_, u_ = 0, q3+1.5*iqr\n    return (series * ((series >= l_) & (series <= u_))).replace(0, np.nan)\n\n# merge all race\nall_fs_df = pd.DataFrame()\nfor k, v in all_race_dict.items():\n    fs_df = v[(v['section']=='Curve')&(v['trakus_index']<=250)]\n    all_fs_df = pd.concat([all_fs_df, fs_df])\n    \nall_fs_df['speed'] = remove_iqr_out(all_fs_df['speed']) # remove outlier\nall_fs_df = all_fs_df.dropna()\n\nall_fs_df = all_fs_df[all_fs_df['finishing_place'].isin(target_position)]\nall_fs_df['radius'] = all_fs_df.apply(lambda x : get_radius(x['track_id'], x['longitude'], x['latitude']), axis=1)\nall_fs_df['cf_accel'] = all_fs_df.apply(lambda x : get_cfa(x['speed'], x['radius']), axis=1)\nall_fs_df['cf_g'] = all_fs_df['cf_accel'] / 9.80665 # represent G-force\n\nkey_cols = ['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']\n\ntemp_ = all_fs_df.groupby(key_cols).agg({'cf_g':['mean', 'max']})\ntemp_.columns = ['mean_cf_g', 'max_cf_g']\ntemp_ = temp_.reset_index()\ntemp_ = temp_.groupby(['track_id', 'finishing_place'])[['mean_cf_g', 'max_cf_g']].mean()#.unstack()\ntemp_ = temp_.stack().unstack(1)\n\ntemp_cmap = get_continuous_cmap([BACK_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR, DIRT_COLOR])\ndisplay(temp_.style.background_gradient(cmap=temp_cmap, axis=1))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:56:47.843237Z","iopub.execute_input":"2022-11-07T04:56:47.844112Z","iopub.status.idle":"2022-11-07T04:57:38.746317Z","shell.execute_reply.started":"2022-11-07T04:56:47.844077Z","shell.execute_reply":"2022-11-07T04:57:38.744885Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\"mean_cf_g\" is the average rotational acceleration in the corner section for each horse, then averaged by rank.  \nConversely, \"max_cf_g\" is the average of the maximum rotational acceleration at the corner interval for each horse, then by rank.  \n  \n> **INSIGHT**  \n> In terms of rotational acceleration, the top horse with a higher average speed has a higher rotational acceleration because it is proportional to the speed square.  \n> The rotational acceleration is approximately 0.3 G, which means that about 0.3 of your weight is applied by force.  \n> If the horse is doing cornering training, exposing it to a higher level of G-force would also help.  ","metadata":{}},{"cell_type":"markdown","source":"## <b>3.3. <span style=\"color:#DAA520\">L</span>ast Straight Line</b>  ","metadata":{}},{"cell_type":"markdown","source":"---\n### What is the average speed?  \nLet's check the average speed in last straight section.  ","metadata":{}},{"cell_type":"code","source":"def remove_iqr_out(series):\n    q3 = series.quantile(0.75)\n    q1 = series.quantile(0.25)\n    iqr = q3 - q1\n    l_, u_ = 0, q3+1.5*iqr\n    return (series * ((series >= l_) & (series <= u_))).replace(0, np.nan)\n\n# merge all race\nall_fs_df = pd.DataFrame()\nfor k, v in all_race_dict.items():\n#     fs_df = v[(v['section']=='Curve')&(v['trakus_index']<=250)]    \n    fs_df = v[(v['section']=='Last Straight')]  \n    \n    temp_ = pd.DataFrame()\n    for fp in fs_df['finishing_place'].unique():\n        cur_ = fs_df[fs_df['finishing_place']==fp].copy()\n        cur_['on_race'] = cur_['is_finish'].apply(lambda x : np.nan if not x else x)\n        cur_['on_race'] = cur_['on_race'].fillna(method='bfill')\n        cur_['on_race'] = cur_['on_race'].fillna(False)\n        temp_ = pd.concat([temp_, cur_])\n\n    all_fs_df = pd.concat([all_fs_df, temp_])\n    \nall_fs_df = all_fs_df[all_fs_df['on_race']]\nall_fs_df['speed'] = remove_iqr_out(all_fs_df['speed']) # remove outlier\nall_fs_df = all_fs_df.dropna()\n\ntemp_ = all_fs_df[['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']].drop_duplicates().\\\n        groupby(['finishing_place'])[['program_number']].count().rename(columns={'program_number':'count'}).T\ndisplay(temp_)\ntarget_position = list((temp_.T[temp_.T >= 200]).dropna().index) # over 200\nprint(f\"I'll only consider ranks in the next order. : {target_position}\")\n\nall_fs_df = all_fs_df[all_fs_df['finishing_place'].isin(target_position)]\nprint(f\"Total Horse : {len(all_fs_df[['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']].drop_duplicates())}\")\n\nprint(\"\\nAverage speed over the first straight section\")\ntemp_ = all_fs_df.groupby(['track_id', 'finishing_place'])[['speed']].mean().unstack()\ntemp_cmap = get_continuous_cmap([BACK_COLOR, BACK_COLOR, BACK_COLOR, BACK_COLOR, DIRT_COLOR])\ndisplay(temp_.style.background_gradient(cmap=temp_cmap, axis=1))\n\n\n# visualization\ntemp_ = all_fs_df.groupby(['track_id', 'finishing_place', 'trakus_index'])[['speed']].mean().unstack().dropna(axis=1)\ntemp_.columns = [c[1] for c in temp_.columns]\ntemp_ = temp_.T\ntemp_.columns = [f\"{c[0]}_{c[1]}\" for c in temp_.columns] # track_rank\n\nfig = go.Figure()\nfor track_name, marker_sym in zip(['Aqueduct', 'Belmont', 'Saratoga'], ['circle', 'square', 'diamond']):\n    cur_cols = [f\"{track_name}_{r}\" for r in target_position]\n    cur_ = temp_[cur_cols].dropna().copy()\n    for i, rank in enumerate(cur_.columns):\n        color_idx = (len(cur_.columns) - i) / len(cur_.columns)\n        fig.add_trace(go.Scatter(x=cur_.index, y=cur_[rank], name=f\"finish rank {rank.split('_')[1]}\", legendgroup=track_name, legendgrouptitle_text=track_name,\n                                 mode='markers', marker_color=f\"rgb{bg_cmap(color_idx, bytes=True)}\", marker_symbol=marker_sym))\n\n# fig = go.Figure()\n# for i, rank in enumerate(temp_.columns):\n#     color_idx = (len(temp_.columns) - i) / len(temp_.columns)\n#     fig.add_trace(go.Scatter(x=temp_.index, y=temp_[rank], name=f\"finish rank {rank}\",\n#                              mode='markers', marker_color=f\"rgb{bg_cmap(color_idx, bytes=True)}\"))\n\nfig.update_layout(title_text=f'<b>Average Velocity by Final Rank</b>', template=TEMPLATE, hovermode=\"x\", height=400)\nfig.update_yaxes(title='average velocity')\nfig.update_xaxes(title='trakus_index')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:57:38.747991Z","iopub.execute_input":"2022-11-07T04:57:38.748794Z","iopub.status.idle":"2022-11-07T04:57:57.735586Z","shell.execute_reply.started":"2022-11-07T04:57:38.748759Z","shell.execute_reply":"2022-11-07T04:57:57.734354Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **INSIGHT**  \n> As I expected, the first place horse is the fastest.  \n> The special thing is that the average speed difference between the first and second places in the last section is the biggest.  ","metadata":{}},{"cell_type":"markdown","source":"---\n### The beginning and end of the last straight section \nIf a horse enters the last straight section first, how many times does it cross the finish line first? In the last section, there will be fewer chances to turn the tables, so it will be relatively high.  \n  \nLet me show you an example. The table below shows the beginning of the last section of a particular race.  \nIt means that some horses entered the first last section when trakus_index was 176. (Based on the final ranking, the 2nd, 6th, and 8th place horses came in first.) What the blank means is that the horse has not yet entered the last section. The 1st horse entered after an increase of one trakus_index.  \nThe value entered, on the other hand, is the angle to the fixed position for each track. In the previous paragraph of \"Angle of Rotation\", it was determined that the larger this angle, the more the horse will run in front. Therefore, based on the table below, we can see that the second horse entered first.","metadata":{}},{"cell_type":"code","source":"key_cols = ['track_id', 'race_date', 'race_number', 'program_number', 'finishing_place']\n\n# merge all race\nresult_df = pd.DataFrame()\nall_fs_df = pd.DataFrame()\nlast_in_rank = pd.DataFrame()\nfor k, v in all_race_dict.items():\n    fs_df = v[(v['section']=='Curve')&(v['trakus_index']<=250)]    \n    temp_ = pd.DataFrame()\n    for fp in fs_df['finishing_place'].unique():\n        cur_ = fs_df[fs_df['finishing_place']==fp].copy()\n        cur_['on_race'] = True\n        temp_ = pd.concat([temp_, cur_])\n    temp_df = pd.pivot_table(temp_[temp_['on_race']], index='trakus_index', columns='finishing_place', values='rotation_angle')\n    temp_df = temp_df.fillna(-1) # dummy\n    temp_df = pd.DataFrame(temp_df.iloc[0, :].rank(ascending=False, method='min')).T\n    last_in_rank = pd.concat([last_in_rank, temp_df], ignore_index=True)\n\nlast_in_rank = last_in_rank[target_position].fillna(6)\nlir_df = pd.DataFrame()\nfor c in last_in_rank.columns:\n    cur_ = last_in_rank[[c]].copy()\n    cur_ = cur_.rename(columns={c:'last_in_rank'})\n    cur_['finishing_place'] = c\n    lir_df = pd.concat([lir_df, cur_], ignore_index=True)\nlir_df['section'] = 'Curve'    \n    \nresult_df = pd.concat([result_df, lir_df], ignore_index=True)\n\n\n# merge all race\n# all_fs_df = pd.DataFrame()\nlast_in_rank = pd.DataFrame()\nfor k, v in all_race_dict.items():\n#     fs_df = v[(v['section']=='Curve')&(v['trakus_index']<=250)]    \n    fs_df = v[(v['section']=='Last Straight')]  \n    \n    temp_ = pd.DataFrame()\n    for fp in fs_df['finishing_place'].unique():\n        cur_ = fs_df[fs_df['finishing_place']==fp].copy()\n        cur_['on_race'] = cur_['is_finish'].apply(lambda x : np.nan if not x else x)\n        cur_['on_race'] = cur_['on_race'].fillna(method='bfill')\n        cur_['on_race'] = cur_['on_race'].fillna(False)\n        temp_ = pd.concat([temp_, cur_])\n        \n    temp_df = pd.pivot_table(temp_[temp_['on_race']], index='trakus_index', columns='finishing_place', values='rotation_angle')\n    temp_df = temp_df.fillna(-1) # dummy\n    temp_df = pd.DataFrame(temp_df.iloc[0, :].rank(ascending=False, method='min')).T\n    \n    last_in_rank = pd.concat([last_in_rank, temp_df], ignore_index=True)\n#     break\n\nlast_in_rank = last_in_rank[target_position].fillna(6)\nlir_df = pd.DataFrame()\nfor c in last_in_rank.columns:\n    cur_ = last_in_rank[[c]].copy()\n    cur_ = cur_.rename(columns={c:'last_in_rank'})\n    cur_['finishing_place'] = c\n    lir_df = pd.concat([lir_df, cur_], ignore_index=True)\nlir_df['section'] = 'Last Straight'\n    \nresult_df = pd.concat([result_df, lir_df], ignore_index=True)\n\nc_df = result_df[result_df['section']=='Curve'].copy()\nl_df = result_df[result_df['section']=='Last Straight'].copy()\n\ncate_ = ['curve in top3 vs last top3', 'curve in top1 vs last top1', 'last in top3 vs last top3', 'last in top1 vs last top1'] # ['curve_top', 'curve_1', 'last_top', 'last_1']\nfscore_ = [\n    f1_score(c_df['finishing_place'].apply(lambda x : 1 if x<=3 else 0), c_df['last_in_rank'].apply(lambda x : 1 if x<=3 else 0)),\n    f1_score(c_df['finishing_place'].apply(lambda x : 1 if x==1 else 0), c_df['last_in_rank'].apply(lambda x : 1 if x==1 else 0)),\n    f1_score(l_df['finishing_place'].apply(lambda x : 1 if x<=3 else 0), l_df['last_in_rank'].apply(lambda x : 1 if x<=3 else 0)),\n    f1_score(l_df['finishing_place'].apply(lambda x : 1 if x==1 else 0), l_df['last_in_rank'].apply(lambda x : 1 if x==1 else 0))\n]\n\n# sample - start of last straight\ndisplay(pd.pivot_table(temp_[temp_['on_race']], index='trakus_index', columns='finishing_place', values='rotation_angle').fillna(\"\").head(5))","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:57:57.737603Z","iopub.execute_input":"2022-11-07T04:57:57.738372Z","iopub.status.idle":"2022-11-07T04:58:29.471322Z","shell.execute_reply.started":"2022-11-07T04:57:57.738320Z","shell.execute_reply":"2022-11-07T04:58:29.470077Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Based on the information above, we can see how much horse can win if horse enter a certain section first. Let me express it as [f-score](https://en.wikipedia.org/wiki/F-score), which is used as an indicator of the classification model.  \n- curve in top3 vs last top3 : If the horse enters the top 3 in the corner section vs if the horse enters the final 3  \n- curve in top1 vs last top1 : If the horse enters the corner section first vs. the final win  \n- last in top3 vs last top3 : If the horse enters the top 3 in the last section vs if the horse enters the final 3  \n- last in top1 vs last top1 : If the horse enters the last section first vs. the final win  ","metadata":{}},{"cell_type":"code","source":"fig = go.Figure(data=[go.Bar(x=cate_, y=fscore_, marker_color=[LINE_COLOR, LINE_COLOR, LINE_COLOR, DIRT_COLOR])])\nfig.update_layout(title_text=f'<b>F1 Score of comparing each interval rank with the final rank</b>', template=TEMPLATE, yaxis_range=[0, 0.8])\nfig.update_yaxes(title='f1score')\nfig.update_xaxes(title='trakus_index')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:58:29.475538Z","iopub.execute_input":"2022-11-07T04:58:29.475933Z","iopub.status.idle":"2022-11-07T04:58:29.522613Z","shell.execute_reply.started":"2022-11-07T04:58:29.475901Z","shell.execute_reply":"2022-11-07T04:58:29.521081Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> **INSIGHT**  \n> When a horse enters the top3 in each section, the probability of entering the final top three is similar.  \n> If the horse enters the last section first, more than half of the time it has won.  \n> This is far superior to the first time a horse enters a corner and wins.  ","metadata":{"execution":{"iopub.status.busy":"2022-10-31T08:13:05.731755Z","iopub.execute_input":"2022-10-31T08:13:05.732176Z","iopub.status.idle":"2022-10-31T08:13:05.739714Z","shell.execute_reply.started":"2022-10-31T08:13:05.732139Z","shell.execute_reply":"2022-10-31T08:13:05.738235Z"}}},{"cell_type":"markdown","source":"# <b>4. <span style=\"color:#DAA520\">P</span>rediction of Winning Probability while Driving</b>  \nUnfortunately, most Insights showed similar results. The characteristic seen in the winning horses was definitely speed. But this is a consequential story. Based on the winning horses, of course, it would have been important to be fast.  \nThen, can we expect the rest of the race to win if we only look at the driving data? Previous analysis has provided all required driving data. The section information, location, speed, distance, angle, etc. are obtained, and the last thing we need is a point-in-time ranking. Previously, angles across the track were used to determine instantaneous ranking, but for straight sections, the error may be large, so I calculated using the following statement.  \n- Calculate Real-Time Horse Ranking. : https://www.kaggle.com/competitions/big-data-derby-2022/discussion/361710  \n- With reference to the above, we obtain the average vector of all horses at the same time, and rank them by projecting the position vector of each horse into the average vector. I also find the length of this square vector and accumulate it to find the imaginary distance that we have run so far.  \n  \nThe following methodology was used to determine the future winning probability at each trakus_index.  \n- Target  \n  - If you're running (N+1)th, how many should you beat? : You have to beat the N.  \n  - If you have 100 fts left, and you are running (N+1)th, how many people should you beat per 1 ft? : You have to beat N per feet.  \n  - A generalization of the above example is as follows  \n    - Ranking to vary per distance for the remaining time = (Current Rank - Finish Rank) / Remaining Distance  \n      - *For your information, the goal here is to win the championship, so if the first place is determined, all competitions will be over.*  \n      - *Therefore, the final ranking above and the remaining distance are calculated based on when the first horse crossed the finish line.*  \n  - Calculate the above value based on the trackus_index of all horses and set it to the value we need to predict.  \n- Model  \n  - The model learns to predict the above target value with driving data at each trackus_index.  \n  - That is, the model predicts at each trakus_index, which horse will beat how many others on average in the future.  \n- Prediction  \n  - Simulate with the estimated value and the remaining distance.  \n    - An easy example is that there is a 50% chance of a coin coming forward, but if you throw it 100 times, you may not get 50 times.  \n    - Similarly, we expected to beat 0.1 per foot, and if we had 10 feet left, we could beat 0 per foot without always beating 1.  \n    - We can simulate this using a binomial distribution. So we have to think of the prediction as a probability.     \n  - This simulation is done on every horse at every moment.  \n    - Since every horse is running at every moment, it is also calculated how many other horses can beat.  \n  - Therefore, the final ranking is calculated considering the expected ranking of all horses, and the probability of winning is calculated by checking the number of times they have won first place.  \n- Others  \n  - I have limited the following.  \n    - Because the information on the track is different, only the data driven by *Aqueduct* was used.  \n    - Since the number of horses can affect the horses that have to be put aside, I used only the data of the final six horses as in the previous analysis.  \n  - The test data used approximately 19.6%.  ","metadata":{}},{"cell_type":"markdown","source":"## <b>4.1. <span style=\"color:#DAA520\">M</span>odeling</b>  ","metadata":{}},{"cell_type":"code","source":"# merge all race\nmax_rank = 6\nall_fs_df = pd.DataFrame()\ntrain_fs_df = pd.DataFrame()\ntest_fs_df = pd.DataFrame()\ntotal_race_number = 1\nfor k, v in all_race_dict.items():\n    fs_df = v.copy()\n    \n    if len(fs_df['finishing_place'].unique()) < max_rank:\n        continue\n    if v['track_id'].unique()[0] != 'Aqueduct':\n        continue\n    \n    temp_ = pd.DataFrame()\n    # for fp in fs_df['finishing_place'].unique():\n    for fp in range(1, max_rank+1):\n        cur_ = fs_df[fs_df['finishing_place']==fp].copy()\n        cur_['on_race'] = cur_['is_finish'].apply(lambda x : np.nan if not x else x)\n        cur_['on_race'] = cur_['on_race'].fillna(method='bfill')\n        cur_['on_race'] = cur_['on_race'].fillna(False)\n        \n        temp_ = pd.concat([temp_, cur_])\n\n    win_ = temp_[(temp_['finishing_place']==1) & (temp_['is_finish']==True)].copy()\n    win_trakus_index = win_['trakus_index'].values[0]\n\n\n    # actual rank\n    long_df = pd.pivot_table(temp_[temp_['on_race']], index='trakus_index', columns='finishing_place', values='longitude_diff') # x\n    long_df['mean'] = long_df.mean(axis=1)\n    lati_df = pd.pivot_table(temp_[temp_['on_race']], index='trakus_index', columns='finishing_place', values='latitude_diff') # y\n    lati_df['mean'] = lati_df.mean(axis=1)\n    \n    if len(long_df.columns) != max_rank+1: # 1~max_rank, mean\n        continue\n\n    def get_projection_len(x, y, x_p, y_p):\n        if (x!=x) or (y!=y):\n            return 0\n        v = np.array([x, y])\n        xy = np.array([x_p, y_p])\n        xy_norm = np.sqrt(sum(xy**2))     \n        vprime = (np.dot(v, xy)/xy_norm**2)*xy\n        return np.sqrt(sum(vprime**2))\n\n    project_len_df = long_df.iloc[:, :-1].copy() # temp\n    x_p = long_df['mean'].tolist()\n    y_p = lati_df['mean'].tolist()\n    for fp in range(1, max_rank+1):\n        x_ = long_df[fp].tolist()\n        y_ = lati_df[fp].tolist()    \n        project_len_df[fp] = [get_projection_len(x, y, x_p[i], y_p[i]) for i, (x, y) in enumerate(zip(x_, y_))]\n    project_len_df = project_len_df.cumsum() # projection length (imaginary distance)\n    trakus_rank_df = project_len_df.rank(ascending=False, axis=1)\n\n    # scaling win max=100\n    project_len_df = 100 * project_len_df / project_len_df.loc[win_trakus_index, 1]\n    win_projection_len = project_len_df.loc[win_trakus_index, 1]\n\n    # remain projection len\n    remain_df = win_projection_len - project_len_df\n    remain_df = remain_df.applymap(lambda x : np.nan if x < 0 else x).fillna(method='ffill', axis=1) # error because of calc cur_rank\n\n\n    trakus_diff_rank_df = trakus_rank_df.copy()\n    for c in trakus_diff_rank_df.columns:\n        trakus_diff_rank_df[c] = trakus_diff_rank_df[c] - c # current rank - final rank : The ranking that will change in the future\n\n    target_df = trakus_diff_rank_df.div(remain_df)\n    target_df = target_df.applymap(lambda x : 0 if x < 0 else x) # I don't care if the ranking is pushed back\n\n    target_df = target_df.loc[:win_trakus_index-1]\n\n    rank_ = pd.melt(trakus_rank_df.reset_index(), id_vars=['trakus_index']).rename(columns={'value':'cur_rank'})\n    remain_ = pd.melt(remain_df.reset_index(), id_vars=['trakus_index']).rename(columns={'value':'ramain_proj_len'})\n    target_ = pd.melt(target_df.reset_index(), id_vars=['trakus_index']).rename(columns={'value':'target'})\n\n    fs_df = pd.merge(fs_df, rank_, on=['trakus_index', 'finishing_place']) # add cur_rank\n    fs_df = pd.merge(fs_df, remain_, on=['trakus_index', 'finishing_place']) # add remain projection length\n    fs_df = pd.merge(fs_df, target_, on=['trakus_index', 'finishing_place']) # add target\n    fs_df = fs_df[fs_df['trakus_index']<=win_trakus_index]\n    \n    # preprocessing\n    fs_df['target'] = fs_df['target'].apply(lambda x : 0.999999 if x >=1 else x)\n    fs_df['section'] = fs_df['section'].apply(lambda x : 1 if x=='First Straight' else 2 if x=='Curve' else 3)\n    \n    \n    # additional features\n    fs_add_df = pd.DataFrame()\n    for fp in range(1, max_rank+1):\n        cur_ = fs_df[fs_df['finishing_place']==fp].copy()\n        for c in ['longitude', 'latitude', 'distance', 'speed', 'steering_angle', 'rotation_angle', 'section']:\n            for n in range(1,3+1):\n                cur_[f\"{c}_lag{n}\"] = cur_[c].shift(n)\n        cur_[f\"acceleration\"] = cur_['speed'].diff(1)\n        for n in range(1,3+1):\n                cur_[f\"acceleration_lag{n}\"] = cur_[f\"speed_lag{n}\"].diff(n)\n        fs_add_df = pd.concat([fs_add_df, cur_])\n    fs_add_df = fs_add_df.dropna()\n    \n    fs_add_df['TOTAL_RACE_NUM'] = total_race_number    \n    total_race_number += 1\n    all_fs_df = pd.concat([all_fs_df, fs_add_df])\n    \n    # split train & split\n#     if k <= 120:\n#         train_fs_df = pd.concat([train_fs_df, fs_add_df])\n#     else:\n#         test_fs_df = pd.concat([test_fs_df, fs_add_df])\n\n# split train & test\nrandom.seed(1234)\ntest_total_race_nums = random.sample(range(1, total_race_number+1), int(total_race_number*0.2))\ntrain_total_race_nums = [n for n in range(1, total_race_number+1) if not n in test_total_race_nums]\ntest_fs_df = all_fs_df[all_fs_df['TOTAL_RACE_NUM'].isin(test_total_race_nums)]\ntrain_fs_df = all_fs_df[all_fs_df['TOTAL_RACE_NUM'].isin(train_total_race_nums)]\n       \nprint(f\"Train size : {len(train_fs_df)}\")\nprint(f\"Test size : {len(test_fs_df)}\")\nprint(f\"Test ratio : {len(test_fs_df) / (len(train_fs_df)+len(test_fs_df)) : .3f}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:58:29.524906Z","iopub.execute_input":"2022-11-07T04:58:29.525465Z","iopub.status.idle":"2022-11-07T04:59:09.061113Z","shell.execute_reply.started":"2022-11-07T04:58:29.525394Z","shell.execute_reply":"2022-11-07T04:59:09.059548Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The data below was used as the learning factor for the model.  \n- latitude, longitude : The position of the horse at every moment.  \n- speed : It's the speed of the horse.  \n- acceleration : It's the acceleration of the horse.  \n- steering_angle : The angle difference between the current direction of progress and the previous direction of progress.  \n- rotation_angle : The angle between the horse and the fixed position within the track.  \n  \nI applied lag to the above factors. (t, t-1, t-2, t-3). I didn't put the ranking in every moment. It was also used to create a target, so that the model could focus only on driving information.  ","metadata":{}},{"cell_type":"code","source":"from xgboost import XGBRegressor\nfrom sklearn.metrics import mean_squared_error\n\nmodel = XGBRegressor(n_estimators=100, max_depth=5)\n\nx_cols = [\n        'latitude', 'longitude', 'latitude_lag1', 'longitude_lag1', 'latitude_lag2', 'longitude_lag2', 'latitude_lag3', 'longitude_lag3',\n        'speed', 'speed_lag1', 'speed_lag2', 'speed_lag3',\n        'acceleration', 'acceleration_lag1', 'acceleration_lag2', 'acceleration_lag3',\n        'steering_angle', 'steering_angle_lag1', 'steering_angle_lag2', 'steering_angle_lag3',\n        'rotation_angle', 'rotation_angle_lag1', 'rotation_angle_lag2', 'rotation_angle_lag3',\n        ]\n\ny_col = 'target'\n\nX_train = train_fs_df[x_cols]\ny_train = train_fs_df[[y_col]]\n\nX_test = test_fs_df[x_cols]\ny_test = test_fs_df[[y_col]]\n\nmodel.fit(X_train,y_train)\n\ntrain_pred_df = train_fs_df.copy()\ntrain_pred_df['pred'] = model.predict(X_train)\ntrain_pred_df['pred'] = train_pred_df['pred'].apply(lambda x : 0 if x<0 else 0.999999 if x>=1 else x)\n\ntest_pred_df = test_fs_df.copy()\ntest_pred_df['pred'] = model.predict(X_test)\ntest_pred_df['pred'] = test_pred_df['pred'].apply(lambda x : 0 if x<0 else 0.999999 if x>=1 else x)\n\ntrain_rmse = mean_squared_error(train_pred_df[y_col], train_pred_df['pred'], squared=False)\ntest_rmse = mean_squared_error(test_pred_df[y_col], test_pred_df['pred'], squared=False)\n\nprint(f\"Train RMSE : {train_rmse : .3f}\")\nprint(f\"Test RMSE : {test_rmse : .3f}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:59:09.062698Z","iopub.execute_input":"2022-11-07T04:59:09.063069Z","iopub.status.idle":"2022-11-07T04:59:40.069543Z","shell.execute_reply.started":"2022-11-07T04:59:09.063038Z","shell.execute_reply":"2022-11-07T04:59:40.068221Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I used xgb for the model, and below is the feature importance (base on weight) after model training. Approximately, the importance was high in the order of position (latitude, longitude), rotaion_angle, speed, acceleration, and steering angle.  \nWhat stands out is that location information tends to be extreme. The past location seems to be significantly less important because there are already factors calculated as changes in location.  ","metadata":{"execution":{"iopub.status.busy":"2022-11-06T08:26:30.407919Z","iopub.execute_input":"2022-11-06T08:26:30.408241Z","iopub.status.idle":"2022-11-06T08:26:30.413376Z","shell.execute_reply.started":"2022-11-06T08:26:30.408216Z","shell.execute_reply":"2022-11-06T08:26:30.412362Z"}}},{"cell_type":"code","source":"feature_important = model.get_booster().get_score(importance_type='weight')\nkeys = list(feature_important.keys())\nvalues = list(feature_important.values())\nf_imp_df = pd.DataFrame(data=values, index=keys, columns=[\"score\"]).sort_values(by=\"score\", ascending=True)\nf_imp_df\n\n# visualization\ntemp_colors = {\n    'latitude':'rgb(37,37,37)',\n    'longitude':'rgb(37,37,37)',\n    'rotation_angle':'rgb(50,136,189)',\n    'speed':'rgb(213,62,79)',\n    'acceleration':'rgb(252,141,89)',\n    'steering_angle':'rgb(153,213,148)',\n    \n}\nfig = go.Figure(go.Bar(\n            x=f_imp_df['score'],\n            y=f_imp_df.index,\n            marker_color=[temp_colors[f.split('_lag')[0]] for f in f_imp_df.index],\n            orientation='h'))\nfig.update_layout(title_text=f'<b>Feature Score in XGBRegressor</b>', template=TEMPLATE)\nfig.update_yaxes(title='feature name')\nfig.update_xaxes(title='weight score')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:59:40.071046Z","iopub.execute_input":"2022-11-07T04:59:40.071909Z","iopub.status.idle":"2022-11-07T04:59:40.126724Z","shell.execute_reply.started":"2022-11-07T04:59:40.071871Z","shell.execute_reply":"2022-11-07T04:59:40.125324Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## <b>4.2. <span style=\"color:#DAA520\">T</span>est Example</b>  \nWe saw the RMSE for the target earlier, but in fact, this value is for calculating the winning probability, so it is difficult to feel it. So let's look at some samples. For reference, in the visualization below, the interval information was displayed based on the actual 1st place horse.  ","metadata":{}},{"cell_type":"markdown","source":"---  \n### Good Case  \nThe results below is the case that the actual No.1 horse has a high probability of winning during the race. In addition, at the last seconds of the race, the winning probability of the No.1 horse exceeds 80%.  ","metadata":{}},{"cell_type":"code","source":"np.random.seed(123)\nsample_num = 7\nsample_race_date = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==test_total_race_nums[sample_num]]['race_date'].unique()[0]\nsample_race_number = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==test_total_race_nums[sample_num]]['race_number'].unique()[0]\n\nsample_df = test_pred_df[(test_pred_df['race_date']==sample_race_date) & (test_pred_df['race_number']==sample_race_number)]\n\nrank_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='cur_rank')\nremain_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='ramain_proj_len')\npred_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='pred')\n\nwin_prob = pred_.copy() #pd.DataFrame()\nsize = 1000\nfor i, row in pred_.iterrows():\n    cur_win_prob = pd.DataFrame(index=range(size))\n    for fp in range(1, max_rank+1):\n        fp_rank_diff = np.random.binomial(remain_.loc[i][fp], row[fp], size)\n        pred_rank = rank_.loc[i][fp] - fp_rank_diff\n        cur_win_prob[fp] = pred_rank\n    cur_win_prob = cur_win_prob.rank(method='min', axis=1)\n    for fp in range(1, 6+1):\n        win_prob.loc[i, fp] = sum(cur_win_prob[fp]==1) / size\n\n# norm\nwin_prob = win_prob.divide(win_prob.sum(axis=1), axis=0)\n\n# section info for visualization\nsection_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='section')\nsection_[1] = section_[1].diff().fillna(1)\nsection_ = section_[section_[1]!=0][[1]]\n\n# visualization\nshow_df = win_prob.cumsum(axis=1).copy()\ntemp_colors = [DIRT_COLOR, 'Silver', 'rgb(205, 127, 50)', # gold, silver, bronze\n               'rgb(116,169,207)', 'rgb(43,140,190)', 'rgb(37,37,37)'] # skyblue, blue, black\n\nfig = go.Figure()\nfor i, c in enumerate(show_df.columns):\n    color_idx = (len(show_df.columns) - i) / len(show_df.columns)\n    fig.add_trace(go.Scatter(x=show_df[c].index, y=show_df[c], hoverinfo='x+y', mode='lines', name=c, line=dict(width=0.5, color=temp_colors[i]), fill='tonexty'))\nfig.add_vline(x=section_.index[0], line_width=2, line_color=\"darkgreen\", annotation_text=\"First Straight\", annotation_position=\"bottom right\")\nfig.add_vline(x=section_.index[1], line_width=2, line_color=\"darkgreen\", annotation_text=\"Curve\", annotation_position=\"bottom right\")\nfig.add_vline(x=section_.index[-1], line_width=2, line_color=\"darkgreen\", annotation_text=\"Last Straight\", annotation_position=\"bottom right\")\nfig.update_layout(title_text=f'<b>Win Probability in each trakus index</b><br>: Aqueduct, race date = {sample_race_date}, race number = {sample_race_number}', template=TEMPLATE, yaxis_range=(0, 1))\nfig.update_yaxes(title='win probability')\nfig.update_xaxes(title='trakus_index')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:59:40.128785Z","iopub.execute_input":"2022-11-07T04:59:40.129587Z","iopub.status.idle":"2022-11-07T04:59:42.278812Z","shell.execute_reply.started":"2022-11-07T04:59:40.129540Z","shell.execute_reply":"2022-11-07T04:59:42.277875Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"---  \n### Bad Case  \nThe results below is the case that the actual No.1 horse has very low probability of winning during the race. In addition, after the middle of the game, the winning probability of the top horse is close to 0%. The winning probability of the 4th place horse was predicted to be high. ","metadata":{}},{"cell_type":"code","source":"np.random.seed(123)\nsample_num = 0\nsample_race_date = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==test_total_race_nums[sample_num]]['race_date'].unique()[0]\nsample_race_number = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==test_total_race_nums[sample_num]]['race_number'].unique()[0]\n\nsample_df = test_pred_df[(test_pred_df['race_date']==sample_race_date) & (test_pred_df['race_number']==sample_race_number)]\n\nrank_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='cur_rank')\nremain_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='ramain_proj_len')\npred_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='pred')\n\nwin_prob = pred_.copy() #pd.DataFrame()\nsize = 1000\nfor i, row in pred_.iterrows():\n    cur_win_prob = pd.DataFrame(index=range(size))\n    for fp in range(1, max_rank+1):\n        fp_rank_diff = np.random.binomial(remain_.loc[i][fp], row[fp], size)\n        pred_rank = rank_.loc[i][fp] - fp_rank_diff\n        cur_win_prob[fp] = pred_rank\n    cur_win_prob = cur_win_prob.rank(method='min', axis=1)\n    for fp in range(1, 6+1):\n        win_prob.loc[i, fp] = sum(cur_win_prob[fp]==1) / size\n\n# norm\nwin_prob = win_prob.divide(win_prob.sum(axis=1), axis=0)\n\n# section info for visualization\nsection_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='section')\nsection_[1] = section_[1].diff().fillna(1)\nsection_ = section_[section_[1]!=0][[1]]\n\n# visualization\nshow_df = win_prob.cumsum(axis=1).copy()\ntemp_colors = [DIRT_COLOR, 'Silver', 'rgb(205, 127, 50)', # gold, silver, bronze\n               'rgb(116,169,207)', 'rgb(43,140,190)', 'rgb(37,37,37)'] # skyblue, blue, black\n\nfig = go.Figure()\nfor i, c in enumerate(show_df.columns):\n    color_idx = (len(show_df.columns) - i) / len(show_df.columns)\n    fig.add_trace(go.Scatter(x=show_df[c].index, y=show_df[c], hoverinfo='x+y', mode='lines', name=c, line=dict(width=0.5, color=temp_colors[i]), fill='tonexty'))\nfig.add_vline(x=section_.index[0], line_width=2, line_color=\"darkgreen\", annotation_text=\"First Straight\", annotation_position=\"bottom right\")\nfig.add_vline(x=section_.index[1], line_width=2, line_color=\"darkgreen\", annotation_text=\"Curve\", annotation_position=\"bottom right\")\nfig.add_vline(x=section_.index[-1], line_width=2, line_color=\"darkgreen\", annotation_text=\"Last Straight\", annotation_position=\"bottom right\")\nfig.update_layout(title_text=f'<b>Win Probability in each trakus index</b><br>: Aqueduct, race date = {sample_race_date}, race number = {sample_race_number}', template=TEMPLATE, yaxis_range=(0, 1))\nfig.update_yaxes(title='win probability')\nfig.update_xaxes(title='trakus_index')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:59:42.280370Z","iopub.execute_input":"2022-11-07T04:59:42.281032Z","iopub.status.idle":"2022-11-07T04:59:44.437703Z","shell.execute_reply.started":"2022-11-07T04:59:42.280994Z","shell.execute_reply":"2022-11-07T04:59:44.436560Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"---  \n### Dramatic Reversal Case  \nThis is a case where the chance of winning rises sharply in the second half. In the middle, the second-place horse prevailed, and in the second half, the third-place horse prevailed, but at the end, the chances of winning the first-place horse rose sharply.  ","metadata":{}},{"cell_type":"code","source":"np.random.seed(123)\nsample_num = 5\nsample_race_date = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==test_total_race_nums[sample_num]]['race_date'].unique()[0]\nsample_race_number = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==test_total_race_nums[sample_num]]['race_number'].unique()[0]\n\nsample_df = test_pred_df[(test_pred_df['race_date']==sample_race_date) & (test_pred_df['race_number']==sample_race_number)]\n\nrank_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='cur_rank')\nremain_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='ramain_proj_len')\npred_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='pred')\n\nwin_prob = pred_.copy() #pd.DataFrame()\nsize = 1000\nfor i, row in pred_.iterrows():\n    cur_win_prob = pd.DataFrame(index=range(size))\n    for fp in range(1, max_rank+1):\n        fp_rank_diff = np.random.binomial(remain_.loc[i][fp], row[fp], size)\n        pred_rank = rank_.loc[i][fp] - fp_rank_diff\n        cur_win_prob[fp] = pred_rank\n    cur_win_prob = cur_win_prob.rank(method='min', axis=1)\n    for fp in range(1, 6+1):\n        win_prob.loc[i, fp] = sum(cur_win_prob[fp]==1) / size\n\n# norm\nwin_prob = win_prob.divide(win_prob.sum(axis=1), axis=0)\n\n# section info for visualization\nsection_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='section')\nsection_[1] = section_[1].diff().fillna(1)\nsection_ = section_[section_[1]!=0][[1]]\n\n# visualization\nshow_df = win_prob.cumsum(axis=1).copy()\ntemp_colors = [DIRT_COLOR, 'Silver', 'rgb(205, 127, 50)', # gold, silver, bronze\n               'rgb(116,169,207)', 'rgb(43,140,190)', 'rgb(37,37,37)'] # skyblue, blue, black\n\nfig = go.Figure()\nfor i, c in enumerate(show_df.columns):\n    color_idx = (len(show_df.columns) - i) / len(show_df.columns)\n    fig.add_trace(go.Scatter(x=show_df[c].index, y=show_df[c], hoverinfo='x+y', mode='lines', name=c, line=dict(width=0.5, color=temp_colors[i]), fill='tonexty'))\nfig.add_vline(x=section_.index[0], line_width=2, line_color=\"darkgreen\", annotation_text=\"First Straight\", annotation_position=\"bottom right\")\nfig.add_vline(x=section_.index[1], line_width=2, line_color=\"darkgreen\", annotation_text=\"Curve\", annotation_position=\"bottom right\")\nfig.add_vline(x=section_.index[-1], line_width=2, line_color=\"darkgreen\", annotation_text=\"Last Straight\", annotation_position=\"bottom right\")\nfig.update_layout(title_text=f'<b>Win Probability in each trakus index</b><br>: Aqueduct, race date = {sample_race_date}, race number = {sample_race_number}', template=TEMPLATE, yaxis_range=(0, 1))\nfig.update_yaxes(title='win probability')\nfig.update_xaxes(title='trakus_index')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:59:44.439624Z","iopub.execute_input":"2022-11-07T04:59:44.439967Z","iopub.status.idle":"2022-11-07T04:59:46.607108Z","shell.execute_reply.started":"2022-11-07T04:59:44.439935Z","shell.execute_reply":"2022-11-07T04:59:46.605681Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"---  \n### Ideal Case  \nThis is when the odds of winning first, second, and third places are proportional without any significant change during driving. Also, as time goes by, the probability of winning the first place increases, and the probability of winning the rest of the horses gradually decreases. ","metadata":{}},{"cell_type":"code","source":"np.random.seed(123)\nsample_num = 6\nsample_race_date = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==test_total_race_nums[sample_num]]['race_date'].unique()[0]\nsample_race_number = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==test_total_race_nums[sample_num]]['race_number'].unique()[0]\n\nsample_df = test_pred_df[(test_pred_df['race_date']==sample_race_date) & (test_pred_df['race_number']==sample_race_number)]\n\nrank_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='cur_rank')\nremain_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='ramain_proj_len')\npred_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='pred')\n\nwin_prob = pred_.copy() #pd.DataFrame()\nsize = 1000\nfor i, row in pred_.iterrows():\n    cur_win_prob = pd.DataFrame(index=range(size))\n    for fp in range(1, max_rank+1):\n        fp_rank_diff = np.random.binomial(remain_.loc[i][fp], row[fp], size)\n        pred_rank = rank_.loc[i][fp] - fp_rank_diff\n        cur_win_prob[fp] = pred_rank\n    cur_win_prob = cur_win_prob.rank(method='min', axis=1)\n    for fp in range(1, 6+1):\n        win_prob.loc[i, fp] = sum(cur_win_prob[fp]==1) / size\n\n# norm\nwin_prob = win_prob.divide(win_prob.sum(axis=1), axis=0)\n\n# section info for visualization\nsection_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='section')\nsection_[1] = section_[1].diff().fillna(1)\nsection_ = section_[section_[1]!=0][[1]]\n\n# visualization\nshow_df = win_prob.cumsum(axis=1).copy()\ntemp_colors = [DIRT_COLOR, 'Silver', 'rgb(205, 127, 50)', # gold, silver, bronze\n               'rgb(116,169,207)', 'rgb(43,140,190)', 'rgb(37,37,37)'] # skyblue, blue, black\n\nfig = go.Figure()\nfor i, c in enumerate(show_df.columns):\n    color_idx = (len(show_df.columns) - i) / len(show_df.columns)\n    fig.add_trace(go.Scatter(x=show_df[c].index, y=show_df[c], hoverinfo='x+y', mode='lines', name=c, line=dict(width=0.5, color=temp_colors[i]), fill='tonexty'))\nfig.add_vline(x=section_.index[0], line_width=2, line_color=\"darkgreen\", annotation_text=\"First Straight\", annotation_position=\"bottom right\")\nfig.add_vline(x=section_.index[1], line_width=2, line_color=\"darkgreen\", annotation_text=\"Curve\", annotation_position=\"bottom right\")\nfig.add_vline(x=section_.index[-1], line_width=2, line_color=\"darkgreen\", annotation_text=\"Last Straight\", annotation_position=\"bottom right\")\nfig.update_layout(title_text=f'<b>Win Probability in each trakus index</b><br>: Aqueduct, race date = {sample_race_date}, race number = {sample_race_number}', template=TEMPLATE, yaxis_range=(0, 1))\nfig.update_yaxes(title='win probability')\nfig.update_xaxes(title='trakus_index')\nfig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:59:46.608887Z","iopub.execute_input":"2022-11-07T04:59:46.609387Z","iopub.status.idle":"2022-11-07T04:59:48.924473Z","shell.execute_reply.started":"2022-11-07T04:59:46.609339Z","shell.execute_reply":"2022-11-07T04:59:48.923174Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## <b>4.3. <span style=\"color:#DAA520\">O</span>thers</b>  \nLet's check the average expected winning probability per game for the first place horse. If all six horses run the same race, the chance of winning is 1/6 (=0.167). When predicting the test data with our model, the average winning probability of the top horse is as follows.  \n- The average value here is the average of the expected winning probability at each point in each game and then the average for all games.  ","metadata":{}},{"cell_type":"code","source":"np.random.seed(123)\nsample_num = 0\nresults_mean = [] # mean\nresults_last = [] # last 1 second\nfor ttrn in test_total_race_nums:\n    sample_race_date = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==ttrn]['race_date'].unique()[0]   #'2019-01-18' #'2019-12-22' #'2019-01-18'\n    sample_race_number = test_pred_df[test_pred_df['TOTAL_RACE_NUM']==ttrn]['race_number'].unique()[0]  #    8 #9 #8\n\n    sample_df = test_pred_df[(test_pred_df['race_date']==sample_race_date) & (test_pred_df['race_number']==sample_race_number)]\n\n    rank_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='cur_rank')\n    remain_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='ramain_proj_len')\n    pred_ = pd.pivot_table(sample_df, index='trakus_index', columns='finishing_place', values='pred')\n\n    win_prob = pred_.copy() #pd.DataFrame()\n    size = 1000\n    for i, row in pred_.iterrows():\n        cur_win_prob = pd.DataFrame(index=range(size))\n        for fp in range(1, max_rank+1):\n            fp_rank_diff = np.random.binomial(remain_.loc[i][fp], row[fp], size)\n            pred_rank = rank_.loc[i][fp] - fp_rank_diff\n            cur_win_prob[fp] = pred_rank\n        cur_win_prob = cur_win_prob.rank(method='min', axis=1)\n        for fp in range(1, 6+1):\n            win_prob.loc[i, fp] = sum(cur_win_prob[fp]==1) / size\n\n    # norm\n    win_prob = win_prob.divide(win_prob.sum(axis=1), axis=0)    \n    \n    results_mean.append(win_prob[1].mean())\n    results_last.append(sum(win_prob[1].tail(4) > 0.5)) # last 1 second\n    \nprint(f\"Base winning probability : {1/6 : .4f}\")\nprint(f\"The mean winning probability of 1st horse : {np.mean(results_mean) : .4f}\")\nprint(f\"Ratio : {np.mean(results_mean)/(1/6) : .4f}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T04:59:48.926679Z","iopub.execute_input":"2022-11-07T04:59:48.927580Z","iopub.status.idle":"2022-11-07T05:00:46.242869Z","shell.execute_reply.started":"2022-11-07T04:59:48.927517Z","shell.execute_reply":"2022-11-07T05:00:46.241628Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"With driving data, the winning probability of the No. 1 horse at every moment is approximately **2.3 times** higher than when everyone is driving the same. Similar information already exists as \"odds\". If we check the \"odds\" of the first and non-first-place horses for the same test data, the following is.","metadata":{}},{"cell_type":"code","source":"print(f\"Mean Odds of Non-1st-place: {test_pred_df[test_pred_df['finishing_place']!=1].drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'finishing_place'])['odds'].mean() : .4f}\")\nprint(f\"Mean Odds of 1st-place : {test_pred_df[test_pred_df['finishing_place']==1].drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'finishing_place'])['odds'].mean() : .4f}\")\nratio = test_pred_df[test_pred_df['finishing_place']!=1].drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'finishing_place'])['odds'].mean() /\\\ntest_pred_df[test_pred_df['finishing_place']==1].drop_duplicates(subset=['track_id', 'race_date', 'race_number', 'finishing_place'])['odds'].mean()\nprint(f\"Ratio : {ratio : .4f}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T05:00:46.244465Z","iopub.execute_input":"2022-11-07T05:00:46.244859Z","iopub.status.idle":"2022-11-07T05:00:46.337581Z","shell.execute_reply.started":"2022-11-07T05:00:46.244823Z","shell.execute_reply":"2022-11-07T05:00:46.336317Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Surprisingly, *the ratio of the winning probability calculated by the model to the existing \"odds\" was very similar.* \"odds\" can be considered to be calculated by collective intelligence for past horse racing results. This analogy is well reflected in the model.  \nFinally, in test matches, about 74% had a 50% or more chance of winning the last one second of the actual No. 1 horse.  \n- This is the case with the highest winning probability of the first place at the end (last 1 second), as seen in the previous example test results: \"Good Case\", \"Dramatic Reversal\" and \"Ideal Case\".  ","metadata":{}},{"cell_type":"code","source":"print(f\"Based on the last second, reasonably predicted rate : {sum([1 for x in results_last if x==4]) / len(results_last) : .4f}\")","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-11-07T05:00:46.339465Z","iopub.execute_input":"2022-11-07T05:00:46.340153Z","iopub.status.idle":"2022-11-07T05:00:46.347745Z","shell.execute_reply.started":"2022-11-07T05:00:46.340015Z","shell.execute_reply":"2022-11-07T05:00:46.346395Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>5. <span style=\"color:#DAA520\">S</span>ummary</b>  \nSo far, we have created a model that predicts real-time winning probability by analyzing driving data.  \nAs the first step, we divided the race section first and checked the relevant statistics for each zone. The driving data checked for each track section are as follows.  \n(** sign means a different result than normal.)\n\n1. First straight\n  - Acceleration : The time reached to a specific speed has been checked. The first place horse was the best.\n  - Average Speed : The average speed in this straight section has been checked. The first place horse was the fastest.\n  - Rotation angle : As an alternative to real-time ranking, we checked the angle between the fixed position inside the track and location of the horse. The angle of the No. 1 horse was the largest on average (can be considered ahead)    \n  \n  \n2. Curve\n  - Average Speed : We checked the average speed in this curved section, the top horse was the fastest.\n  - Travel distance : The distance traveled in the curved section. The shorter the distance, the more it ran inside the corner. The high ranking did not mean that the distance traveled was short. **  \n  - Time required : The time it took to pass through the curved section. The first place horse was the fastest.\n  - Standard deviation of steering angle : The degree of shaking during cornering. For the top position, the steering angle deviation was small.\n  - Moving area : The approximate area between the center of the corner and the curved path. The smaller the area, the more it runs inside the corner. For the top tier, the area was large. **  \n  - Difference between the minimum radius point and the half time point: The minimum radius point should normally be the mid-curve point, and the smaller the change in velocity on the curve, the smaller the difference. This value was small for the parent of the Aqueduct. **  \n  - Graviy Acceleration : We checked the force applied during cornering. Approximately 0.3 G is applied.  \n  \n  \n3. Last straight\n  - Average Speed : We checked the average speed in the last section. The first place horse was the fastest.\n  - Comparison of the entry ranking of the straight section and final ranking : When horse first enter the last straight section, the F-score is 0.55 when we compare the actual first place.\n  \n  \nConsidering the above analysis, I have determined five basic items of driving data. The five factors are position (latitude, hardness), speed, acceleration, steering angle, and rotation angle. We took these factors and considered the ranking at a certain point in time to predict whether we could win the first place for the rest of the distance and time.  \nAbout 74% of the test data predicted a 50% chance of winning the first place at the last minute. The ratio of the average expected winning probability to the default winning probability for the first place to the overall interval was similar to that of the first place horse and the non-first place horses.  \n  \n**Need for improvement**  \n1. Track information  \n  - I analyzed it by referring to the actual map, but it would be nice to have official data for each stadium.  \n  - For example, by driving course, latitude and longitude data at multiple points  \n2. Modeling  \n  - The previous data results are the results of training and predicting a specific number of horses in a specific stadium.  \n  - We will be able to extend this to further generalize the model.  \n  - In addition, a variety of modeling methodologies will be available.  \n  \n**Further works**  \n1. Simulation for first-time horses  \n  - If we have practice driving data for horses that are competing for the first time, we can predict the winning probability by driving by combining with the existing driving data.  \n2. Virtual competition simulation  \n  - We will be able to combine existing driving data to compete against horses that have not yet competed.  \n3. Target specific sections, specific situations  \n  - Instead of running the entire race, it can also be simulated by replacing it with a first-place probability for a particular section or a chance of injury.  \n  - In case of injury prediction : If normal is 0 and injury is 1, it seems that target = (state change)/(remaining distance) can be set.  \n    - For the actual injured horse, the state change is 1, and for the normal horse, the state change is a status change of 0.  ","metadata":{}},{"cell_type":"markdown","source":"<body>\n  <div class=\"alert\", style=\"background-color: lightgray;\" align=\"center\"><font size=4; color=black>I send my deepest condolences to the victims of the Itaewon disaster.</font> <img src=\"https://www.pngmart.com/files/9/Black-Ribbon-Transparent-Background.png\" alt=\"finger snap\" width=\"25\" height=\"25\"/></div>\n</body>","metadata":{"execution":{"iopub.status.busy":"2022-11-07T06:47:43.538561Z","iopub.execute_input":"2022-11-07T06:47:43.539029Z","iopub.status.idle":"2022-11-07T06:47:43.549827Z","shell.execute_reply.started":"2022-11-07T06:47:43.538993Z","shell.execute_reply":"2022-11-07T06:47:43.547769Z"}}}]}