{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Setup","metadata":{"id":"YapuF_pI6LWm"}},{"cell_type":"code","source":"%%capture\n!pip install catboost -q","metadata":{"id":"WU2OmQEisD7I","execution":{"iopub.status.busy":"2022-11-09T16:29:31.175476Z","iopub.execute_input":"2022-11-09T16:29:31.175791Z","iopub.status.idle":"2022-11-09T16:29:39.792864Z","shell.execute_reply.started":"2022-11-09T16:29:31.175768Z","shell.execute_reply":"2022-11-09T16:29:39.790181Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport random\nimport numpy as np \nimport math\nimport pandas as pd \nimport plotly.express as px \nimport plotly.figure_factory as ff \nimport matplotlib.pyplot as plt \nimport plotly.graph_objs as go\nimport seaborn as sns \nimport time\nfrom math import cos, sin\nfrom plotly.subplots import make_subplots \nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score, accuracy_score, f1_score\nfrom scipy.special import softmax\n\nfrom plotly.offline import plot, iplot, init_notebook_mode\ninit_notebook_mode(connected=True)\n\nfrom catboost import CatBoostClassifier","metadata":{"id":"b6WFtjL-5lfs","outputId":"95e6f420-f3fa-4661-b1e5-f79a10286e1e","execution":{"iopub.status.busy":"2022-11-09T16:29:39.795886Z","iopub.execute_input":"2022-11-09T16:29:39.796264Z","iopub.status.idle":"2022-11-09T16:29:39.804955Z","shell.execute_reply.started":"2022-11-09T16:29:39.796237Z","shell.execute_reply":"2022-11-09T16:29:39.803618Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pd.options.display.max_columns = 100  # чтоб выводил все столбцы на экран\npd.options.display.float_format = (    \"{:,.3f}\".format )\nplt.rcParams[\"figure.figsize\"] = (12, 8)\nsns.set(rc={\"figure.figsize\":(20, 7)})","metadata":{"id":"VkLdeXeW5lfv","execution":{"iopub.status.busy":"2022-11-09T16:29:39.806621Z","iopub.execute_input":"2022-11-09T16:29:39.806958Z","iopub.status.idle":"2022-11-09T16:29:39.824093Z","shell.execute_reply.started":"2022-11-09T16:29:39.806932Z","shell.execute_reply":"2022-11-09T16:29:39.823419Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Goal of the Competition 🐴 \n\nThe goal of this competition is to analyze horse racing tactics, drafting strategies, and path efficiency. You will develop a model using never-before-released coordinate data along with basic race information.\n\nYour work will help racing horse owners, trainers, and veterinarians better understand how equine performance and welfare fit together. With better data analysis, equine welfare could significantly improve.\n\n## Context 🐴\n\nInjury prevention is a critical component in modern athletics. Sports that involve animals, such as horse racing, are no different than human sport. Typically, efficiency in movement correlates to both improvements in performance and injury prevention.\n\nA wealth of data is now collected, including measures for heart rate, EKG, longitudinal movement, dorsal/ventral movement, medial/lateral deviation, total power and total landing vibration. Your data science skills and analysis are needed to decipher what makes the most positive impact.\n\nIn this competition, you will create a model to interpret one aspect of this new data. You’ll be among the first to access X/Y coordinate mapping of horses during races. Using the data, you might analyze jockey decision making, compare race surfaces, or measure the relative importance of drafting. With considerable data, contestants can flex their creativity problem solving skills.\n\nThe New York Racing Association (NYRA) and the New York Thoroughbred Horsemen's Association (NYTHA) conduct world class thoroughbred racing at Aqueduct Racetrack, Belmont Park and Saratoga Race Course.\n\nWith your help, NYRA and NYTHA will better understand their vast data set, which could lead to new ways of racing and training in a highly traditional industry. With improved use of horse tracking data, you could help improve equine welfare, performance and rider decision making.\n\n## Columns in dataset 🐴\n***\n\n- `track_id` \n- `race_date`\n- `race_number` \n- `program_number`\n- `trakus_index`:  Trakus is a speed-distance curve measurement system for thoroughbred horseracing that measures the speed of each horse in the race with respect to its distance travelled in the race and from that measurement, the distance between horses at the time of POC (point of call) can be measured.\n- `latitude` \n- `longitude` \n- `distance_id` \n- `course_type`\n- `track_condition` \n- `run_up_distance`: Run-up is the distance from where the gate is placed and the timing of the race begins – that is, the point at which the horses reach the published distance of the race.\n- `race_type` \n- `purse`: In horse racing, the term purse refers to the total amount of money paid out to the owners of horses racing at a particular track over a given period of time, or to the percentages of a race's total purse that are awarded to each of the highest finishers. \n- `post_time`: Post time is the designated starting time of a horse race.\n- `weight_carried` \n- `jockey`: Jockey's name.\n- `odds`","metadata":{"id":"FcZYaG-l5lfv"}},{"cell_type":"code","source":"df_start = pd.read_csv('../input/big-data-derby-2022/nyra_start_table.csv', \n                       header=None, \n                       parse_dates=['race_date'],\n                       names=['track_id',\n                              'race_date',\n                              'race_number',\n                              'program_number',\n                              'weight_carried',\n                              'jockey',\n                              'odds',\n                              'position_at_finish'])\ndf_race = pd.read_csv('../input/big-data-derby-2022/nyra_race_table.csv', parse_dates=['race_date'],)\ndf_tracking = pd.read_csv('../input/big-data-derby-2022/nyra_tracking_table.csv', parse_dates=['race_date'],)","metadata":{"id":"8N0e2w6O5lf0","execution":{"iopub.status.busy":"2022-11-09T16:29:39.825487Z","iopub.execute_input":"2022-11-09T16:29:39.825945Z","iopub.status.idle":"2022-11-09T16:29:43.801845Z","shell.execute_reply.started":"2022-11-09T16:29:39.825912Z","shell.execute_reply":"2022-11-09T16:29:43.800343Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's combine all available data into one frame for easy work:","metadata":{"id":"AShb4ZwMOTmz"}},{"cell_type":"code","source":"df = pd.merge(left=df_tracking, right=df_race, how='left', on=['track_id', \n                                                          'race_date', \n                                                          'race_number'])\n\ndf = pd.merge(left=df, right=df_start, \n         how='left', on=['track_id', \n                         'race_date', \n                         'race_number', \n                         'program_number'])\n\ndf['race_date'] = pd.to_datetime(df['race_date'])\ndf['day'], df['month'] = df['race_date'].dt.day, df['race_date'].dt.month","metadata":{"id":"qkEE7hls5lf1","execution":{"iopub.status.busy":"2022-11-09T16:29:43.805087Z","iopub.execute_input":"2022-11-09T16:29:43.805762Z","iopub.status.idle":"2022-11-09T16:29:48.688202Z","shell.execute_reply.started":"2022-11-09T16:29:43.805724Z","shell.execute_reply":"2022-11-09T16:29:48.686915Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_tracking['race_date'] = pd.to_datetime(df_tracking['race_date'])\ndf_tracking['day'], df_tracking['month'] = df_tracking['race_date'].dt.day, df_tracking['race_date'].dt.month","metadata":{"id":"I2F5O8mo5lf2","execution":{"iopub.status.busy":"2022-11-09T16:29:48.689581Z","iopub.execute_input":"2022-11-09T16:29:48.689947Z","iopub.status.idle":"2022-11-09T16:29:49.291594Z","shell.execute_reply.started":"2022-11-09T16:29:48.689911Z","shell.execute_reply":"2022-11-09T16:29:49.290249Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Foreword","metadata":{"id":"_Ngy9t6Ew1v_"}},{"cell_type":"markdown","source":"The motivation for creating this work was the desire to find out the answer to the question - what could machine learning tell us from the coordinate data that we were provided with. What if we strip away all information about jockeys, previous races, chances of winning and focus only on the location of the horses and their movements? How does the probability of winning change as the horse approaches the finish line? What does this probability depend on?","metadata":{"id":"XCXCg7Z4w3YW"}},{"cell_type":"markdown","source":"Answering these questions will require data transformation. We have location data for horses in a geographic coordinate system. In order to make it easier to work with the data, we need to reduce these coordinates to a simplified form, looking at which a person or a computer can determine the position of the horses relative to the finish line. The idea is to straighten the trajectory in the form of a line along the X axis. Then it becomes easy to understand who wins the race: whoever has a higher X value is closer to victory.","metadata":{"id":"RHcI7XO3x5z5"}},{"cell_type":"markdown","source":"In order to get new coordinates of the position of the horses relative to the new center of coordinates, you need to understand how to straighten the trajectories of the horses running around the stadium.\n\nLet us take as the origin point the average of the coordinates of all horses in the race at a certain point in time. Let's see how this coordinate changes over time (subtracting the previous values ​​from the next) - this will be our approximate representation of the stadium. Now, having found the angle of inclination of the vector of change of the average over the coordinates of all horses, we can expand the motion vectors of all horses to this very angle, which will build them around the axis we need.","metadata":{"id":"JfyX2yzsKoKJ"}},{"cell_type":"markdown","source":"For a better understanding, we will call the averaged coordinates at a certain time of the race - the average center of mass of all horses. (ACM)","metadata":{"id":"ktwhTLz2CWjZ"}},{"cell_type":"markdown","source":"### Data transformation","metadata":{"id":"8lkOxcq4b_HM"}},{"cell_type":"code","source":"df = df.sort_values(by = \"trakus_index\", ascending = True)","metadata":{"id":"zqRHNmWSpW4A","execution":{"iopub.status.busy":"2022-11-09T16:29:49.292729Z","iopub.execute_input":"2022-11-09T16:29:49.293076Z","iopub.status.idle":"2022-11-09T16:29:52.634377Z","shell.execute_reply.started":"2022-11-09T16:29:49.293045Z","shell.execute_reply":"2022-11-09T16:29:52.632956Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head()","metadata":{"id":"ymCR09m3LgNV","outputId":"4f0a30ae-da16-4462-b92c-7072d8e1f1a3","execution":{"iopub.status.busy":"2022-11-09T16:29:52.636675Z","iopub.execute_input":"2022-11-09T16:29:52.637098Z","iopub.status.idle":"2022-11-09T16:29:52.663411Z","shell.execute_reply.started":"2022-11-09T16:29:52.637063Z","shell.execute_reply":"2022-11-09T16:29:52.661579Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # вычисляем текущее значение ACM\nmask = ['race_number', 'track_id', 'race_type', 'race_date', 'trakus_index']\ndf_temp = df.groupby(mask).agg({'longitude': ['mean'], 'latitude': ['mean']}).reset_index()\ndf_temp.columns = [*[x for x in mask],'long_mean','lat_mean']\n# вычисляем значение ACM на шаге n-1\ndf_temp['long_mean_last'] = df_temp['long_mean'].shift().bfill()   \ndf_temp['lat_mean_last'] = df_temp['lat_mean'].shift().bfill()  \n\ndf = df.merge(df_temp, on=mask, how='left')\n\n# # заполним значения ACM на шаге n-1 в начале гонки теми же значениями что и на шаге n\ndf.loc[df['trakus_index']==1, 'long_mean_last'] = df.loc[df['trakus_index']==1, 'long_mean']\ndf.loc[df['trakus_index']==1, 'lat_mean_last'] = df.loc[df['trakus_index']==1, 'lat_mean'] ","metadata":{"id":"V580sC_yPZZ4","execution":{"iopub.status.busy":"2022-11-09T16:29:52.664898Z","iopub.execute_input":"2022-11-09T16:29:52.665307Z","iopub.status.idle":"2022-11-09T16:29:56.982313Z","shell.execute_reply.started":"2022-11-09T16:29:52.665281Z","shell.execute_reply":"2022-11-09T16:29:56.981218Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = df.sort_values(by = \"trakus_index\", ascending = True)","metadata":{"id":"kMg_G82bkUXR","execution":{"iopub.status.busy":"2022-11-09T16:29:56.983559Z","iopub.execute_input":"2022-11-09T16:29:56.983803Z","iopub.status.idle":"2022-11-09T16:29:58.454446Z","shell.execute_reply.started":"2022-11-09T16:29:56.983778Z","shell.execute_reply":"2022-11-09T16:29:58.452963Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Считаем вектор изменения ACM","metadata":{"id":"5dlC_k_gtBLA"}},{"cell_type":"code","source":"df['long_mean_v'] = df['long_mean'] - df['long_mean_last']\ndf['lat_mean_v'] = df['lat_mean'] - df['lat_mean_last']","metadata":{"id":"QzkdB6jnAvLg","execution":{"iopub.status.busy":"2022-11-09T16:29:58.456182Z","iopub.execute_input":"2022-11-09T16:29:58.456481Z","iopub.status.idle":"2022-11-09T16:29:58.495899Z","shell.execute_reply.started":"2022-11-09T16:29:58.456456Z","shell.execute_reply":"2022-11-09T16:29:58.494475Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's create the functions necessary for further calculations:","metadata":{"id":"UPHJy-3jlI4e"}},{"cell_type":"code","source":"def angle_between(x, y): \n    vector_1 = [x, y]\n    vector_2 = [1, 0]\n    a = np.arctan2(*vector_1[::-1]) - np.arctan2(*vector_2[::-1])\n    if a<0:\n        a = 2*math.pi+a\n    if a>0:\n        a = -a\n    return a","metadata":{"id":"KNVJpgpNeJM1","execution":{"iopub.status.busy":"2022-11-09T16:29:58.497559Z","iopub.execute_input":"2022-11-09T16:29:58.497896Z","iopub.status.idle":"2022-11-09T16:29:58.504456Z","shell.execute_reply.started":"2022-11-09T16:29:58.497871Z","shell.execute_reply":"2022-11-09T16:29:58.503454Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Определяем угол поворота вектора ACM","metadata":{"id":"TegY3wjTtGdj"}},{"cell_type":"code","source":"df['theta'] = df[['long_mean_v','lat_mean_v']].apply(lambda x: angle_between(x.long_mean_v, x.lat_mean_v, ), axis=1) ","metadata":{"id":"bzN7MAh7WKk4","execution":{"iopub.status.busy":"2022-11-09T16:29:58.505928Z","iopub.execute_input":"2022-11-09T16:29:58.506314Z","iopub.status.idle":"2022-11-09T16:32:11.752670Z","shell.execute_reply.started":"2022-11-09T16:29:58.506280Z","shell.execute_reply":"2022-11-09T16:32:11.751466Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Вычисляем матрицу поворота для этого угла","metadata":{"id":"3_zDrP4StIfC"}},{"cell_type":"code","source":"df['rot'] = df['theta'].apply(lambda x: np.array([[cos(x), -sin(x)], [sin(x), cos(x)]]) )","metadata":{"id":"7lId-htcchWh","execution":{"iopub.status.busy":"2022-11-09T16:32:11.757115Z","iopub.execute_input":"2022-11-09T16:32:11.757456Z","iopub.status.idle":"2022-11-09T16:32:24.473175Z","shell.execute_reply.started":"2022-11-09T16:32:11.757432Z","shell.execute_reply":"2022-11-09T16:32:24.471936Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# запомним координаты вектора лошади относительно прошлого значения вектора ACM на шаге n-1\ndf.loc[:,'long_rel'] = df.loc[:,'longitude'] - df.loc[:,'long_mean_last']  \ndf.loc[:,'lat_rel'] = df.loc[:,'latitude'] - df.loc[:,'lat_mean_last'] \n\n# загоняем координаты этого вектора лошади в один столбец\ndf['v'] = df[['long_rel','lat_rel']].apply(lambda x: np.array([x.long_rel, x.lat_rel]) , axis=1) ","metadata":{"id":"E8f73nlJczvo","execution":{"iopub.status.busy":"2022-11-09T16:32:24.474341Z","iopub.execute_input":"2022-11-09T16:32:24.474558Z","iopub.status.idle":"2022-11-09T16:34:09.606206Z","shell.execute_reply.started":"2022-11-09T16:32:24.474536Z","shell.execute_reply":"2022-11-09T16:34:09.605063Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# умножаем вектор на матрицу поворота - получаем вектор движения лошади с учетом поворота\ndf_dot = df[['rot','v']].apply(lambda x: np.dot(x.rot, x.v), axis=1) ","metadata":{"id":"weZ2Glkef2wK","execution":{"iopub.status.busy":"2022-11-09T16:34:09.607975Z","iopub.execute_input":"2022-11-09T16:34:09.608287Z","iopub.status.idle":"2022-11-09T16:36:04.177373Z","shell.execute_reply.started":"2022-11-09T16:34:09.608253Z","shell.execute_reply":"2022-11-09T16:36:04.175925Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's get the new coordinates of the horses in the columns:","metadata":{"id":"z6HrL3Y694KV"}},{"cell_type":"code","source":"df['x'] = df_dot.apply(lambda x: x[0]) \ndf['y'] = df_dot.apply(lambda x: x[1]) \n\nmask = ['race_number', 'track_id', 'race_type', 'race_date', 'program_number']\ndf.loc[df['trakus_index']==1, 'x'] = np.nan\ndf.loc[df['trakus_index']==1, 'y'] = np.nan\ndf['x'] = df.groupby(mask)['x'].bfill()  \ndf['y'] = df.groupby(mask)['y'].bfill()  ","metadata":{"id":"fBh76ARzjG4Y","execution":{"iopub.status.busy":"2022-11-09T16:36:04.178832Z","iopub.execute_input":"2022-11-09T16:36:04.179150Z","iopub.status.idle":"2022-11-09T16:36:20.660374Z","shell.execute_reply.started":"2022-11-09T16:36:04.179125Z","shell.execute_reply":"2022-11-09T16:36:20.659309Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = df.sort_values(by = \"trakus_index\", ascending = True)","metadata":{"id":"M0nq96i_927w","execution":{"iopub.status.busy":"2022-11-09T16:36:20.661949Z","iopub.execute_input":"2022-11-09T16:36:20.662312Z","iopub.status.idle":"2022-11-09T16:36:26.068513Z","shell.execute_reply.started":"2022-11-09T16:36:20.662286Z","shell.execute_reply":"2022-11-09T16:36:26.066868Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Features' generation","metadata":{"id":"UzzACI2WdnA8"}},{"cell_type":"code","source":"mask = ['race_number', 'track_id', 'race_type', 'race_date', 'program_number']\n# находим координаты предыдущего положения лошади. понадобится позже для вычисления вектора лошади\ndf['long_last'] = df.groupby(mask)['longitude'].shift()\ndf['lat_last'] = df.groupby(mask)['latitude'].shift()\n# заполним пропуски прошлых координат в начале гонки теми же значениями что и на текущий момент\ndf['long_last'] = df['long_last'].fillna(df['longitude'])\ndf['lat_last'] = df['lat_last'].fillna(df['latitude'])","metadata":{"id":"2ycE6Ax15zU8","execution":{"iopub.status.busy":"2022-11-09T16:36:26.070455Z","iopub.execute_input":"2022-11-09T16:36:26.070866Z","iopub.status.idle":"2022-11-09T16:36:32.220805Z","shell.execute_reply.started":"2022-11-09T16:36:26.070830Z","shell.execute_reply":"2022-11-09T16:36:32.219489Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = df.sort_values(by = \"trakus_index\", ascending = True)","metadata":{"id":"L4wfujsR-zek","execution":{"iopub.status.busy":"2022-11-09T16:36:32.222571Z","iopub.execute_input":"2022-11-09T16:36:32.222889Z","iopub.status.idle":"2022-11-09T16:36:37.878992Z","shell.execute_reply.started":"2022-11-09T16:36:32.222864Z","shell.execute_reply":"2022-11-09T16:36:37.877155Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Velosities calculation","metadata":{"id":"qUalgnDKvI63"}},{"cell_type":"code","source":"# считаем вектор движения лошади. нужен только для вычисления скорости лошади\ndf['long_s'] = df['longitude'] - df['long_last'] \ndf['lat_s'] = df['latitude'] - df['lat_last']\n#считаем скорость лошади каждую четверть секунды\ndf['speed'] = (df['long_s']**2 + df['lat_s']**2)**0.5\n\n# Вычисляем сглаженную скорость\nmask = ['race_number', 'track_id', 'race_type', 'race_date', 'program_number']\ndf_temp = df.groupby(mask)['speed'].rolling(10, min_periods=0).mean().reset_index() \ndf_temp.columns = [*[x for x in mask], 'index','rolling_speed']\ndf_temp = df_temp[['index', 'rolling_speed']] \ndf = df.reset_index().merge(df_temp, on='index', how='left') \ndel df['index']\n\ndf = df.sort_values(by = \"trakus_index\", ascending = True)\n\n# и вычисляем среднюю скорость лошади за уже пройденный участок пути\ndf_temp  = df.groupby(mask)['speed'].rolling(10000, min_periods=0).mean().reset_index()\ndf_temp.columns = [*[x for x in mask], 'index','avg_speed']\ndf_temp = df_temp[['index', 'avg_speed']]\n\ndf = df.reset_index().merge(df_temp, on='index', how='left')\ndel df['index']","metadata":{"id":"fSYvegw6hdxk","execution":{"iopub.status.busy":"2022-11-09T16:36:37.881439Z","iopub.execute_input":"2022-11-09T16:36:37.881858Z","iopub.status.idle":"2022-11-09T16:38:26.821455Z","shell.execute_reply.started":"2022-11-09T16:36:37.881822Z","shell.execute_reply":"2022-11-09T16:38:26.819866Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = df.sort_values(by = \"trakus_index\", ascending = True)","metadata":{"id":"AzjnYHdHdhPx","execution":{"iopub.status.busy":"2022-11-09T16:38:26.823295Z","iopub.execute_input":"2022-11-09T16:38:26.823553Z","iopub.status.idle":"2022-11-09T16:38:32.703037Z","shell.execute_reply.started":"2022-11-09T16:38:26.823529Z","shell.execute_reply":"2022-11-09T16:38:32.702040Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Prior to this, only individual features were calculated. Now we give our future model information about other horses, apply softmax to the speeds of all horses at the moment:","metadata":{"id":"yj-FxNEyxyS1"}},{"cell_type":"code","source":"mask = ['race_number', 'track_id', 'race_type', 'race_date', 'trakus_index']\ndf[\"speed_probs\"] = df.groupby(mask)[\"speed\"].transform(softmax)\ndf[\"rolling_speed_probs\"] = df.groupby(mask)[\"rolling_speed\"].transform(softmax)\ndf[\"avg_speed_probs\"] = df.groupby(mask)[\"avg_speed\"].transform(softmax)","metadata":{"id":"vc9wkl1GyL8j","execution":{"iopub.status.busy":"2022-11-09T16:38:32.704410Z","iopub.execute_input":"2022-11-09T16:38:32.704874Z","iopub.status.idle":"2022-11-09T16:52:48.057912Z","shell.execute_reply.started":"2022-11-09T16:38:32.704849Z","shell.execute_reply":"2022-11-09T16:52:48.056616Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And also apply softmax to the x coordinate, which shows the placement of horses relative to each other:","metadata":{"id":"Amvja0zZNTHo"}},{"cell_type":"code","source":"df[\"approach_finish_probs\"] = df.groupby(mask)[\"x\"].transform(softmax)","metadata":{"id":"qA6TB_RyNGAP","execution":{"iopub.status.busy":"2022-11-09T16:52:48.059232Z","iopub.execute_input":"2022-11-09T16:52:48.059461Z","iopub.status.idle":"2022-11-09T16:57:15.404426Z","shell.execute_reply.started":"2022-11-09T16:52:48.059438Z","shell.execute_reply":"2022-11-09T16:57:15.403152Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's find path percentiles for all races:","metadata":{"id":"3UPHo6Ne_uYl"}},{"cell_type":"code","source":"def find_race_percentiles(data):\n  uniq_trackus_ind = pd.Series( data['trakus_index'].unique() )\n  # ставим маркеры перцентилей пути, за которые потом будем цепляться при обучении и прогнозе модели\n  for x in range(0,100,10):\n    el = uniq_trackus_ind.quantile(x/100, 'nearest')\n    data.loc[data['trakus_index']== el, 'time_percent' ] = x   \n  return data[['trakus_index', 'time_percent']]","metadata":{"id":"OYvGZ6Kc--z8","execution":{"iopub.status.busy":"2022-11-09T16:57:15.405618Z","iopub.execute_input":"2022-11-09T16:57:15.405852Z","iopub.status.idle":"2022-11-09T16:57:15.412108Z","shell.execute_reply.started":"2022-11-09T16:57:15.405827Z","shell.execute_reply":"2022-11-09T16:57:15.410578Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mask = ['race_number', 'track_id', 'race_type', 'race_date']\ndf_temp = df.groupby(mask).apply(find_race_percentiles)\n\ndf = df.join(df_temp['time_percent'], how='left')","metadata":{"id":"tbqcapJw8eDt","execution":{"iopub.status.busy":"2022-11-09T16:57:15.413632Z","iopub.execute_input":"2022-11-09T16:57:15.413962Z","iopub.status.idle":"2022-11-09T16:57:59.791383Z","shell.execute_reply.started":"2022-11-09T16:57:15.413939Z","shell.execute_reply":"2022-11-09T16:57:59.789864Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = df.sort_values(by = \"trakus_index\", ascending = True)","metadata":{"id":"gYPNcfbfkbQt","execution":{"iopub.status.busy":"2022-11-09T16:57:59.792796Z","iopub.execute_input":"2022-11-09T16:57:59.793513Z","iopub.status.idle":"2022-11-09T16:58:02.083029Z","shell.execute_reply.started":"2022-11-09T16:57:59.793459Z","shell.execute_reply":"2022-11-09T16:58:02.081828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Cleaning up...","metadata":{"id":"Spo_j6IkyRVD"}},{"cell_type":"code","source":"del df['long_mean']\ndel df['lat_mean']\ndel df['long_rel']\ndel df['lat_rel']\ndel df['long_last']\ndel df['lat_last']\ndel df['long_mean_v']\ndel df['long_s']\ndel df['lat_s']\ndel df['lat_mean_v']\ndel df['long_mean_last']\ndel df['lat_mean_last']\ndel df['rot']\ndel df['v']","metadata":{"id":"IYmUjEP0jlF5","execution":{"iopub.status.busy":"2022-11-09T16:58:02.084444Z","iopub.execute_input":"2022-11-09T16:58:02.084697Z","iopub.status.idle":"2022-11-09T16:58:03.274791Z","shell.execute_reply.started":"2022-11-09T16:58:02.084667Z","shell.execute_reply":"2022-11-09T16:58:03.273526Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's determine the order of the horses at each point in time","metadata":{"id":"Cxk_X6HxyG3e"}},{"cell_type":"code","source":"mask = ['race_number', 'track_id', 'race_type', 'race_date', 'trakus_index']\ndf['horse_rank_on_race'] = df.groupby(mask)['x'].rank(method='first', ascending=False)  # ранкируем лошадей по x","metadata":{"id":"g9TQyYjMkcwI","execution":{"iopub.status.busy":"2022-11-09T16:58:03.485398Z","iopub.execute_input":"2022-11-09T16:58:03.485768Z","iopub.status.idle":"2022-11-09T16:58:07.829352Z","shell.execute_reply.started":"2022-11-09T16:58:03.485743Z","shell.execute_reply":"2022-11-09T16:58:07.828703Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Adding unique race id","metadata":{"id":"nHfHwFWTA4no"}},{"cell_type":"code","source":"mask = ['race_number', 'track_id', 'race_type', 'race_date']\ndf['id_race'] = df.groupby(mask).ngroup()","metadata":{"id":"PfQFD3Rqmol3","execution":{"iopub.status.busy":"2022-11-09T16:58:07.830517Z","iopub.execute_input":"2022-11-09T16:58:07.831210Z","iopub.status.idle":"2022-11-09T16:58:09.748899Z","shell.execute_reply.started":"2022-11-09T16:58:07.831185Z","shell.execute_reply":"2022-11-09T16:58:09.748171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's create a target feature for forecasting. We will predict whether the horse will come first or not.","metadata":{"id":"X5ylUopPlr_p"}},{"cell_type":"code","source":"df['position'] = (df['position_at_finish']== 1)*1","metadata":{"id":"H-h-rgxGdCSF","execution":{"iopub.status.busy":"2022-11-09T16:58:09.750368Z","iopub.execute_input":"2022-11-09T16:58:09.750698Z","iopub.status.idle":"2022-11-09T16:58:09.770773Z","shell.execute_reply.started":"2022-11-09T16:58:09.750667Z","shell.execute_reply":"2022-11-09T16:58:09.769657Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Visualization","metadata":{"id":"kfC07_qYm56j"}},{"cell_type":"markdown","source":"Let's visualize one of the races. This is how the race looked like when using the location data in the geographic coordinate system:","metadata":{"id":"c5eJCPvs0F6Q"}},{"cell_type":"code","source":"df_temp = df[df[\"race_number\"] == 6].copy()\ndf_temp = df_temp[df_temp[\"track_id\"] == \"BEL\"]\ndf_temp = df_temp[df_temp[\"race_type\"] == \"ALW\"]\ndf_temp = df_temp[df_temp['race_date']=='2019-04-27']\ndf_temp['program_number'] = df_temp['program_number'].astype(np.int32)\n\ndf_temp = df_temp.sort_values(by = \"trakus_index\", ascending = True)","metadata":{"id":"0xXgFkgnE3-2","execution":{"iopub.status.busy":"2022-11-09T16:58:09.772034Z","iopub.execute_input":"2022-11-09T16:58:09.772309Z","iopub.status.idle":"2022-11-09T16:58:10.247897Z","shell.execute_reply.started":"2022-11-09T16:58:09.772284Z","shell.execute_reply":"2022-11-09T16:58:10.246927Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = px.scatter(df_temp, x = \"longitude\", y = \"latitude\",\n                 hover_name=\"program_number\",\n                  animation_group=\"program_number\",\n                  animation_frame = \"trakus_index\",\n                  range_y = (40.712, 40.7185),\n                  range_x = (-73.729, -73.716),\n                  color = \"program_number\", \n                 color_discrete_sequence=['red'],\n                color_continuous_scale = \"Turbo\",\n                 title = \"Test\"\n                )\n\nfig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 20\nfig.update_layout(template = \"plotly_dark\", font = dict(family = \"PT Sans\", size = 12))\nfig.show()","metadata":{"id":"x6iHTj3WE7FR","outputId":"c56d467c-34ac-4a77-9199-e96517b54a3a","execution":{"iopub.status.busy":"2022-11-09T17:23:01.723247Z","iopub.execute_input":"2022-11-09T17:23:01.724126Z","iopub.status.idle":"2022-11-09T17:23:03.122995Z","shell.execute_reply.started":"2022-11-09T17:23:01.724090Z","shell.execute_reply":"2022-11-09T17:23:03.121893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And now with the converted data:","metadata":{"id":"6C3ZWu-D0WvC"}},{"cell_type":"code","source":"fig = px.scatter(df_temp, x = \"x\", y = \"y\",\n                 hover_name=\"program_number\",\n                  animation_group=\"program_number\",\n                  animation_frame = \"trakus_index\",\n                  range_y = (-0.0000225, 0.0000245),\n                  range_x = (-0.00005, 0.00023),\n                  color = \"program_number\", \n                 color_discrete_sequence=['red'],\n                color_continuous_scale = \"Turbo\",\n                 title = \"Test\"\n                )\n\nfig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 50\nfig.update_layout(template = \"plotly_dark\", font = dict(family = \"PT Sans\", size = 12))\nfig.show()","metadata":{"id":"Fyl-c5HYnSZz","outputId":"984c8342-0f0d-46e4-f1b9-d947f6b2e557","execution":{"iopub.status.busy":"2022-11-09T17:23:06.906681Z","iopub.execute_input":"2022-11-09T17:23:06.906970Z","iopub.status.idle":"2022-11-09T17:23:08.315705Z","shell.execute_reply.started":"2022-11-09T17:23:06.906946Z","shell.execute_reply":"2022-11-09T17:23:08.314696Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As one can see, horse number 3 disappears from radar somewhere around 10 seconds into the race. This is due to the fact that the jockey could not stay in the saddle at the start. One can watch the whole race on [YouTube](https://youtu.be/J8-Esm3sw6o?t=5939).","metadata":{"id":"rZh0o7uR0crY"}},{"cell_type":"markdown","source":"Let's check how horses' speeds change over time:","metadata":{"id":"DcCrkL9M062I"}},{"cell_type":"code","source":"sns.lineplot(x='trakus_index', y = 'speed', data=df_temp, hue='program_number').set(title='The speed of the horses in the race', xlabel='Time', ylabel='Speed'  )\nplt.legend(loc='lower right', title='program_number')\nNone","metadata":{"id":"uSb3-07ohEUh","outputId":"e3235e4a-46ff-46f7-bc1c-c490b9cddb91","execution":{"iopub.status.busy":"2022-11-09T16:58:13.932127Z","iopub.execute_input":"2022-11-09T16:58:13.932349Z","iopub.status.idle":"2022-11-09T16:58:14.306807Z","shell.execute_reply.started":"2022-11-09T16:58:13.932326Z","shell.execute_reply":"2022-11-09T16:58:14.305751Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.lineplot(x='trakus_index', y = 'rolling_speed', data=df_temp, hue='program_number').set(title='The averaged speed of horses in the race', xlabel='Time', ylabel='Speed' )\nplt.legend(loc='lower right', title='program_number')\nNone","metadata":{"id":"hfmneScOhJSX","outputId":"8563ff0b-68a1-4000-e5a2-79a3624a3d2e","execution":{"iopub.status.busy":"2022-11-09T16:58:14.308580Z","iopub.execute_input":"2022-11-09T16:58:14.308925Z","iopub.status.idle":"2022-11-09T16:58:14.644878Z","shell.execute_reply.started":"2022-11-09T16:58:14.308895Z","shell.execute_reply":"2022-11-09T16:58:14.643517Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.lineplot(x='trakus_index', y = 'avg_speed', data=df_temp, hue='program_number').set(title='The average cumpulative speed of horses in the race', xlabel='Time', ylabel='Speed')\nplt.legend(loc='lower right', title='program_number')\nNone","metadata":{"id":"Nx0csQPzhH7S","outputId":"991efab1-e9b6-450c-d6df-b98d85f84037","execution":{"iopub.status.busy":"2022-11-09T16:58:14.646240Z","iopub.execute_input":"2022-11-09T16:58:14.646575Z","iopub.status.idle":"2022-11-09T16:58:14.981499Z","shell.execute_reply.started":"2022-11-09T16:58:14.646542Z","shell.execute_reply":"2022-11-09T16:58:14.980206Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Speeds features are not precise, but they help to estimate speeds of horses relative to each other.","metadata":{"id":"0XRFpRlIdWsc"}},{"cell_type":"markdown","source":"## Data preparation","metadata":{"id":"s9kNj12Cs-Kw"}},{"cell_type":"markdown","source":"Fixing pseudo-randomness","metadata":{"id":"KNpBupnh-F61"}},{"cell_type":"code","source":"RSTATE=12345","metadata":{"id":"OgQ93Ku6tExV","execution":{"iopub.status.busy":"2022-11-09T16:58:14.982847Z","iopub.execute_input":"2022-11-09T16:58:14.983158Z","iopub.status.idle":"2022-11-09T16:58:14.988233Z","shell.execute_reply.started":"2022-11-09T16:58:14.983132Z","shell.execute_reply":"2022-11-09T16:58:14.986686Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Delete the feature directly related to the target (data leak):","metadata":{"id":"tDWRIX-v-JyP"}},{"cell_type":"code","source":"del df['position_at_finish']","metadata":{"id":"0IkokB4469lR","execution":{"iopub.status.busy":"2022-11-09T16:58:14.989454Z","iopub.execute_input":"2022-11-09T16:58:14.989687Z","iopub.status.idle":"2022-11-09T16:58:15.001581Z","shell.execute_reply.started":"2022-11-09T16:58:14.989663Z","shell.execute_reply":"2022-11-09T16:58:15.000139Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Separating the features for training and the target feature.","metadata":{"id":"brJFUwYFAina"}},{"cell_type":"markdown","source":"Now, in order to fairly divide the samples into training, validation and test, we need a race identifier.\nLet's use 70% of the data for training, 30% persent for test and validation purposes.","metadata":{"id":"Lcej3965Ar_I"}},{"cell_type":"markdown","source":"After that, we will choose a certain moment of the race by the value in `time_percent` and make predictions.\nIf you use the standard division into samples, then with further indexing, the proportions of the samples will be violated.","metadata":{"id":"CA22hWFxvecR"}},{"cell_type":"code","source":"q = set(range( len(df['id_race'].unique() ) ) ) # делаем сет из значений id_race\ntrain_idx = set( (random.sample(q, int(0.7*len(q) ) ) ) )\ntest_idx = q - train_idx\nval_idx = set( (random.sample(q, int(0.5*len(q) ) ) ) )\ntest_idx = test_idx - val_idx","metadata":{"id":"l_idq1YEduqv","execution":{"iopub.status.busy":"2022-11-09T16:58:15.002694Z","iopub.execute_input":"2022-11-09T16:58:15.002926Z","iopub.status.idle":"2022-11-09T16:58:15.045916Z","shell.execute_reply.started":"2022-11-09T16:58:15.002904Z","shell.execute_reply":"2022-11-09T16:58:15.045289Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train = df.query('id_race in @train_idx').drop([\"position\"], axis=1)\ny_train = df.query('id_race in @train_idx')[\"position\"]\nX_val = df.query('id_race in @val_idx').drop([\"position\"], axis=1)\ny_val = df.query('id_race in @val_idx')[\"position\"]\nX_test = df.query('id_race in @test_idx').drop([\"position\"], axis=1)\ny_test = df.query('id_race in @test_idx')[\"position\"]","metadata":{"id":"uXs_C5W9g-pR","execution":{"iopub.status.busy":"2022-11-09T16:58:15.052368Z","iopub.execute_input":"2022-11-09T16:58:15.052695Z","iopub.status.idle":"2022-11-09T16:58:19.499198Z","shell.execute_reply.started":"2022-11-09T16:58:15.052671Z","shell.execute_reply":"2022-11-09T16:58:19.498047Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"At this stage, we will get rid of all the features that provide some information on ​​other races. We will also remove the useless features:","metadata":{"id":"rp5sF3zQzTpU"}},{"cell_type":"code","source":"del_cols = ['id_race', 'odds', 'jockey', 'purse', 'trakus_index', 'race_date', 'longitude', 'latitude']\nfor i in del_cols:\n  del X_train[i]\n  del X_val[i]\n  del X_test[i]","metadata":{"id":"oXkG16pyi3u6","execution":{"iopub.status.busy":"2022-11-09T16:58:19.500952Z","iopub.execute_input":"2022-11-09T16:58:19.501251Z","iopub.status.idle":"2022-11-09T16:58:19.514399Z","shell.execute_reply.started":"2022-11-09T16:58:19.501226Z","shell.execute_reply":"2022-11-09T16:58:19.512994Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's make a list of categorical features","metadata":{"id":"RFenwkJu-RuN"}},{"cell_type":"code","source":"cat_cols = []\nfor x in X_train.select_dtypes(include='object'):\n  cat_cols.append(x)\ncat_cols","metadata":{"id":"4jwetm3ssVpb","outputId":"603e8264-9fbf-4d8a-8bd9-4b292231f465","execution":{"iopub.status.busy":"2022-11-09T16:58:19.515736Z","iopub.execute_input":"2022-11-09T16:58:19.516201Z","iopub.status.idle":"2022-11-09T16:58:20.109885Z","shell.execute_reply.started":"2022-11-09T16:58:19.516142Z","shell.execute_reply":"2022-11-09T16:58:20.109223Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training","metadata":{"id":"7lzEsxscR9MC"}},{"cell_type":"markdown","source":"To predict the winner, we will use Catboost because of the simplicity of its work with categorical features.","metadata":{"id":"ecjc4aNcP-8C"}},{"cell_type":"markdown","source":"Let's try to predict the winner for each percentile of the race path:","metadata":{"id":"60mMxbI1FA2H"}},{"cell_type":"code","source":"roc_auc_scores = []\naccuracy_one = []\nfeature_importances = []\nfor q in range(0,100,10):\n  X_train_ = X_train[X_train['time_percent']==q]\n  y_train_ = y_train[X_train['time_percent']==q]\n  X_val_ = X_val[X_val['time_percent']==q]\n  y_val_ = y_val[X_val['time_percent']==q]\n  X_test_ = X_test[X_test['time_percent']==q]\n  y_test_ = y_test[X_test['time_percent']==q]\n  \n  cat_model = CatBoostClassifier(auto_class_weights='Balanced', \n                          iterations=1000,\n                          random_seed=RSTATE,\n                          od_type='Iter',\n                          od_wait=15,\n                          )\n\n  cat_model.fit(X_train_, y_train_, \n            cat_features=cat_cols, \n            eval_set=[(X_val_, y_val_)], \n            logging_level='Silent',\n            early_stopping_rounds=15, use_best_model=True )\n  \n  y_pred = cat_model.predict_proba(X_test_)\n  yy = (y_pred[:,1]>0.5)*1  # получаем предсказания победителя \n  tt = y_test_.reset_index()  \n  # получаем accuracy для класса \"победитель\"\n  accuracy_one.append( len(tt.loc[(tt['position']== yy) & (tt['position']==1)  ]) / len(tt[tt['position']==1]) )\n  roc_auc_scores.append( roc_auc_score(y_test_,y_pred[:,1] ) )","metadata":{"id":"rAtcOoSKLQGH","execution":{"iopub.status.busy":"2022-11-09T16:58:20.110854Z","iopub.execute_input":"2022-11-09T16:58:20.111679Z","iopub.status.idle":"2022-11-09T17:01:12.153527Z","shell.execute_reply.started":"2022-11-09T16:58:20.111652Z","shell.execute_reply":"2022-11-09T17:01:12.151858Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"g = sns.lineplot(x = range(0,100,10), y= roc_auc_scores )\ng.set_xticks(range(0,100,10)) \nplt.title('Winner prediction at a certain point of the race')\nplt.xlabel('Path percentage')\nplt.ylabel('Test ROC AUC')\nplt.show()","metadata":{"id":"8wlEwNoyLh5-","outputId":"813de1da-5542-430f-8d7e-93c9a806859c","execution":{"iopub.status.busy":"2022-11-09T17:01:12.155201Z","iopub.execute_input":"2022-11-09T17:01:12.155507Z","iopub.status.idle":"2022-11-09T17:01:12.396665Z","shell.execute_reply.started":"2022-11-09T17:01:12.155481Z","shell.execute_reply":"2022-11-09T17:01:12.395982Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"g = sns.lineplot(x = range(0,100,10), y= accuracy_one )\ng.set_xticks(range(0,100,10)) \nplt.title('Winner prediction at a certain point of the race')\nplt.xlabel('Path percentage')\nplt.ylabel('Test accuracy of the 1st class')\nplt.show()","metadata":{"id":"7BO16eLCB2v3","outputId":"3efa4103-66d2-4a85-8ddd-904531eb3fd3","execution":{"iopub.status.busy":"2022-11-09T17:01:12.397516Z","iopub.execute_input":"2022-11-09T17:01:12.398634Z","iopub.status.idle":"2022-11-09T17:01:12.628324Z","shell.execute_reply.started":"2022-11-09T17:01:12.398578Z","shell.execute_reply":"2022-11-09T17:01:12.627198Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As one can see, the performance metrics of the prediction model grow with as the horses approach the finish line.","metadata":{"id":"4_YI1n3hQSgT"}},{"cell_type":"markdown","source":"Let's put all the metrics in the table:","metadata":{"id":"lvDa_qWYWZi7"}},{"cell_type":"code","source":"ds_results = pd.DataFrame({'class 1 accuracy ': accuracy_one,  'roc_auc': roc_auc_scores}, index=range(0,100,10) )","metadata":{"id":"Ne8xvBLBKThJ","execution":{"iopub.status.busy":"2022-11-09T17:01:12.630857Z","iopub.execute_input":"2022-11-09T17:01:12.631703Z","iopub.status.idle":"2022-11-09T17:01:12.639886Z","shell.execute_reply.started":"2022-11-09T17:01:12.631675Z","shell.execute_reply":"2022-11-09T17:01:12.638521Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ds_results","metadata":{"id":"llUawTHuWPEY","outputId":"30c53f46-08a9-4e5e-fc06-701d8d69919b","execution":{"iopub.status.busy":"2022-11-09T17:01:12.641496Z","iopub.execute_input":"2022-11-09T17:01:12.641750Z","iopub.status.idle":"2022-11-09T17:01:12.654324Z","shell.execute_reply.started":"2022-11-09T17:01:12.641725Z","shell.execute_reply":"2022-11-09T17:01:12.653499Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Feature Importance","metadata":{"id":"FE83CTzBLTJL"}},{"cell_type":"markdown","source":"Let's take a look at the feature importance in the last section of the race:","metadata":{"id":"r0Kpyudhb7bY"}},{"cell_type":"code","source":"feature_importance = cat_model.feature_importances_\nsorted_idx = np.argsort(feature_importance)\nfig = plt.figure(figsize=(12, 6))\nplt.barh(range(len(sorted_idx)), feature_importance[sorted_idx], align='center')\nplt.yticks(range(len(sorted_idx)), np.array(X_test.columns)[sorted_idx])\nplt.title('Feature Importance')\nNone","metadata":{"id":"F1jcyCipadmn","outputId":"69307e99-2e3c-40f6-c6a0-8e0ccf48f4b0","execution":{"iopub.status.busy":"2022-11-09T17:01:12.655500Z","iopub.execute_input":"2022-11-09T17:01:12.655816Z","iopub.status.idle":"2022-11-09T17:01:12.987230Z","shell.execute_reply.started":"2022-11-09T17:01:12.655784Z","shell.execute_reply":"2022-11-09T17:01:12.985872Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Interestingly, the model singled out as important not only such banal features as the rank of a horse at a particular point in time, the x coordinate relative to the average mass vector of horses, but also the y coordinate. Apparently, there are some more advantageous arrangements of horses along the width of the track.","metadata":{"id":"NIFK3ITrcJNo"}},{"cell_type":"markdown","source":"## Conclusion","metadata":{"id":"PXBk9l-4LgO6"}},{"cell_type":"markdown","source":"Getting involved in solving this problem, we had no clear understanding of how ML can help. At first, the idea was to simplify the data, to make it easily perceived by a human. And later curiosity arose, what if we feed the data to the machine, set some simple task and see what happens.\nWith this motivation, we managed to complete this work. \n\nThe main conclusion is simple - as the horses approach the finish line, it becomes easier to predict who will win the race. And this understanding directly depends on the path traveled.\n\nThere seems to be a lot more to do and think about with this code. If you decide to improve it, please write to leoiwfyls@gmail.com","metadata":{"id":"v-yHaO6FPPcJ"}}]}