{"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":"# Derby","metadata":{}},{"cell_type":"markdown","source":"### 1. Read and process data","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport statsmodels.api as sm\nimport logging\nimport tensorflow as tf\nfrom collections import defaultdict\nimport matplotlib.pyplot as plt\n%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:45:57.868861Z","iopub.execute_input":"2022-11-16T05:45:57.869185Z","iopub.status.idle":"2022-11-16T05:45:57.876583Z","shell.execute_reply.started":"2022-11-16T05:45:57.869161Z","shell.execute_reply":"2022-11-16T05:45:57.875383Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# directory containing data files\n# all files must have headers\ndname = \"../input/big-data-derby-2022\"  #r\"C:\\prog\\cygwin\\home\\samit_000\\RLPy\\data\\derby\"\ndtype = {\"program_number\": str}\nstartDf = pd.read_csv(os.path.join(dname, \"nyra_start_table.csv\"), header=None)\nstartDf.columns = ['track_id', 'race_date', 'race_number', 'program_number', 'weight_carried', 'jockey', 'odds', 'position_at_finish']\nstartDf.loc[:, \"race_date\"] = pd.to_datetime(startDf.loc[:, \"race_date\"])\nstartDf.loc[:, \"program_number\"] = startDf.loc[:, \"program_number\"].astype(str)\nraceDf = pd.read_csv(os.path.join(dname, \"nyra_race_table.csv\"), parse_dates=[\"race_date\"], dtype=dtype)\ntrackDf = pd.read_csv(os.path.join(dname, \"nyra_tracking_table.csv\"), parse_dates=[\"race_date\"], dtype=dtype)\ncompleteDf = pd.read_csv(os.path.join(dname, \"nyra_2019_complete.csv\"), header=None)\ncompleteDf.columns = ['track_id', 'race_date', 'race_number', 'program_number',\n       'trakus_index', 'latitude', 'longitude', 'distance_id', 'course_type',\n       'track_condition', 'run_up_distance', 'race_type', 'purse', 'post_time',\n       'weight_carried', 'jockey', 'odds', 'position_at_finish']\ncompleteDf.loc[:, \"race_date\"] = pd.to_datetime(completeDf.loc[:, \"race_date\"])\ncompleteDf.loc[:, \"program_number\"] = completeDf.loc[:, \"program_number\"].astype(str)\n","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:45:57.993835Z","iopub.execute_input":"2022-11-16T05:45:57.994312Z","iopub.status.idle":"2022-11-16T05:46:09.877243Z","shell.execute_reply.started":"2022-11-16T05:45:57.994278Z","shell.execute_reply":"2022-11-16T05:46:09.875342Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 2. Plot Data Distributions","metadata":{}},{"cell_type":"markdown","source":"#### 2.1 Races won by each horse","metadata":{}},{"cell_type":"code","source":"import seaborn as sns\nfirstpos = startDf.loc[startDf.position_at_finish == 1, :].reset_index(drop=True)\nfirstpos = firstpos[[\"program_number\", \"position_at_finish\"]].groupby([\"program_number\"]).sum().reset_index(drop=False)\nfirstpos.rename(columns={\"position_at_finish\": \"Wins\"}, inplace=True)\nfirstpos.sort_values(by=[\"program_number\"], inplace=True)\nsns.barplot(data=firstpos, x=\"program_number\", y=\"Wins\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:09.880338Z","iopub.execute_input":"2022-11-16T05:46:09.880700Z","iopub.status.idle":"2022-11-16T05:46:10.097041Z","shell.execute_reply.started":"2022-11-16T05:46:09.880674Z","shell.execute_reply":"2022-11-16T05:46:10.095795Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 2.2 Races won by each jockey","metadata":{}},{"cell_type":"code","source":"firstpos = startDf.loc[startDf.position_at_finish == 1, :].reset_index(drop=True)\nfirstpos = firstpos[[\"jockey\", \"position_at_finish\"]].groupby([\"jockey\"]).sum().reset_index(drop=False)\nfirstpos.rename(columns={\"position_at_finish\": \"Wins\"}, inplace=True)\njockeys = firstpos.jockey.unique()\njockey_map = {j:i for i,j in enumerate(jockeys)}\nfirstpos.loc[:, \"jockey\"] = firstpos.jockey.map(jockey_map)\nsns.barplot(data=firstpos, x=\"jockey\", y=\"Wins\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:10.098200Z","iopub.execute_input":"2022-11-16T05:46:10.098430Z","iopub.status.idle":"2022-11-16T05:46:10.747668Z","shell.execute_reply.started":"2022-11-16T05:46:10.098407Z","shell.execute_reply":"2022-11-16T05:46:10.746309Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"startDf.columns","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:10.749378Z","iopub.execute_input":"2022-11-16T05:46:10.749723Z","iopub.status.idle":"2022-11-16T05:46:10.758571Z","shell.execute_reply.started":"2022-11-16T05:46:10.749690Z","shell.execute_reply":"2022-11-16T05:46:10.756917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 2.3 Races won by horses on different courses","metadata":{}},{"cell_type":"code","source":"firstpos = completeDf.loc[(completeDf.trakus_index == 1) & (completeDf.position_at_finish == 1), :].reset_index(drop=True)\nfirstpos = firstpos[[\"program_number\", \"position_at_finish\", \"course_type\"]].groupby([\"program_number\", \"course_type\"]).sum().reset_index(drop=False)\nfirstpos.rename(columns={\"position_at_finish\": \"Wins\"}, inplace=True)\nfirstpos.sort_values(by=[\"program_number\"], inplace=True)\nsns.barplot(data=firstpos, x=\"program_number\", y=\"Wins\", hue=\"course_type\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:10.761882Z","iopub.execute_input":"2022-11-16T05:46:10.762196Z","iopub.status.idle":"2022-11-16T05:46:11.925214Z","shell.execute_reply.started":"2022-11-16T05:46:10.762166Z","shell.execute_reply":"2022-11-16T05:46:11.924226Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 2.4 Percentage of races won by each horse per course type","metadata":{}},{"cell_type":"code","source":"allpos = completeDf.loc[completeDf.trakus_index == 1, :].reset_index(drop=True)\nallpos.loc[:, \"race_count\"] = 1\nallpos = allpos[[\"program_number\", \"race_count\", \"course_type\"]].groupby([\"program_number\", \"course_type\"]).sum().reset_index(drop=False)\nallpos = pd.merge(firstpos, allpos, on=[\"program_number\", \"course_type\"], how=\"left\")\nallpos.loc[:, \"Win Percent\"] = allpos.Wins / allpos.race_count\nsns.barplot(data=allpos, x=\"program_number\", y=\"Win Percent\", hue=\"course_type\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:11.926252Z","iopub.execute_input":"2022-11-16T05:46:11.927527Z","iopub.status.idle":"2022-11-16T05:46:13.101051Z","shell.execute_reply.started":"2022-11-16T05:46:11.927467Z","shell.execute_reply":"2022-11-16T05:46:13.099549Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 3. Build Models To Predict Race-Winning Horse","metadata":{}},{"cell_type":"markdown","source":"#### 3.1 Data Processing","metadata":{}},{"cell_type":"markdown","source":"##### Map categorical columns to integers\n##### Convert post_time to a fraction : seconds/seconds_in_day \n##### Race month\n##### Distance_id, runup_distance and purse to fractions (normalized using constants)","metadata":{}},{"cell_type":"code","source":"def mapCategoricalCol(df, col, map=None):\n    if map is None:\n        return {t:i for i,t in enumerate(df.loc[:, col].unique())}\n    df.loc[:, col] = df.loc[:, col].map(map)\n    return df\n\ndef convertOddsToProb(df, odds):\n    df.loc[:, odds] = df.loc[:, odds].astype(np.float32)\n    df.loc[:, odds] = df.loc[:, odds] / 100.0\n    df.loc[:, odds] = df.loc[:, odds] / (df.loc[:, odds] + 1.0)\n\n    \ndef processData():\n    trackMap = mapCategoricalCol(completeDf, \"track_id\")\n    mapCategoricalCol(startDf, \"track_id\", trackMap)\n    mapCategoricalCol(trackDf, \"track_id\", trackMap)\n    mapCategoricalCol(raceDf, \"track_id\", trackMap)\n    mapCategoricalCol(completeDf, \"track_id\", trackMap)\n\n    jockMap = mapCategoricalCol(completeDf, \"jockey\")\n    mapCategoricalCol(startDf, \"jockey\", jockMap)\n    mapCategoricalCol(completeDf, \"jockey\", jockMap)\n\n    convertOddsToProb(startDf, \"odds\")\n    convertOddsToProb(completeDf, \"odds\")\n\n    courseMap = mapCategoricalCol(completeDf, \"course_type\")\n    mapCategoricalCol(raceDf, \"course_type\", courseMap)\n    mapCategoricalCol(completeDf, \"course_type\", courseMap)\n\n    raceMap = mapCategoricalCol(completeDf, \"race_type\")\n    mapCategoricalCol(raceDf, \"race_type\", raceMap)\n    mapCategoricalCol(completeDf, \"race_type\", raceMap)\n\n    trackCondMap = mapCategoricalCol(completeDf, \"track_condition\")\n    mapCategoricalCol(raceDf, \"track_condition\", trackCondMap)\n    mapCategoricalCol(completeDf, \"track_condition\", trackCondMap)\n\n    raceDf.loc[:, \"post_time\"] = raceDf.loc[:, \"post_time\"].astype(np.int32)\n    hours, minutes = np.divmod(raceDf.post_time.values, 100)\n    raceDf.loc[:, \"post_time\"] = (60*hours + minutes) / (24.0 * 60)\n\n    completeDf.loc[:, \"post_time\"] = completeDf.loc[:, \"post_time\"].astype(np.int32)\n    hours, minutes = np.divmod(completeDf.post_time.values, 100)\n    completeDf.loc[:, \"post_time\"] = (60 * hours + minutes) / (24.0 * 60)\n\n    startDf.loc[:, \"race_month\"] = startDf.loc[:, \"race_date\"].dt.month\n    raceDf.loc[:, \"race_month\"] = raceDf.loc[:, \"race_date\"].dt.month\n    trackDf.loc[:, \"race_month\"] = trackDf.loc[:, \"race_date\"].dt.month\n    completeDf.loc[:, \"race_month\"] = completeDf.loc[:, \"race_date\"].dt.month\n\n    raceDf.loc[:, \"distance_id\"] = raceDf.loc[:, \"distance_id\"] / 1000.0\n    completeDf.loc[:, \"distance_id\"] = completeDf.loc[:, \"distance_id\"] / 2000.0\n\n    raceDf.loc[:, \"run_up_distance\"] = raceDf.loc[:, \"run_up_distance\"] / 300.0\n    completeDf.loc[:, \"run_up_distance\"] = completeDf.loc[:, \"run_up_distance\"] / 300.0\n\n    raceDf.loc[:, \"purse\"] = raceDf.loc[:, \"purse\"] / 1.5E6\n    completeDf.loc[:, \"purse\"] = completeDf.loc[:, \"purse\"] / 1.5E6","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:13.102918Z","iopub.execute_input":"2022-11-16T05:46:13.103506Z","iopub.status.idle":"2022-11-16T05:46:13.123855Z","shell.execute_reply.started":"2022-11-16T05:46:13.103463Z","shell.execute_reply":"2022-11-16T05:46:13.122295Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"processData()","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:13.124909Z","iopub.execute_input":"2022-11-16T05:46:13.125230Z","iopub.status.idle":"2022-11-16T05:46:17.618773Z","shell.execute_reply.started":"2022-11-16T05:46:13.125201Z","shell.execute_reply":"2022-11-16T05:46:17.617756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 3.2 Set Training Data to 80% of all Data","metadata":{}},{"cell_type":"code","source":"trainPerc = 0.8\n# INFO:DerbyRaceModel:[ 9  9 10 10]","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:17.620056Z","iopub.execute_input":"2022-11-16T05:46:17.620518Z","iopub.status.idle":"2022-11-16T05:46:17.624396Z","shell.execute_reply.started":"2022-11-16T05:46:17.620487Z","shell.execute_reply":"2022-11-16T05:46:17.623171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 3.3 Add Additional Column for Tracking Data","metadata":{}},{"cell_type":"markdown","source":"##### distxcomplete = distance run * percent of race completed","metadata":{}},{"cell_type":"code","source":"def additionalFeature(complete_df):\n    groupdf = complete_df.groupby([\"track_id\", \"race_date\", \"race_number\", \"program_number\"])\n    dflist = []\n    for key, df in groupdf:\n        sortdf = df.sort_values(by=[\"trakus_index\"]).reset_index(drop=True)\n        max_trakus = sortdf.loc[sortdf.shape[0]-1, \"trakus_index\"]\n        lat = sortdf.loc[:, \"latitude\"].values\n        longit = sortdf.loc[:, \"longitude\"].values\n        diff_sq = (lat[1:] - lat[0:-1])**2 + (longit[1:] - longit[0:-1])**2\n        sum_sq = np.cumsum(diff_sq)\n        sortdf.loc[:, \"distance_run\"] = 0.0\n        sortdf.loc[1:, \"distance_run\"] = sum_sq\n        sortdf.loc[:, \"race_completed\"] = sortdf.loc[:, \"trakus_index\"] / max_trakus\n        sortdf.loc[:, \"distXcomplete\"] = sortdf.loc[:, \"distance_run\"] * sortdf.loc[:, \"race_completed\"]\n        dflist.append(sortdf)\n    return pd.concat(dflist, axis=0, sort=True, ignore_index=True)\n\ncompleteDf = additionalFeature(completeDf)","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:46:17.626784Z","iopub.execute_input":"2022-11-16T05:46:17.627169Z","iopub.status.idle":"2022-11-16T05:47:11.808260Z","shell.execute_reply.started":"2022-11-16T05:46:17.627135Z","shell.execute_reply":"2022-11-16T05:47:11.806522Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Process for model building:\n#### 1. For each race in the test set, build a pairwise prediction model to predict which of the two horses wins the race using training data.\n####   Take a race from the test set, identify the horses running in that race, for each pair of horses in the race, check if the pairwise model exists. If it does not, build the model using training dataset\n#### 2. For each pair of horses, build 4 models to predict the probability that first horse in a pair finishes ahead of the second horse in a race:\n##### a. Logistic regression model using the data available before race begins.\n##### b. Neural network model using the data available before race begins.\n##### c. Logistic model using tracking and static data.\n##### d. Neural network model using tracking and static data.\n#### 3. Predict the race results using the 4 models\n#### 4. Check how many races had the winning horse predicted correctly by the 4 models","metadata":{}},{"cell_type":"code","source":"class DerbyRaceModel(object):\n    logger = logging.getLogger(\"DerbyRaceModel\")\n\n    def __init__(self, start_df, race_df, track_df, complete_df, train_perc=0.8, max_horses=10, max_trakus=50, mod_trakus=10):\n        self.maxTrakus = max_trakus\n        self.modTrakus = mod_trakus\n        dtype = {\"program_number\": object}\n        self.startDf = start_df\n        self.raceDf = race_df\n        self.trackDf = track_df\n        self.completeDf = complete_df\n        self.minTrakus = self.completeDf.trakus_index.min()\n        self.trackMap, self.jockMap, self.courseMap, self.raceMap = None, None, None, None\n        self.trackCondMap = None\n        self.trainPerc = train_perc\n        self.regressCommonCols = [\"track_id\", \"race_month\", \"distance_id\", \"course_type\", \"track_condition\",\n                       \"run_up_distance\", \"race_type\", \"purse\", \"post_time\"]\n        self.regressSharedCols = [\"jockey\"]\n        self.resultCol = \"position_at_finish\"\n        self.joinCols = list(self.raceDf.columns) + [\"trakus_index\", \"race_month\"]\n        \n        self.horses = sorted(self.completeDf.loc[:, \"program_number\"].unique())\n        self.raceDates = sorted(self.completeDf.loc[self.completeDf.program_number.isin(self.horses), \"race_date\"].unique())\n        self.trainingRaceDates = self.raceDates[0:int(train_perc * len(self.raceDates))]\n        # for pairs of horses\n        self.staticModels = {}\n        self.dynamicModels = {}\n        self.constProb = {}\n        self.lossFn = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n\n    def fastestInGroup(self, horses, prob_matrix):\n        return np.argmax(np.prod(prob_matrix, axis=1))\n\n    def predictFinishRank(self, horses, prob_matrix):\n        result = []\n        req_len = len(horses)\n        while len(result) < req_len:\n            k = self.fastestInGroup(horses, prob_matrix)\n            result.append(horses[k])\n            prob_matrix[:, k] = 1.0\n            prob_matrix[k, k] = 0.0\n        return result\n\n    def fitModel(self, endog, exog):\n        logit_model = sm.GLM(endog, exog, family=sm.families.Binomial())\n        fm = logit_model.fit_regularized(alpha=0.01, L1_wt=1)\n        #self.logger.info(fm.params)\n        # self.logger.info(fm.pvalues)  #params\n        nonzero_cols = [i for i, f in enumerate(fm.params[1:]) if f != 0]\n        nm = self.neuralNetModel(exog[:, 1:], endog, nonzero_cols)\n        return fm, nm\n\n    def predictAndCompare(self, endog, exog, model, nn_model):\n        pred1 = model.predict(exog)\n        nonzero_cols = [f != 0 for f in model.params]\n        nonzero_cols[0] = False\n        pred2 = nn_model.predict(exog[:, nonzero_cols])\n        return pred1, pred2  #, self.lossFn(endog, pred1).numpy(), self.lossFn(endog, pred2[:, 0]).numpy()\n\n    def logisticAndNNModels(self, df, ratio_cols, horse1, horse2, model_dict, fit=True):\n        common_cols = self.regressCommonCols\n        both_cols = self.regressSharedCols\n        result_col = self.resultCol\n        join_cols = self.joinCols\n\n        bcols = []\n        for bc in both_cols:\n            bcols.append(bc + \"_h1\")\n            bcols.append(bc + \"_h2\")\n\n        exog_cols = ratio_cols + common_cols + bcols\n\n        h1df = df.loc[df.program_number == horse1, :].reset_index(drop=True)\n        h2df = df.loc[df.program_number == horse2, :].reset_index(drop=True)\n\n        if (h1df.shape[0] == 0) or (h2df.shape[0] == 0):\n            val = self.calculateConstProb(horse1, horse2)\n            return val, val\n\n        h1h2df = pd.merge(h1df, h2df, on=join_cols, how=\"inner\", suffixes=(\"_h1\", \"_h2\"))\n        for rc in ratio_cols:\n            h1h2df.loc[:, rc] = (1 + h1h2df.loc[:, rc + \"_h1\"]) / (1 + h1h2df.loc[:, rc + \"_h2\"])\n\n        exog_df = h1h2df.loc[:, exog_cols]\n        missing_data_rows = pd.isnull(exog_df).values.sum(axis=1)\n        missing_data_rows = np.where(missing_data_rows > 0, True, False)\n        exog = exog_df.values[~missing_data_rows]\n        if exog.shape[0] == 0:\n            self.logger.info(\"No data horse1: %s, horse2: %s\", horse1, horse2)\n            val = self.calculateConstProb(horse1, horse2)\n            return val, val\n        exog = sm.add_constant(exog, has_constant=\"add\")\n        endog = np.where(h1h2df.loc[:, result_col + \"_h1\"].values < h1h2df.loc[:, result_col + \"_h2\"].values,\n                         1, 0)\n        endog = endog[~missing_data_rows]\n        if missing_data_rows.sum():\n            self.logger.info(\"Dropping %d rows with NaN values for horse1: %s - horse2: %s\", missing_data_rows, horse1, horse2)\n        key = (horse1, horse2)\n        if fit:\n            try:\n                fm, nm = self.fitModel(endog, exog)\n                model_dict[key] = [fm, nm]\n            except Exception as e:\n                self.logger.info(\"Cannot build model horse1 %s, horse2: %s\", horse1, horse2)\n                return self.calculateConstProb(horse1, horse2)\n        fm, nn = model_dict.get(key, [None, None])\n        return self.predictAndCompare(endog, exog, fm, nn)\n\n    def fitEvalStaticModel(self, horse1, horse2, track_id=None, race_date=None, race_number=None):\n        fit = (track_id is None)\n        if fit:\n            rdates = self.raceDates\n            ntrain = int(self.trainPerc * len(rdates))\n            train_dates = rdates[0:ntrain]\n            completeDf = self.completeDf.loc[self.completeDf.program_number.isin(self.horses) & self.completeDf.race_date.isin(train_dates), :].reset_index(drop=True)\n            df = completeDf.loc[completeDf.trakus_index == self.minTrakus, :].reset_index(drop=True)\n        else:\n            mask = (self.completeDf.track_id == track_id) & (self.completeDf.race_date == race_date) & (self.completeDf.race_number == race_number) & (self.completeDf.trakus_index == self.minTrakus)\n            df = self.completeDf.loc[mask, :].reset_index(drop=True)\n        ratio_cols = [\"weight_carried\", \"odds\"]\n        return self.logisticAndNNModels(df, ratio_cols, horse1, horse2, self.staticModels, fit=fit)\n\n    def fitEvalDynamicModel(self, horse1, horse2, track_id=None, race_date=None, race_number=None):\n        fit = (track_id is None)\n        if fit:\n            rdates = self.raceDates\n            ntrain = int(self.trainPerc * len(rdates))\n            train_dates = rdates[0:ntrain]\n            df = self.completeDf.loc[self.completeDf.program_number.isin(self.horses) & self.completeDf.race_date.isin(train_dates), :].reset_index(drop=True)\n        else:\n            mask = (self.completeDf.track_id == track_id) & (self.completeDf.race_date == race_date) & (\n                        self.completeDf.race_number == race_number) & (self.completeDf.trakus_index == self.minTrakus)\n            df = self.completeDf.loc[mask, :].reset_index(drop=True)\n        if self.maxTrakus is not None:\n            df = df.loc[df.trakus_index <= self.maxTrakus].reset_index(drop=True)\n        df = df.loc[df.trakus_index.mod(self.modTrakus) == 0, :].reset_index(drop=True)\n        ratio_cols = [\"weight_carried\", \"odds\", \"distXcomplete\"]\n        return self.logisticAndNNModels(df, ratio_cols, horse1, horse2, self.dynamicModels, fit=fit)\n\n    def neuralNetModel(self, exog, endog, cols):\n        if (exog.shape[0] == 0) or (len(cols) == 0):\n            return None\n        exog = exog[:, cols]\n        model = tf.keras.Sequential()\n        model.add(tf.keras.layers.Dense(5, input_shape=(exog.shape[1],)))\n        model.add(tf.keras.layers.Dense(10, activation=\"relu\"))\n        model.add(tf.keras.layers.Dense(1))\n        model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n                      optimizer=tf.keras.optimizers.Adam())\n        model.fit(exog, endog, batch_size=10, epochs=20)\n        return model\n\n    def buildModelForPair(self, h1, h2):\n        self.fitEvalStaticModel(h1, h2)\n        self.fitEvalDynamicModel(h1, h2)\n\n    def winProbMatrix(self, horses, track_id, race_date, race_number):\n        nhorse = len(horses)\n        matrix1 = np.ndarray((nhorse, nhorse), dtype=np.float32)\n        matrix1[:, :] = 0.1\n        matrix2 = matrix1.copy()\n        matrix3 = matrix1.copy()\n        matrix4 = matrix1.copy()\n        for h1 in range(nhorse):\n            matrix1[h1, h1] = 1.0\n            matrix2[h1, h1] = 1.0\n            matrix3[h1, h1], matrix4[h1, h1] = 1.0, 1.0\n            for h2 in range(h1+1, nhorse):\n                key = (horses[h1], horses[h2])\n                if key in self.staticModels:\n                    matrix1[h1, h2], matrix2[h1, h2] = self.fitEvalStaticModel(key[0], key[1], track_id, race_date, race_number)\n                else:\n                    matrix1[h1, h2] = self.constProb.get(key, 0.5)\n                    matrix2[h1, h2] = self.constProb.get(key, 0.5)\n                if key in self.dynamicModels:\n                    matrix3[h1, h2], matrix4[h1, h2] = self.fitEvalDynamicModel(key[0], key[1], track_id, race_date, race_number)\n                else:\n                    matrix3[h1, h2] = self.constProb.get(key, 0.5)\n                    matrix4[h1, h2] = self.constProb.get(key, 0.5)\n        return matrix1, matrix2, matrix3, matrix4\n\n    def calculateConstProb(self, horse1, horse2):\n        rdates = self.raceDates\n        ntrain = int(self.trainPerc * len(rdates))\n        train_dates = rdates[0:ntrain]\n        h1 = self.completeDf.loc[(self.completeDf.program_number == horse1) & self.completeDf.race_date.isin(train_dates) & (self.completeDf.position_at_finish == 1), :].shape[0]\n        h2 = self.completeDf.loc[(self.completeDf.program_number == horse2) & self.completeDf.race_date.isin(train_dates) & (self.completeDf.position_at_finish == 1), :].shape[0]\n        if h1 + h2 == 0:\n            return 0.5\n        return h1 / (h1 + h2)\n\n    def fitDataForHorses(self, horses):\n        for i, h1 in enumerate(horses):\n            for j in range(i+1, len(horses)):\n                h2 = horses[j]\n                key = (h1, h2)\n                if key not in self.staticModels:\n                    self.buildModelForPair(h1, h2)\n                    if self.staticModels.get(key, None) is None:\n                        self.constProb[key] = self.calculateConstProb(h1, h2)\n\n    def predictRaceResults(self, track_id, race_date, race_number):\n        mask = ((self.completeDf.track_id == track_id) & (self.completeDf.race_date == race_date) &\n                (self.completeDf.race_number == race_number))\n        horses = sorted(self.completeDf.loc[mask, \"program_number\"].unique())\n        finish = self.completeDf.loc[mask & (self.completeDf.trakus_index == 1), [\"program_number\", \"position_at_finish\"]]\n        finish.sort_values(by=[\"position_at_finish\"], inplace=True)\n        actual = finish.loc[:, \"program_number\"].values\n        self.fitDataForHorses(horses)\n        static_logit, static_nn, dynamic_logit, dynamic_nn = self.winProbMatrix(horses, track_id, race_date, race_number)\n        result1 = self.predictFinishRank(horses, static_logit)\n        result2 = self.predictFinishRank(horses, static_nn)\n        result3 = self.predictFinishRank(horses, dynamic_logit)\n        result4 = self.predictFinishRank(horses, dynamic_nn)\n        return actual, result1, result2, result3, result4\n\n    def predict(self):\n        rdates = self.raceDates\n        ntrain = int(self.trainPerc * len(rdates))\n        test_dates = rdates[ntrain:]\n        completeDf = self.completeDf.loc[self.completeDf.race_date.isin(test_dates) & (self.completeDf.trakus_index == self.minTrakus), :].reset_index(drop=True)\n        rows = []\n        grp = completeDf.groupby([\"track_id\", \"race_date\", \"race_number\"])\n        correct = np.zeros(4, dtype=np.int32)\n        max_cnt = 100\n        cnt = 0\n        for grp_name, df in grp:\n            if cnt == max_cnt:\n                break\n            self.logger.info(cnt)\n            self.logger.info(correct)\n            track_id, race_date, race_number = grp_name\n            res = self.predictRaceResults(track_id, race_date, race_number)\n            rows.append([track_id, race_date, race_number, \",\".join(res[0]), \",\".join(res[1]), \",\".join(res[2]),\n                         \",\".join(res[3]), \",\".join(res[4])])\n            for j in range(4):\n                if res[0][0] == res[j+1][0]:\n                    correct[j] += 1\n            cnt += 1\n        df = pd.DataFrame.from_records(rows, columns=[\"track_id\", \"race_date\", \"race_number\", \"actual\", \"static_logit\",\n                                                      \"static_nn\", \"dynamic_logit\", \"dynamic_nn\"])\n        self.logger.info(correct)\n        return df\n\n    \nderby = DerbyRaceModel(startDf, raceDf, trackDf, completeDf)\ndf = derby.predict()\nprint(df)","metadata":{"execution":{"iopub.status.busy":"2022-11-16T05:47:11.810667Z","iopub.execute_input":"2022-11-16T05:47:11.810979Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Check how many race winners were correctly predicted by each model","metadata":{}},{"cell_type":"code","source":"actual_winners = df.actual.apply(lambda x: x.split(\",\")[0]).values\nstatic_logit_winners = df.static_logit.apply(lambda x: x.split(\",\")[0]).values\nstatic_nn_winners = df.static_nn.apply(lambda x: x.split(\",\")[0]).values\ndynamic_logit_winners = df.dynamic_logit.apply(lambda x: x.split(\",\")[0]).values\ndynamic_nn_winners = df.dynamic_nn.apply(lambda x: x.split(\",\")[0]).values\nresult_df = pd.DataFrame({\"Total\": [actual_winners.shape[0]],\n                         \"StaticLogit\": [(static_logit_winners == actual_winners).sum()],\n                         \"StaticNN\": [(static_nn_winners == actual_winners).sum()],\n                         \"DynamicLogit\": [(dynamic_logit_winners == actual_winners).sum()],\n                         \"DynamicNN\": [(dynamic_nn_winners == actual_winners).sum()]})\nresult_df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 4. Conclusions\n#### 4.1 Models built using both tracking and static data outperform the ones built using only static data.\n#### 4.2 Neural network model performs just as well as logistic regression model when using tracking + static data.\n#### 4.3 Overall, the prediction accuracy of the models is about 10%.","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}