{"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 os, gc, pickle, math\nimport numpy as np \nimport pandas as pd \nimport itertools as it\nimport seaborn as sns\nfrom tqdm.auto import tqdm\n\n!pip install utm\nimport utm\n\nimport lightgbm as lgb\nfrom lightgbm import log_evaluation\n\nimport matplotlib.pyplot as plt\n\nfrom sklearn.metrics import log_loss, accuracy_score, confusion_matrix, recall_score, precision_score, f1_score\nfrom sklearn.preprocessing import LabelBinarizer\nfrom scipy import special, optimize\nimport scipy","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-09-07T08:25:03.924385Z","iopub.execute_input":"2022-09-07T08:25:03.925094Z","iopub.status.idle":"2022-09-07T08:25:14.835887Z","shell.execute_reply.started":"2022-09-07T08:25:03.925036Z","shell.execute_reply":"2022-09-07T08:25:14.834632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CFG:\n    seed = 42\n    nbr = 4000\n    target = 'finishing_place'\n    \ndef define_seed(seed):\n    os.environ['PYTHONHASHSEED']=str(seed)\n    random.seed(seed)\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n\ndic_models = {}\ndic_params = {}\ndir_data = '../input/bdd-parquet/'","metadata":{"execution":{"iopub.status.busy":"2022-09-07T08:25:14.838362Z","iopub.execute_input":"2022-09-07T08:25:14.838735Z","iopub.status.idle":"2022-09-07T08:25:14.855085Z","shell.execute_reply.started":"2022-09-07T08:25:14.838700Z","shell.execute_reply":"2022-09-07T08:25:14.854088Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Clean the datas","metadata":{}},{"cell_type":"code","source":"df_race = pd.read_parquet(dir_data+'nyra_race_table.parquet')\ndf_start = pd.read_parquet(dir_data+'nyra_start_table.parquet')\ndf_horse = pd.read_parquet(dir_data+'df_horse.parquet')\ndf_track = pd.read_parquet(dir_data+'nyra_tracking_table.parquet')\n\ndf_race['race_date'] = pd.to_datetime(df_race.race_date)\ndf_start['race_date'] = pd.to_datetime(df_start.race_date)\ndf_horse['race_date'] = pd.to_datetime(df_horse.race_date)\ndf_track['race_date'] = pd.to_datetime(df_track.race_date)\n\ndf_start['program_number'] = df_start['program_number'].apply(lambda x : x.replace(' ',''))\ndf_track['program_number'] = df_track['program_number'].apply(lambda x : x.replace(' ',''))","metadata":{"execution":{"iopub.status.busy":"2022-09-07T08:25:14.857195Z","iopub.execute_input":"2022-09-07T08:25:14.857555Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#### RACES DICTIONNARIES ### \ndic_track_condition = df_race[['track_id','race_date','race_number', 'track_condition']].set_index(['track_id','race_date','race_number']).to_dict()['track_condition']\ndic_course_type = df_race[['track_id','race_date','race_number', 'course_type']].set_index(['track_id','race_date','race_number']).to_dict()['course_type']\ndic_race_type = df_race[['track_id','race_date','race_number', 'track_condition']].set_index(['track_id','race_date','race_number']).to_dict()['track_condition']\n\ndf_temp = df_start.copy()\ndf_temp['jockey_id'] = df_temp.groupby(['jockey']).ngroup()\ndf_temp = df_temp.merge(df_horse[['track_id','race_date','race_number','program_number', 'horse_id']], on=['track_id','race_date','race_number','program_number'])\ndic_horse_id = df_temp[['track_id','race_date','race_number', 'program_number', 'horse_id']].set_index(['track_id','race_date','race_number', 'program_number']).to_dict()['horse_id']\ndic_jockey_id = df_temp[['track_id','race_date','race_number', 'program_number', 'jockey_id']].set_index(['track_id','race_date','race_number', 'program_number']).to_dict()['jockey_id']\n\ndf_temp['couple_horse_jockey'] = df_temp.groupby(['horse_id', 'jockey_id']).ngroup()\ndic_couple_horse_jockey = df_temp[['track_id','race_date','race_number', 'program_number', 'couple_horse_jockey']].set_index(['track_id', 'race_date', 'race_number', 'program_number']).to_dict()['couple_horse_jockey']\n\n\ndf_temp['qid'] = df_temp.groupby(['track_id','race_date','race_number']).ngroup()\ndic_qid = df_temp[['track_id','race_date','race_number','qid']].set_index(['track_id','race_date','race_number']).to_dict()['qid']\ndel df_temp","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Split\n\nThe data will not be seen by the model and used for prediction test. <br>\n\n![image.png](attachment:995962bc-af2b-4530-891e-5c24ad44e981.png)","metadata":{},"attachments":{"995962bc-af2b-4530-891e-5c24ad44e981.png":{"image/png":"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mzVr1ttvv11aWtqrV69zzjnnqquuGjx4MO1k7fP51qxZ8/LLL7/77rvkLHDQoEEPPfTQBRdckJ2dXVZWtmjRoiVLluzfvx8h9Oabb27cuPGqq67q27dvZGurQwT9jgAgPMrLy8kgufPOO3kv5ocffphMuOZAysjI2LZtG8b4p59+IjsVwzBatGjRokULc224++67TU/SWbNmIYRycnKI8BMnTnTp0gUhdO2115KT27S0NPPLaAUFBQcOHIhyCwCADqNGjTIMw+v1/vzzz+RAhXDixIkOHToghNq1a1daWooxXr58OenPHo+HGRoPPvig3+/HGAcCgXXr1pH+v379eozxqVOnxo8fjxC65JJLiGSfz/f999/n5OSQtORraOaykpGRcdddd5GY1dXV//jHP8j95OTk7OzszMxMYqycdtppc+bMwRivWbNm0KBB9MLUsmXLZ5999vjx4//1X/+FELrlllvMSq1fv/788883q5CdnU2ybtWq1YwZM8iJBcb4kUceQQgNHTp04MCBJHJ6erppAbz44otmTMDn87344otkLl2yZAkdVF9f//LLL5OZNikpKSsry1T0lClTTpw4YcacNm0amXXT09OJlkm0G264oby8fN26dfx3DIn2Y07sz6MAIDTS09N79+6dkpKyYcOGrKysVq1aDRo0iJzl3nXXXYFAIDk5+Zprrvnqq6++++67J554onXr1gih5557jnz5hUhACKWmppLJMSkpidyZPXs2Qqh///4zZ8585ZVXiMzS0tKXXnopdtUFAAGkJ1955ZWZmZl+v3/JkiX0bn758uVHjx5FCE2cOJGYLHfccQdCKC0t7brrrvv222+Li4v//Oc/Z2dnI4SmTZu2YcMGYS6GYZA4eXl55M6ePXvGjh1bXl6elpY2aNCgt9566/HHH09LSzMfcplpt2/f/vjjjyOE2rdv/+c//3nlypWffPLJdddd5/F4jh49Smydrl27/ulPf7rtttuIu/fQoUOff/75UaNGBQIBMiTN70Ps3r37oYce+vrrrxFCl1122aJFi9asWXP77bdnZWUdP378oYceMv3BiRvKypUr165dm5eXd/fdd3/wwQcXXXQRCX333XfJuRSgZvbs2bfddlttbW1OTs7111//1VdfPfbYY+3bt0cIvfbaa08++SSJVlxcPGfOHL/fP3jw4H/+85/Lly9/9dVXzzzzTITQ66+/PmvWrA4dOtx999233347scvHjRv35JNPMrZvzIixGQYActSnQU8++WRqaurpp5+enp7+0UcfmfeJoyhC6I9//CMdf+XKlWQfs3TpUrJjfv/99xFC+fn5x48fxxhXVlYWFRUhhFJSUh577DEzIVkbPB7PBRdcEMHaAkCo7Nu3jzgvjx49mh4pN9xwA9mgr1ixIhAI7Nmzh5z9TJs2jU7+3XffkR6+Zs0aLDoNqq6uvu666xBCEydOJB+ffvpphFBGRsY999xjytm1a9eAAQMQQunp6eQ0yOfzEbukXbt29CCtq6sjEoYNG1ZSUkJubtu27dxzz83JyZk5cya5c+jQoeuvvx4hdPvtt5M706dPJ5bWzTffXF1dbQr817/+Rb7QNHHixCNHjmCMp02bRo5yzzvvvE2bNpFofr//iiuuQAjl5eV9+eWXLjV/k0d2GrRjx45x48YhhPr16/f111+b91evXn322WcjhH7961+vXbsWY/z8888TDzPyQJOwd+/eSy+9FCH0wAMPVFZWYox3795NEn788cckTiAQoM8vYwKcBgFNmOTk5J07d86aNevyyy9HwT1odnb2mWee2bFjx3Hjxvn9fuLcV1dXZ36JxtY1csCAAQ8//LApsHv37jfffHMgENi9e3dtbW3EawUADunQocOwYcO8Xu9XX31F3qeFEDpx4gT56mVRUVHfvn0Nw8jJyRk2bFjXrl3Hjh0bCASIQ2ttba3Tb0SfPHny9ddfRwj179//r3/9KwqOlC5duvz973+nY3o8nsLCwm7dug0YMGDEiBEIIb/fT767YD5QMyEugPX19RUVFcJ8S0pKfvjhh0AgMHz48FtvvTUtLY0sYwihm2+++b//+78RQj/99NPGjRtJfL/fn5GRceutt5LtDcbY4/FcffXVnTp1Ki0tJedkgIKffvrpiy++8Hq9V155JTmcIwwePHjKlCmpqakbN24kFjNCyDAMv99/4MCBqqoqosqOHTveeeedf/vb3y677DJif586dYp4tZ88edJMFXNfdXCRBpowfr8/Pz9/6NChpn2DMc7MzFy4cCFC6NixY7t3766srCwrKysrK3vzzTfJN7/UeDyeW265BVFfEDXdBulvmQFAnEA66nXXXff1119XVFQsXry4V69eqampy5YtIyv9rbfeSnw7WrZsSQ5+ysrKdu7cWVlZWVpaWlpaOnPmTEc5lpWVHTp0KCMjY+LEiWYByN9evXqNGDHi+++/JzENwzj77LO3b9/u8/mOHj166NChkydPHjt2bNOmTYpMZaOsrq6O7EN69OjRq1cvJmaPHj1SUlKOHTtmWlH19fWXXnopefJiDmfze0wwltX4/f7t27dXV1d36dKloKBg3759J0+eJI2WkZFRVVVF3tSwY8cOhFBeXl7Lli2PHDny0EMPLV269LrrruvWrVvbtm3POecc4sjFEFeND2YQ0ITx+/19+/alv61ApuO9e/fOnj37nXfekfk6qDG/J+9aQQEgYpCOet555+Xk5FRUVMydO/faa69NTU19//33SU8+77zzyKOxQCCwa9euWbNmzZ07NxzPmCNHjlRUVOTk5BQWFiLrSPF4PLm5uZjyDaqpqVm9evXrr7/+ySeflJaWhpwpQujQoUO7du1CCLVr147/SZCsrCzixsd/bR7BcHaOedK2e/fum266SRaNtPb48eNXr179yiuvkC8VLliwACHUtm3ba6+9duzYsQMHDiTfw49PwAwCmjCGYVRVVTHfpSev89+9e7dhGG3atCGHsRdccMGBAwcWLVqEnfx6KwA0CTDG2dnZF1988RtvvLF8+fIDBw4kJSV99dVXfr9//PjxnTp1IkbAL7/8cu6555aUlHg8nrZt26akpBiGccEFF+zevZs4HWtiGlU1NTXmTdPOoMej3+//4osviH9Jenp6u3btDMPIz88fNGjQqlWrfv75Z0fVJA/UEELCZ9MtWrTIyMiAAe46ycnJrVu3Tk9PpzVr/uAu+epJRkbGc88997vf/e6ZZ55ZtmyZx+Opqak5cuTIs88+++yzz/7lL3+5//774/ZV4GAGAU0bfpP34osv/vLLLwih0aNHP/XUU3369CFfMzl58uRpp50Gzj1Ac+XGG2+cO3fuiRMnVq1adfDgwbKyMoTQVVddZb7I54UXXiA20KWXXvrUU0/16NGDGDRHjx41X9ysg/n6RP7nO/x+f3V1tTkqDx8+PGPGDIRQ69atb7zxxvvuu8/8rtl777131VVXOapgRkYGOVQgxhDDli1bDh482KlTp3h4I18zwDwN6tmz50svvUS/t4mE1tXVkTdhIoSqqqpqamoGDx48b948hNCOHTuWLFkye/bsFStWVFVVzZs378ILL1T/KG8Mge4CNDfWrFnj9/vbtWs3d+7cAQMGmF+1XbduHf8ORgBoBhCzY/DgweQL5++888706dN9Pl/btm3PPvts80vsq1atQgh16dJl3rx5RUVFxAbCGNNerjrZdejQoaCgoLKy8t1336VPXzDGO3bs+Oabb8wnVidOnFi/fj1CaPTo0c8884xpA1VWVpI3HwrrIitGQUEBeYXpli1byFbH5NixY+QJeOfOncmjOiBMyEueEEJHjx7dtm0bE7ps2bL777//scceI53no48+uu222+bOnUt+haNbt27k2/X33nuvYRjHjx+vrKxEVAeLq2eUYAYBzQ3y6xnZ2dnHjx8nX8Wsra09cODA008/TTaRwq0kADRpMMZJSUk33nhjXl7eokWL5s+fn5aWNmHChPz8fLLk+P1+4iXTokWLsrIyMjRqamr27t37zDPPECGaQyM7O5u8yWL58uUffvgh+QIBeak0+SKCuciZv2hBfmeDRDt16tTSpUvJy0sxxuYPBZrItistWrRo06YNyff99983T3YDgcCnn366dOlShNDgwYPhB8jChyhr4MCBPXr0KCkpefvtt3fs2GFqcPfu3S+88MILL7wwZ84c4ob/3XffzZkzZ9asWTt37qQltGrViryi07SMyX2fz0d6YExqxwBmENDcGDlyZFpa2ubNm6dPn/7jjz/+/PPPn3/++dixYz/99FMy6hYsWLBnz55YFxMA3GfChAmmCVJRUTF+/HjzNDQpKYn4Sq9du3b69Olr1qxZu3bt/PnzL7/8cvKDMxjjzz77jDllEZKVlfXAAw+kpqZWVlb+7ne/e+eddzZv3rxs2bJHH32UfP/LNHry8/PPPvtsv9+/YMGCmTNnbtiwYdWqVdOnT580aRLx0S4pKfnqq6/Id7tIkqqqqs8///ybb745dOgQ86tV6enpF198cVFRUVlZ2ZNPPvm3v/1t69atO3fu/Pe//z1t2rRffvmlsLDw17/+NfN7WAxxsvQ2CQYNGnTNNdcghIqLi6dOnbp48eKNGzcuXrz4lltuIS9dGz9+/MiRIxFCRUVFGRkZixYtevjhh5csWbJr164tW7a8/vrrL730UkVFxZAhQzp16oSC7yxACC1atOjzzz8nz21jj4vvIAIAd6moqCCbzrvuuot/feK0adNSU1PPOuusw4cPkztke7F161bylhS6n7do0aJPnz7t2rUj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ABIUMIMAAAAAAEhQwAwCAAAAACBBATMIAAAAAIAEBcwgAAAAAAASFDCDAAAAAABIUMAMAgAAAAAgQQEzCAAAAACABAXMIAAAAAAAEhQwgwAAAAAASFDADAIAAAAAIEEBMwgAAAAAgAQFzCAAAAAAABIUMIMAAAAAAEhQwAwCAAAAACBBATMIAAAAAIAEBcwgAAAAAAASFDCDgGZOIBCIdREAIOHAGMe6CEAEaU76BTMIaLbU1tZ6PJ7y8vLmNGIBoElQUVEB4665UlNTU1dXF+tSuAaYQUCzZfv27enp6Tt27IADIQCIJnV1dfv27UtKStqzZ0+sywK4TEVFxYkTJ6qqqo4fPx7rsrgDmEFAs2Xp0qWdO3desWIFmEEAEE0qKyvXr1+fn5//448/xrosgMvs3bu3vLy8rq5u+/btsS6LO4AZBDRbPvjgg7Fjx86fP7++vj7WZQGABOLEiRMrVqwYPnz4J598EuuyAC6zbt06n8+XlZW1cuXKWJfFHcAMAponX3/9dXV19Q033JCenj5//ny/3x/rEgFAQlBVVbVw4cKCgoJrrrmmpKSk2SyWAEJo//79K1euPOuss84777y1a9fu3r071iVyATCDgGbI5s2b77vvvilTpnTq1Omee+55/PHHly9fDo/GACDS1NXVLVy48N///vddd93Vr1+/Cy644IEHHti3b1+sywW4QEVFxb/+9a/Dhw9fddVVl112WSAQeOGFF5qBh5D3kUceiXUZAMBNvvzyy/vuu++ss876n//5n7S0tC5dupw4cWL69OnZ2dlFRUUeD5j+ABARTp069cYbb8yYMWPSpEnjx4/3eDzt27dft27dO++8061bt/bt28e6gEDo7N+//+mnn167du3UqVOHDBmSlZXVsmXLzz77bNWqVb17927VqlWsCxg6BnynEWjqYIzLy8v379+/fv364uLiysrKM844Y+LEifn5+SRCZWXl22+//f333xuGMXz48IEDB3bq1Kl169aGYRiGEdvCA0CTxufzlZWV7dq1a/Xq1cuXL09LS/vNb34zfvz41NRUEmHXrl2zZ8/euHFjfn7+yJEje/fu3a5duxYtWsS22IAOtbW1JSUl27dvX7Zs2YYNG9q2bTtu3LgRI0aQzSTGeMWKFe+///7+/ft79ux57rnn9ujRo23btmlpabEuuDPszaBAILBp06bly5dv3rz54MGDFRUV9fX1sHgAcQLGOCkpKTU1NTs7Oz8/v3379meeeebAgQPT09NR8B1fhmH4fL4NGzasWLFiz549JSUl5eXlNTU1Pp8v1sUHgAYwxl6vNysrq23btj179jzzzDP79+8vW1H27du3YsWKdevW7du37/jx47W1tVEurUlycmPQBQYAACAASURBVHJ6enrLli3btm17+umnn3XWWT179jQMwxx6CKGKiorVq1evWrXqwIEDpaWlJ0+erK2t9fv90V9HSCNnZma2adOme/fuZK7IyMiIcjEUYIy3bNmyfPnyTZs2HThwoLy8PCYLLsbY4/GkpKRkZGS0bt26oKCgd+/ew4YNI0d6tHIPHTr0ww8/bNiw4dChQ2VlZVVVVXV1dYFAICZlTkpKys7OLiwsLCoqOuuss3r37u31em0TqsygQCCwY8eO1157bdOmTf379+/YsWNeXl5mZib4WABxgmEY9fX1ycnJqampWVlZZLhmZWWRUIwxGYrmRU1NzaFDh0pLSysrK2trawOBAJyGAvGDx+Opqak5duzYL7/88vPPP7dt23by5MmDBw9OTk424xw9evQ///nPt99+27Fjx27duuXn52dnZ5tmR9QgOfp8vrS0NLIJycvLKygoIEXlhx5CqLy8/NChQ8ePH6+qqqqvr4/VOuLxeGpra48dO3bgwIGff/65ZcuWkyZNGjZsWEpKSkzKY4Ix3r1792uvvbZu3bq+fft26tSJLLjRn6MMwyBzY2pqanp6ek5OTps2bfLz8xmd0so9cuQI2V6eOnXK5/PFal71eDxVVVVlZWV79+5dv359r169pkyZ0r17d7UvhHTwBAKB77777oEHHhg9evTFF19cUFCQm5trLjAAEM/Q41N9EwDijerq6mPHjh05cuSbb7758MMPp06dOmHCBHIstG3btrvvvrt9+/bjxo3r2rXraaedlp2drbPfjRpNZegRc/Po0aOLFy9+//33r7766okTJ2ZmZsaqPBjjZcuW3XPPPaNGjRozZkxhYWFubm68PTpsKsqtrKwsKys7fPjw559/vnDhwieeeGLkyJGKYZIkvIsx3rp16x133DF58uRJkybl5eUxofFWbSAxEe5LUPC0lgE6LRDPmA8a0tPT27Vr165du+7du+fl5T333HO5ubmjR4+uqKi47bbbevXq9b//+79FRUXC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"}}},{"cell_type":"code","source":"print('*'*30, 'split_train_test', '*'*30)\nprint(df_start.shape)\narray_dates = np.sort(df_race.race_date.unique())\ndate_train = list(array_dates[:int(len(array_dates)*.8)])\ndate_test = list(array_dates[int(len(array_dates)*.8):])\n\ndf_start_ts = df_start.iloc[df_start.index[df_start.race_date.isin(date_test)]].reset_index(drop=True)\ndf_start_ts.to_parquet('df_start_ts.parquet')\n\ndf_track_ts = df_track.iloc[df_track.index[df_track.race_date.isin(date_test)]].reset_index(drop=True)\ndf_track_ts.to_parquet('df_track_ts.parquet')\n\ndf_race_ts = df_race.iloc[df_race.index[df_race.race_date.isin(date_test)]].reset_index(drop=True)\ndf_race_ts.to_parquet('df_race_ts.parquet')\n\ndf_start = df_start.iloc[df_start.index[df_start.race_date.isin(date_train)]].reset_index(drop=True)\ndf_track = df_track.iloc[df_track.index[df_track.race_date.isin(date_train)]].reset_index(drop=True)\ndf_race = df_race.iloc[df_race.index[df_race.race_date.isin(date_train)]].reset_index(drop=True)\ndel df_start_ts, df_track_ts, df_race_ts\nprint(df_start.shape)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Features engineering","metadata":{}},{"cell_type":"code","source":"def get_races(df_race) :\n    ### split the post_time in hours/minutes\n    df_race['minutes_start'] = df_race.post_time.apply(lambda x : int(str(x)[-2:]))\n    df_race['hours_start'] = df_race.post_time.apply(lambda x : int(str(x)[:-2]))\n\n    ### Count the number of races during the day\n    ### We suppose we know the number of races per day in advances (races are planned)\n    df_race['count_race_during_day'] = df_race.race_date.map(df_race.groupby(['race_date'])['track_id'].count())\n\n    ### Sort by date, add day, day of week and month values\n    df_race['day_race_date'] = df_race['race_date'].dt.day\n    df_race['day_of_week'] = df_race['race_date'].dt.dayofweek\n    df_race['month_race_date'] = df_race['race_date'].dt.month\n\n    ### Count the number of days since last race\n    df_race = df_race.sort_values('race_date') .reset_index(drop = True)\n    df_race['diff_race_date'] =df_race['race_date'].diff()\n    df_race.loc[df_race['diff_race_date'] == '0 days',  'diff_race_date'] = np.NAN\n    df_race['diff_race_date'].ffill(inplace = True)\n    df_race['diff_race_date'] = df_race.diff_race_date / np.timedelta64(1, 'D') \n    df_race['diff_race_date'].fillna(0, inplace = True)\n    df_race['diff_race_date'] = df_race['diff_race_date'].astype('int')\n    return df_race\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_speed(points):\n    speed = [0]\n    points = points.values\n    for i in range(1, len(points)):\n        speed.append(4 * np.linalg.norm(np.array(points[i]) - np.array(points[i-1])))\n    return speed\n\ndef getAngle(a, b, c, thresh=5):\n    ang = 180 - math.degrees(math.atan2(c[1]-b[1], c[0]-b[0]) - math.atan2(a[1]-b[1], a[0]-b[0]))\n    if ang < -180:\n        ang += 360\n    if ang > 180:\n        ang -= 360\n    if abs(ang) > thresh:\n        return np.nan\n    return ang\n\ndef get_turning_angle(points):\n    turning_angle = [0]\n    for i in range(1, len(points.values) - 1):\n        turning_angle.append(getAngle(points.values[i-1], points.values[i], points.values[i+1]))\n    turning_angle.append(0)\n    return turning_angle\n\ndef lateral_distance(p1, p2, p3):\n    return round(np.cross(p2-p1, p1-p3) / np.linalg.norm(p2-p1), 2)\n\ndef longitudinal_distance(p1, p2, p3):\n    return round(np.dot(p2-p1, p3-p2) / np.linalg.norm(p2-p1), 2)\n\ndef get_lateral_distances(points):\n    return [[lateral_distance(point1[0], point1[1], point2[1]) for point2 in points.values] for point1 in points.values]\n\ndef get_longitudinal_distances(points):\n    return [[longitudinal_distance(point1[0], point1[1], point2[1]) for point2 in points.values] for point1 in points.values]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_track(df_track) :\n    df_track['jockey_id'] = df_track.set_index(['track_id','race_date','race_number', 'program_number'], drop = False).index.map(dic_jockey_id.get)\n    df_track['horse_id'] = df_track.set_index(['track_id','race_date','race_number', 'program_number'], drop = False).index.map(dic_horse_id.get)\n    df_track['couple_horse_jockey'] = df_track.set_index(['track_id','race_date','race_number', 'program_number'], drop = False).index.map(dic_couple_horse_jockey.get)\n    df_track = df_track.sort_values(['track_id', 'race_date', 'race_number', 'program_number', 'trakus_index']).reset_index(drop=True)\n    \n    # Thanks to : \n    # https://www.kaggle.com/code/shapital/calculating-speed-turning-angle-and-position\n    df_track['xy'] = np.array(utm.from_latlon(df_track['latitude'].values, df_track['longitude'].values)[:2]).T.tolist()\n    df_track['speed_of_horse'] = df_track.groupby(['track_id', 'race_date', 'race_number', 'program_number'])['xy'].transform(get_speed)\n    df_track[\"acceleration_of_horse\"] = df_track.groupby(['track_id', 'race_date', 'race_number', 'program_number'])['speed_of_horse'].diff()\n    \n    ### Replace tracking issues with nan\n    df_track.loc[(df_track.speed_of_horse > 30) & (df_track.acceleration_of_horse > 30), ['speed_of_horse', 'acceleration_of_horse']] = np.nan\n    df_track.loc[(df_track.speed_of_horse > 43), ['speed_of_horse', 'acceleration_of_horse']] = np.nan\n    df_track.loc[(df_track.acceleration_of_horse < -30), ['acceleration_of_horse']] = np.nan\n    return df_track","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Historical infos","metadata":{}},{"cell_type":"code","source":"dic_transformation_track ={\n                'speed_of_horse' : ['mean', 'max', 'std'],\n                'acceleration_of_horse' : ['mean', 'min', 'max', 'std'],\n            }\n\ndic_transformation_start ={\n                'odds' : ['mean', 'max', 'min', 'std'],\n            }\ncol_stats = ['horse_id', 'jockey_id', 'couple_horse_jockey']\n\ndef get_stats(df_start_original, df_track):\n    global dic_params\n    df_start = df_start_original.copy()\n    df_start = df_start.merge(df_horse, on = ['track_id','race_date','race_number', 'program_number'])\n    df_start['jockey_id'] = df_start.set_index(['track_id','race_date','race_number', 'program_number'], drop = False).index.map(dic_jockey_id.get)\n    df_start['horse_id'] = df_start.set_index(['track_id','race_date','race_number', 'program_number'], drop = False).index.map(dic_horse_id.get)\n    df_start['couple_horse_jockey'] = df_start.set_index(['track_id','race_date','race_number', 'program_number'], drop = False).index.map(dic_couple_horse_jockey.get)\n    df_start['race_date_int'] = df_start['race_date'].astype('int')\n                                 \n    for col in col_stats :        \n        group = df_track.groupby([col]).agg(dic_transformation_track)\n        group.columns = ['_'.join(x)+'_'+col for x in group.columns]\n        dic_params[col] = group.to_dict()\n        \n        group = df_start.groupby([col]).agg(dic_transformation_start)\n        group.columns = ['_'.join(x)+'_'+col for x in group.columns]\n        dic_params[col] = group.to_dict()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_fe(df):\n    global df_horse, df_race\n    df = df.merge(df_race, on = ['track_id','race_date','race_number'])\n    df = df.merge(df_horse, on = ['track_id','race_date','race_number', 'program_number'])\n    df['jockey_id'] = df.set_index(['track_id','race_date','race_number', 'program_number'], drop = False).index.map(dic_jockey_id.get)\n    df['couple_horse_jockey'] = df.set_index(['track_id','race_date','race_number', 'program_number'], drop = False).index.map(dic_couple_horse_jockey.get)\n    df['finishing_place'] = df.finishing_place - 1\n    df = df.sort_values(['track_id', 'race_date', 'race_number', 'program_number']).reset_index(drop=True)\n\n    group = df.groupby(['race_date','jockey_id'])['race_number'].count()\n    group.name = 'count_race_during_day_jockey'\n    df = df.merge(group, on = ['race_date','jockey_id'], how='left')\n\n    group = df.drop_duplicates(['race_date','jockey_id'], keep = 'last')[['race_date','jockey_id']]\n    group['diff_race_date_jockey'] = group.groupby(['jockey_id'])['race_date'].diff()\n    group.loc[group['diff_race_date_jockey'] == '0 days',  'diff_race_date_jockey'] = np.NAN\n    group['diff_race_date_jockey'].ffill(inplace = True)\n    group['diff_race_date_jockey'] = group.diff_race_date_jockey / np.timedelta64(1, 'D') \n    group['diff_race_date_jockey'].fillna(0, inplace = True)\n    group['diff_race_date_jockey'] = group['diff_race_date_jockey'].astype('int')\n    df = df.merge(group, on = ['race_date','jockey_id'], how='left')\n\n    for col in col_stats :\n        for key in dic_params[col].keys() :\n            df[key] = df[col].map(dic_params[col][key])\n            \n    df = df.sort_values(['track_id', 'race_date', 'race_number', 'program_number']).reset_index(drop=True)\n    return df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Split in train/validate and calculate FE","metadata":{}},{"cell_type":"code","source":"array_dates = np.sort(df_race.race_date.unique())\nprint('len df_start :',df_start.shape[0])\ntr_date = list(array_dates[:len(array_dates)-(int(len(array_dates)*.2))])\nts_date = list(array_dates[len(array_dates)-(int(len(array_dates)*.2)):])\n\nprint(f'tr_date: {min(tr_date)}, {max(tr_date)}' )\nprint(f'ts_date: {min(ts_date)}, {max(ts_date)}' )\ndf_race_tr = df_race.iloc[df_race.index[df_race.race_date.isin(tr_date)]].reset_index(drop=True).copy()\ndf_track_tr = df_track.iloc[df_track.index[df_track.race_date.isin(tr_date)]].reset_index(drop=True).copy()\ndf_start_tr = df_start.iloc[df_start.index[df_start.race_date.isin(tr_date)]].reset_index(drop=True).copy()\ndf_start_ts = df_start.iloc[df_start.index[df_start.race_date.isin(ts_date)]].reset_index(drop=True).copy()\n\nprint('get_stats')\ndf_race_tr = get_races(df_race_tr)\ndf_track_tr = get_track(df_track_tr)\nget_stats(df_start_tr, df_track_tr)\n\nprint('get_fe')\ndf_start_tr = get_fe(df_start_tr)\ndf_start_ts = get_fe(df_start_ts)\n\ndf_start_tr = df_start_tr[df_start_tr[CFG.target] <= 11].reset_index(drop=True)\ndf_start_ts = df_start_ts[df_start_ts[CFG.target] <= 11].reset_index(drop=True)\n\ndic_models['x_tr'] = df_start_tr\ndic_models['x_ts'] = df_start_ts\ndic_models['y_tr'] = df_start_tr[CFG.target]\ndic_models['y_ts'] = df_start_ts[CFG.target]\n\nprint('len df_start_tr :',df_start_tr.shape[0])\nfor col in df_start_tr.columns :\n    if (df_start_tr[col].isnull().sum() > df_start_tr.shape[0]*.8) :\n        print(f'{col} : {df_start_tr[col].isnull().sum()}')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"features_lgbm = list(set(df_start_tr.columns) - set(['track_id', 'race_date', 'race_number', 'program_number', 'post_time', 'horse_name', 'finishing_place', 'jockey', 'course_type', 'race_type', 'track_condition']))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# LGBM","metadata":{}},{"cell_type":"code","source":"def plot_features_importance(feat_importance) :\n    NUM_FEATURES = min(len(feat_importance-1), 15)\n    feat_importance['avg'] = feat_importance.mean(axis=1)\n    feat_importance = feat_importance.sort_values('avg',ascending=False)\n    plt.figure(figsize=(10,5*NUM_FEATURES//10))\n    plt.barh(np.arange(NUM_FEATURES,0,-1), feat_importance['avg'].values[:NUM_FEATURES])\n    plt.yticks(np.arange(NUM_FEATURES,0,-1), feat_importance.index[:NUM_FEATURES])\n    plt.title(f'Feature Importance - Top {NUM_FEATURES}')\n    plt.show()\n    print(feat_importance[feat_importance.avg == 0].index)\n    feat_importance.to_csv(f'feat_importance.csv',index=False)\n            \nparams = {\n        'objective': 'multiclass',\n        'num_classes' : 12,\n        'boosting': 'gbdt', \n        'seed': CFG.seed,\n        'learning_rate': 0.01,\n        'n_jobs': -1,\n        'verbose':-1\n        }    \n\ndef get_lgbm(list_features = features_lgbm):\n    global dic_models, features_lgbm\n    feat_importance=pd.DataFrame(index = list_features)\n    gc.collect()\n    x_tr = dic_models['x_tr'][features_lgbm]\n    x_ts = dic_models['x_ts'][features_lgbm]\n    y_tr = dic_models['y_tr'] \n    y_ts = dic_models['y_ts'] \n    lgb_tr = lgb.Dataset(x_tr, y_tr, feature_name = features_lgbm)\n    lgb_ts = lgb.Dataset(x_ts, y_ts, feature_name = features_lgbm, reference=lgb_tr)\n\n    model = lgb.train(\n        params = params,\n        train_set = lgb_tr,\n        num_boost_round = CFG.nbr,\n        valid_sets = [lgb_tr, lgb_ts],\n        callbacks=[log_evaluation(250), es]\n        )\n\n    y_pr = model.predict(x_ts)\n    print(f'CV logloss : {log_loss(y_ts, y_pr)}')\n    y_pr = [np.argmax(line) for line in y_pr]\n    print(f'CV accuracy_score : {accuracy_score(y_ts, y_pr)}')\n\n    dic_models['lgbm'] = model\n    feat_importance[\"Importance\"]=model.feature_importance()\n    plot_features_importance(feat_importance)\n        \ndef pred_lgbm(df, list_features = features_lgbm):\n    global features_lgbm\n    pred = dic_models['lgbm'].predict(df[list_features])\n    pred_total = [np.argmax(line) for line in pred]\n    return pred_total","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"es = lgb.early_stopping(100, first_metric_only=True, verbose=True)\n\ngc.collect()\nprint('*'*30, 'LGBM', '*'*30)\nget_lgbm()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Test the model on unseen data","metadata":{}},{"cell_type":"code","source":"df_start = pd.read_parquet('df_start_ts.parquet')\ndf_track = pd.read_parquet('df_track_ts.parquet')\ndf_race = pd.read_parquet('df_race_ts.parquet')\n\nprint('get_stats')\ndf_race = get_races(df_race)\ndf_track = get_track(df_track)\n\nprint('get_fe')\ndf_start = get_fe(df_start)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_start['Pred'] = pred_lgbm(df_start)   \ncm = confusion_matrix(df_start[CFG.target], df_start['Pred'])\nf = plt.figure(figsize=(15,10))\nax= f.add_subplot()\nsns.heatmap(cm, annot=True, fmt='g', ax=ax);\nax.set_xlabel('Predicted labels');ax.set_ylabel('True labels'); \nax.set_title('Confusion Matrix'); ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Confusion matrix on Top 3 position.","metadata":{}},{"cell_type":"code","source":"df_start.loc[df_start[CFG.target] < 3, CFG.target]=0\ndf_start.loc[df_start[CFG.target] >=3, CFG.target]=3\ndf_start.loc[df_start['Pred'] < 3, 'Pred'] = 0\ndf_start.loc[df_start['Pred'] >=3, 'Pred'] = 3\ncm = pd.DataFrame(confusion_matrix(df_start[CFG.target], df_start['Pred']), columns=['Top 3', 'After'], index=['Top 3', 'After'])\nf = plt.figure(figsize=(15,10))\nax= f.add_subplot()\nsns.heatmap(cm, annot=True, fmt='g', ax=ax);\nax.set_xlabel('Predicted labels');ax.set_ylabel('True labels'); \nax.set_title('Confusion Matrix'); \n\nbaseline_acc = accuracy_score(df_start[CFG.target] , df_start['Pred']) \nprint(f'OOF TOP 3 : accuracy_score : {baseline_acc}')","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}