From 48bef96c64e7534fa3af5e5474cabdf8dddbf291 Mon Sep 17 00:00:00 2001 From: "[Jeff Johannsen}" Date: Sat, 9 Mar 2024 00:21:32 -0700 Subject: [PATCH] ... --- .gitignore | 2 + notebooks/baseline/autoMLDL.ipynb | 3447 -------- notebooks/baseline/deep_learning.ipynb | 991 --- notebooks/baseline/eda.ipynb | 3558 --------- notebooks/baseline/etl.ipynb | 6962 ----------------- notebooks/baseline/mapping.json | 1 - notebooks/baseline/model_eval.ipynb | 2782 ------- notebooks/playground.ipynb | 634 -- src/NBAStats_game_states.py | 307 + src/NBAStats_prior_states.py | 618 ++ src/__init__.py | 0 src/evaluation.py | 147 + src/game_states.py | 363 - src/linear_model.py | 218 + src/mlp_model.py | 0 src/player_mapper.py | 59 - src/predictions.py | 50 + src/team_mapper.py | 563 -- src/utils.py | 679 ++ src/xgb_model.py | 0 web_app/app.py | 411 +- ....png => nba-los-angeles-clippers-logo.png} | Bin web_app/templates/index.html | 218 +- 23 files changed, 2402 insertions(+), 19608 deletions(-) delete mode 100644 notebooks/baseline/autoMLDL.ipynb delete mode 100644 notebooks/baseline/deep_learning.ipynb delete mode 100644 notebooks/baseline/eda.ipynb delete mode 100644 notebooks/baseline/etl.ipynb delete mode 100644 notebooks/baseline/mapping.json delete mode 100644 notebooks/baseline/model_eval.ipynb delete mode 100644 notebooks/playground.ipynb create mode 100644 src/NBAStats_game_states.py create mode 100644 src/NBAStats_prior_states.py create mode 100644 src/__init__.py create mode 100644 src/evaluation.py delete mode 100644 src/game_states.py create mode 100644 src/linear_model.py create mode 100644 src/mlp_model.py delete mode 100644 src/player_mapper.py create mode 100644 src/predictions.py delete mode 100644 src/team_mapper.py create mode 100644 src/utils.py create mode 100644 src/xgb_model.py rename web_app/static/img/team_logos/{nba-la-clippers-logo.png => nba-los-angeles-clippers-logo.png} (100%) diff --git a/.gitignore b/.gitignore index 06bca00..283f5c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ data .nba_ai_venv +models +wandb # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/notebooks/baseline/autoMLDL.ipynb b/notebooks/baseline/autoMLDL.ipynb deleted file mode 100644 index 8eb026b..0000000 --- a/notebooks/baseline/autoMLDL.ipynb +++ /dev/null @@ -1,3447 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "51bb7a2e", - "metadata": {}, - "source": [ - "# NBA AI - AutoML and AutoDL" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "comparative-marathon", - "metadata": {}, - "source": [ - "PyCaret\n", - "* Main Site - https://pycaret.org/\n", - "* Docs - https://pycaret.readthedocs.io/en/latest/\n", - "\n", - "AutoKeras\n", - "* Main Site - https://autokeras.com/" - ] - }, - { - "cell_type": "markdown", - "id": "2b0502cd", - "metadata": {}, - "source": [ - "## Table of Contents\n", - "\n", - "* [Data Setup](#data-setup)\n", - "* [AutoML Classification](#automl-classification)\n", - "* [AutoML Regression](#automl-regression)\n", - "* [AutoDL Classification](#autodl-classification)\n", - "* [AutoDL Regression](#autodl-regression)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "residential-geneva", - "metadata": {}, - "source": [ - "### Imports and Global Settings" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "chinese-single", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using TensorFlow backend\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-01-25 22:03:55.401063: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.\n", - "2024-01-25 22:03:55.456585: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.\n", - "2024-01-25 22:03:55.457907: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n", - "To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", - "2024-01-25 22:03:56.446741: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT\n" - ] - } - ], - "source": [ - "import datetime\n", - "import pandas as pd\n", - "import numpy as np\n", - "from pycaret.classification import ClassificationExperiment\n", - "from pycaret.regression import RegressionExperiment\n", - "import autokeras as ak\n", - "from sklearn.model_selection import train_test_split\n", - "from sklearn.metrics import (\n", - " r2_score,\n", - " mean_absolute_error,\n", - " accuracy_score,\n", - " precision_score,\n", - ")\n", - "\n", - "# Pandas Settings\n", - "pd.set_option(\"display.max_columns\", 1000)\n", - "pd.set_option(\"display.max_rows\", 1000)\n", - "pd.options.display.max_info_columns = 200\n", - "pd.options.display.precision = 5" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "ready-routine", - "metadata": {}, - "source": [ - "### Load Data" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "annual-chest", - "metadata": {}, - "outputs": [], - "source": [ - "df_2021_2022 = pd.read_csv(\"../data/nba_ai/cleaned_data_2021-2022.csv\")\n", - "df_2022_2023 = pd.read_csv(\"../data/nba_ai/cleaned_data_2022-2023.csv\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "adb5a0e5", - "metadata": {}, - "source": [ - "\n", - "\n", - "## Data Preparation" - ] - }, - { - "cell_type": "markdown", - "id": "15187561", - "metadata": {}, - "source": [ - "### Train Test Split" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e36e93d3", - "metadata": {}, - "outputs": [], - "source": [ - "def prepare_datasets(train_df, cls_target, reg_target, test_df=None, test_size=0.3):\n", - " \"\"\"\n", - " Prepares datasets for training and testing for both classification and regression targets,\n", - " ensuring time-sensitive splitting based on a 'date' column.\n", - "\n", - " Parameters:\n", - " train_df (DataFrame): The training dataframe.\n", - " cls_target (str): The name of the classification target column.\n", - " reg_target (str): The name of the regression target column.\n", - " test_df (DataFrame, optional): An optional testing dataframe. If not provided, a portion of the training data is used.\n", - " test_size (float, optional): The proportion of the dataset to include in the test split (if test_df is not provided).\n", - "\n", - " Returns:\n", - " tuple: A tuple containing six dataframes:\n", - " (X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg).\n", - " \"\"\"\n", - "\n", - " # Sort the dataframe based on the 'date' column\n", - " train_df = train_df.sort_values(by=\"date\")\n", - "\n", - " # If a test dataframe is not provided, split the training dataframe\n", - " if test_df is None:\n", - " X_train, X_test, y_train, y_test = train_test_split(\n", - " train_df.drop([cls_target, reg_target], axis=1),\n", - " train_df[[cls_target, reg_target]],\n", - " test_size=test_size,\n", - " shuffle=False, # Important to maintain time order\n", - " )\n", - " else:\n", - " # If a test dataframe is provided, ensure it is also sorted by date\n", - " test_df = test_df.sort_values(by=\"date\")\n", - "\n", - " # Use provided test dataframe and separate features and targets\n", - " X_train = train_df.drop([cls_target, reg_target], axis=1)\n", - " y_train = train_df[[cls_target, reg_target]]\n", - " X_test = test_df.drop([cls_target, reg_target], axis=1)\n", - " y_test = test_df[[cls_target, reg_target]]\n", - "\n", - " # Separate classification and regression targets\n", - " y_train_cls = y_train[[cls_target]]\n", - " y_train_reg = y_train[[reg_target]]\n", - " y_test_cls = y_test[[cls_target]]\n", - " y_test_reg = y_test[[reg_target]]\n", - "\n", - " return X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "9d81f7c2", - "metadata": {}, - "outputs": [], - "source": [ - "X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg = prepare_datasets(\n", - " df_2021_2022, \"CLS_TARGET\", \"REG_TARGET\", test_df=df_2022_2023\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "3ab2a0af", - "metadata": {}, - "source": [ - "### Features" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "c737d5b1", - "metadata": {}, - "outputs": [], - "source": [ - "betting_feature_set = [\n", - " \"home_opening_spread\",\n", - " \"opening_total\",\n", - " \"home_moneyline\",\n", - " \"road_moneyline\",\n", - "]\n", - "\n", - "base_feature_set = [\n", - " \"day_of_season\",\n", - " \"home_team_rest\",\n", - " \"road_team_rest\",\n", - " \"home_win_pct\",\n", - " \"road_win_pct\",\n", - " \"home_win_pct_l2w\",\n", - " \"road_win_pct_l2w\",\n", - " \"home_avg_pts\",\n", - " \"road_avg_pts\",\n", - " \"home_avg_pts_l2w\",\n", - " \"road_avg_pts_l2w\",\n", - " \"home_avg_oeff\",\n", - " \"road_avg_oeff\",\n", - " \"home_avg_oeff_l2w\",\n", - " \"road_avg_oeff_l2w\",\n", - " \"home_avg_deff\",\n", - " \"road_avg_deff\",\n", - " \"home_avg_deff_l2w\",\n", - " \"road_avg_deff_l2w\",\n", - " \"home_avg_eFG%\",\n", - " \"road_avg_eFG%\",\n", - " \"home_avg_eFG%_l2w\",\n", - " \"road_avg_eFG%_l2w\",\n", - " \"home_avg_TOV%\",\n", - " \"road_avg_TOV%\",\n", - " \"home_avg_TOV%_l2w\",\n", - " \"road_avg_TOV%_l2w\",\n", - " \"home_avg_ORB%\",\n", - " \"road_avg_ORB%\",\n", - " \"home_avg_ORB%_l2w\",\n", - " \"road_avg_ORB%_l2w\",\n", - " \"home_avg_FT%\",\n", - " \"road_avg_FT%\",\n", - " \"home_avg_FT%_l2w\",\n", - " \"road_avg_FT%_l2w\",\n", - " \"home_avg_pts_allowed\",\n", - " \"road_avg_pts_allowed\",\n", - " \"home_avg_pts_allowed_l2w\",\n", - " \"road_avg_pts_allowed_l2w\",\n", - "]\n", - "\n", - "lineup_vectors = [\"home_lineup_vector\", \"road_lineup_vector\"]" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "9f846ed5", - "metadata": {}, - "outputs": [], - "source": [ - "features = base_feature_set" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "4c2a424a", - "metadata": {}, - "outputs": [], - "source": [ - "def flatten_vector_columns(df, vector_columns):\n", - " \"\"\"\n", - " Flatten vector columns into separate feature columns.\n", - "\n", - " This function takes a DataFrame and a list of column names that store vector data as strings\n", - " (typically after being read from a CSV file), and returns a new DataFrame where the vectors\n", - " have been flattened into separate feature columns.\n", - "\n", - " Parameters:\n", - " df (pandas.DataFrame): The input DataFrame.\n", - " vector_columns (list): A list of column names in df that store vector data as strings.\n", - "\n", - " Returns:\n", - " pandas.DataFrame: The DataFrame with vector columns flattened.\n", - " \"\"\"\n", - " for column in vector_columns:\n", - " if column not in df.columns:\n", - " continue\n", - " # Convert the string representation of the vector into a numpy array\n", - " df[column] = df[column].apply(\n", - " lambda x: np.array(x.strip(\"[]\").replace(\"\\n\", \" \").split(), dtype=float)\n", - " )\n", - "\n", - " # Flatten the numpy array into separate columns\n", - " vector_df = pd.DataFrame(df[column].tolist(), index=df.index)\n", - " vector_df.columns = [f\"{column}_{i}\" for i in range(vector_df.shape[1])]\n", - "\n", - " # Drop the original vector column and concatenate the new DataFrame\n", - " df = df.drop(column, axis=1)\n", - " df = pd.concat([df, vector_df], axis=1)\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "5e7973bb", - "metadata": {}, - "outputs": [], - "source": [ - "X_train = X_train[features]\n", - "X_test = X_test[features]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "5b8c35d8", - "metadata": {}, - "outputs": [], - "source": [ - "# Flatten lineup vectors\n", - "X_train = flatten_vector_columns(X_train, lineup_vectors)\n", - "X_test = flatten_vector_columns(X_test, lineup_vectors)" - ] - }, - { - "cell_type": "markdown", - "id": "30fec5a8", - "metadata": {}, - "source": [ - "### Combined Data" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "64f8f8c5", - "metadata": {}, - "outputs": [], - "source": [ - "combined_train_df = pd.concat([X_train, y_train_cls, y_train_reg], axis=1)\n", - "combined_test_df = pd.concat([X_test, y_test_cls, y_test_reg], axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "8165bd78", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Int64Index: 1323 entries, 0 to 1322\n", - "Data columns (total 41 columns):\n", - " # Column Non-Null Count Dtype \n", - "--- ------ -------------- ----- \n", - " 0 day_of_season 1323 non-null int64 \n", - " 1 home_team_rest 1323 non-null int64 \n", - " 2 road_team_rest 1323 non-null int64 \n", - " 3 home_win_pct 1323 non-null float64\n", - " 4 road_win_pct 1323 non-null float64\n", - " 5 home_win_pct_l2w 1323 non-null float64\n", - " 6 road_win_pct_l2w 1323 non-null float64\n", - " 7 home_avg_pts 1323 non-null float64\n", - " 8 road_avg_pts 1323 non-null float64\n", - " 9 home_avg_pts_l2w 1323 non-null float64\n", - " 10 road_avg_pts_l2w 1323 non-null float64\n", - " 11 home_avg_oeff 1323 non-null float64\n", - " 12 road_avg_oeff 1323 non-null float64\n", - " 13 home_avg_oeff_l2w 1323 non-null float64\n", - " 14 road_avg_oeff_l2w 1323 non-null float64\n", - " 15 home_avg_deff 1323 non-null float64\n", - " 16 road_avg_deff 1323 non-null float64\n", - " 17 home_avg_deff_l2w 1323 non-null float64\n", - " 18 road_avg_deff_l2w 1323 non-null float64\n", - " 19 home_avg_eFG% 1323 non-null float64\n", - " 20 road_avg_eFG% 1323 non-null float64\n", - " 21 home_avg_eFG%_l2w 1323 non-null float64\n", - " 22 road_avg_eFG%_l2w 1323 non-null float64\n", - " 23 home_avg_TOV% 1323 non-null float64\n", - " 24 road_avg_TOV% 1323 non-null float64\n", - " 25 home_avg_TOV%_l2w 1323 non-null float64\n", - " 26 road_avg_TOV%_l2w 1323 non-null float64\n", - " 27 home_avg_ORB% 1323 non-null float64\n", - " 28 road_avg_ORB% 1323 non-null float64\n", - " 29 home_avg_ORB%_l2w 1323 non-null float64\n", - " 30 road_avg_ORB%_l2w 1323 non-null float64\n", - " 31 home_avg_FT% 1323 non-null float64\n", - " 32 road_avg_FT% 1323 non-null float64\n", - " 33 home_avg_FT%_l2w 1323 non-null float64\n", - " 34 road_avg_FT%_l2w 1323 non-null float64\n", - " 35 home_avg_pts_allowed 1323 non-null float64\n", - " 36 road_avg_pts_allowed 1323 non-null float64\n", - " 37 home_avg_pts_allowed_l2w 1323 non-null float64\n", - " 38 road_avg_pts_allowed_l2w 1323 non-null float64\n", - " 39 CLS_TARGET 1323 non-null bool \n", - " 40 REG_TARGET 1323 non-null int64 \n", - "dtypes: bool(1), float64(36), int64(4)\n", - "memory usage: 425.1 KB\n" - ] - } - ], - "source": [ - "combined_train_df.info(verbose=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "8545214b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Int64Index: 1320 entries, 0 to 1319\n", - "Data columns (total 41 columns):\n", - " # Column Non-Null Count Dtype \n", - "--- ------ -------------- ----- \n", - " 0 day_of_season 1320 non-null int64 \n", - " 1 home_team_rest 1320 non-null int64 \n", - " 2 road_team_rest 1320 non-null int64 \n", - " 3 home_win_pct 1320 non-null float64\n", - " 4 road_win_pct 1320 non-null float64\n", - " 5 home_win_pct_l2w 1320 non-null float64\n", - " 6 road_win_pct_l2w 1320 non-null float64\n", - " 7 home_avg_pts 1320 non-null float64\n", - " 8 road_avg_pts 1320 non-null float64\n", - " 9 home_avg_pts_l2w 1320 non-null float64\n", - " 10 road_avg_pts_l2w 1320 non-null float64\n", - " 11 home_avg_oeff 1320 non-null float64\n", - " 12 road_avg_oeff 1320 non-null float64\n", - " 13 home_avg_oeff_l2w 1320 non-null float64\n", - " 14 road_avg_oeff_l2w 1320 non-null float64\n", - " 15 home_avg_deff 1320 non-null float64\n", - " 16 road_avg_deff 1320 non-null float64\n", - " 17 home_avg_deff_l2w 1320 non-null float64\n", - " 18 road_avg_deff_l2w 1320 non-null float64\n", - " 19 home_avg_eFG% 1320 non-null float64\n", - " 20 road_avg_eFG% 1320 non-null float64\n", - " 21 home_avg_eFG%_l2w 1320 non-null float64\n", - " 22 road_avg_eFG%_l2w 1320 non-null float64\n", - " 23 home_avg_TOV% 1320 non-null float64\n", - " 24 road_avg_TOV% 1320 non-null float64\n", - " 25 home_avg_TOV%_l2w 1320 non-null float64\n", - " 26 road_avg_TOV%_l2w 1320 non-null float64\n", - " 27 home_avg_ORB% 1320 non-null float64\n", - " 28 road_avg_ORB% 1320 non-null float64\n", - " 29 home_avg_ORB%_l2w 1320 non-null float64\n", - " 30 road_avg_ORB%_l2w 1320 non-null float64\n", - " 31 home_avg_FT% 1320 non-null float64\n", - " 32 road_avg_FT% 1320 non-null float64\n", - " 33 home_avg_FT%_l2w 1320 non-null float64\n", - " 34 road_avg_FT%_l2w 1320 non-null float64\n", - " 35 home_avg_pts_allowed 1320 non-null float64\n", - " 36 road_avg_pts_allowed 1320 non-null float64\n", - " 37 home_avg_pts_allowed_l2w 1320 non-null float64\n", - " 38 road_avg_pts_allowed_l2w 1320 non-null float64\n", - " 39 CLS_TARGET 1320 non-null bool \n", - " 40 REG_TARGET 1320 non-null int64 \n", - "dtypes: bool(1), float64(36), int64(4)\n", - "memory usage: 424.1 KB\n" - ] - } - ], - "source": [ - "combined_test_df.info(verbose=True)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "solar-integration", - "metadata": {}, - "source": [ - "\n", - "\n", - "## AutoML Classification" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "intense-english", - "metadata": {}, - "source": [ - "### Setup and Preprocessing" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "b8afd6c9", - "metadata": {}, - "outputs": [], - "source": [ - "py_cls = ClassificationExperiment()" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "infrared-taxation", - "metadata": {}, - "outputs": [], - "source": [ - "setup_params_cls = {\n", - " \"data\": combined_train_df,\n", - " \"test_data\": combined_test_df,\n", - " \"target\": \"CLS_TARGET\",\n", - " \"ignore_features\": [\"REG_TARGET\"],\n", - " \"index\": False,\n", - " \"session_id\": 42,\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "divine-elephant", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
 DescriptionValue
0Session id42
1TargetCLS_TARGET
2Target typeBinary
3Original data shape(2643, 41)
4Transformed data shape(2643, 40)
5Transformed train set shape(1323, 40)
6Transformed test set shape(1320, 40)
7Ignore features1
8Numeric features39
9PreprocessTrue
10Imputation typesimple
11Numeric imputationmean
12Categorical imputationmode
13Fold GeneratorStratifiedKFold
14Fold Number10
15CPU Jobs-1
16Use GPUFalse
17Log ExperimentFalse
18Experiment Nameclf-default-name
19USIa836
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "py_cls.setup(**setup_params_cls)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "applicable-italy", - "metadata": {}, - "source": [ - "### Compare Models" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "breeding-republican", - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "text/html": [], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
 ModelAccuracyAUCRecallPrec.F1KappaMCCTT (Sec)
rfRandom Forest Classifier0.54050.53560.42410.52340.45790.07150.07490.5490
lightgbmLight Gradient Boosting Machine0.53600.52080.46680.52080.48310.06640.06760.5010
ridgeRidge Classifier0.53440.00000.43210.51770.46280.06070.06310.4070
xgboostExtreme Gradient Boosting0.53290.54160.46700.51250.47440.06050.06170.4250
ldaLinear Discriminant Analysis0.53060.54160.43210.51450.46500.05360.05560.4420
etExtra Trees Classifier0.52760.53650.42250.50870.45600.04680.04880.5920
gbcGradient Boosting Classifier0.52680.53790.50940.51890.48180.05240.06150.4750
lrLogistic Regression0.52380.53910.42230.49950.45220.03910.03950.7270
dummyDummy Classifier0.52230.50000.00000.00000.00000.00000.00000.4550
dtDecision Tree Classifier0.52230.51990.46390.50220.47240.04010.04080.4070
nbNaive Bayes0.52080.55140.09420.43010.11240.00450.00670.3870
rbfsvmSVM - Radial Kernel0.51700.50570.00940.28670.0177-0.0097-0.02540.4700
knnK Neighbors Classifier0.51250.50820.51420.48870.48460.02490.02660.3970
svmSVM - Linear Kernel0.50940.00000.31250.19650.21410.00240.00370.4290
gpcGaussian Process Classifier0.50420.49530.48080.47910.46810.00620.00771.0220
adaAda Boost Classifier0.50340.51670.48270.50650.44580.00520.02000.5130
mlpMLP Classifier0.49580.52280.46720.47600.3907-0.0102-0.01150.4190
qdaQuadratic Discriminant Analysis0.49060.48190.49260.46990.4746-0.0181-0.01850.4190
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e54841374fa84e5d9ce11b438e7808f8", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Processing: 0%| | 0/77 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
 AccuracyAUCRecallPrec.F1KappaMCC
Fold       
00.53380.50200.25000.53330.34040.04800.0563
10.56390.52480.54690.54690.54690.12660.1266
20.47370.47990.41270.44070.4262-0.0589-0.0590
30.53030.55450.41270.50980.45610.05080.0517
40.56820.57570.44440.56000.49560.12670.1293
50.56820.59680.38100.57140.45710.12180.1288
60.53030.49670.30160.51350.38000.04150.0453
70.50000.45500.26980.45950.3400-0.0204-0.0223
80.58330.58550.47620.57690.52170.15860.1608
90.55300.58530.74600.52220.61440.12060.1317
Mean0.54050.53560.42410.52340.45790.07150.0749
Std0.03230.04800.13900.04290.08520.06780.0695
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "4e775d8768414dc0922685eb27c90e7f", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Processing: 0%| | 0/4 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
 AccuracyAUCRecallPrec.F1KappaMCC
Fold       
00.51130.54400.00000.00000.0000-0.0150-0.0838
10.51130.47740.56250.49320.52550.02610.0264
20.47370.48570.57140.45570.5070-0.0423-0.0436
30.60610.60840.69840.57140.62860.21800.2230
40.56820.61000.52380.55000.53660.13280.1329
50.57580.60430.57140.55380.56250.15090.1510
60.48480.49640.46030.46030.4603-0.0324-0.0324
70.52270.50520.52380.50000.51160.04550.0455
80.59850.58240.68250.56580.61870.20250.2065
90.54550.57940.77780.51580.62030.10870.1236
Mean0.53980.54930.53720.46660.49710.07950.0749
Std0.04390.05120.20070.16050.17400.09130.1023
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0068a35af74b41d7a0c1b6d36f4ac485", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Processing: 0%| | 0/7 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "train_predictions_cls = py_cls.predict_model(tuned_model_cls, data=X_train)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "45e0bceb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "test_predictions_cls = py_cls.predict_model(tuned_model_cls, data=X_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "f5d76fe5", - "metadata": {}, - "outputs": [], - "source": [ - "train_accuracy = accuracy_score(train_predictions_cls[\"prediction_label\"], y_train_cls)\n", - "train_precision = precision_score(\n", - " train_predictions_cls[\"prediction_label\"], y_train_cls\n", - ")\n", - "\n", - "test_accuracy = accuracy_score(test_predictions_cls[\"prediction_label\"], y_test_cls)\n", - "test_precision = precision_score(test_predictions_cls[\"prediction_label\"], y_test_cls)" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "b1491088", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train Accuracy: 1.00\n", - "Train Precision: 0.99\n", - "Test Accuracy: 0.50\n", - "Test Precision: 0.39\n" - ] - } - ], - "source": [ - "print(f\"Train Accuracy: {train_accuracy:.2f}\")\n", - "print(f\"Train Precision: {train_precision:.2f}\")\n", - "print(f\"Test Accuracy: {test_accuracy:.2f}\")\n", - "print(f\"Test Precision: {test_precision:.2f}\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "parental-cancellation", - "metadata": {}, - "source": [ - "### Model Finalization and Storage" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "dd84b0b3", - "metadata": {}, - "outputs": [], - "source": [ - "final_model_cls = py_cls.finalize_model(tuned_model_cls)" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "1db8c1a4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Classification_RandomForest_100_50_2024-01-25_22-09-20'" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "problem_type = \"Classification\"\n", - "base_model = \"RandomForest\"\n", - "train_performance = round(train_accuracy * 100)\n", - "test_performance = round(test_accuracy * 100)\n", - "\n", - "model_id = f\"{problem_type}_{base_model}_{train_performance}_{test_performance}_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}\"\n", - "\n", - "model_id" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "071f4a17", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Transformation Pipeline and Model Successfully Saved\n" - ] - }, - { - "data": { - "text/plain": [ - "(Pipeline(memory=FastMemory(location=/tmp/joblib),\n", - " steps=[('numerical_imputer',\n", - " TransformerWrapper(exclude=None,\n", - " include=['day_of_season', 'home_team_rest',\n", - " 'road_team_rest', 'home_win_pct',\n", - " 'road_win_pct', 'home_win_pct_l2w',\n", - " 'road_win_pct_l2w', 'home_avg_pts',\n", - " 'road_avg_pts', 'home_avg_pts_l2w',\n", - " 'road_avg_pts_l2w',\n", - " 'home_avg_oeff', 'road_avg_oeff',\n", - " 'home_avg_oeff...\n", - " RandomForestClassifier(bootstrap=True, ccp_alpha=0.0,\n", - " class_weight=None, criterion='gini',\n", - " max_depth=None, max_features='sqrt',\n", - " max_leaf_nodes=None, max_samples=None,\n", - " min_impurity_decrease=0.0,\n", - " min_samples_leaf=1, min_samples_split=2,\n", - " min_weight_fraction_leaf=0.0,\n", - " n_estimators=100, n_jobs=-1,\n", - " oob_score=False, random_state=42,\n", - " verbose=0, warm_start=False))],\n", - " verbose=False),\n", - " '../models/AutoML/Classification_RandomForest_100_50_2024-01-25_22-09-20.pkl')" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "py_cls.save_model(final_model_cls, f\"../models/AutoML/{model_id}\")" - ] - }, - { - "cell_type": "markdown", - "id": "dc751042", - "metadata": {}, - "source": [ - "\n", - "\n", - "## AutoML Regression" - ] - }, - { - "cell_type": "markdown", - "id": "80c2899a", - "metadata": {}, - "source": [ - "### Setup and Preprocessing" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "303ff0d7", - "metadata": {}, - "outputs": [], - "source": [ - "py_reg = RegressionExperiment()" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "fd35bde5", - "metadata": {}, - "outputs": [], - "source": [ - "setup_params_reg = {\n", - " \"data\": combined_train_df,\n", - " \"test_data\": combined_test_df,\n", - " \"target\": \"REG_TARGET\",\n", - " \"ignore_features\": [\"CLS_TARGET\"],\n", - " \"index\": False,\n", - " \"session_id\": 42,\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "6e25c1b8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
 DescriptionValue
0Session id42
1TargetREG_TARGET
2Target typeRegression
3Original data shape(2643, 41)
4Transformed data shape(2643, 40)
5Transformed train set shape(1323, 40)
6Transformed test set shape(1320, 40)
7Ignore features1
8Numeric features39
9PreprocessTrue
10Imputation typesimple
11Numeric imputationmean
12Categorical imputationmode
13Fold GeneratorKFold
14Fold Number10
15CPU Jobs-1
16Use GPUFalse
17Log ExperimentFalse
18Experiment Namereg-default-name
19USI63a2
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "py_reg.setup(**setup_params_reg)" - ] - }, - { - "cell_type": "markdown", - "id": "10ce1df1", - "metadata": {}, - "source": [ - "### Compare Models" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "2a547580", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
 ModelMAEMSERMSER2RMSLEMAPETT (Sec)
etExtra Trees Regressor11.6363216.417814.68550.06691.23841.20310.6390
rfRandom Forest Regressor11.6487217.432514.71740.06111.23921.20050.4530
ridgeRidge Regression11.7438220.818714.81020.04631.29301.21870.3320
brBayesian Ridge11.7699221.819114.84130.04281.36651.16290.3810
enElastic Net11.7846223.342214.88150.03561.36451.17810.3650
huberHuber Regressor11.8000223.656314.90160.03301.28281.20740.3600
lassoLasso Regression11.8559225.895314.95810.02431.37691.18170.3340
llarLasso Least Angle Regression11.8567225.915214.95870.02431.37691.18180.3790
lrLinear Regression11.8617227.783115.01960.01491.26711.25970.3720
adaAdaBoost Regressor11.9364225.918015.01310.02241.14341.27290.5480
ompOrthogonal Matching Pursuit12.1105233.862315.2560-0.00761.48721.09290.3910
lightgbmLight Gradient Boosting Machine12.1551233.677315.2617-0.01061.18951.33900.6030
dummyDummy Regressor12.2601235.053515.2985-0.01061.48501.02410.5930
gbrGradient Boosting Regressor12.2819250.750715.6159-0.08901.23661.34830.6520
knnK Neighbors Regressor12.4649245.239915.6260-0.05711.15711.35940.3700
xgboostExtreme Gradient Boosting12.7335256.568115.9770-0.10931.18021.50650.7320
parPassive Aggressive Regressor15.0228357.330418.5707-0.53541.04142.04820.3740
dtDecision Tree Regressor16.5521435.484520.8325-0.89771.02912.28190.4200
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "4e2ac6d9f976429889b3e6b8db573ca1", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Processing: 0%| | 0/77 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
 MAEMSERMSER2RMSLEMAPE
Fold      
012.2211241.778415.5492-0.09931.33521.3510
111.7215200.692714.1666-0.01091.22841.1547
212.6383256.347916.0109-0.03831.31731.3369
310.8538180.301113.42760.11381.20111.1625
411.3572211.091114.52900.06321.29901.0093
59.9938170.209013.04640.10831.17461.1831
612.0451239.322815.47010.09021.27201.3455
711.5034214.835214.65730.16281.17531.0818
811.5808220.730614.85700.07231.10571.1833
912.5722239.016315.46020.14891.28341.1973
Mean11.6487217.432514.71740.06111.23921.2005
Std0.761826.45570.91110.08050.07040.1082
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "01f1ad95821f44718d7f5924ecde075d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Processing: 0%| | 0/4 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
 MAEMSERMSER2RMSLEMAPE
Fold      
012.1072234.844015.3246-0.06771.32921.3184
111.2822191.962113.85500.03311.22111.0409
212.4648244.670815.64200.00901.38131.2765
310.7754175.423913.24480.13771.24501.1462
411.3832207.384614.40090.07961.31781.0169
59.7389164.741812.83520.13701.18901.0654
611.8156230.935415.19660.12211.31161.2140
711.6055222.342314.91110.13361.23221.0820
811.5662211.082714.52870.11281.17751.1118
912.6840244.343315.63150.12991.39501.0785
Mean11.5423212.773114.55700.08271.28001.1351
Std0.805426.62640.93060.06620.07360.0971
\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "afcf05bd6d9948148978db63da80c8b5", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Processing: 0%| | 0/7 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "train_predictions_reg = py_reg.predict_model(tuned_model_reg, data=X_train)" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "bfb2148a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "test_predictions_reg = py_reg.predict_model(tuned_model_reg, data=X_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "0efece01", - "metadata": {}, - "outputs": [], - "source": [ - "train_mae = mean_absolute_error(train_predictions_reg[\"prediction_label\"], y_train_reg)\n", - "train_r2 = r2_score(train_predictions_reg[\"prediction_label\"], y_train_reg)\n", - "\n", - "test_mae = mean_absolute_error(test_predictions_reg[\"prediction_label\"], y_test_reg)\n", - "test_r2 = r2_score(test_predictions_reg[\"prediction_label\"], y_test_reg)" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "d6940137", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train MAE: 9.96\n", - "Train R2: -4.62\n", - "Test MAE: 10.52\n", - "Test R2: -10.19\n" - ] - } - ], - "source": [ - "print(f\"Train MAE: {train_mae:.2f}\")\n", - "print(f\"Train R2: {train_r2:.2f}\")\n", - "print(f\"Test MAE: {test_mae:.2f}\")\n", - "print(f\"Test R2: {test_r2:.2f}\")" - ] - }, - { - "cell_type": "markdown", - "id": "cd90b613", - "metadata": {}, - "source": [ - "### Model Finalization and Storage" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "dea3db02", - "metadata": {}, - "outputs": [], - "source": [ - "final_model_reg = py_reg.finalize_model(tuned_model_reg)" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "59191ff3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Regression_RandomForest_9.96_10.52_2024-01-25_22-12-33'" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "problem_type = \"Regression\"\n", - "base_model = \"RandomForest\"\n", - "train_performance = round(train_mae, 2)\n", - "test_performance = round(test_mae, 2)\n", - "\n", - "model_id = f\"{problem_type}_{base_model}_{train_performance}_{test_performance}_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}\"\n", - "\n", - "model_id" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "901830e7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Transformation Pipeline and Model Successfully Saved\n" - ] - }, - { - "data": { - "text/plain": [ - "(Pipeline(memory=FastMemory(location=/tmp/joblib),\n", - " steps=[('numerical_imputer',\n", - " TransformerWrapper(exclude=None,\n", - " include=['day_of_season', 'home_team_rest',\n", - " 'road_team_rest', 'home_win_pct',\n", - " 'road_win_pct', 'home_win_pct_l2w',\n", - " 'road_win_pct_l2w', 'home_avg_pts',\n", - " 'road_avg_pts', 'home_avg_pts_l2w',\n", - " 'road_avg_pts_l2w',\n", - " 'home_avg_oeff', 'road_avg_oeff',\n", - " 'home_avg_oeff...\n", - " ('actual_estimator',\n", - " RandomForestRegressor(bootstrap=True, ccp_alpha=0.0,\n", - " criterion='squared_error', max_depth=5,\n", - " max_features=1.0, max_leaf_nodes=None,\n", - " max_samples=None,\n", - " min_impurity_decrease=0.01,\n", - " min_samples_leaf=2, min_samples_split=7,\n", - " min_weight_fraction_leaf=0.0,\n", - " n_estimators=280, n_jobs=-1,\n", - " oob_score=False, random_state=42,\n", - " verbose=0, warm_start=False))],\n", - " verbose=False),\n", - " '../models/AutoML/Regression_RandomForest_9.96_10.52_2024-01-25_22-12-33.pkl')" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "py_reg.save_model(final_model_reg, f\"../models/AutoML/{model_id}\")" - ] - }, - { - "cell_type": "markdown", - "id": "7208c0df", - "metadata": {}, - "source": [ - "\n", - "\n", - "## AutoDL Classification" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "d65b4807", - "metadata": {}, - "outputs": [], - "source": [ - "ak_cls = ak.StructuredDataClassifier(\n", - " max_trials=10,\n", - " overwrite=True,\n", - " loss=\"accuracy\",\n", - " seed=42,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "f3996d29", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Trial 10 Complete [00h 00m 06s]\n", - "val_accuracy: 0.5872340202331543\n", - "\n", - "Best val_accuracy So Far: 0.5872340202331543\n", - "Total elapsed time: 00h 00m 56s\n", - "Epoch 1/8\n", - "42/42 [==============================] - 1s 3ms/step - loss: 0.7526 - accuracy: 0.5246\n", - "Epoch 2/8\n", - "42/42 [==============================] - 0s 3ms/step - loss: 0.7258 - accuracy: 0.4898\n", - "Epoch 3/8\n", - "42/42 [==============================] - 0s 5ms/step - loss: 0.7064 - accuracy: 0.5246\n", - "Epoch 4/8\n", - "42/42 [==============================] - 0s 3ms/step - loss: 0.6895 - accuracy: 0.5465\n", - "Epoch 5/8\n", - "42/42 [==============================] - 0s 3ms/step - loss: 0.6935 - accuracy: 0.5457\n", - "Epoch 6/8\n", - "42/42 [==============================] - 0s 3ms/step - loss: 0.6947 - accuracy: 0.5548\n", - "Epoch 7/8\n", - "42/42 [==============================] - 0s 3ms/step - loss: 0.6838 - accuracy: 0.5669\n", - "Epoch 8/8\n", - "42/42 [==============================] - 0s 3ms/step - loss: 0.6881 - accuracy: 0.5624\n", - "INFO:tensorflow:Assets written to: ./structured_data_classifier/best_model/assets\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Assets written to: ./structured_data_classifier/best_model/assets\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 48, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ak_cls.fit(X_train, y_train_cls)" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "d4b8bd61", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "42/42 [==============================] - 0s 2ms/step - loss: 0.7032 - accuracy: 0.4932\n", - "[0.7032389640808105, 0.49318182468414307]\n" - ] - } - ], - "source": [ - "# Evaluate the best model with testing data.\n", - "print(ak_cls.evaluate(X_test, y_test_cls))" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "f369dde6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model: \"model\"\n", - "_________________________________________________________________\n", - " Layer (type) Output Shape Param # \n", - "=================================================================\n", - " input_1 (InputLayer) [(None, 39)] 0 \n", - " \n", - " multi_category_encoding (M (None, 39) 0 \n", - " ultiCategoryEncoding) \n", - " \n", - " normalization (Normalizati (None, 39) 79 \n", - " on) \n", - " \n", - " dense (Dense) (None, 32) 1280 \n", - " \n", - " re_lu (ReLU) (None, 32) 0 \n", - " \n", - " dense_1 (Dense) (None, 32) 1056 \n", - " \n", - " re_lu_1 (ReLU) (None, 32) 0 \n", - " \n", - " dropout (Dropout) (None, 32) 0 \n", - " \n", - " dense_2 (Dense) (None, 1) 33 \n", - " \n", - " classification_head_1 (Act (None, 1) 0 \n", - " ivation) \n", - " \n", - "=================================================================\n", - "Total params: 2448 (9.57 KB)\n", - "Trainable params: 2369 (9.25 KB)\n", - "Non-trainable params: 79 (320.00 Byte)\n", - "_________________________________________________________________\n" - ] - } - ], - "source": [ - "ak_cls_model = ak_cls.export_model()\n", - "ak_cls_model.summary()" - ] - }, - { - "cell_type": "markdown", - "id": "20dd8b44", - "metadata": {}, - "source": [ - "\n", - "\n", - "### Evaluate Model" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "3129d167", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "42/42 [==============================] - 0s 2ms/step\n", - "42/42 [==============================] - 0s 2ms/step\n" - ] - } - ], - "source": [ - "train_pred = ak_cls_model.predict(X_train)\n", - "test_pred = ak_cls_model.predict(X_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "8f549e67", - "metadata": {}, - "outputs": [], - "source": [ - "train_pred = train_pred.flatten()\n", - "train_pred_labels = [True if x > 0.5 else False for x in train_pred]" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "id": "11b1a1b6", - "metadata": {}, - "outputs": [], - "source": [ - "test_pred = test_pred.flatten()\n", - "test_pred_labels = [True if x > 0.5 else False for x in test_pred]" - ] - }, - { - "cell_type": "code", - "execution_count": 54, - "id": "aef72043", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Training Accuracy: 0.60\n", - "Training Precision: 0.62\n" - ] - } - ], - "source": [ - "train_accuracy = accuracy_score(y_train_cls, train_pred_labels)\n", - "train_precision = precision_score(y_train_cls, train_pred_labels)\n", - "print(f\"Training Accuracy: {train_accuracy:.2f}\")\n", - "print(f\"Training Precision: {train_precision:.2f}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 55, - "id": "dd7fa4d0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Testing Accuracy: 0.49\n", - "Testing Precision: 0.39\n" - ] - } - ], - "source": [ - "test_accuracy = accuracy_score(y_test_cls, test_pred_labels)\n", - "test_auc = precision_score(y_test_cls, test_pred_labels)\n", - "print(f\"Testing Accuracy: {test_accuracy:.2f}\")\n", - "print(f\"Testing Precision: {test_precision:.2f}\")" - ] - }, - { - "cell_type": "markdown", - "id": "18260e52", - "metadata": {}, - "source": [ - "### Model Storage" - ] - }, - { - "cell_type": "code", - "execution_count": 56, - "id": "82cee602", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Classification_AutoKeras_60_49_2024-01-25_22-13-39'" - ] - }, - "execution_count": 56, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "problem_type = \"Classification\"\n", - "base_model = \"AutoKeras\"\n", - "train_performance = round(train_accuracy * 100)\n", - "test_performance = round(test_accuracy * 100)\n", - "\n", - "model_id = f\"{problem_type}_{base_model}_{train_performance}_{test_performance}_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}\"\n", - "\n", - "model_id" - ] - }, - { - "cell_type": "code", - "execution_count": 57, - "id": "42e6d79b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Assets written to: ../models/AutoDL/Classification_AutoKeras_60_49_2024-01-25_22-13-39/assets\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Assets written to: ../models/AutoDL/Classification_AutoKeras_60_49_2024-01-25_22-13-39/assets\n" - ] - } - ], - "source": [ - "ak_cls_model.save(f\"../models/AutoDL/{model_id}\", save_format=\"tf\")" - ] - }, - { - "cell_type": "markdown", - "id": "4dd2b3f3", - "metadata": {}, - "source": [ - "\n", - "\n", - "## AutoDL Regression" - ] - }, - { - "cell_type": "code", - "execution_count": 58, - "id": "0c110b56", - "metadata": {}, - "outputs": [], - "source": [ - "ak_reg = ak.StructuredDataRegressor(\n", - " max_trials=10,\n", - " overwrite=True,\n", - " loss=\"mae\",\n", - " seed=42,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 59, - "id": "93c4635b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Trial 10 Complete [00h 00m 06s]\n", - "val_loss: 12.362330436706543\n", - "\n", - "Best val_loss So Far: 12.128240585327148\n", - "Total elapsed time: 00h 01m 00s\n", - "Epoch 1/9\n", - "42/42 [==============================] - 1s 3ms/step - loss: 12.3618 - mean_squared_error: 236.8551\n", - "Epoch 2/9\n", - "42/42 [==============================] - 0s 2ms/step - loss: 12.1866 - mean_squared_error: 230.5129\n", - "Epoch 3/9\n", - "42/42 [==============================] - 0s 2ms/step - loss: 11.9350 - mean_squared_error: 222.9816\n", - "Epoch 4/9\n", - "42/42 [==============================] - 0s 2ms/step - loss: 11.6449 - mean_squared_error: 215.8968\n", - "Epoch 5/9\n", - "42/42 [==============================] - 0s 2ms/step - loss: 11.4193 - mean_squared_error: 210.7298\n", - "Epoch 6/9\n", - "42/42 [==============================] - 0s 2ms/step - loss: 11.2874 - mean_squared_error: 209.4724\n", - "Epoch 7/9\n", - "42/42 [==============================] - 0s 2ms/step - loss: 11.1748 - mean_squared_error: 206.2092\n", - "Epoch 8/9\n", - "42/42 [==============================] - 0s 2ms/step - loss: 11.1102 - mean_squared_error: 205.2659\n", - "Epoch 9/9\n", - "42/42 [==============================] - 0s 2ms/step - loss: 11.0501 - mean_squared_error: 203.5940\n", - "INFO:tensorflow:Assets written to: ./structured_data_regressor/best_model/assets\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Assets written to: ./structured_data_regressor/best_model/assets\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 59, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ak_reg.fit(X_train, y_train_reg)" - ] - }, - { - "cell_type": "code", - "execution_count": 60, - "id": "fe00c53f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "42/42 [==============================] - 0s 2ms/step - loss: 10.8165 - mean_squared_error: 189.7712\n", - "[10.816460609436035, 189.7711944580078]\n" - ] - } - ], - "source": [ - "print(ak_reg.evaluate(X_test, y_test_reg))" - ] - }, - { - "cell_type": "code", - "execution_count": 61, - "id": "379f9994", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model: \"model\"\n", - "_________________________________________________________________\n", - " Layer (type) Output Shape Param # \n", - "=================================================================\n", - " input_1 (InputLayer) [(None, 39)] 0 \n", - " \n", - " multi_category_encoding (M (None, 39) 0 \n", - " ultiCategoryEncoding) \n", - " \n", - " normalization (Normalizati (None, 39) 79 \n", - " on) \n", - " \n", - " dense (Dense) (None, 32) 1280 \n", - " \n", - " re_lu (ReLU) (None, 32) 0 \n", - " \n", - " dense_1 (Dense) (None, 128) 4224 \n", - " \n", - " re_lu_1 (ReLU) (None, 128) 0 \n", - " \n", - " regression_head_1 (Dense) (None, 1) 129 \n", - " \n", - "=================================================================\n", - "Total params: 5712 (22.32 KB)\n", - "Trainable params: 5633 (22.00 KB)\n", - "Non-trainable params: 79 (320.00 Byte)\n", - "_________________________________________________________________\n" - ] - } - ], - "source": [ - "ak_reg_model = ak_reg.export_model()\n", - "ak_reg_model.summary()" - ] - }, - { - "cell_type": "markdown", - "id": "dbfa7391", - "metadata": {}, - "source": [ - "\n", - "\n", - "### Evaluate Model" - ] - }, - { - "cell_type": "code", - "execution_count": 62, - "id": "b872a569", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "42/42 [==============================] - 0s 3ms/step\n", - "42/42 [==============================] - 0s 2ms/step\n" - ] - } - ], - "source": [ - "train_pred = ak_reg_model.predict(X_train)\n", - "test_pred = ak_reg_model.predict(X_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 63, - "id": "b884561e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Training MAE: 10.98\n", - "Training R2: 0.13\n" - ] - } - ], - "source": [ - "train_mae = mean_absolute_error(y_train_reg, train_pred)\n", - "train_r2 = r2_score(y_train_reg, train_pred)\n", - "print(f\"Training MAE: {train_mae:.2f}\")\n", - "print(f\"Training R2: {train_r2:.2f}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 64, - "id": "bed2eea5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Testing MAE: 10.82\n", - "Testing R2: -0.02\n" - ] - } - ], - "source": [ - "test_mae = mean_absolute_error(y_test_reg, test_pred)\n", - "test_r2 = r2_score(y_test_reg, test_pred)\n", - "print(f\"Testing MAE: {test_mae:.2f}\")\n", - "print(f\"Testing R2: {test_r2:.2f}\")" - ] - }, - { - "cell_type": "markdown", - "id": "7f45f1f2", - "metadata": {}, - "source": [ - "### Model Storage" - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "id": "1df08d8b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Regression_AutoKeras_10.98_10.82_2024-01-25_22-14-51'" - ] - }, - "execution_count": 65, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "problem_type = \"Regression\"\n", - "base_model = \"AutoKeras\"\n", - "train_performance = round(train_mae, 2)\n", - "test_performance = round(test_mae, 2)\n", - "\n", - "model_id = f\"{problem_type}_{base_model}_{train_performance}_{test_performance}_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}\"\n", - "\n", - "model_id" - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "id": "427bb9d5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Assets written to: ../models/AutoDL/Regression_AutoKeras_10.98_10.82_2024-01-25_22-14-51/assets\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Assets written to: ../models/AutoDL/Regression_AutoKeras_10.98_10.82_2024-01-25_22-14-51/assets\n" - ] - } - ], - "source": [ - "ak_reg_model.save(f\"../models/AutoDL/{model_id}\", save_format=\"tf\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/baseline/deep_learning.ipynb b/notebooks/baseline/deep_learning.ipynb deleted file mode 100644 index f896786..0000000 --- a/notebooks/baseline/deep_learning.ipynb +++ /dev/null @@ -1,991 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# NBA AI - Deep Learning Base Model - PyTorch" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1. Neural Networks Structure\n", - "- A neural network comprises various layers: the input layer (for receiving data), hidden layers (where computation and feature extraction occur), and the output layer (producing the final prediction). Each layer contains neurons that perform weighted sums of their inputs followed by an activation function.\n", - "- **PyTorch Layers**: In PyTorch, `torch.nn.Module` is the base class for all neural network modules. Use `nn.Linear` for fully connected layers, suitable for tabular data. `nn.Conv2d` is used for convolutional layers, which are effective for image data due to their ability to capture spatial hierarchies. `nn.LSTM` or `nn.GRU` layers are used for sequential data like time series or text, capable of capturing temporal dynamics.\n", - "\n", - "### 2. Activation Functions\n", - "- Activation functions introduce non-linear properties to the network. This non-linearity is crucial as it allows the network to learn complex patterns. ReLU (Rectified Linear Unit) is widely used in hidden layers due to its computational efficiency and ability to mitigate the vanishing gradient problem. However, other functions like sigmoid (squashes outputs between 0 and 1) and tanh (outputs between -1 and 1) are also important, particularly in output layers for binary classification or when normalized output is required.\n", - "- **PyTorch Implementation**: PyTorch provides these activation functions in `torch.nn.functional`. For example, `F.relu` for applying ReLU, `F.sigmoid` for sigmoid, and `F.tanh` for the tanh function. They are typically used within the `forward` method of a `torch.nn.Module` class.\n", - "\n", - "### 3. Loss Functions\n", - "- The choice of the loss function is crucial and should align with the nature of the problem. For regression tasks, Mean Squared Error (MSE) is commonly used as it penalizes larger errors more severely. For classification tasks, Cross-Entropy Loss is standard as it measures the difference between two probability distributions - the actual labels and the predicted probabilities.\n", - "- **PyTorch Implementation**: PyTorch's `torch.nn` module contains various loss functions. `nn.MSELoss()` is used for regression tasks, and `nn.CrossEntropyLoss()` is used for multi-class classification tasks. CrossEntropyLoss in PyTorch combines a SoftMax activation with the negative log-likelihood loss in one single class.\n", - "\n", - "### 4. Optimizers\n", - "- Optimizers are algorithms used for changing the attributes of the neural network such as weights and learning rate to reduce the losses. Optimizers aim to minimize (or maximize) the loss (or objective) function. While the SGD optimizer is straightforward and effective for many problems, Adam is popular due to its adaptive learning rate capabilities, which can lead to faster convergence.\n", - "- **PyTorch Implementation**: Optimizers in PyTorch are available under `torch.optim`. For instance, `torch.optim.SGD` for stochastic gradient descent and `torch.optim.Adam` for the Adam optimizer. They require the parameters to optimize (typically obtained using `model.parameters()`) and a learning rate.\n", - "\n", - "### 5. Backpropagation and Gradient Descent\n", - "- Backpropagation is a mechanism used to update the weights of the network efficiently. It calculates the gradient (partial derivatives) of the loss function with respect to each weight in the network by the chain rule, enabling efficient computation of gradients. Gradient Descent, on the other hand, is an optimization algorithm used to minimize the loss function by iteratively moving in the direction of steepest descent as defined by the negative of the gradient.\n", - "- **PyTorch Mechanism**: In PyTorch, backpropagation is implemented through automatic differentiation provided by the `torch.autograd` module. Use `loss.backward()` to compute the gradient of the loss with respect to each weight and `optimizer.step()` to perform a single optimization step.\n", - "\n", - "### 6. Overfitting and Underfitting\n", - "- Overfitting occurs when a model learns the training data too well, including its noise and outliers, resulting in poor performance on new data. Underfitting occurs when a model is too simple to capture the underlying pattern in the data, leading to poor performance both on the training and new data. Both issues are critical in the development of robust models.\n", - "- **PyTorch Tools**: PyTorch offers various tools to combat overfitting. For instance, `nn.Dropout` is a layer that randomly zeroes some of the elements of the input tensor with probability `p` during training, which helps prevent overfitting by providing a form of regularization.\n", - "\n", - "### 7. Regularization Techniques\n", - "- Regularization techniques are used to prevent overfitting, which is a common problem in deep learning models. These techniques add information or constraints to the loss function or the network itself to reduce its complexity. Common techniques include L1 and L2 regularization, which add penalties to the loss function based on the size of the weights, and dropout, which randomly drops units from the neural network during training to prevent the network from becoming too dependent on certain pathways.\n", - "- **PyTorch Implementation**: In PyTorch, L1/L2 regularization is often included in the optimizer's weight decay parameter. Dropout is implemented as a layer (`nn.Dropout`) and can be added to the network architecture in `torch.nn`.\n", - "\n", - "### 8. Batch Size and Epochs\n", - "- The batch size and the number of epochs are hyperparameters that have significant effects on the training process and model performance. The batch size determines how many examples you look at before making a weight update. Smaller batch sizes provide a regularizing effect and lower generalization error. The number of epochs determines how many times the entire dataset is passed forward and backward through the neural network.\n", - "- **PyTorch Usage**: In PyTorch, the batch size is set when creating a DataLoader object (`torch.utils.data.DataLoader`), which also handles the shuffling and organization of the data. The number of epochs is controlled manually in the training loop.\n", - "\n", - "### 9. Learning Rate\n", - "- The learning rate is a hyperparameter that controls how much to change the model in response to the estimated error each time the model weights are updated. Choosing a learning rate is critical as it makes the model converge too slowly or diverge when too large.\n", - "- **PyTorch Implementation**: In PyTorch, the learning rate is set when defining an optimizer, e.g., `torch.optim.SGD(model.parameters(), lr=0.01)`. PyTorch also provides learning rate schedulers (e.g., `torch.optim.lr_scheduler`), which adjust the learning rate during training, typically reducing it according to a pre-defined schedule or in response to model performance.\n", - "\n", - "### 10. Data Preprocessing\n", - "- Data preprocessing involves transforming raw data into an understandable format. In deep learning, it often includes normalization (scaling input data to a standard range), and in the case of images, augmentation techniques such as rotations, scaling, and flipping can be used to artificially expand the dataset.\n", - "- **PyTorch Tools**: For image data, PyTorch offers the `torchvision.transforms` module, which provides common image transformations. For other data types, custom transformations can be applied to the dataset before passing it to a DataLoader.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Table of Contents\n", - "\n", - "* [Data Setup](#data-setup)\n", - "* [MLP Regression](#mlp-regression)\n", - "* [MLP Classification](#mlp-classification)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Imports and Global Settings" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import datetime\n", - "import numpy as np\n", - "import pandas as pd\n", - "from sklearn.model_selection import train_test_split\n", - "from sklearn.metrics import (\n", - " r2_score,\n", - " mean_absolute_error,\n", - " accuracy_score,\n", - " precision_score,\n", - ")\n", - "\n", - "import torch\n", - "import torch.nn as nn\n", - "import torch.optim as optim\n", - "from torch.utils.data import TensorDataset, DataLoader\n", - "\n", - "# Pandas Settings\n", - "pd.set_option(\"display.max_columns\", 1000)\n", - "pd.set_option(\"display.max_rows\", 1000)\n", - "pd.options.display.max_info_columns = 200\n", - "pd.options.display.precision = 5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load Data" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "df_2021_2022 = pd.read_csv(\"../data/nba_ai/cleaned_data_2021-2022.csv\")\n", - "df_2022_2023 = pd.read_csv(\"../data/nba_ai/cleaned_data_2022-2023.csv\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "## Data Preparation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Train Test Split" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "def prepare_datasets(train_df, cls_target, reg_target, test_df=None, test_size=0.3):\n", - " \"\"\"\n", - " Prepares datasets for training and testing for both classification and regression targets,\n", - " ensuring time-sensitive splitting based on a 'date' column.\n", - "\n", - " Parameters:\n", - " train_df (DataFrame): The training dataframe.\n", - " cls_target (str): The name of the classification target column.\n", - " reg_target (str): The name of the regression target column.\n", - " test_df (DataFrame, optional): An optional testing dataframe. If not provided, a portion of the training data is used.\n", - " test_size (float, optional): The proportion of the dataset to include in the test split (if test_df is not provided).\n", - "\n", - " Returns:\n", - " tuple: A tuple containing six dataframes:\n", - " (X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg).\n", - " \"\"\"\n", - "\n", - " # Sort the dataframe based on the 'date' column\n", - " train_df = train_df.sort_values(by=\"date\")\n", - "\n", - " # If a test dataframe is not provided, split the training dataframe\n", - " if test_df is None:\n", - " X_train, X_test, y_train, y_test = train_test_split(\n", - " train_df.drop([cls_target, reg_target], axis=1),\n", - " train_df[[cls_target, reg_target]],\n", - " test_size=test_size,\n", - " shuffle=False, # Important to maintain time order\n", - " )\n", - " else:\n", - " # If a test dataframe is provided, ensure it is also sorted by date\n", - " test_df = test_df.sort_values(by=\"date\")\n", - "\n", - " # Use provided test dataframe and separate features and targets\n", - " X_train = train_df.drop([cls_target, reg_target], axis=1)\n", - " y_train = train_df[[cls_target, reg_target]]\n", - " X_test = test_df.drop([cls_target, reg_target], axis=1)\n", - " y_test = test_df[[cls_target, reg_target]]\n", - "\n", - " # Separate classification and regression targets\n", - " y_train_cls = y_train[[cls_target]]\n", - " y_train_reg = y_train[[reg_target]]\n", - " y_test_cls = y_test[[cls_target]]\n", - " y_test_reg = y_test[[reg_target]]\n", - "\n", - " return X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg = prepare_datasets(\n", - " df_2021_2022, \"CLS_TARGET\", \"REG_TARGET\", test_df=df_2022_2023\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Features" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "betting_feature_set = [\n", - " \"home_opening_spread\",\n", - " \"opening_total\",\n", - " \"home_moneyline\",\n", - " \"road_moneyline\",\n", - "]\n", - "\n", - "base_feature_set = [\n", - " \"day_of_season\",\n", - " \"home_team_rest\",\n", - " \"road_team_rest\",\n", - " \"home_win_pct\",\n", - " \"road_win_pct\",\n", - " \"home_win_pct_l2w\",\n", - " \"road_win_pct_l2w\",\n", - " \"home_avg_pts\",\n", - " \"road_avg_pts\",\n", - " \"home_avg_pts_l2w\",\n", - " \"road_avg_pts_l2w\",\n", - " \"home_avg_oeff\",\n", - " \"road_avg_oeff\",\n", - " \"home_avg_oeff_l2w\",\n", - " \"road_avg_oeff_l2w\",\n", - " \"home_avg_deff\",\n", - " \"road_avg_deff\",\n", - " \"home_avg_deff_l2w\",\n", - " \"road_avg_deff_l2w\",\n", - " \"home_avg_eFG%\",\n", - " \"road_avg_eFG%\",\n", - " \"home_avg_eFG%_l2w\",\n", - " \"road_avg_eFG%_l2w\",\n", - " \"home_avg_TOV%\",\n", - " \"road_avg_TOV%\",\n", - " \"home_avg_TOV%_l2w\",\n", - " \"road_avg_TOV%_l2w\",\n", - " \"home_avg_ORB%\",\n", - " \"road_avg_ORB%\",\n", - " \"home_avg_ORB%_l2w\",\n", - " \"road_avg_ORB%_l2w\",\n", - " \"home_avg_FT%\",\n", - " \"road_avg_FT%\",\n", - " \"home_avg_FT%_l2w\",\n", - " \"road_avg_FT%_l2w\",\n", - " \"home_avg_pts_allowed\",\n", - " \"road_avg_pts_allowed\",\n", - " \"home_avg_pts_allowed_l2w\",\n", - " \"road_avg_pts_allowed_l2w\",\n", - "]\n", - "\n", - "lineup_vectors = [\"home_lineup_vector\", \"road_lineup_vector\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "features = base_feature_set" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def flatten_vector_columns(df, vector_columns):\n", - " \"\"\"\n", - " Flatten vector columns into separate feature columns.\n", - "\n", - " This function takes a DataFrame and a list of column names that store vector data as strings\n", - " (typically after being read from a CSV file), and returns a new DataFrame where the vectors\n", - " have been flattened into separate feature columns.\n", - "\n", - " Parameters:\n", - " df (pandas.DataFrame): The input DataFrame.\n", - " vector_columns (list): A list of column names in df that store vector data as strings.\n", - "\n", - " Returns:\n", - " pandas.DataFrame: The DataFrame with vector columns flattened.\n", - " \"\"\"\n", - " for column in vector_columns:\n", - " if column not in df.columns:\n", - " continue\n", - " # Convert the string representation of the vector into a numpy array\n", - " df[column] = df[column].apply(\n", - " lambda x: np.array(x.strip(\"[]\").replace(\"\\n\", \" \").split(), dtype=float)\n", - " )\n", - "\n", - " # Flatten the numpy array into separate columns\n", - " vector_df = pd.DataFrame(df[column].tolist(), index=df.index)\n", - " vector_df.columns = [f\"{column}_{i}\" for i in range(vector_df.shape[1])]\n", - "\n", - " # Drop the original vector column and concatenate the new DataFrame\n", - " df = df.drop(column, axis=1)\n", - " df = pd.concat([df, vector_df], axis=1)\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X_train = X_train[features]\n", - "X_test = X_test[features]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Flatten lineup vectors\n", - "X_train = flatten_vector_columns(X_train, lineup_vectors)\n", - "X_test = flatten_vector_columns(X_test, lineup_vectors)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Combined Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "combined_train_df = pd.concat([X_train, y_train_cls, y_train_reg], axis=1)\n", - "combined_test_df = pd.concat([X_test, y_test_cls, y_test_reg], axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Int64Index: 1323 entries, 0 to 1322\n", - "Data columns (total 41 columns):\n", - " # Column Non-Null Count Dtype \n", - "--- ------ -------------- ----- \n", - " 0 day_of_season 1323 non-null int64 \n", - " 1 home_team_rest 1323 non-null int64 \n", - " 2 road_team_rest 1323 non-null int64 \n", - " 3 home_win_pct 1323 non-null float64\n", - " 4 road_win_pct 1323 non-null float64\n", - " 5 home_win_pct_l2w 1323 non-null float64\n", - " 6 road_win_pct_l2w 1323 non-null float64\n", - " 7 home_avg_pts 1323 non-null float64\n", - " 8 road_avg_pts 1323 non-null float64\n", - " 9 home_avg_pts_l2w 1323 non-null float64\n", - " 10 road_avg_pts_l2w 1323 non-null float64\n", - " 11 home_avg_oeff 1323 non-null float64\n", - " 12 road_avg_oeff 1323 non-null float64\n", - " 13 home_avg_oeff_l2w 1323 non-null float64\n", - " 14 road_avg_oeff_l2w 1323 non-null float64\n", - " 15 home_avg_deff 1323 non-null float64\n", - " 16 road_avg_deff 1323 non-null float64\n", - " 17 home_avg_deff_l2w 1323 non-null float64\n", - " 18 road_avg_deff_l2w 1323 non-null float64\n", - " 19 home_avg_eFG% 1323 non-null float64\n", - " 20 road_avg_eFG% 1323 non-null float64\n", - " 21 home_avg_eFG%_l2w 1323 non-null float64\n", - " 22 road_avg_eFG%_l2w 1323 non-null float64\n", - " 23 home_avg_TOV% 1323 non-null float64\n", - " 24 road_avg_TOV% 1323 non-null float64\n", - " 25 home_avg_TOV%_l2w 1323 non-null float64\n", - " 26 road_avg_TOV%_l2w 1323 non-null float64\n", - " 27 home_avg_ORB% 1323 non-null float64\n", - " 28 road_avg_ORB% 1323 non-null float64\n", - " 29 home_avg_ORB%_l2w 1323 non-null float64\n", - " 30 road_avg_ORB%_l2w 1323 non-null float64\n", - " 31 home_avg_FT% 1323 non-null float64\n", - " 32 road_avg_FT% 1323 non-null float64\n", - " 33 home_avg_FT%_l2w 1323 non-null float64\n", - " 34 road_avg_FT%_l2w 1323 non-null float64\n", - " 35 home_avg_pts_allowed 1323 non-null float64\n", - " 36 road_avg_pts_allowed 1323 non-null float64\n", - " 37 home_avg_pts_allowed_l2w 1323 non-null float64\n", - " 38 road_avg_pts_allowed_l2w 1323 non-null float64\n", - " 39 CLS_TARGET 1323 non-null bool \n", - " 40 REG_TARGET 1323 non-null int64 \n", - "dtypes: bool(1), float64(36), int64(4)\n", - "memory usage: 425.1 KB\n" - ] - } - ], - "source": [ - "combined_train_df.info(verbose=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Int64Index: 1320 entries, 0 to 1319\n", - "Data columns (total 41 columns):\n", - " # Column Non-Null Count Dtype \n", - "--- ------ -------------- ----- \n", - " 0 day_of_season 1320 non-null int64 \n", - " 1 home_team_rest 1320 non-null int64 \n", - " 2 road_team_rest 1320 non-null int64 \n", - " 3 home_win_pct 1320 non-null float64\n", - " 4 road_win_pct 1320 non-null float64\n", - " 5 home_win_pct_l2w 1320 non-null float64\n", - " 6 road_win_pct_l2w 1320 non-null float64\n", - " 7 home_avg_pts 1320 non-null float64\n", - " 8 road_avg_pts 1320 non-null float64\n", - " 9 home_avg_pts_l2w 1320 non-null float64\n", - " 10 road_avg_pts_l2w 1320 non-null float64\n", - " 11 home_avg_oeff 1320 non-null float64\n", - " 12 road_avg_oeff 1320 non-null float64\n", - " 13 home_avg_oeff_l2w 1320 non-null float64\n", - " 14 road_avg_oeff_l2w 1320 non-null float64\n", - " 15 home_avg_deff 1320 non-null float64\n", - " 16 road_avg_deff 1320 non-null float64\n", - " 17 home_avg_deff_l2w 1320 non-null float64\n", - " 18 road_avg_deff_l2w 1320 non-null float64\n", - " 19 home_avg_eFG% 1320 non-null float64\n", - " 20 road_avg_eFG% 1320 non-null float64\n", - " 21 home_avg_eFG%_l2w 1320 non-null float64\n", - " 22 road_avg_eFG%_l2w 1320 non-null float64\n", - " 23 home_avg_TOV% 1320 non-null float64\n", - " 24 road_avg_TOV% 1320 non-null float64\n", - " 25 home_avg_TOV%_l2w 1320 non-null float64\n", - " 26 road_avg_TOV%_l2w 1320 non-null float64\n", - " 27 home_avg_ORB% 1320 non-null float64\n", - " 28 road_avg_ORB% 1320 non-null float64\n", - " 29 home_avg_ORB%_l2w 1320 non-null float64\n", - " 30 road_avg_ORB%_l2w 1320 non-null float64\n", - " 31 home_avg_FT% 1320 non-null float64\n", - " 32 road_avg_FT% 1320 non-null float64\n", - " 33 home_avg_FT%_l2w 1320 non-null float64\n", - " 34 road_avg_FT%_l2w 1320 non-null float64\n", - " 35 home_avg_pts_allowed 1320 non-null float64\n", - " 36 road_avg_pts_allowed 1320 non-null float64\n", - " 37 home_avg_pts_allowed_l2w 1320 non-null float64\n", - " 38 road_avg_pts_allowed_l2w 1320 non-null float64\n", - " 39 CLS_TARGET 1320 non-null bool \n", - " 40 REG_TARGET 1320 non-null int64 \n", - "dtypes: bool(1), float64(36), int64(4)\n", - "memory usage: 424.1 KB\n" - ] - } - ], - "source": [ - "combined_test_df.info(verbose=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "## Multi-Layer Perceptron (MLP) - Regression" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Data Conversion" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "# Convert Pandas DataFrames to PyTorch Tensors\n", - "X_train_tensor = torch.tensor(X_train.values, dtype=torch.float32)\n", - "y_train_tensor = torch.tensor(y_train_reg.values, dtype=torch.float32)\n", - "\n", - "# Create a TensorDataset - this wraps tensors into a dataset\n", - "train_data = TensorDataset(X_train_tensor, y_train_tensor)\n", - "\n", - "# DataLoader for batching and shuffling\n", - "train_loader = DataLoader(train_data, batch_size=32, shuffle=True)\n", - "\n", - "# Note: Shuffle is set to True for training data. For time-series data, consider the impact of shuffling." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Definition\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "# Define a simple regression neural network\n", - "class RegressionModel(nn.Module):\n", - " def __init__(self, input_size, hidden_size, output_size):\n", - " super(RegressionModel, self).__init__()\n", - " self.layer1 = nn.Linear(input_size, hidden_size)\n", - " self.relu = nn.ReLU()\n", - " self.layer2 = nn.Linear(hidden_size, output_size)\n", - "\n", - " def forward(self, x):\n", - " x = self.relu(self.layer1(x))\n", - " x = self.layer2(x)\n", - " return x" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "# Instantiate the model\n", - "reg_mlp_model = RegressionModel(\n", - " input_size=X_train.shape[1], hidden_size=5, output_size=1\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Training\n" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 0, Loss: 74.28823852539062\n", - "Epoch 10, Loss: 96.0931396484375\n", - "Epoch 20, Loss: 137.65530395507812\n", - "Epoch 30, Loss: 195.98065185546875\n", - "Epoch 40, Loss: 92.8561019897461\n", - "Epoch 50, Loss: 105.47020721435547\n", - "Epoch 60, Loss: 335.4338684082031\n", - "Epoch 70, Loss: 134.19989013671875\n", - "Epoch 80, Loss: 315.7101135253906\n", - "Epoch 90, Loss: 275.2518005371094\n" - ] - } - ], - "source": [ - "# Define loss function and optimizer\n", - "loss_function = nn.MSELoss()\n", - "optimizer = optim.Adam(reg_mlp_model.parameters(), lr=0.001)\n", - "\n", - "# Training loop\n", - "num_epochs = 100 # Set the number of epochs\n", - "for epoch in range(num_epochs):\n", - " for inputs, targets in train_loader:\n", - " optimizer.zero_grad() # Zero the gradient buffers\n", - " outputs = reg_mlp_model(inputs)\n", - " loss = loss_function(outputs, targets)\n", - " loss.backward() # Backpropagation\n", - " optimizer.step() # Update weights\n", - "\n", - " # Optional: Print the loss every few epochs\n", - " if epoch % 10 == 0:\n", - " print(f\"Epoch {epoch}, Loss: {loss.item()}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Evaluation and Prediction" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "X_test_tensor = torch.tensor(X_test.values, dtype=torch.float32)\n", - "\n", - "# Disable gradient computation for evaluation and prediction\n", - "with torch.no_grad():\n", - " train_predictions_reg = reg_mlp_model(X_train_tensor)\n", - " test_predictions_reg = reg_mlp_model(X_test_tensor)\n", - "\n", - "# Convert predictions to a NumPy array or Pandas Series for further evaluation\n", - "train_predictions_reg_np = train_predictions_reg.numpy()\n", - "test_predictions_reg_np = test_predictions_reg.numpy()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "train_mae = mean_absolute_error(train_predictions_reg_np, y_train_reg)\n", - "train_r2 = r2_score(train_predictions_reg_np, y_train_reg)\n", - "\n", - "test_mae = mean_absolute_error(test_predictions_reg_np, y_test_reg)\n", - "test_r2 = r2_score(test_predictions_reg_np, y_test_reg)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train MAE: 11.81\n", - "Train R2: -30.65\n", - "Test MAE: 10.73\n", - "Test R2: -38.40\n" - ] - } - ], - "source": [ - "print(f\"Train MAE: {train_mae:.2f}\")\n", - "print(f\"Train R2: {train_r2:.2f}\")\n", - "print(f\"Test MAE: {test_mae:.2f}\")\n", - "print(f\"Test R2: {test_r2:.2f}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Saving and Loading" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Regression_MLP_11.81_10.73_2024-01-03_15-50-13'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "problem_type = \"Regression\"\n", - "base_model = \"MLP\"\n", - "train_performance = round(train_mae, 2)\n", - "test_performance = round(test_mae, 2)\n", - "\n", - "model_id = f\"{problem_type}_{base_model}_{train_performance}_{test_performance}_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}\"\n", - "\n", - "model_id" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "# Save the model state\n", - "torch.save(reg_mlp_model.state_dict(), f\"../models/{model_id}.pth\")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "# To load the model, first initialize the model structure, then load the state\n", - "# reg_mlp_model = RegressionModel(input_size=10, hidden_size=5, output_size=1)\n", - "# reg_mlp_model.load_state_dict(torch.load(f\"../models/{model_id}.pth\"))\n", - "# reg_mlp_model.eval() # Set the model to evaluation mode" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "## Multi-Layer Perceptron (MLP) - Classification" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Data Conversion" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "# Convert Pandas DataFrames to PyTorch Tensors\n", - "X_train_tensor = torch.tensor(X_train.values, dtype=torch.float32)\n", - "y_train_tensor = torch.tensor(y_train_cls.values, dtype=torch.float32)\n", - "\n", - "# Create a TensorDataset - this wraps tensors into a dataset\n", - "train_data = TensorDataset(X_train_tensor, y_train_tensor)\n", - "\n", - "# DataLoader for batching and shuffling\n", - "train_loader = DataLoader(train_data, batch_size=32, shuffle=True)\n", - "\n", - "# Note: Shuffle is set to True for training data. For time-series data, consider the impact of shuffling." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Definition\n" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "class ClassificationModel(nn.Module):\n", - " def __init__(self, input_size, hidden_size):\n", - " super(ClassificationModel, self).__init__()\n", - " self.layer1 = nn.Linear(input_size, hidden_size)\n", - " self.relu = nn.ReLU()\n", - " self.layer2 = nn.Linear(\n", - " hidden_size, 1\n", - " ) # Output size is 1 for binary classification\n", - "\n", - " def forward(self, x):\n", - " x = self.relu(self.layer1(x))\n", - " x = torch.sigmoid(\n", - " self.layer2(x)\n", - " ) # Sigmoid activation for binary classification\n", - " return x" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [], - "source": [ - "# Instantiate the model\n", - "cls_mlp_model = ClassificationModel(input_size=X_train.shape[1], hidden_size=5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Training\n" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 0, Loss: 0.7315127849578857\n", - "Epoch 10, Loss: 0.6890249848365784\n", - "Epoch 20, Loss: 0.690775990486145\n", - "Epoch 30, Loss: 0.6824485063552856\n", - "Epoch 40, Loss: 0.704181969165802\n", - "Epoch 50, Loss: 0.6825932860374451\n", - "Epoch 60, Loss: 0.7145680785179138\n", - "Epoch 70, Loss: 0.6818594336509705\n", - "Epoch 80, Loss: 0.6659519672393799\n", - "Epoch 90, Loss: 0.6734258532524109\n" - ] - } - ], - "source": [ - "# Define loss function and optimizer for binary classification\n", - "loss_function = nn.BCELoss()\n", - "optimizer = optim.Adam(cls_mlp_model.parameters(), lr=0.001)\n", - "\n", - "# Training loop\n", - "num_epochs = 100\n", - "for epoch in range(num_epochs):\n", - " for inputs, targets in train_loader:\n", - " optimizer.zero_grad()\n", - " outputs = cls_mlp_model(inputs)\n", - " loss = loss_function(outputs, targets)\n", - " loss.backward()\n", - " optimizer.step()\n", - "\n", - " if epoch % 10 == 0:\n", - " print(f\"Epoch {epoch}, Loss: {loss.item()}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Evaluation and Prediction" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "X_test_tensor = torch.tensor(X_test.values, dtype=torch.float32)\n", - "\n", - "# Disable gradient computation for evaluation and prediction\n", - "with torch.no_grad():\n", - " train_predictions_cls = cls_mlp_model(X_train_tensor)\n", - " test_predictions_cls = cls_mlp_model(X_test_tensor)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "# Assuming your model outputs probabilities for class 1\n", - "threshold = 0.5\n", - "\n", - "# Convert probabilities to class labels based on the threshold\n", - "train_predictions_cls_np = train_predictions_cls.numpy() > threshold\n", - "test_predictions_cls_np = test_predictions_cls.numpy() > threshold" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [], - "source": [ - "train_accuracy = accuracy_score(train_predictions_cls_np, y_train_cls)\n", - "train_precision = precision_score(train_predictions_cls_np, y_train_cls)\n", - "\n", - "test_accuracy = accuracy_score(test_predictions_cls_np, y_test_cls)\n", - "test_precision = precision_score(test_predictions_cls_np, y_test_cls)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train Accuracy: 0.52\n", - "Train Precision: 0.00\n", - "Test Accuracy: 0.48\n", - "Test Precision: 0.00\n" - ] - } - ], - "source": [ - "print(f\"Train Accuracy: {train_accuracy:.2f}\")\n", - "print(f\"Train Precision: {train_precision:.2f}\")\n", - "print(f\"Test Accuracy: {test_accuracy:.2f}\")\n", - "print(f\"Test Precision: {test_precision:.2f}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Model Saving and Loading" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Classification_MLP_11.81_10.73_2024-01-03_15-50-20'" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "problem_type = \"Classification\"\n", - "base_model = \"MLP\"\n", - "train_performance = round(train_mae, 2)\n", - "test_performance = round(test_mae, 2)\n", - "\n", - "model_id = f\"{problem_type}_{base_model}_{train_performance}_{test_performance}_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}\"\n", - "\n", - "model_id" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "# Save the model state\n", - "torch.save(cls_mlp_model.state_dict(), f\"../models/{model_id}.pth\")" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "# To load the model, first initialize the model structure, then load the state\n", - "# cls_mlp_model = ClassificationModel(input_size=10, hidden_size=5, output_size=1)\n", - "# cls_mlp_model.load_state_dict(torch.load(f\"../models/{model_id}.pth\"))\n", - "# cls_mlp_model.eval() # Set the model to evaluation mode" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "nba_venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/baseline/eda.ipynb b/notebooks/baseline/eda.ipynb deleted file mode 100644 index 87f2484..0000000 --- a/notebooks/baseline/eda.ipynb +++ /dev/null @@ -1,3558 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# NBA AI Exploratory Data Analysis" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Imports and Global Settings" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import seaborn as sns\n", - "import matplotlib.pyplot as plt\n", - "from sklearn.feature_selection import mutual_info_classif, mutual_info_regression\n", - "from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor\n", - "\n", - "pd.set_option(\"display.max_columns\", None)\n", - "pd.set_option(\"display.max_rows\", None)\n", - "sns.set_style(\"whitegrid\")\n", - "sns.set_context(\"notebook\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Loading Data" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "df = pd.read_csv(\"../data/nba_ai/full_cleaned_data_2021-2022.csv\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1road_ot2road_ot3road_ot4road_ot5road_froad_minroad_fgroad_fgaroad_3proad_3paroad_ftroad_ftaroad_orroad_drroad_totroad_aroad_pfroad_stroad_toroad_to_toroad_blroad_ptsroad_possroad_paceroad_oeffroad_deffroad_team_rest_daysroad_moneylineroad_starting_lineupmain_refcrewday_of_seasonhome_team_game_numroad_team_game_numhome_eFG%home_TOV%home_ORB%home_FT%road_eFG%road_TOV%road_ORB%road_FT%winnerloserhome_winshome_losseshome_win_pctroad_winsroad_lossesroad_win_pcthome_wins_l2whome_losses_l2whome_win_pct_l2wroad_wins_l2wroad_losses_l2wroad_win_pct_l2whome_avg_1qroad_avg_1qhome_avg_1q_l2wroad_avg_1q_l2whome_avg_2qroad_avg_2qhome_avg_2q_l2wroad_avg_2q_l2whome_avg_3qroad_avg_3qhome_avg_3q_l2wroad_avg_3q_l2whome_avg_4qroad_avg_4qhome_avg_4q_l2wroad_avg_4q_l2whome_avg_ot1road_avg_ot1home_avg_ot1_l2wroad_avg_ot1_l2whome_avg_ot2road_avg_ot2home_avg_ot2_l2wroad_avg_ot2_l2whome_avg_ot3road_avg_ot3home_avg_ot3_l2wroad_avg_ot3_l2whome_avg_ot4road_avg_ot4home_avg_ot4_l2wroad_avg_ot4_l2whome_avg_ot5road_avg_ot5home_avg_ot5_l2wroad_avg_ot5_l2whome_avg_froad_avg_fhome_avg_f_l2wroad_avg_f_l2whome_avg_minroad_avg_minhome_avg_min_l2wroad_avg_min_l2whome_avg_fgroad_avg_fghome_avg_fg_l2wroad_avg_fg_l2whome_avg_fgaroad_avg_fgahome_avg_fga_l2wroad_avg_fga_l2whome_avg_3proad_avg_3phome_avg_3p_l2wroad_avg_3p_l2whome_avg_3paroad_avg_3pahome_avg_3pa_l2wroad_avg_3pa_l2whome_avg_ftroad_avg_fthome_avg_ft_l2wroad_avg_ft_l2whome_avg_ftaroad_avg_ftahome_avg_fta_l2wroad_avg_fta_l2whome_avg_orroad_avg_orhome_avg_or_l2wroad_avg_or_l2whome_avg_drroad_avg_drhome_avg_dr_l2wroad_avg_dr_l2whome_avg_totroad_avg_tothome_avg_tot_l2wroad_avg_tot_l2whome_avg_aroad_avg_ahome_avg_a_l2wroad_avg_a_l2whome_avg_pfroad_avg_pfhome_avg_pf_l2wroad_avg_pf_l2whome_avg_stroad_avg_sthome_avg_st_l2wroad_avg_st_l2whome_avg_toroad_avg_tohome_avg_to_l2wroad_avg_to_l2whome_avg_to_toroad_avg_to_tohome_avg_to_to_l2wroad_avg_to_to_l2whome_avg_blroad_avg_blhome_avg_bl_l2wroad_avg_bl_l2whome_avg_ptsroad_avg_ptshome_avg_pts_l2wroad_avg_pts_l2whome_avg_possroad_avg_posshome_avg_poss_l2wroad_avg_poss_l2whome_avg_paceroad_avg_pacehome_avg_pace_l2wroad_avg_pace_l2whome_avg_oeffroad_avg_oeffhome_avg_oeff_l2wroad_avg_oeff_l2whome_avg_deffroad_avg_deffhome_avg_deff_l2wroad_avg_deff_l2whome_avg_eFG%road_avg_eFG%home_avg_eFG%_l2wroad_avg_eFG%_l2whome_avg_TOV%road_avg_TOV%home_avg_TOV%_l2wroad_avg_TOV%_l2whome_avg_ORB%road_avg_ORB%home_avg_ORB%_l2wroad_avg_ORB%_l2whome_avg_FT%road_avg_FT%home_avg_FT%_l2wroad_avg_FT%_l2whome_avg_pts_allowedroad_avg_pts_allowedhome_avg_pts_allowed_l2wroad_avg_pts_allowed_l2wREG_TARGETCLS_TARGETCLS_TARGET_closing_spreadREG_TARGET_OUCLS_TARGET_OU_OPENCLS_TARGET_OU_CLOSEhome_team_restroad_team_resthome_lineup_vectorroad_lineup_vector
0NBA 2021-2022 Regular Season221000012021-10-19Milwaukee37293130NaNNaNNaNNaNNaN127240481051745141813415425198789127102.843098102.843098123.489085101.1249203+-1.5240.5-2.0234.0-1262022-2022 Regular SeasonKhris Middleton,Giannis Antetokounmpo,Brook Lo...Brooklyn25342619NaNNaNNaNNaNNaN104240378417321323539441917312139104102.843098102.843098101.124920123.4890853+105Kevin Durant,Blake Griffin,Nic Claxton,Joe Har...Josh TivenJacyn Goble,Natalie Sago1110.5380950.0661590.2452830.1333330.5416670.1213590.1086960.154762MilwaukeeBrooklyn000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.023TrueTrue231FalseFalse77[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...
1NBA 2021-2022 Regular Season221000022021-10-19LA Lakers34252629NaNNaNNaNNaNNaN11424045951542919540452125717184114113.282595113.282595100.633288106.8125253+-5.5230.5-3.0226.5-1542022-2022 Regular SeasonLeBron James,Anthony Davis,DeAndre Jordan,Kent...Golden State32213038NaNNaNNaNNaNNaN121240419314392530941503018917172121113.282595113.282595106.812525100.6332883+130Andrew Wiggins,Draymond Green,Kevon Looney,Jor...Sean WrightMark Lindsay,Ray Acosta1110.5526320.1483190.1111110.0947370.5161290.1379870.2000000.268817Golden StateLA Lakers000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0-7FalseFalse235TrueTrue77[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. ...
2NBA 2021-2022 Regular Season221000132021-10-20Portland23263636NaNNaNNaNNaNNaN121240459312351922940492522512135121105.079957105.079957115.150408118.0053773+-5.5231.5-6.5234.0-2552022-2022 Regular SeasonNorman Powell,Robert Covington,Jusuf Nurkic,CJ...Sacramento24383824NaNNaNNaNNaNNaN124240429217412329736432422610104124105.079957105.079957118.005377115.1504083+208Harrison Barnes,Maurice Harkless,Richaun Holme...Sean WrightNick Buchert,Phenizee Ransom2110.5483870.1123790.2093020.2043010.5489130.0871380.1489360.250000SacramentoPortland000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0-3FalseFalse245TrueTrue77[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...
3NBA 2021-2022 Regular Season221000122021-10-20Phoenix20382416NaNNaNNaNNaNNaN9824036871437121711344523189181839899.68525099.68525098.309429110.3473183+-6.5224.5-6.0224.0-2402022-2022 Regular SeasonMikal Bridges,Jae Crowder,Deandre Ayton,Devin ...Denver26253425NaNNaNNaNNaNNaN110240448317395964046252091719111099.68525099.685250110.34731898.3094293+190Michael Porter Jr.,Aaron Gordon,Nikola Jokic,W...Leon WoodKevin Cutler,Marc Davis2110.4942530.1600280.2115380.1379310.6325300.1793130.1463410.060241DenverPhoenix000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0-12FalseFalse208FalseFalse77[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...
4NBA 2021-2022 Regular Season221000112021-10-20Utah27272924NaNNaNNaNNaNNaN107240409114471315124153181961010510794.96531394.965313112.67271890.5593813+-11.5222.0-13.5221.5-14282022-2022 Regular SeasonBojan Bogdanovic,Royce O'Neale,Rudy Gobert,Don...Oklahoma City18242123NaNNaNNaNNaNNaN862403491735111815355019154141528694.96531394.96531390.559381112.6727183+790Luguentz Dort,Darius Bazley,Derrick Favors,Jos...Zach ZarbaMark Lindsay,Ray Acosta2110.5164840.0929370.2608700.1428570.4120880.1316710.2678570.120879UtahOklahoma City000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.021TrueTrue193FalseFalse77[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ...
\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2021-2022 Regular Season 22100001 2021-10-19 Milwaukee 37 \n", - "1 NBA 2021-2022 Regular Season 22100002 2021-10-19 LA Lakers 34 \n", - "2 NBA 2021-2022 Regular Season 22100013 2021-10-20 Portland 23 \n", - "3 NBA 2021-2022 Regular Season 22100012 2021-10-20 Phoenix 20 \n", - "4 NBA 2021-2022 Regular Season 22100011 2021-10-20 Utah 27 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 29 31 30 NaN NaN NaN NaN \n", - "1 25 26 29 NaN NaN NaN NaN \n", - "2 26 36 36 NaN NaN NaN NaN \n", - "3 38 24 16 NaN NaN NaN NaN \n", - "4 27 29 24 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 127 240 48 105 17 45 14 \n", - "1 NaN 114 240 45 95 15 42 9 \n", - "2 NaN 121 240 45 93 12 35 19 \n", - "3 NaN 98 240 36 87 14 37 12 \n", - "4 NaN 107 240 40 91 14 47 13 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 18 13 41 54 25 19 8 7 \n", - "1 19 5 40 45 21 25 7 17 \n", - "2 22 9 40 49 25 22 5 12 \n", - "3 17 11 34 45 23 18 9 18 \n", - "4 15 12 41 53 18 19 6 10 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 8 9 127 102.843098 102.843098 123.489085 \n", - "1 18 4 114 113.282595 113.282595 100.633288 \n", - "2 13 5 121 105.079957 105.079957 115.150408 \n", - "3 18 3 98 99.685250 99.685250 98.309429 \n", - "4 10 5 107 94.965313 94.965313 112.672718 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 101.124920 3+ -1.5 240.5 \n", - "1 106.812525 3+ -5.5 230.5 \n", - "2 118.005377 3+ -5.5 231.5 \n", - "3 110.347318 3+ -6.5 224.5 \n", - "4 90.559381 3+ -11.5 222.0 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -2.0 234.0 -126 2022 \n", - "1 -3.0 226.5 -154 2022 \n", - "2 -6.5 234.0 -255 2022 \n", - "3 -6.0 224.0 -240 2022 \n", - "4 -13.5 221.5 -1428 2022 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2022 Regular Season Khris Middleton,Giannis Antetokounmpo,Brook Lo... \n", - "1 -2022 Regular Season LeBron James,Anthony Davis,DeAndre Jordan,Kent... \n", - "2 -2022 Regular Season Norman Powell,Robert Covington,Jusuf Nurkic,CJ... \n", - "3 -2022 Regular Season Mikal Bridges,Jae Crowder,Deandre Ayton,Devin ... \n", - "4 -2022 Regular Season Bojan Bogdanovic,Royce O'Neale,Rudy Gobert,Don... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 road_ot2 \\\n", - "0 Brooklyn 25 34 26 19 NaN NaN \n", - "1 Golden State 32 21 30 38 NaN NaN \n", - "2 Sacramento 24 38 38 24 NaN NaN \n", - "3 Denver 26 25 34 25 NaN NaN \n", - "4 Oklahoma City 18 24 21 23 NaN NaN \n", - "\n", - " road_ot3 road_ot4 road_ot5 road_f road_min road_fg road_fga road_3p \\\n", - "0 NaN NaN NaN 104 240 37 84 17 \n", - "1 NaN NaN NaN 121 240 41 93 14 \n", - "2 NaN NaN NaN 124 240 42 92 17 \n", - "3 NaN NaN NaN 110 240 44 83 17 \n", - "4 NaN NaN NaN 86 240 34 91 7 \n", - "\n", - " road_3pa road_ft road_fta road_or road_dr road_tot road_a road_pf \\\n", - "0 32 13 23 5 39 44 19 17 \n", - "1 39 25 30 9 41 50 30 18 \n", - "2 41 23 29 7 36 43 24 22 \n", - "3 39 5 9 6 40 46 25 20 \n", - "4 35 11 18 15 35 50 19 15 \n", - "\n", - " road_st road_to road_to_to road_bl road_pts road_poss road_pace \\\n", - "0 3 12 13 9 104 102.843098 102.843098 \n", - "1 9 17 17 2 121 113.282595 113.282595 \n", - "2 6 10 10 4 124 105.079957 105.079957 \n", - "3 9 17 19 1 110 99.685250 99.685250 \n", - "4 4 14 15 2 86 94.965313 94.965313 \n", - "\n", - " road_oeff road_deff road_team_rest_days road_moneyline \\\n", - "0 101.124920 123.489085 3+ 105 \n", - "1 106.812525 100.633288 3+ 130 \n", - "2 118.005377 115.150408 3+ 208 \n", - "3 110.347318 98.309429 3+ 190 \n", - "4 90.559381 112.672718 3+ 790 \n", - "\n", - " road_starting_lineup main_ref \\\n", - "0 Kevin Durant,Blake Griffin,Nic Claxton,Joe Har... Josh Tiven \n", - "1 Andrew Wiggins,Draymond Green,Kevon Looney,Jor... Sean Wright \n", - "2 Harrison Barnes,Maurice Harkless,Richaun Holme... Sean Wright \n", - "3 Michael Porter Jr.,Aaron Gordon,Nikola Jokic,W... Leon Wood \n", - "4 Luguentz Dort,Darius Bazley,Derrick Favors,Jos... Zach Zarba \n", - "\n", - " crew day_of_season home_team_game_num \\\n", - "0 Jacyn Goble,Natalie Sago 1 1 \n", - "1 Mark Lindsay,Ray Acosta 1 1 \n", - "2 Nick Buchert,Phenizee Ransom 2 1 \n", - "3 Kevin Cutler,Marc Davis 2 1 \n", - "4 Mark Lindsay,Ray Acosta 2 1 \n", - "\n", - " road_team_game_num home_eFG% home_TOV% home_ORB% home_FT% road_eFG% \\\n", - "0 1 0.538095 0.066159 0.245283 0.133333 0.541667 \n", - "1 1 0.552632 0.148319 0.111111 0.094737 0.516129 \n", - "2 1 0.548387 0.112379 0.209302 0.204301 0.548913 \n", - "3 1 0.494253 0.160028 0.211538 0.137931 0.632530 \n", - "4 1 0.516484 0.092937 0.260870 0.142857 0.412088 \n", - "\n", - " road_TOV% road_ORB% road_FT% winner loser home_wins \\\n", - "0 0.121359 0.108696 0.154762 Milwaukee Brooklyn 0 \n", - "1 0.137987 0.200000 0.268817 Golden State LA Lakers 0 \n", - "2 0.087138 0.148936 0.250000 Sacramento Portland 0 \n", - "3 0.179313 0.146341 0.060241 Denver Phoenix 0 \n", - "4 0.131671 0.267857 0.120879 Utah Oklahoma City 0 \n", - "\n", - " home_losses home_win_pct road_wins road_losses road_win_pct \\\n", - "0 0 0.0 0 0 0.0 \n", - "1 0 0.0 0 0 0.0 \n", - "2 0 0.0 0 0 0.0 \n", - "3 0 0.0 0 0 0.0 \n", - "4 0 0.0 0 0 0.0 \n", - "\n", - " home_wins_l2w home_losses_l2w home_win_pct_l2w road_wins_l2w \\\n", - "0 0 0 0.0 0 \n", - "1 0 0 0.0 0 \n", - "2 0 0 0.0 0 \n", - "3 0 0 0.0 0 \n", - "4 0 0 0.0 0 \n", - "\n", - " road_losses_l2w road_win_pct_l2w home_avg_1q road_avg_1q \\\n", - "0 0 0.0 0.0 0.0 \n", - "1 0 0.0 0.0 0.0 \n", - "2 0 0.0 0.0 0.0 \n", - "3 0 0.0 0.0 0.0 \n", - "4 0 0.0 0.0 0.0 \n", - "\n", - " home_avg_1q_l2w road_avg_1q_l2w home_avg_2q road_avg_2q \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_2q_l2w road_avg_2q_l2w home_avg_3q road_avg_3q \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_3q_l2w road_avg_3q_l2w home_avg_4q road_avg_4q \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_4q_l2w road_avg_4q_l2w home_avg_ot1 road_avg_ot1 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot1_l2w road_avg_ot1_l2w home_avg_ot2 road_avg_ot2 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot2_l2w road_avg_ot2_l2w home_avg_ot3 road_avg_ot3 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot3_l2w road_avg_ot3_l2w home_avg_ot4 road_avg_ot4 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot4_l2w road_avg_ot4_l2w home_avg_ot5 road_avg_ot5 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot5_l2w road_avg_ot5_l2w home_avg_f road_avg_f home_avg_f_l2w \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_f_l2w home_avg_min road_avg_min home_avg_min_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_min_l2w home_avg_fg road_avg_fg home_avg_fg_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_fg_l2w home_avg_fga road_avg_fga home_avg_fga_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_fga_l2w home_avg_3p road_avg_3p home_avg_3p_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_3p_l2w home_avg_3pa road_avg_3pa home_avg_3pa_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_3pa_l2w home_avg_ft road_avg_ft home_avg_ft_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_ft_l2w home_avg_fta road_avg_fta home_avg_fta_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_fta_l2w home_avg_or road_avg_or home_avg_or_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_or_l2w home_avg_dr road_avg_dr home_avg_dr_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_dr_l2w home_avg_tot road_avg_tot home_avg_tot_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_tot_l2w home_avg_a road_avg_a home_avg_a_l2w road_avg_a_l2w \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pf road_avg_pf home_avg_pf_l2w road_avg_pf_l2w home_avg_st \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_st home_avg_st_l2w road_avg_st_l2w home_avg_to road_avg_to \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_to_l2w road_avg_to_l2w home_avg_to_to road_avg_to_to \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_to_to_l2w road_avg_to_to_l2w home_avg_bl road_avg_bl \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_bl_l2w road_avg_bl_l2w home_avg_pts road_avg_pts \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pts_l2w road_avg_pts_l2w home_avg_poss road_avg_poss \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_poss_l2w road_avg_poss_l2w home_avg_pace road_avg_pace \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pace_l2w road_avg_pace_l2w home_avg_oeff road_avg_oeff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_oeff_l2w road_avg_oeff_l2w home_avg_deff road_avg_deff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_deff_l2w road_avg_deff_l2w home_avg_eFG% road_avg_eFG% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_eFG%_l2w road_avg_eFG%_l2w home_avg_TOV% road_avg_TOV% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_TOV%_l2w road_avg_TOV%_l2w home_avg_ORB% road_avg_ORB% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ORB%_l2w road_avg_ORB%_l2w home_avg_FT% road_avg_FT% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_FT%_l2w road_avg_FT%_l2w home_avg_pts_allowed \\\n", - "0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 \n", - "\n", - " road_avg_pts_allowed home_avg_pts_allowed_l2w road_avg_pts_allowed_l2w \\\n", - "0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 \n", - "\n", - " REG_TARGET CLS_TARGET CLS_TARGET_closing_spread REG_TARGET_OU \\\n", - "0 23 True True 231 \n", - "1 -7 False False 235 \n", - "2 -3 False False 245 \n", - "3 -12 False False 208 \n", - "4 21 True True 193 \n", - "\n", - " CLS_TARGET_OU_OPEN CLS_TARGET_OU_CLOSE home_team_rest road_team_rest \\\n", - "0 False False 7 7 \n", - "1 True True 7 7 \n", - "2 True True 7 7 \n", - "3 False False 7 7 \n", - "4 False False 7 7 \n", - "\n", - " home_lineup_vector \\\n", - "0 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... \n", - "1 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... \n", - "2 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... \n", - "3 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... \n", - "4 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... \n", - "\n", - " road_lineup_vector \n", - "0 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... \n", - "1 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. ... \n", - "2 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... \n", - "3 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... \n", - "4 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. ... " - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Feature Selection" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "def feature_selection_analysis(df, cls_target, reg_target, feature_cols):\n", - " \"\"\"\n", - " Perform feature selection analysis on a given dataframe.\n", - "\n", - " Parameters:\n", - " df (DataFrame): The dataframe containing the features and target variables.\n", - " cls_target (str): The name of the classification target column.\n", - " reg_target (str): The name of the regression target column.\n", - " feature_cols (list): A list of column names representing the features to be evaluated.\n", - "\n", - " Returns:\n", - " DataFrame: A dataframe with features ranked based on different metrics.\n", - "\n", - " The function calculates the following metrics for feature selection:\n", - " - Point-biserial Correlation: Measures the correlation between a binary classification target and continuous features.\n", - " - Pearson Correlation: Measures the linear correlation between a continuous regression target and continuous features.\n", - " - Mutual Information (Classification/Regression): Quantifies the amount of information obtained about one random variable through observing the other random variable.\n", - " - Random Forest Feature Importance: The importance of features as determined by a Random Forest Classifier/Regressor.\n", - " \"\"\"\n", - "\n", - " # Extract the features for analysis\n", - " features = [col for col in feature_cols]\n", - "\n", - " # Calculate point-biserial correlation for classification\n", - " cls_correlations = df[features].corrwith(df[cls_target]).abs()\n", - "\n", - " # Calculate Pearson correlation for regression\n", - " reg_correlations = df[features].corrwith(df[reg_target]).abs()\n", - "\n", - " # Compute mutual information for classification and regression\n", - " mi_classif = mutual_info_classif(df[features], df[cls_target])\n", - " mi_reg = mutual_info_regression(df[features], df[reg_target])\n", - "\n", - " # Feature importance using RandomForest for classification and regression\n", - " rf_classif = RandomForestClassifier(n_estimators=50, random_state=1)\n", - " rf_classif.fit(df[features], df[cls_target])\n", - " classif_importance = rf_classif.feature_importances_\n", - "\n", - " rf_reg = RandomForestRegressor(n_estimators=50, random_state=1)\n", - " rf_reg.fit(df[features], df[reg_target])\n", - " reg_importance = rf_reg.feature_importances_\n", - "\n", - " # Combine results into a DataFrame\n", - " feature_selection_df = pd.DataFrame(\n", - " {\n", - " \"Feature\": features,\n", - " \"Point-Biserial Correlation (Classification)\": cls_correlations.values,\n", - " \"Pearson Correlation (Regression)\": reg_correlations.values,\n", - " \"Mutual Information (Classification)\": mi_classif,\n", - " \"Mutual Information (Regression)\": mi_reg,\n", - " \"Random Forest Feature Importance (Classification)\": classif_importance,\n", - " \"Random Forest Feature Importance (Regression)\": reg_importance,\n", - " }\n", - " )\n", - "\n", - " # Round values to 2 decimals\n", - " for col in feature_selection_df.columns[1:]: # Exclude 'Feature' column\n", - " feature_selection_df[col] = feature_selection_df[col].round(2)\n", - "\n", - " # Rank features for each metric, using 'min' method for tie-breaking\n", - " for metric in feature_selection_df.columns[1:]:\n", - " rank_col = f\"Rank - {metric}\"\n", - " feature_selection_df[rank_col] = feature_selection_df[metric].rank(\n", - " ascending=False, method=\"min\"\n", - " )\n", - "\n", - " # Calculate the average rank\n", - " rank_columns = [\n", - " col for col in feature_selection_df.columns if col.startswith(\"Rank\")\n", - " ]\n", - " feature_selection_df[\"Average Rank\"] = (\n", - " feature_selection_df[rank_columns].mean(axis=1).round(2)\n", - " )\n", - "\n", - " # Sort by average rank\n", - " feature_selection_df.sort_values(\"Average Rank\", inplace=True)\n", - "\n", - " return feature_selection_df" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "betting_feature_set = [\n", - " \"home_opening_spread\",\n", - " \"opening_total\",\n", - " \"home_moneyline\",\n", - " \"road_moneyline\",\n", - "]\n", - "\n", - "base_feature_set = [\n", - " \"day_of_season\",\n", - " \"home_team_rest\",\n", - " \"road_team_rest\",\n", - " \"home_win_pct\",\n", - " \"road_win_pct\",\n", - " \"home_win_pct_l2w\",\n", - " \"road_win_pct_l2w\",\n", - " \"home_avg_pts\",\n", - " \"road_avg_pts\",\n", - " \"home_avg_pts_l2w\",\n", - " \"road_avg_pts_l2w\",\n", - " \"home_avg_oeff\",\n", - " \"road_avg_oeff\",\n", - " \"home_avg_oeff_l2w\",\n", - " \"road_avg_oeff_l2w\",\n", - " \"home_avg_deff\",\n", - " \"road_avg_deff\",\n", - " \"home_avg_deff_l2w\",\n", - " \"road_avg_deff_l2w\",\n", - " \"home_avg_eFG%\",\n", - " \"road_avg_eFG%\",\n", - " \"home_avg_eFG%_l2w\",\n", - " \"road_avg_eFG%_l2w\",\n", - " \"home_avg_TOV%\",\n", - " \"road_avg_TOV%\",\n", - " \"home_avg_TOV%_l2w\",\n", - " \"road_avg_TOV%_l2w\",\n", - " \"home_avg_ORB%\",\n", - " \"road_avg_ORB%\",\n", - " \"home_avg_ORB%_l2w\",\n", - " \"road_avg_ORB%_l2w\",\n", - " \"home_avg_FT%\",\n", - " \"road_avg_FT%\",\n", - " \"home_avg_FT%_l2w\",\n", - " \"road_avg_FT%_l2w\",\n", - " \"home_avg_pts_allowed\",\n", - " \"road_avg_pts_allowed\",\n", - " \"home_avg_pts_allowed_l2w\",\n", - " \"road_avg_pts_allowed_l2w\",\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "features_to_evaluate = base_feature_set + betting_feature_set\n", - "\n", - "feature_selection_analysis_df = feature_selection_analysis(\n", - " df, \"CLS_TARGET\", \"REG_TARGET\", features_to_evaluate\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
FeaturePoint-Biserial Correlation (Classification)Pearson Correlation (Regression)Mutual Information (Classification)Mutual Information (Regression)Random Forest Feature Importance (Classification)Random Forest Feature Importance (Regression)Rank - Point-Biserial Correlation (Classification)Rank - Pearson Correlation (Regression)Rank - Mutual Information (Classification)Rank - Mutual Information (Regression)Rank - Random Forest Feature Importance (Classification)Rank - Random Forest Feature Importance (Regression)Average Rank
42road_moneyline0.060.420.010.130.030.121.01.08.01.01.01.02.17
41home_moneyline0.050.400.010.130.030.124.03.08.01.01.01.03.00
37home_avg_pts_allowed_l2w0.020.100.020.040.020.0214.08.03.06.015.05.08.50
39home_opening_spread0.010.410.030.110.020.0227.02.02.03.015.05.09.00
35home_avg_pts_allowed0.030.100.020.010.020.029.08.03.023.015.05.010.50
15home_avg_deff0.020.090.010.030.020.0214.010.08.012.015.05.010.67
22road_avg_eFG%_l2w0.040.050.010.000.030.027.014.08.032.01.05.011.17
23home_avg_TOV%0.020.060.020.000.030.0214.013.03.032.01.05.011.33
20road_avg_eFG%0.030.050.000.010.030.029.014.017.023.01.05.011.50
28road_avg_ORB%0.030.020.000.040.030.029.032.017.06.01.05.011.67
33home_avg_FT%_l2w0.040.020.000.030.030.027.032.017.012.01.05.012.33
3home_win_pct0.000.180.010.050.020.0239.04.08.04.015.05.012.50
17home_avg_deff_l2w0.020.080.010.010.020.0214.011.08.023.015.05.012.67
30road_avg_ORB%_l2w0.050.030.000.010.030.034.028.017.023.01.03.012.67
10road_avg_pts_l2w0.010.050.040.020.020.0227.014.01.016.015.05.013.00
27home_avg_ORB%0.050.010.000.020.030.034.040.017.016.01.03.013.50
8road_avg_pts0.010.050.000.040.020.0227.014.017.06.015.05.014.00
9home_avg_pts_l2w0.020.020.010.030.020.0214.032.08.012.015.05.014.33
25home_avg_TOV%_l2w0.010.050.010.000.030.0227.014.08.032.01.05.014.50
14road_avg_oeff_l2w0.020.050.000.010.020.0214.014.017.023.015.05.014.67
29home_avg_ORB%_l2w0.030.010.000.040.020.029.040.017.06.015.05.015.33
31home_avg_FT%0.030.030.000.000.030.029.028.017.032.01.05.015.33
12road_avg_oeff0.020.050.000.000.020.0214.014.017.032.015.05.016.17
34road_avg_FT%_l2w0.020.050.000.000.020.0214.014.017.032.015.05.016.17
0day_of_season0.010.010.020.010.030.0227.040.03.023.01.05.016.50
19home_avg_eFG%0.020.020.000.020.020.0214.032.017.016.015.05.016.50
26road_avg_TOV%_l2w0.020.020.000.000.030.0214.032.017.032.01.05.016.83
11home_avg_oeff0.010.020.000.040.020.0227.032.017.06.015.05.017.00
32road_avg_FT%0.000.040.000.010.030.0239.022.017.023.01.05.017.83
18road_avg_deff_l2w0.010.040.000.010.020.0227.022.017.023.015.05.018.17
13home_avg_oeff_l2w0.010.040.010.050.020.0127.022.08.04.015.037.018.83
7home_avg_pts0.020.010.000.010.020.0214.040.017.023.015.05.019.00
24road_avg_TOV%0.020.020.000.000.020.0214.032.017.032.015.05.019.17
4road_win_pct0.010.180.000.020.020.0127.04.017.016.015.037.019.33
5home_win_pct_l2w0.060.150.000.020.010.011.06.017.016.040.037.019.50
1home_team_rest0.060.070.000.030.010.011.012.017.012.040.037.019.83
36road_avg_pts_allowed0.000.030.000.020.020.0239.028.017.016.015.05.020.00
2road_team_rest0.020.040.020.040.010.0114.022.03.06.040.037.020.33
40opening_total0.010.020.000.000.020.0227.032.017.032.015.05.021.33
38road_avg_pts_allowed_l2w0.000.040.000.000.020.0239.022.017.032.015.05.021.67
16road_avg_deff0.000.030.000.000.020.0239.028.017.032.015.05.022.67
6road_win_pct_l2w0.010.120.000.020.010.0127.07.017.016.040.037.024.00
21home_avg_eFG%_l2w0.010.040.000.000.020.0127.022.017.032.015.037.025.00
\n", - "
" - ], - "text/plain": [ - " Feature Point-Biserial Correlation (Classification) \\\n", - "42 road_moneyline 0.06 \n", - "41 home_moneyline 0.05 \n", - "37 home_avg_pts_allowed_l2w 0.02 \n", - "39 home_opening_spread 0.01 \n", - "35 home_avg_pts_allowed 0.03 \n", - "15 home_avg_deff 0.02 \n", - "22 road_avg_eFG%_l2w 0.04 \n", - "23 home_avg_TOV% 0.02 \n", - "20 road_avg_eFG% 0.03 \n", - "28 road_avg_ORB% 0.03 \n", - "33 home_avg_FT%_l2w 0.04 \n", - "3 home_win_pct 0.00 \n", - "17 home_avg_deff_l2w 0.02 \n", - "30 road_avg_ORB%_l2w 0.05 \n", - "10 road_avg_pts_l2w 0.01 \n", - "27 home_avg_ORB% 0.05 \n", - "8 road_avg_pts 0.01 \n", - "9 home_avg_pts_l2w 0.02 \n", - "25 home_avg_TOV%_l2w 0.01 \n", - "14 road_avg_oeff_l2w 0.02 \n", - "29 home_avg_ORB%_l2w 0.03 \n", - "31 home_avg_FT% 0.03 \n", - "12 road_avg_oeff 0.02 \n", - "34 road_avg_FT%_l2w 0.02 \n", - "0 day_of_season 0.01 \n", - "19 home_avg_eFG% 0.02 \n", - "26 road_avg_TOV%_l2w 0.02 \n", - "11 home_avg_oeff 0.01 \n", - "32 road_avg_FT% 0.00 \n", - "18 road_avg_deff_l2w 0.01 \n", - "13 home_avg_oeff_l2w 0.01 \n", - "7 home_avg_pts 0.02 \n", - "24 road_avg_TOV% 0.02 \n", - "4 road_win_pct 0.01 \n", - "5 home_win_pct_l2w 0.06 \n", - "1 home_team_rest 0.06 \n", - "36 road_avg_pts_allowed 0.00 \n", - "2 road_team_rest 0.02 \n", - "40 opening_total 0.01 \n", - "38 road_avg_pts_allowed_l2w 0.00 \n", - "16 road_avg_deff 0.00 \n", - "6 road_win_pct_l2w 0.01 \n", - "21 home_avg_eFG%_l2w 0.01 \n", - "\n", - " Pearson Correlation (Regression) Mutual Information (Classification) \\\n", - "42 0.42 0.01 \n", - "41 0.40 0.01 \n", - "37 0.10 0.02 \n", - "39 0.41 0.03 \n", - "35 0.10 0.02 \n", - "15 0.09 0.01 \n", - "22 0.05 0.01 \n", - "23 0.06 0.02 \n", - "20 0.05 0.00 \n", - "28 0.02 0.00 \n", - "33 0.02 0.00 \n", - "3 0.18 0.01 \n", - "17 0.08 0.01 \n", - "30 0.03 0.00 \n", - "10 0.05 0.04 \n", - "27 0.01 0.00 \n", - "8 0.05 0.00 \n", - "9 0.02 0.01 \n", - "25 0.05 0.01 \n", - "14 0.05 0.00 \n", - "29 0.01 0.00 \n", - "31 0.03 0.00 \n", - "12 0.05 0.00 \n", - "34 0.05 0.00 \n", - "0 0.01 0.02 \n", - "19 0.02 0.00 \n", - "26 0.02 0.00 \n", - "11 0.02 0.00 \n", - "32 0.04 0.00 \n", - "18 0.04 0.00 \n", - "13 0.04 0.01 \n", - "7 0.01 0.00 \n", - "24 0.02 0.00 \n", - "4 0.18 0.00 \n", - "5 0.15 0.00 \n", - "1 0.07 0.00 \n", - "36 0.03 0.00 \n", - "2 0.04 0.02 \n", - "40 0.02 0.00 \n", - "38 0.04 0.00 \n", - "16 0.03 0.00 \n", - "6 0.12 0.00 \n", - "21 0.04 0.00 \n", - "\n", - " Mutual Information (Regression) \\\n", - "42 0.13 \n", - "41 0.13 \n", - "37 0.04 \n", - "39 0.11 \n", - "35 0.01 \n", - "15 0.03 \n", - "22 0.00 \n", - "23 0.00 \n", - "20 0.01 \n", - "28 0.04 \n", - "33 0.03 \n", - "3 0.05 \n", - "17 0.01 \n", - "30 0.01 \n", - "10 0.02 \n", - "27 0.02 \n", - "8 0.04 \n", - "9 0.03 \n", - "25 0.00 \n", - "14 0.01 \n", - "29 0.04 \n", - "31 0.00 \n", - "12 0.00 \n", - "34 0.00 \n", - "0 0.01 \n", - "19 0.02 \n", - "26 0.00 \n", - "11 0.04 \n", - "32 0.01 \n", - "18 0.01 \n", - "13 0.05 \n", - "7 0.01 \n", - "24 0.00 \n", - "4 0.02 \n", - "5 0.02 \n", - "1 0.03 \n", - "36 0.02 \n", - "2 0.04 \n", - "40 0.00 \n", - "38 0.00 \n", - "16 0.00 \n", - "6 0.02 \n", - "21 0.00 \n", - "\n", - " Random Forest Feature Importance (Classification) \\\n", - "42 0.03 \n", - "41 0.03 \n", - "37 0.02 \n", - "39 0.02 \n", - "35 0.02 \n", - "15 0.02 \n", - "22 0.03 \n", - "23 0.03 \n", - "20 0.03 \n", - "28 0.03 \n", - "33 0.03 \n", - "3 0.02 \n", - "17 0.02 \n", - "30 0.03 \n", - "10 0.02 \n", - "27 0.03 \n", - "8 0.02 \n", - "9 0.02 \n", - "25 0.03 \n", - "14 0.02 \n", - "29 0.02 \n", - "31 0.03 \n", - "12 0.02 \n", - "34 0.02 \n", - "0 0.03 \n", - "19 0.02 \n", - "26 0.03 \n", - "11 0.02 \n", - "32 0.03 \n", - "18 0.02 \n", - "13 0.02 \n", - "7 0.02 \n", - "24 0.02 \n", - "4 0.02 \n", - "5 0.01 \n", - "1 0.01 \n", - "36 0.02 \n", - "2 0.01 \n", - "40 0.02 \n", - "38 0.02 \n", - "16 0.02 \n", - "6 0.01 \n", - "21 0.02 \n", - "\n", - " Random Forest Feature Importance (Regression) \\\n", - "42 0.12 \n", - "41 0.12 \n", - "37 0.02 \n", - "39 0.02 \n", - "35 0.02 \n", - "15 0.02 \n", - "22 0.02 \n", - "23 0.02 \n", - "20 0.02 \n", - "28 0.02 \n", - "33 0.02 \n", - "3 0.02 \n", - "17 0.02 \n", - "30 0.03 \n", - "10 0.02 \n", - "27 0.03 \n", - "8 0.02 \n", - "9 0.02 \n", - "25 0.02 \n", - "14 0.02 \n", - "29 0.02 \n", - "31 0.02 \n", - "12 0.02 \n", - "34 0.02 \n", - "0 0.02 \n", - "19 0.02 \n", - "26 0.02 \n", - "11 0.02 \n", - "32 0.02 \n", - "18 0.02 \n", - "13 0.01 \n", - "7 0.02 \n", - "24 0.02 \n", - "4 0.01 \n", - "5 0.01 \n", - "1 0.01 \n", - "36 0.02 \n", - "2 0.01 \n", - "40 0.02 \n", - "38 0.02 \n", - "16 0.02 \n", - "6 0.01 \n", - "21 0.01 \n", - "\n", - " Rank - Point-Biserial Correlation (Classification) \\\n", - "42 1.0 \n", - "41 4.0 \n", - "37 14.0 \n", - "39 27.0 \n", - "35 9.0 \n", - "15 14.0 \n", - "22 7.0 \n", - "23 14.0 \n", - "20 9.0 \n", - "28 9.0 \n", - "33 7.0 \n", - "3 39.0 \n", - "17 14.0 \n", - "30 4.0 \n", - "10 27.0 \n", - "27 4.0 \n", - "8 27.0 \n", - "9 14.0 \n", - "25 27.0 \n", - "14 14.0 \n", - "29 9.0 \n", - "31 9.0 \n", - "12 14.0 \n", - "34 14.0 \n", - "0 27.0 \n", - "19 14.0 \n", - "26 14.0 \n", - "11 27.0 \n", - "32 39.0 \n", - "18 27.0 \n", - "13 27.0 \n", - "7 14.0 \n", - "24 14.0 \n", - "4 27.0 \n", - "5 1.0 \n", - "1 1.0 \n", - "36 39.0 \n", - "2 14.0 \n", - "40 27.0 \n", - "38 39.0 \n", - "16 39.0 \n", - "6 27.0 \n", - "21 27.0 \n", - "\n", - " Rank - Pearson Correlation (Regression) \\\n", - "42 1.0 \n", - "41 3.0 \n", - "37 8.0 \n", - "39 2.0 \n", - "35 8.0 \n", - "15 10.0 \n", - "22 14.0 \n", - "23 13.0 \n", - "20 14.0 \n", - "28 32.0 \n", - "33 32.0 \n", - "3 4.0 \n", - "17 11.0 \n", - "30 28.0 \n", - "10 14.0 \n", - "27 40.0 \n", - "8 14.0 \n", - "9 32.0 \n", - "25 14.0 \n", - "14 14.0 \n", - "29 40.0 \n", - "31 28.0 \n", - "12 14.0 \n", - "34 14.0 \n", - "0 40.0 \n", - "19 32.0 \n", - "26 32.0 \n", - "11 32.0 \n", - "32 22.0 \n", - "18 22.0 \n", - "13 22.0 \n", - "7 40.0 \n", - "24 32.0 \n", - "4 4.0 \n", - "5 6.0 \n", - "1 12.0 \n", - "36 28.0 \n", - "2 22.0 \n", - "40 32.0 \n", - "38 22.0 \n", - "16 28.0 \n", - "6 7.0 \n", - "21 22.0 \n", - "\n", - " Rank - Mutual Information (Classification) \\\n", - "42 8.0 \n", - "41 8.0 \n", - "37 3.0 \n", - "39 2.0 \n", - "35 3.0 \n", - "15 8.0 \n", - "22 8.0 \n", - "23 3.0 \n", - "20 17.0 \n", - "28 17.0 \n", - "33 17.0 \n", - "3 8.0 \n", - "17 8.0 \n", - "30 17.0 \n", - "10 1.0 \n", - "27 17.0 \n", - "8 17.0 \n", - "9 8.0 \n", - "25 8.0 \n", - "14 17.0 \n", - "29 17.0 \n", - "31 17.0 \n", - "12 17.0 \n", - "34 17.0 \n", - "0 3.0 \n", - "19 17.0 \n", - "26 17.0 \n", - "11 17.0 \n", - "32 17.0 \n", - "18 17.0 \n", - "13 8.0 \n", - "7 17.0 \n", - "24 17.0 \n", - "4 17.0 \n", - "5 17.0 \n", - "1 17.0 \n", - "36 17.0 \n", - "2 3.0 \n", - "40 17.0 \n", - "38 17.0 \n", - "16 17.0 \n", - "6 17.0 \n", - "21 17.0 \n", - "\n", - " Rank - Mutual Information (Regression) \\\n", - "42 1.0 \n", - "41 1.0 \n", - "37 6.0 \n", - "39 3.0 \n", - "35 23.0 \n", - "15 12.0 \n", - "22 32.0 \n", - "23 32.0 \n", - "20 23.0 \n", - "28 6.0 \n", - "33 12.0 \n", - "3 4.0 \n", - "17 23.0 \n", - "30 23.0 \n", - "10 16.0 \n", - "27 16.0 \n", - "8 6.0 \n", - "9 12.0 \n", - "25 32.0 \n", - "14 23.0 \n", - "29 6.0 \n", - "31 32.0 \n", - "12 32.0 \n", - "34 32.0 \n", - "0 23.0 \n", - "19 16.0 \n", - "26 32.0 \n", - "11 6.0 \n", - "32 23.0 \n", - "18 23.0 \n", - "13 4.0 \n", - "7 23.0 \n", - "24 32.0 \n", - "4 16.0 \n", - "5 16.0 \n", - "1 12.0 \n", - "36 16.0 \n", - "2 6.0 \n", - "40 32.0 \n", - "38 32.0 \n", - "16 32.0 \n", - "6 16.0 \n", - "21 32.0 \n", - "\n", - " Rank - Random Forest Feature Importance (Classification) \\\n", - "42 1.0 \n", - "41 1.0 \n", - "37 15.0 \n", - "39 15.0 \n", - "35 15.0 \n", - "15 15.0 \n", - "22 1.0 \n", - "23 1.0 \n", - "20 1.0 \n", - "28 1.0 \n", - "33 1.0 \n", - "3 15.0 \n", - "17 15.0 \n", - "30 1.0 \n", - "10 15.0 \n", - "27 1.0 \n", - "8 15.0 \n", - "9 15.0 \n", - "25 1.0 \n", - "14 15.0 \n", - "29 15.0 \n", - "31 1.0 \n", - "12 15.0 \n", - "34 15.0 \n", - "0 1.0 \n", - "19 15.0 \n", - "26 1.0 \n", - "11 15.0 \n", - "32 1.0 \n", - "18 15.0 \n", - "13 15.0 \n", - "7 15.0 \n", - "24 15.0 \n", - "4 15.0 \n", - "5 40.0 \n", - "1 40.0 \n", - "36 15.0 \n", - "2 40.0 \n", - "40 15.0 \n", - "38 15.0 \n", - "16 15.0 \n", - "6 40.0 \n", - "21 15.0 \n", - "\n", - " Rank - Random Forest Feature Importance (Regression) Average Rank \n", - "42 1.0 2.17 \n", - "41 1.0 3.00 \n", - "37 5.0 8.50 \n", - "39 5.0 9.00 \n", - "35 5.0 10.50 \n", - "15 5.0 10.67 \n", - "22 5.0 11.17 \n", - "23 5.0 11.33 \n", - "20 5.0 11.50 \n", - "28 5.0 11.67 \n", - "33 5.0 12.33 \n", - "3 5.0 12.50 \n", - "17 5.0 12.67 \n", - "30 3.0 12.67 \n", - "10 5.0 13.00 \n", - "27 3.0 13.50 \n", - "8 5.0 14.00 \n", - "9 5.0 14.33 \n", - "25 5.0 14.50 \n", - "14 5.0 14.67 \n", - "29 5.0 15.33 \n", - "31 5.0 15.33 \n", - "12 5.0 16.17 \n", - "34 5.0 16.17 \n", - "0 5.0 16.50 \n", - "19 5.0 16.50 \n", - "26 5.0 16.83 \n", - "11 5.0 17.00 \n", - "32 5.0 17.83 \n", - "18 5.0 18.17 \n", - "13 37.0 18.83 \n", - "7 5.0 19.00 \n", - "24 5.0 19.17 \n", - "4 37.0 19.33 \n", - "5 37.0 19.50 \n", - "1 37.0 19.83 \n", - "36 5.0 20.00 \n", - "2 37.0 20.33 \n", - "40 5.0 21.33 \n", - "38 5.0 21.67 \n", - "16 5.0 22.67 \n", - "6 37.0 24.00 \n", - "21 37.0 25.00 " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "feature_selection_analysis_df" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "nba_venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/baseline/etl.ipynb b/notebooks/baseline/etl.ipynb deleted file mode 100644 index d5efb22..0000000 --- a/notebooks/baseline/etl.ipynb +++ /dev/null @@ -1,6962 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# NBA AI - Data Loading and Cleaning" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Imports and Global Settings" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "import re\n", - "import json\n", - "from datetime import timedelta\n", - "\n", - "pd.set_option(\"display.max_columns\", 100)\n", - "pd.set_option(\"display.max_rows\", 1000)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "def load_and_clean_nba_data(file_path):\n", - " \"\"\"\n", - " Loads and cleans NBA game data from the 'NBA_Box_Score_Team-Stats.xlsx' file provided by BIGDATABALL.\n", - "\n", - " This function is tailored to handle the specific format of the BIGDATABALL NBA dataset. It performs the following steps:\n", - " 1. Loads the first sheet of the 'NBA_Box_Score_Team-Stats.xlsx' file into a Pandas DataFrame.\n", - " 2. Converts column names to snake case for consistency and easier access.\n", - " 3. Converts the 'date' column to a Pandas datetime format for proper time series analysis.\n", - " 4. Concatenates player names in the starting lineup columns into a single 'starting_lineup' column.\n", - " 5. Drops the original starting lineup columns and other specified columns like 'box_score_url' and 'full_game_odds_url'.\n", - "\n", - " Parameters:\n", - " - file_path (str): The file path of the 'NBA_Box_Score_Team-Stats.xlsx' file to be loaded.\n", - "\n", - " Returns:\n", - " - pandas.DataFrame: A cleaned DataFrame containing the NBA game data from BIGDATABALL.\n", - " \"\"\"\n", - "\n", - " # Helper function to convert column names to snake case\n", - " def to_snake_case(name):\n", - " name = re.sub(r\"\\s+\", \"_\", name)\n", - " name = re.sub(r\"\\W+\", \"_\", name)\n", - " name = re.sub(r\"_+\", \"_\", name)\n", - " name = re.sub(r\"^_|_$\", \"\", name)\n", - " return name.lower()\n", - "\n", - " # Load the first sheet into a DataFrame\n", - " df = pd.read_excel(file_path, sheet_name=0)\n", - "\n", - " # Rename columns to snake case\n", - " df.columns = [to_snake_case(col) for col in df.columns]\n", - "\n", - " # Convert 'date' column to datetime\n", - " df[\"date\"] = pd.to_datetime(df[\"date\"])\n", - "\n", - " # Extract the season by finding the last occurrence of four digits\n", - " df[\"season\"] = df[\"bigdataball_dataset\"].str.extract(r\"(\\d{4})(?!.*\\d)\")\n", - "\n", - " # Extract the season type by taking everything after the last four digits\n", - " df[\"season_type\"] = df[\"bigdataball_dataset\"].str.extract(r\"(\\d{4})\\s*(.*)\")[1]\n", - "\n", - " # Filter columns that start with 'unnamed'\n", - " unnamed_columns = [col for col in df.columns if col.startswith(\"unnamed\")]\n", - "\n", - " # Ensure that the 'starting_lineups' column is included\n", - " columns_to_concatenate = [\"starting_lineups\"] + unnamed_columns\n", - "\n", - " # Concatenate the columns to create the full starting lineup\n", - " df[\"starting_lineup\"] = df[columns_to_concatenate].apply(\n", - " lambda row: \",\".join(row.dropna().astype(str)), axis=1\n", - " )\n", - "\n", - " # Drop the original lineup columns\n", - " df.drop(columns=columns_to_concatenate, inplace=True)\n", - "\n", - " # Remove the 'box_score_url' and 'full_game_odds_url' columns\n", - " df = df.drop([\"box_score_url\", \"full_game_odds_url\"], axis=1)\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "df_1 = load_and_clean_nba_data(\"../data/nba_ai/2022-2023_NBA_Box_Score_Team-Stats.xlsx\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddateteamvenue1q2q3q4qot1ot2ot3ot4ot5fminfgfga3p3paftftaordrtotapfsttoto_toblptsposspaceoeffdeffteam_rest_dayscrew_chiefreferee_umpireopening_oddsopening_spreadopening_totalline_movement_1line_movement_2line_movement_3closing_oddsclosing_spreadclosing_totalmoneylinehalftimeseasonseason_typestarting_lineup
0NBA 2022-2023 Regular Season222000012022-10-18PhiladelphiaR29342529NaNNaNNaNNaNNaN117240.040801334242842731162581414311798.68053598.680535118.564416127.6847563+James CapersRay Acosta213.5o -104.0213.5216o216uo216216.5o -093.0216.5+127106.5o -152023-2023 Regular SeasonTobias Harris,P.J. Tucker,Joel Embiid,Tyrese M...
1NBA 2022-2023 Regular Season222000012022-10-18BostonH24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+NaNBrian Forte-4 -10-4.0213.5-3-3-3-3 -08-3.0216.5-150+2 -142023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...
2NBA 2022-2023 Regular Season222000022022-10-18LA LakersR22301938NaNNaNNaNNaNNaN109240.04094104019259394823181221224109114.091809114.09180995.537095107.8079153+Tony BrothersScott Twardoski227.5o -096.0227.5224u224o224u223.5o -107.5223.5+247113.5o -172023-2023 Regular SeasonLonnie Walker IV,LeBron James,Anthony Davis,Ru...
3NBA 2022-2023 Regular Season222000022022-10-18Golden StateH25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+NaNRodney Mott-6 -10-6.0227.5-7.5-7.5-7.5-7.5 -09-7.5223.5-306-2.5 -142023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...
4NBA 2022-2023 Regular Season222000032022-10-19OrlandoR28272826NaNNaNNaNNaNNaN109240.04286113014191038482124518185109101.130503101.130503107.781527111.7368123+Sean CorbinMousa Dagher218o -124.0218.0217.5u215.5oo215215o -123.5215.0+135106u 152023-2023 Regular SeasonPaolo Banchero,Franz Wagner,Wendell Carter Jr....
\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date team venue 1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Philadelphia R 29 \n", - "1 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston H 24 \n", - "2 NBA 2022-2023 Regular Season 22200002 2022-10-18 LA Lakers R 22 \n", - "3 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State H 25 \n", - "4 NBA 2022-2023 Regular Season 22200003 2022-10-19 Orlando R 28 \n", - "\n", - " 2q 3q 4q ot1 ot2 ot3 ot4 ot5 f min fg fga 3p 3pa ft fta \\\n", - "0 34 25 29 NaN NaN NaN NaN NaN 117 240.0 40 80 13 34 24 28 \n", - "1 39 35 28 NaN NaN NaN NaN NaN 126 240.0 46 82 12 35 22 28 \n", - "2 30 19 38 NaN NaN NaN NaN NaN 109 240.0 40 94 10 40 19 25 \n", - "3 34 32 32 NaN NaN NaN NaN NaN 123 240.0 45 99 16 45 17 23 \n", - "4 27 28 26 NaN NaN NaN NaN NaN 109 240.0 42 86 11 30 14 19 \n", - "\n", - " or dr tot a pf st to to_to bl pts poss pace \\\n", - "0 4 27 31 16 25 8 14 14 3 117 98.680535 98.680535 \n", - "1 6 30 36 24 24 8 10 11 3 126 98.680535 98.680535 \n", - "2 9 39 48 23 18 12 21 22 4 109 114.091809 114.091809 \n", - "3 11 37 48 31 23 11 18 18 4 123 114.091809 114.091809 \n", - "4 10 38 48 21 24 5 18 18 5 109 101.130503 101.130503 \n", - "\n", - " oeff deff team_rest_days crew_chief referee_umpire \\\n", - "0 118.564416 127.684756 3+ James Capers Ray Acosta \n", - "1 127.684756 118.564416 3+ NaN Brian Forte \n", - "2 95.537095 107.807915 3+ Tony Brothers Scott Twardoski \n", - "3 107.807915 95.537095 3+ NaN Rodney Mott \n", - "4 107.781527 111.736812 3+ Sean Corbin Mousa Dagher \n", - "\n", - " opening_odds opening_spread opening_total line_movement_1 line_movement_2 \\\n", - "0 213.5o -10 4.0 213.5 216o 216u \n", - "1 -4 -10 -4.0 213.5 -3 -3 \n", - "2 227.5o -09 6.0 227.5 224u 224o \n", - "3 -6 -10 -6.0 227.5 -7.5 -7.5 \n", - "4 218o -12 4.0 218.0 217.5u 215.5o \n", - "\n", - " line_movement_3 closing_odds closing_spread closing_total moneyline \\\n", - "0 o216 216.5o -09 3.0 216.5 +127 \n", - "1 -3 -3 -08 -3.0 216.5 -150 \n", - "2 224u 223.5o -10 7.5 223.5 +247 \n", - "3 -7.5 -7.5 -09 -7.5 223.5 -306 \n", - "4 o215 215o -12 3.5 215.0 +135 \n", - "\n", - " halftime season season_type \\\n", - "0 106.5o -15 2023 -2023 Regular Season \n", - "1 +2 -14 2023 -2023 Regular Season \n", - "2 113.5o -17 2023 -2023 Regular Season \n", - "3 -2.5 -14 2023 -2023 Regular Season \n", - "4 106u 15 2023 -2023 Regular Season \n", - "\n", - " starting_lineup \n", - "0 Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M... \n", - "1 Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "2 Lonnie Walker IV,LeBron James,Anthony Davis,Ru... \n", - "3 Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "4 Paolo Banchero,Franz Wagner,Wendell Carter Jr.... " - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_1.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "def merge_and_transform_nba_data(df):\n", - " \"\"\"\n", - " Transforms the cleaned NBA DataFrame to create a single record for each game with updated handling of referee and odds information.\n", - "\n", - " This function:\n", - " 1. Splits the DataFrame into separate DataFrames for home and road teams.\n", - " 2. Renames and prefixes columns to indicate home or road team.\n", - " 3. Merges these DataFrames to create a single row per game.\n", - " 4. Removes the 'home_venue' and 'road_venue' columns as they are redundant.\n", - " 5. Combines the 'main_ref/crew_chief' and 'crew/referee_umpire' columns from both home and road records.\n", - " 6. Removes unused betting data columns.\n", - "\n", - " Parameters:\n", - " df (pandas.DataFrame): The cleaned DataFrame containing NBA data.\n", - "\n", - " Returns:\n", - " pandas.DataFrame: A transformed DataFrame with one row per game, combined referee information, and betting data.\n", - " \"\"\"\n", - "\n", - " # Splitting the DataFrame into home and road teams\n", - " home_df = df[df[\"venue\"] == \"H\"].copy()\n", - " road_df = df[df[\"venue\"] == \"R\"].copy()\n", - "\n", - " # Renaming columns for clarity\n", - " home_df.rename(columns={\"team\": \"home_team\"}, inplace=True)\n", - " road_df.rename(columns={\"team\": \"road_team\"}, inplace=True)\n", - "\n", - " # Adding a prefix to all relevant columns\n", - " for col in home_df.columns:\n", - " if col not in [\n", - " \"bigdataball_dataset\",\n", - " \"game_id\",\n", - " \"date\",\n", - " \"season\",\n", - " \"season_type\",\n", - " \"home_team\",\n", - " ]:\n", - " home_df.rename(columns={col: \"home_\" + col}, inplace=True)\n", - "\n", - " for col in road_df.columns:\n", - " if col not in [\n", - " \"bigdataball_dataset\",\n", - " \"game_id\",\n", - " \"date\",\n", - " \"season\",\n", - " \"season_type\",\n", - " \"road_team\",\n", - " ]:\n", - " road_df.rename(columns={col: \"road_\" + col}, inplace=True)\n", - "\n", - " # Merging the DataFrames on common columns\n", - " merged_df = pd.merge(\n", - " home_df,\n", - " road_df,\n", - " left_on=[\"bigdataball_dataset\", \"game_id\", \"date\", \"season\", \"season_type\"],\n", - " right_on=[\"bigdataball_dataset\", \"game_id\", \"date\", \"season\", \"season_type\"],\n", - " )\n", - "\n", - " # Removing the 'home_venue' and 'road_venue' columns\n", - " merged_df.drop([\"home_venue\", \"road_venue\"], axis=1, inplace=True)\n", - "\n", - " # Check which set of columns is present and set variables accordingly\n", - " if \"home_main_ref\" in merged_df.columns and \"road_main_ref\" in merged_df.columns:\n", - " main_ref_cols = [\"home_main_ref\", \"road_main_ref\"]\n", - " crew_cols = [\"home_crew\", \"road_crew\"]\n", - " main_ref_output = \"main_ref\"\n", - " crew_output = \"crew\"\n", - " elif (\n", - " \"home_crew_chief\" in merged_df.columns\n", - " and \"road_crew_chief\" in merged_df.columns\n", - " ):\n", - " main_ref_cols = [\"home_crew_chief\", \"road_crew_chief\"]\n", - " crew_cols = [\"home_referee_umpire\", \"road_referee_umpire\"]\n", - " main_ref_output = \"crew_chief\"\n", - " crew_output = \"referee_umpire\"\n", - " else:\n", - " raise ValueError(\"Expected columns not found in DataFrame\")\n", - "\n", - " # Process main_ref/crew_chief columns\n", - " merged_df[main_ref_output] = merged_df.apply(\n", - " lambda x: x[main_ref_cols[0]]\n", - " if pd.notna(x[main_ref_cols[0]])\n", - " else x[main_ref_cols[1]],\n", - " axis=1,\n", - " )\n", - " merged_df.drop(main_ref_cols, axis=1, inplace=True)\n", - "\n", - " # Combine crew/referee_umpire columns\n", - " def combine_crew(crew1, crew2):\n", - " all_crew = set(crew1.split(\",\")) | set(crew2.split(\",\"))\n", - " return \",\".join(sorted(all_crew - {\"\"}))\n", - "\n", - " merged_df[crew_output] = merged_df.apply(\n", - " lambda x: combine_crew(x[crew_cols[0]], x[crew_cols[1]]),\n", - " axis=1,\n", - " )\n", - " merged_df.drop(crew_cols, axis=1, inplace=True)\n", - "\n", - " # Remove unused betting data columns\n", - " merged_df = merged_df.rename(\n", - " columns={\n", - " \"home_opening_total\": \"opening_total\",\n", - " \"home_closing_total\": \"closing_total\",\n", - " }\n", - " )\n", - "\n", - " unused_odds_columns = [\n", - " \"home_line_movement_1\",\n", - " \"home_line_movement_2\",\n", - " \"home_line_movement_3\",\n", - " \"road_line_movement_1\",\n", - " \"road_line_movement_2\",\n", - " \"road_line_movement_3\",\n", - " \"home_halftime\",\n", - " \"road_halftime\",\n", - " \"home_opening_odds\",\n", - " \"road_opening_odds\",\n", - " \"home_closing_odds\",\n", - " \"road_closing_odds\",\n", - " \"road_opening_total\",\n", - " \"road_closing_total\",\n", - " \"road_opening_spread\",\n", - " \"road_closing_spread\",\n", - " ]\n", - " merged_df.drop(unused_odds_columns, axis=1, inplace=True)\n", - "\n", - " return merged_df" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "df_2 = merge_and_transform_nba_data(df_1)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1road_ot2road_ot3road_ot4road_ot5road_froad_minroad_fgroad_fgaroad_3proad_3paroad_ftroad_ftaroad_orroad_drroad_totroad_aroad_pfroad_stroad_toroad_to_toroad_blroad_ptsroad_possroad_paceroad_oeffroad_deffroad_team_rest_daysroad_moneylineroad_starting_lineupcrew_chiefreferee_umpire
0NBA 2022-2023 Regular Season222000012022-10-18Boston24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+-4.0213.5-3.0216.5-1502023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...Philadelphia29342529NaNNaNNaNNaNNaN117240.040801334242842731162581414311798.68053598.680535118.564416127.6847563++127Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M...James CapersBrian Forte,Ray Acosta
1NBA 2022-2023 Regular Season222000022022-10-18Golden State25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+-6.0227.5-7.5223.5-3062023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...LA Lakers22301938NaNNaNNaNNaNNaN109240.04094104019259394823181221224109114.091809114.09180995.537095107.8079153++247Lonnie Walker IV,LeBron James,Anthony Davis,Ru...Tony BrothersRodney Mott,Scott Twardoski
2NBA 2022-2023 Regular Season222000032022-10-19Detroit17403422NaNNaNNaNNaNNaN113240.040941438192412294131211112134113101.130503101.130503111.736812107.7815273+-4.0218.0-3.5215.0-1622023-2023 Regular SeasonSaddiq Bey,Bojan Bogdanovic,Isaiah Stewart,Cad...Orlando28272826NaNNaNNaNNaNNaN109240.04286113014191038482124518185109101.130503101.130503107.781527111.7368123++135Paolo Banchero,Franz Wagner,Wendell Carter Jr....Sean CorbinDavid Guthrie,Mousa Dagher
3NBA 2022-2023 Regular Season222000042022-10-19Indiana25272530NaNNaNNaNNaNNaN107240.03997154214211230422120715155107103.687460103.687460103.194736109.9457933+2.5227.02.5228.5+1142023-2023 Regular SeasonBuddy Hield,Terry Taylor,Jalen Smith,Chris Dua...Washington36242727NaNNaNNaNNaNNaN114240.042921131192414395321195161710114103.687460103.687460109.945793103.1947363+-137Deni Avdija,Kyle Kuzma,Kristaps Porzingis,Brad...Scott FosterAshley Moyer-Gleich,Brent Barnaky
4NBA 2022-2023 Regular Season222000052022-10-19Atlanta26332533NaNNaNNaNNaNNaN117240.04590725202443438301812995117102.889037102.889037113.714740103.9955313+-9.5233.5-10.5234.5-5052023-2023 Regular SeasonDe'Andre Hunter,John Collins,Clint Capela,Dejo...Houston20303027NaNNaNNaNNaNNaN107240.0429893514151539542520415163107102.889037102.889037103.995531113.7147403++378Eric Gordon,Jabari Smith Jr.,Bruno Fernando,Ja...Ed MalloyBen Taylor,Jenna Reneau
\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston 24 \n", - "1 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State 25 \n", - "2 NBA 2022-2023 Regular Season 22200003 2022-10-19 Detroit 17 \n", - "3 NBA 2022-2023 Regular Season 22200004 2022-10-19 Indiana 25 \n", - "4 NBA 2022-2023 Regular Season 22200005 2022-10-19 Atlanta 26 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 39 35 28 NaN NaN NaN NaN \n", - "1 34 32 32 NaN NaN NaN NaN \n", - "2 40 34 22 NaN NaN NaN NaN \n", - "3 27 25 30 NaN NaN NaN NaN \n", - "4 33 25 33 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 126 240.0 46 82 12 35 22 \n", - "1 NaN 123 240.0 45 99 16 45 17 \n", - "2 NaN 113 240.0 40 94 14 38 19 \n", - "3 NaN 107 240.0 39 97 15 42 14 \n", - "4 NaN 117 240.0 45 90 7 25 20 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 28 6 30 36 24 24 8 10 \n", - "1 23 11 37 48 31 23 11 18 \n", - "2 24 12 29 41 31 21 11 12 \n", - "3 21 12 30 42 21 20 7 15 \n", - "4 24 4 34 38 30 18 12 9 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 11 3 126 98.680535 98.680535 127.684756 \n", - "1 18 4 123 114.091809 114.091809 107.807915 \n", - "2 13 4 113 101.130503 101.130503 111.736812 \n", - "3 15 5 107 103.687460 103.687460 103.194736 \n", - "4 9 5 117 102.889037 102.889037 113.714740 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 118.564416 3+ -4.0 213.5 \n", - "1 95.537095 3+ -6.0 227.5 \n", - "2 107.781527 3+ -4.0 218.0 \n", - "3 109.945793 3+ 2.5 227.0 \n", - "4 103.995531 3+ -9.5 233.5 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -3.0 216.5 -150 2023 \n", - "1 -7.5 223.5 -306 2023 \n", - "2 -3.5 215.0 -162 2023 \n", - "3 2.5 228.5 +114 2023 \n", - "4 -10.5 234.5 -505 2023 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2023 Regular Season Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "1 -2023 Regular Season Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "2 -2023 Regular Season Saddiq Bey,Bojan Bogdanovic,Isaiah Stewart,Cad... \n", - "3 -2023 Regular Season Buddy Hield,Terry Taylor,Jalen Smith,Chris Dua... \n", - "4 -2023 Regular Season De'Andre Hunter,John Collins,Clint Capela,Dejo... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 road_ot2 \\\n", - "0 Philadelphia 29 34 25 29 NaN NaN \n", - "1 LA Lakers 22 30 19 38 NaN NaN \n", - "2 Orlando 28 27 28 26 NaN NaN \n", - "3 Washington 36 24 27 27 NaN NaN \n", - "4 Houston 20 30 30 27 NaN NaN \n", - "\n", - " road_ot3 road_ot4 road_ot5 road_f road_min road_fg road_fga road_3p \\\n", - "0 NaN NaN NaN 117 240.0 40 80 13 \n", - "1 NaN NaN NaN 109 240.0 40 94 10 \n", - "2 NaN NaN NaN 109 240.0 42 86 11 \n", - "3 NaN NaN NaN 114 240.0 42 92 11 \n", - "4 NaN NaN NaN 107 240.0 42 98 9 \n", - "\n", - " road_3pa road_ft road_fta road_or road_dr road_tot road_a road_pf \\\n", - "0 34 24 28 4 27 31 16 25 \n", - "1 40 19 25 9 39 48 23 18 \n", - "2 30 14 19 10 38 48 21 24 \n", - "3 31 19 24 14 39 53 21 19 \n", - "4 35 14 15 15 39 54 25 20 \n", - "\n", - " road_st road_to road_to_to road_bl road_pts road_poss road_pace \\\n", - "0 8 14 14 3 117 98.680535 98.680535 \n", - "1 12 21 22 4 109 114.091809 114.091809 \n", - "2 5 18 18 5 109 101.130503 101.130503 \n", - "3 5 16 17 10 114 103.687460 103.687460 \n", - "4 4 15 16 3 107 102.889037 102.889037 \n", - "\n", - " road_oeff road_deff road_team_rest_days road_moneyline \\\n", - "0 118.564416 127.684756 3+ +127 \n", - "1 95.537095 107.807915 3+ +247 \n", - "2 107.781527 111.736812 3+ +135 \n", - "3 109.945793 103.194736 3+ -137 \n", - "4 103.995531 113.714740 3+ +378 \n", - "\n", - " road_starting_lineup crew_chief \\\n", - "0 Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M... James Capers \n", - "1 Lonnie Walker IV,LeBron James,Anthony Davis,Ru... Tony Brothers \n", - "2 Paolo Banchero,Franz Wagner,Wendell Carter Jr.... Sean Corbin \n", - "3 Deni Avdija,Kyle Kuzma,Kristaps Porzingis,Brad... Scott Foster \n", - "4 Eric Gordon,Jabari Smith Jr.,Bruno Fernando,Ja... Ed Malloy \n", - "\n", - " referee_umpire \n", - "0 Brian Forte,Ray Acosta \n", - "1 Rodney Mott,Scott Twardoski \n", - "2 David Guthrie,Mousa Dagher \n", - "3 Ashley Moyer-Gleich,Brent Barnaky \n", - "4 Ben Taylor,Jenna Reneau " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_2.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "def add_sequence_data(df):\n", - " # Calculate the 'Day of Season'\n", - " df[\"day_of_season\"] = (df[\"date\"] - df[\"date\"].min()).dt.days + 1\n", - "\n", - " df = df.sort_values(by=\"date\")\n", - "\n", - " # Function to calculate the game number\n", - " def calculate_game_number(row, team_column, df):\n", - " return len(\n", - " df[\n", - " (\n", - " (df[\"home_team\"] == row[team_column])\n", - " | (df[\"road_team\"] == row[team_column])\n", - " )\n", - " & (df[\"date\"] <= row[\"date\"])\n", - " ]\n", - " )\n", - "\n", - " # Calculate game numbers\n", - " df[\"home_team_game_num\"] = df.apply(\n", - " calculate_game_number, team_column=\"home_team\", df=df, axis=1\n", - " )\n", - " df[\"road_team_game_num\"] = df.apply(\n", - " calculate_game_number, team_column=\"road_team\", df=df, axis=1\n", - " )\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "df_3 = add_sequence_data(df_2)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1road_ot2road_ot3road_ot4road_ot5road_froad_minroad_fgroad_fgaroad_3proad_3paroad_ftroad_ftaroad_orroad_drroad_totroad_aroad_pfroad_stroad_toroad_to_toroad_blroad_ptsroad_possroad_paceroad_oeffroad_deffroad_team_rest_daysroad_moneylineroad_starting_lineupcrew_chiefreferee_umpireday_of_seasonhome_team_game_numroad_team_game_num
0NBA 2022-2023 Regular Season222000012022-10-18Boston24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+-4.0213.5-3.0216.5-1502023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...Philadelphia29342529NaNNaNNaNNaNNaN117240.040801334242842731162581414311798.68053598.680535118.564416127.6847563++127Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M...James CapersBrian Forte,Ray Acosta111
1NBA 2022-2023 Regular Season222000022022-10-18Golden State25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+-6.0227.5-7.5223.5-3062023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...LA Lakers22301938NaNNaNNaNNaNNaN109240.04094104019259394823181221224109114.091809114.09180995.537095107.8079153++247Lonnie Walker IV,LeBron James,Anthony Davis,Ru...Tony BrothersRodney Mott,Scott Twardoski111
13NBA 2022-2023 Regular Season222000142022-10-19Sacramento23322924NaNNaNNaNNaNNaN108240.0398517441319437412725815165108101.731855101.731855106.161438113.0422713+-1.5223.5-3.0229.5-1492023-2023 Regular SeasonHarrison Barnes,KZ Okpala,Domantas Sabonis,Kev...Portland32193331NaNNaNNaNNaNNaN115240.039881128263311334420171111112115101.731855101.731855113.042271106.1614383++124Josh Hart,Jerami Grant,Jusuf Nurkic,Anfernee S...Courtney KirklandBrandon Adair,Justin Van Duyne211
12NBA 2022-2023 Regular Season222000132022-10-19Phoenix24213131NaNNaNNaNNaNNaN107240.04085822192283240252941212510795.82903895.829038111.657178109.5701283+-5.5216.0-4.0218.0-1802023-2023 Regular SeasonMikal Bridges,Cameron Johnson,Deandre Ayton,De...Dallas32301924NaNNaNNaNNaNNaN105240.035751435213453540172261212410595.82903895.829038109.570128111.6571783++149Reggie Bullock,Dorian Finney-Smith,JaVale McGe...Derek RichardsonEric Lewis,Gediminas Petraitis211
11NBA 2022-2023 Regular Season222000122022-10-19Utah37381929NaNNaNNaNNaNNaN123240.042831638233111324327211019201123101.120258101.120258121.637348100.8699963+7.5218.07.0225.5+2132023-2023 Regular SeasonLauri Markkanen,Kelly Olynyk,Jarred Vanderbilt...Denver30232722NaNNaNNaNNaNNaN102240.04083522171810253521231021213102101.120258101.120258100.869996121.6373483+-261Michael Porter Jr.,Aaron Gordon,Nikola Jokic,K...Tony BrothersKevin Cutler,Lauren Holtkamp211
\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston 24 \n", - "1 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State 25 \n", - "13 NBA 2022-2023 Regular Season 22200014 2022-10-19 Sacramento 23 \n", - "12 NBA 2022-2023 Regular Season 22200013 2022-10-19 Phoenix 24 \n", - "11 NBA 2022-2023 Regular Season 22200012 2022-10-19 Utah 37 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 39 35 28 NaN NaN NaN NaN \n", - "1 34 32 32 NaN NaN NaN NaN \n", - "13 32 29 24 NaN NaN NaN NaN \n", - "12 21 31 31 NaN NaN NaN NaN \n", - "11 38 19 29 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 126 240.0 46 82 12 35 22 \n", - "1 NaN 123 240.0 45 99 16 45 17 \n", - "13 NaN 108 240.0 39 85 17 44 13 \n", - "12 NaN 107 240.0 40 85 8 22 19 \n", - "11 NaN 123 240.0 42 83 16 38 23 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 28 6 30 36 24 24 8 10 \n", - "1 23 11 37 48 31 23 11 18 \n", - "13 19 4 37 41 27 25 8 15 \n", - "12 22 8 32 40 25 29 4 12 \n", - "11 31 11 32 43 27 21 10 19 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 11 3 126 98.680535 98.680535 127.684756 \n", - "1 18 4 123 114.091809 114.091809 107.807915 \n", - "13 16 5 108 101.731855 101.731855 106.161438 \n", - "12 12 5 107 95.829038 95.829038 111.657178 \n", - "11 20 1 123 101.120258 101.120258 121.637348 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 118.564416 3+ -4.0 213.5 \n", - "1 95.537095 3+ -6.0 227.5 \n", - "13 113.042271 3+ -1.5 223.5 \n", - "12 109.570128 3+ -5.5 216.0 \n", - "11 100.869996 3+ 7.5 218.0 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -3.0 216.5 -150 2023 \n", - "1 -7.5 223.5 -306 2023 \n", - "13 -3.0 229.5 -149 2023 \n", - "12 -4.0 218.0 -180 2023 \n", - "11 7.0 225.5 +213 2023 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2023 Regular Season Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "1 -2023 Regular Season Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "13 -2023 Regular Season Harrison Barnes,KZ Okpala,Domantas Sabonis,Kev... \n", - "12 -2023 Regular Season Mikal Bridges,Cameron Johnson,Deandre Ayton,De... \n", - "11 -2023 Regular Season Lauri Markkanen,Kelly Olynyk,Jarred Vanderbilt... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 road_ot2 \\\n", - "0 Philadelphia 29 34 25 29 NaN NaN \n", - "1 LA Lakers 22 30 19 38 NaN NaN \n", - "13 Portland 32 19 33 31 NaN NaN \n", - "12 Dallas 32 30 19 24 NaN NaN \n", - "11 Denver 30 23 27 22 NaN NaN \n", - "\n", - " road_ot3 road_ot4 road_ot5 road_f road_min road_fg road_fga \\\n", - "0 NaN NaN NaN 117 240.0 40 80 \n", - "1 NaN NaN NaN 109 240.0 40 94 \n", - "13 NaN NaN NaN 115 240.0 39 88 \n", - "12 NaN NaN NaN 105 240.0 35 75 \n", - "11 NaN NaN NaN 102 240.0 40 83 \n", - "\n", - " road_3p road_3pa road_ft road_fta road_or road_dr road_tot road_a \\\n", - "0 13 34 24 28 4 27 31 16 \n", - "1 10 40 19 25 9 39 48 23 \n", - "13 11 28 26 33 11 33 44 20 \n", - "12 14 35 21 34 5 35 40 17 \n", - "11 5 22 17 18 10 25 35 21 \n", - "\n", - " road_pf road_st road_to road_to_to road_bl road_pts road_poss \\\n", - "0 25 8 14 14 3 117 98.680535 \n", - "1 18 12 21 22 4 109 114.091809 \n", - "13 17 11 11 11 2 115 101.731855 \n", - "12 22 6 12 12 4 105 95.829038 \n", - "11 23 10 21 21 3 102 101.120258 \n", - "\n", - " road_pace road_oeff road_deff road_team_rest_days road_moneyline \\\n", - "0 98.680535 118.564416 127.684756 3+ +127 \n", - "1 114.091809 95.537095 107.807915 3+ +247 \n", - "13 101.731855 113.042271 106.161438 3+ +124 \n", - "12 95.829038 109.570128 111.657178 3+ +149 \n", - "11 101.120258 100.869996 121.637348 3+ -261 \n", - "\n", - " road_starting_lineup crew_chief \\\n", - "0 Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M... James Capers \n", - "1 Lonnie Walker IV,LeBron James,Anthony Davis,Ru... Tony Brothers \n", - "13 Josh Hart,Jerami Grant,Jusuf Nurkic,Anfernee S... Courtney Kirkland \n", - "12 Reggie Bullock,Dorian Finney-Smith,JaVale McGe... Derek Richardson \n", - "11 Michael Porter Jr.,Aaron Gordon,Nikola Jokic,K... Tony Brothers \n", - "\n", - " referee_umpire day_of_season home_team_game_num \\\n", - "0 Brian Forte,Ray Acosta 1 1 \n", - "1 Rodney Mott,Scott Twardoski 1 1 \n", - "13 Brandon Adair,Justin Van Duyne 2 1 \n", - "12 Eric Lewis,Gediminas Petraitis 2 1 \n", - "11 Kevin Cutler,Lauren Holtkamp 2 1 \n", - "\n", - " road_team_game_num \n", - "0 1 \n", - "1 1 \n", - "13 1 \n", - "12 1 \n", - "11 1 " - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_3.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "def add_four_factors(df):\n", - " \"\"\"\n", - " Add the Four Factors columns to an NBA game statistics DataFrame.\n", - "\n", - " This function calculates the Four Factors (Effective Field Goal Percentage,\n", - " Turnover Rate, Offensive Rebound Rate, Free Throw Rate) for both home and road teams\n", - " and adds these as new columns to the provided DataFrame.\n", - "\n", - " Parameters:\n", - " df (pd.DataFrame): A DataFrame containing NBA game statistics.\n", - "\n", - " Returns:\n", - " pd.DataFrame: The original DataFrame with added columns for the Four Factors\n", - " for both home and road teams.\n", - " \"\"\"\n", - "\n", - " # Define the column mappings for home and road teams\n", - " stats_mapping = {\n", - " \"home\": {\n", - " \"fgm\": \"home_fg\",\n", - " \"fga\": \"home_fga\",\n", - " \"3pm\": \"home_3p\",\n", - " \"ftm\": \"home_ft\",\n", - " \"fta\": \"home_fta\",\n", - " \"orb\": \"home_or\",\n", - " \"drb\": \"home_dr\",\n", - " \"tov\": \"home_to_to\",\n", - " },\n", - " \"road\": {\n", - " \"fgm\": \"road_fg\",\n", - " \"fga\": \"road_fga\",\n", - " \"3pm\": \"road_3p\",\n", - " \"ftm\": \"road_ft\",\n", - " \"fta\": \"road_fta\",\n", - " \"orb\": \"road_or\",\n", - " \"drb\": \"road_dr\",\n", - " \"tov\": \"road_to_to\",\n", - " },\n", - " }\n", - "\n", - " # Function to calculate the Four Factors for a given team type\n", - " def calculate_four_factors(df, team_type):\n", - " factors = {}\n", - " stats = stats_mapping[team_type]\n", - "\n", - " # eFG%\n", - " factors[\"eFG%\"] = (df[stats[\"fgm\"]] + 0.5 * df[stats[\"3pm\"]]) / df[stats[\"fga\"]]\n", - "\n", - " # TOV%\n", - " factors[\"TOV%\"] = df[stats[\"tov\"]] / (\n", - " df[stats[\"fga\"]] + 0.44 * df[stats[\"fta\"]] + df[stats[\"tov\"]]\n", - " )\n", - "\n", - " # ORB%\n", - " factors[\"ORB%\"] = df[stats[\"orb\"]] / (\n", - " df[stats[\"orb\"]] + df[stats[\"drb\"]].shift(-1)\n", - " )\n", - "\n", - " # FT%\n", - " factors[\"FT%\"] = df[stats[\"ftm\"]] / df[stats[\"fga\"]]\n", - "\n", - " return pd.DataFrame(factors)\n", - "\n", - " # Calculate Four Factors for both home and road teams\n", - " home_factors = calculate_four_factors(df, \"home\")\n", - " road_factors = calculate_four_factors(df, \"road\")\n", - "\n", - " # Combine the results and add them to the original DataFrame\n", - " four_factors = pd.concat(\n", - " [home_factors.add_prefix(\"home_\"), road_factors.add_prefix(\"road_\")], axis=1\n", - " )\n", - " return pd.concat([df, four_factors], axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "df_4 = add_four_factors(df_3)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1road_ot2road_ot3road_ot4road_ot5road_froad_minroad_fgroad_fgaroad_3proad_3paroad_ftroad_ftaroad_orroad_drroad_totroad_aroad_pfroad_stroad_toroad_to_toroad_blroad_ptsroad_possroad_paceroad_oeffroad_deffroad_team_rest_daysroad_moneylineroad_starting_lineupcrew_chiefreferee_umpireday_of_seasonhome_team_game_numroad_team_game_numhome_eFG%home_TOV%home_ORB%home_FT%road_eFG%road_TOV%road_ORB%road_FT%
0NBA 2022-2023 Regular Season222000012022-10-18Boston24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+-4.0213.5-3.0216.5-1502023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...Philadelphia29342529NaNNaNNaNNaNNaN117240.040801334242842731162581414311798.68053598.680535118.564416127.6847563++127Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M...James CapersBrian Forte,Ray Acosta1110.6341460.1044440.1395350.2682930.5812500.1316780.0930230.300000
1NBA 2022-2023 Regular Season222000022022-10-18Golden State25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+-6.0227.5-7.5223.5-3062023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...LA Lakers22301938NaNNaNNaNNaNNaN109240.04094104019259394823181221224109114.091809114.09180995.537095107.8079153++247Lonnie Walker IV,LeBron James,Anthony Davis,Ru...Tony BrothersRodney Mott,Scott Twardoski1110.5353540.1415980.2291670.1717170.4787230.1732280.2142860.202128
13NBA 2022-2023 Regular Season222000142022-10-19Sacramento23322924NaNNaNNaNNaNNaN108240.0398517441319437412725815165108101.731855101.731855106.161438113.0422713+-1.5223.5-3.0229.5-1492023-2023 Regular SeasonHarrison Barnes,KZ Okpala,Domantas Sabonis,Kev...Portland32193331NaNNaNNaNNaNNaN115240.039881128263311334420171111112115101.731855101.731855113.042271106.1614383++124Josh Hart,Jerami Grant,Jusuf Nurkic,Anfernee S...Courtney KirklandBrandon Adair,Justin Van Duyne2110.5588240.1463060.1111110.1529410.5056820.0968990.2391300.295455
12NBA 2022-2023 Regular Season222000132022-10-19Phoenix24213131NaNNaNNaNNaNNaN107240.04085822192283240252941212510795.82903895.829038111.657178109.5701283+-5.5216.0-4.0218.0-1802023-2023 Regular SeasonMikal Bridges,Cameron Johnson,Deandre Ayton,De...Dallas32301924NaNNaNNaNNaNNaN105240.035751435213453540172261212410595.82903895.829038109.570128111.6571783++149Reggie Bullock,Dorian Finney-Smith,JaVale McGe...Derek RichardsonEric Lewis,Gediminas Petraitis2110.5176470.1124860.2000000.2235290.5600000.1176930.1666670.280000
11NBA 2022-2023 Regular Season222000122022-10-19Utah37381929NaNNaNNaNNaNNaN123240.042831638233111324327211019201123101.120258101.120258121.637348100.8699963+7.5218.07.0225.5+2132023-2023 Regular SeasonLauri Markkanen,Kelly Olynyk,Jarred Vanderbilt...Denver30232722NaNNaNNaNNaNNaN102240.04083522171810253521231021213102101.120258101.120258100.869996121.6373483+-261Michael Porter Jr.,Aaron Gordon,Nikola Jokic,K...Tony BrothersKevin Cutler,Lauren Holtkamp2110.6024100.1714680.2115380.2771080.5120480.1876340.1960780.204819
\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston 24 \n", - "1 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State 25 \n", - "13 NBA 2022-2023 Regular Season 22200014 2022-10-19 Sacramento 23 \n", - "12 NBA 2022-2023 Regular Season 22200013 2022-10-19 Phoenix 24 \n", - "11 NBA 2022-2023 Regular Season 22200012 2022-10-19 Utah 37 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 39 35 28 NaN NaN NaN NaN \n", - "1 34 32 32 NaN NaN NaN NaN \n", - "13 32 29 24 NaN NaN NaN NaN \n", - "12 21 31 31 NaN NaN NaN NaN \n", - "11 38 19 29 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 126 240.0 46 82 12 35 22 \n", - "1 NaN 123 240.0 45 99 16 45 17 \n", - "13 NaN 108 240.0 39 85 17 44 13 \n", - "12 NaN 107 240.0 40 85 8 22 19 \n", - "11 NaN 123 240.0 42 83 16 38 23 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 28 6 30 36 24 24 8 10 \n", - "1 23 11 37 48 31 23 11 18 \n", - "13 19 4 37 41 27 25 8 15 \n", - "12 22 8 32 40 25 29 4 12 \n", - "11 31 11 32 43 27 21 10 19 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 11 3 126 98.680535 98.680535 127.684756 \n", - "1 18 4 123 114.091809 114.091809 107.807915 \n", - "13 16 5 108 101.731855 101.731855 106.161438 \n", - "12 12 5 107 95.829038 95.829038 111.657178 \n", - "11 20 1 123 101.120258 101.120258 121.637348 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 118.564416 3+ -4.0 213.5 \n", - "1 95.537095 3+ -6.0 227.5 \n", - "13 113.042271 3+ -1.5 223.5 \n", - "12 109.570128 3+ -5.5 216.0 \n", - "11 100.869996 3+ 7.5 218.0 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -3.0 216.5 -150 2023 \n", - "1 -7.5 223.5 -306 2023 \n", - "13 -3.0 229.5 -149 2023 \n", - "12 -4.0 218.0 -180 2023 \n", - "11 7.0 225.5 +213 2023 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2023 Regular Season Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "1 -2023 Regular Season Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "13 -2023 Regular Season Harrison Barnes,KZ Okpala,Domantas Sabonis,Kev... \n", - "12 -2023 Regular Season Mikal Bridges,Cameron Johnson,Deandre Ayton,De... \n", - "11 -2023 Regular Season Lauri Markkanen,Kelly Olynyk,Jarred Vanderbilt... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 road_ot2 \\\n", - "0 Philadelphia 29 34 25 29 NaN NaN \n", - "1 LA Lakers 22 30 19 38 NaN NaN \n", - "13 Portland 32 19 33 31 NaN NaN \n", - "12 Dallas 32 30 19 24 NaN NaN \n", - "11 Denver 30 23 27 22 NaN NaN \n", - "\n", - " road_ot3 road_ot4 road_ot5 road_f road_min road_fg road_fga \\\n", - "0 NaN NaN NaN 117 240.0 40 80 \n", - "1 NaN NaN NaN 109 240.0 40 94 \n", - "13 NaN NaN NaN 115 240.0 39 88 \n", - "12 NaN NaN NaN 105 240.0 35 75 \n", - "11 NaN NaN NaN 102 240.0 40 83 \n", - "\n", - " road_3p road_3pa road_ft road_fta road_or road_dr road_tot road_a \\\n", - "0 13 34 24 28 4 27 31 16 \n", - "1 10 40 19 25 9 39 48 23 \n", - "13 11 28 26 33 11 33 44 20 \n", - "12 14 35 21 34 5 35 40 17 \n", - "11 5 22 17 18 10 25 35 21 \n", - "\n", - " road_pf road_st road_to road_to_to road_bl road_pts road_poss \\\n", - "0 25 8 14 14 3 117 98.680535 \n", - "1 18 12 21 22 4 109 114.091809 \n", - "13 17 11 11 11 2 115 101.731855 \n", - "12 22 6 12 12 4 105 95.829038 \n", - "11 23 10 21 21 3 102 101.120258 \n", - "\n", - " road_pace road_oeff road_deff road_team_rest_days road_moneyline \\\n", - "0 98.680535 118.564416 127.684756 3+ +127 \n", - "1 114.091809 95.537095 107.807915 3+ +247 \n", - "13 101.731855 113.042271 106.161438 3+ +124 \n", - "12 95.829038 109.570128 111.657178 3+ +149 \n", - "11 101.120258 100.869996 121.637348 3+ -261 \n", - "\n", - " road_starting_lineup crew_chief \\\n", - "0 Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M... James Capers \n", - "1 Lonnie Walker IV,LeBron James,Anthony Davis,Ru... Tony Brothers \n", - "13 Josh Hart,Jerami Grant,Jusuf Nurkic,Anfernee S... Courtney Kirkland \n", - "12 Reggie Bullock,Dorian Finney-Smith,JaVale McGe... Derek Richardson \n", - "11 Michael Porter Jr.,Aaron Gordon,Nikola Jokic,K... Tony Brothers \n", - "\n", - " referee_umpire day_of_season home_team_game_num \\\n", - "0 Brian Forte,Ray Acosta 1 1 \n", - "1 Rodney Mott,Scott Twardoski 1 1 \n", - "13 Brandon Adair,Justin Van Duyne 2 1 \n", - "12 Eric Lewis,Gediminas Petraitis 2 1 \n", - "11 Kevin Cutler,Lauren Holtkamp 2 1 \n", - "\n", - " road_team_game_num home_eFG% home_TOV% home_ORB% home_FT% road_eFG% \\\n", - "0 1 0.634146 0.104444 0.139535 0.268293 0.581250 \n", - "1 1 0.535354 0.141598 0.229167 0.171717 0.478723 \n", - "13 1 0.558824 0.146306 0.111111 0.152941 0.505682 \n", - "12 1 0.517647 0.112486 0.200000 0.223529 0.560000 \n", - "11 1 0.602410 0.171468 0.211538 0.277108 0.512048 \n", - "\n", - " road_TOV% road_ORB% road_FT% \n", - "0 0.131678 0.093023 0.300000 \n", - "1 0.173228 0.214286 0.202128 \n", - "13 0.096899 0.239130 0.295455 \n", - "12 0.117693 0.166667 0.280000 \n", - "11 0.187634 0.196078 0.204819 " - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_4.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "def add_game_outcome(df):\n", - " \"\"\"\n", - " Add columns indicating the winning and losing teams for each game in an NBA dataset.\n", - "\n", - " This function uses the home and road team points to determine the winner and loser of each game.\n", - " It adds two new columns, 'winner' and 'loser', to the dataset.\n", - "\n", - " Parameters:\n", - " df (pd.DataFrame): A DataFrame containing NBA game statistics, including home and road team points.\n", - "\n", - " Returns:\n", - " pd.DataFrame: The original DataFrame with two new columns 'winner' and 'loser'.\n", - " \"\"\"\n", - "\n", - " # Determine the winning and losing teams based on points\n", - " df[\"winner\"] = df.apply(\n", - " lambda x: x[\"home_team\"] if x[\"home_pts\"] > x[\"road_pts\"] else x[\"road_team\"],\n", - " axis=1,\n", - " )\n", - " df[\"loser\"] = df.apply(\n", - " lambda x: x[\"road_team\"] if x[\"home_pts\"] > x[\"road_pts\"] else x[\"home_team\"],\n", - " axis=1,\n", - " )\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "df_5 = add_game_outcome(df_4)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
home_teamroad_teamhome_ptsroad_ptswinnerloser
0BostonPhiladelphia126117BostonPhiladelphia
1Golden StateLA Lakers123109Golden StateLA Lakers
13SacramentoPortland108115PortlandSacramento
12PhoenixDallas107105PhoenixDallas
11UtahDenver123102UtahDenver
\n", - "
" - ], - "text/plain": [ - " home_team road_team home_pts road_pts winner loser\n", - "0 Boston Philadelphia 126 117 Boston Philadelphia\n", - "1 Golden State LA Lakers 123 109 Golden State LA Lakers\n", - "13 Sacramento Portland 108 115 Portland Sacramento\n", - "12 Phoenix Dallas 107 105 Phoenix Dallas\n", - "11 Utah Denver 123 102 Utah Denver" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_5[[\"home_team\", \"road_team\", \"home_pts\", \"road_pts\", \"winner\", \"loser\"]].head()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "def add_win_loss_info(df):\n", - " \"\"\"\n", - " Add cumulative and last two weeks' statistics for wins, losses,\n", - " and winning percentage for both home and road teams in an NBA dataset.\n", - " \"\"\"\n", - " # Initialization\n", - " wins, losses = {}, {}\n", - "\n", - " # Define the columns to be added\n", - " columns = [\n", - " \"home_wins\",\n", - " \"home_losses\",\n", - " \"home_win_pct\",\n", - " \"road_wins\",\n", - " \"road_losses\",\n", - " \"road_win_pct\",\n", - " \"home_wins_l2w\",\n", - " \"home_losses_l2w\",\n", - " \"home_win_pct_l2w\",\n", - " \"road_wins_l2w\",\n", - " \"road_losses_l2w\",\n", - " \"road_win_pct_l2w\",\n", - " ]\n", - " for col in columns:\n", - " df[col] = 0\n", - "\n", - " # Iterate through the DataFrame\n", - " for index, row in df.iterrows():\n", - " date = pd.to_datetime(row[\"date\"])\n", - " home_team, road_team = row[\"home_team\"], row[\"road_team\"]\n", - " home_win = row[\"winner\"] == home_team\n", - "\n", - " # Update cumulative stats\n", - " df.at[index, \"home_wins\"] = wins.get(home_team, 0)\n", - " df.at[index, \"home_losses\"] = losses.get(home_team, 0)\n", - " df.at[index, \"road_wins\"] = wins.get(road_team, 0)\n", - " df.at[index, \"road_losses\"] = losses.get(road_team, 0)\n", - "\n", - " # Update cumulative winning percentages\n", - " if wins.get(home_team, 0) + losses.get(home_team, 0) > 0:\n", - " df.at[index, \"home_win_pct\"] = wins[home_team] / (\n", - " wins[home_team] + losses[home_team]\n", - " )\n", - " if wins.get(road_team, 0) + losses.get(road_team, 0) > 0:\n", - " df.at[index, \"road_win_pct\"] = wins[road_team] / (\n", - " wins[road_team] + losses[road_team]\n", - " )\n", - "\n", - " # Filter for last two weeks' games\n", - " l2w_start_date = date - timedelta(days=14)\n", - " l2w_games = df[(df[\"date\"] >= l2w_start_date) & (df[\"date\"] < date)]\n", - "\n", - " # Calculate last two weeks' wins and losses\n", - " l2w_home_wins = len(\n", - " l2w_games[\n", - " (l2w_games[\"home_team\"] == home_team)\n", - " & (l2w_games[\"winner\"] == home_team)\n", - " ]\n", - " )\n", - " l2w_home_losses = len(\n", - " l2w_games[\n", - " (l2w_games[\"home_team\"] == home_team)\n", - " & (l2w_games[\"winner\"] != home_team)\n", - " ]\n", - " )\n", - " l2w_road_wins = len(\n", - " l2w_games[\n", - " (l2w_games[\"road_team\"] == road_team)\n", - " & (l2w_games[\"winner\"] == road_team)\n", - " ]\n", - " )\n", - " l2w_road_losses = len(\n", - " l2w_games[\n", - " (l2w_games[\"road_team\"] == road_team)\n", - " & (l2w_games[\"winner\"] != road_team)\n", - " ]\n", - " )\n", - "\n", - " # Update last two weeks' stats in the DataFrame\n", - " df.at[index, \"home_wins_l2w\"] = l2w_home_wins\n", - " df.at[index, \"home_losses_l2w\"] = l2w_home_losses\n", - " df.at[index, \"road_wins_l2w\"] = l2w_road_wins\n", - " df.at[index, \"road_losses_l2w\"] = l2w_road_losses\n", - " df.at[index, \"home_win_pct_l2w\"] = (\n", - " l2w_home_wins / (l2w_home_wins + l2w_home_losses)\n", - " if l2w_home_wins + l2w_home_losses > 0\n", - " else 0\n", - " )\n", - " df.at[index, \"road_win_pct_l2w\"] = (\n", - " l2w_road_wins / (l2w_road_wins + l2w_road_losses)\n", - " if l2w_road_wins + l2w_road_losses > 0\n", - " else 0\n", - " )\n", - "\n", - " # Update cumulative wins and losses for next iteration\n", - " if home_team not in wins:\n", - " wins[home_team] = 0\n", - " if home_team not in losses:\n", - " losses[home_team] = 0\n", - " if road_team not in wins:\n", - " wins[road_team] = 0\n", - " if road_team not in losses:\n", - " losses[road_team] = 0\n", - "\n", - " wins[home_team] += int(home_win)\n", - " losses[home_team] += int(not home_win)\n", - " wins[road_team] += int(not home_win)\n", - " losses[road_team] += int(home_win)\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "df_6 = add_win_loss_info(df_5)" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1...road_fgroad_fgaroad_3proad_3paroad_ftroad_ftaroad_orroad_drroad_totroad_aroad_pfroad_stroad_toroad_to_toroad_blroad_ptsroad_possroad_paceroad_oeffroad_deffroad_team_rest_daysroad_moneylineroad_starting_lineupcrew_chiefreferee_umpireday_of_seasonhome_team_game_numroad_team_game_numhome_eFG%home_TOV%home_ORB%home_FT%road_eFG%road_TOV%road_ORB%road_FT%winnerloserhome_winshome_losseshome_win_pctroad_winsroad_lossesroad_win_pcthome_wins_l2whome_losses_l2whome_win_pct_l2wroad_wins_l2wroad_losses_l2wroad_win_pct_l2w
0NBA 2022-2023 Regular Season222000012022-10-18Boston24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+-4.0213.5-3.0216.5-1502023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...Philadelphia29342529NaN...40801334242842731162581414311798.68053598.680535118.564416127.6847563++127Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M...James CapersBrian Forte,Ray Acosta1110.6341460.1044440.1395350.2682930.5812500.1316780.0930230.300000BostonPhiladelphia000.0000.0000.0000.0
1NBA 2022-2023 Regular Season222000022022-10-18Golden State25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+-6.0227.5-7.5223.5-3062023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...LA Lakers22301938NaN...4094104019259394823181221224109114.091809114.09180995.537095107.8079153++247Lonnie Walker IV,LeBron James,Anthony Davis,Ru...Tony BrothersRodney Mott,Scott Twardoski1110.5353540.1415980.2291670.1717170.4787230.1732280.2142860.202128Golden StateLA Lakers000.0000.0000.0000.0
13NBA 2022-2023 Regular Season222000142022-10-19Sacramento23322924NaNNaNNaNNaNNaN108240.0398517441319437412725815165108101.731855101.731855106.161438113.0422713+-1.5223.5-3.0229.5-1492023-2023 Regular SeasonHarrison Barnes,KZ Okpala,Domantas Sabonis,Kev...Portland32193331NaN...39881128263311334420171111112115101.731855101.731855113.042271106.1614383++124Josh Hart,Jerami Grant,Jusuf Nurkic,Anfernee S...Courtney KirklandBrandon Adair,Justin Van Duyne2110.5588240.1463060.1111110.1529410.5056820.0968990.2391300.295455PortlandSacramento000.0000.0000.0000.0
12NBA 2022-2023 Regular Season222000132022-10-19Phoenix24213131NaNNaNNaNNaNNaN107240.04085822192283240252941212510795.82903895.829038111.657178109.5701283+-5.5216.0-4.0218.0-1802023-2023 Regular SeasonMikal Bridges,Cameron Johnson,Deandre Ayton,De...Dallas32301924NaN...35751435213453540172261212410595.82903895.829038109.570128111.6571783++149Reggie Bullock,Dorian Finney-Smith,JaVale McGe...Derek RichardsonEric Lewis,Gediminas Petraitis2110.5176470.1124860.2000000.2235290.5600000.1176930.1666670.280000PhoenixDallas000.0000.0000.0000.0
11NBA 2022-2023 Regular Season222000122022-10-19Utah37381929NaNNaNNaNNaNNaN123240.042831638233111324327211019201123101.120258101.120258121.637348100.8699963+7.5218.07.0225.5+2132023-2023 Regular SeasonLauri Markkanen,Kelly Olynyk,Jarred Vanderbilt...Denver30232722NaN...4083522171810253521231021213102101.120258101.120258100.869996121.6373483+-261Michael Porter Jr.,Aaron Gordon,Nikola Jokic,K...Tony BrothersKevin Cutler,Lauren Holtkamp2110.6024100.1714680.2115380.2771080.5120480.1876340.1960780.204819UtahDenver000.0000.0000.0000.0
\n", - "

5 rows × 106 columns

\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston 24 \n", - "1 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State 25 \n", - "13 NBA 2022-2023 Regular Season 22200014 2022-10-19 Sacramento 23 \n", - "12 NBA 2022-2023 Regular Season 22200013 2022-10-19 Phoenix 24 \n", - "11 NBA 2022-2023 Regular Season 22200012 2022-10-19 Utah 37 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 39 35 28 NaN NaN NaN NaN \n", - "1 34 32 32 NaN NaN NaN NaN \n", - "13 32 29 24 NaN NaN NaN NaN \n", - "12 21 31 31 NaN NaN NaN NaN \n", - "11 38 19 29 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 126 240.0 46 82 12 35 22 \n", - "1 NaN 123 240.0 45 99 16 45 17 \n", - "13 NaN 108 240.0 39 85 17 44 13 \n", - "12 NaN 107 240.0 40 85 8 22 19 \n", - "11 NaN 123 240.0 42 83 16 38 23 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 28 6 30 36 24 24 8 10 \n", - "1 23 11 37 48 31 23 11 18 \n", - "13 19 4 37 41 27 25 8 15 \n", - "12 22 8 32 40 25 29 4 12 \n", - "11 31 11 32 43 27 21 10 19 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 11 3 126 98.680535 98.680535 127.684756 \n", - "1 18 4 123 114.091809 114.091809 107.807915 \n", - "13 16 5 108 101.731855 101.731855 106.161438 \n", - "12 12 5 107 95.829038 95.829038 111.657178 \n", - "11 20 1 123 101.120258 101.120258 121.637348 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 118.564416 3+ -4.0 213.5 \n", - "1 95.537095 3+ -6.0 227.5 \n", - "13 113.042271 3+ -1.5 223.5 \n", - "12 109.570128 3+ -5.5 216.0 \n", - "11 100.869996 3+ 7.5 218.0 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -3.0 216.5 -150 2023 \n", - "1 -7.5 223.5 -306 2023 \n", - "13 -3.0 229.5 -149 2023 \n", - "12 -4.0 218.0 -180 2023 \n", - "11 7.0 225.5 +213 2023 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2023 Regular Season Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "1 -2023 Regular Season Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "13 -2023 Regular Season Harrison Barnes,KZ Okpala,Domantas Sabonis,Kev... \n", - "12 -2023 Regular Season Mikal Bridges,Cameron Johnson,Deandre Ayton,De... \n", - "11 -2023 Regular Season Lauri Markkanen,Kelly Olynyk,Jarred Vanderbilt... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 ... road_fg \\\n", - "0 Philadelphia 29 34 25 29 NaN ... 40 \n", - "1 LA Lakers 22 30 19 38 NaN ... 40 \n", - "13 Portland 32 19 33 31 NaN ... 39 \n", - "12 Dallas 32 30 19 24 NaN ... 35 \n", - "11 Denver 30 23 27 22 NaN ... 40 \n", - "\n", - " road_fga road_3p road_3pa road_ft road_fta road_or road_dr \\\n", - "0 80 13 34 24 28 4 27 \n", - "1 94 10 40 19 25 9 39 \n", - "13 88 11 28 26 33 11 33 \n", - "12 75 14 35 21 34 5 35 \n", - "11 83 5 22 17 18 10 25 \n", - "\n", - " road_tot road_a road_pf road_st road_to road_to_to road_bl \\\n", - "0 31 16 25 8 14 14 3 \n", - "1 48 23 18 12 21 22 4 \n", - "13 44 20 17 11 11 11 2 \n", - "12 40 17 22 6 12 12 4 \n", - "11 35 21 23 10 21 21 3 \n", - "\n", - " road_pts road_poss road_pace road_oeff road_deff \\\n", - "0 117 98.680535 98.680535 118.564416 127.684756 \n", - "1 109 114.091809 114.091809 95.537095 107.807915 \n", - "13 115 101.731855 101.731855 113.042271 106.161438 \n", - "12 105 95.829038 95.829038 109.570128 111.657178 \n", - "11 102 101.120258 101.120258 100.869996 121.637348 \n", - "\n", - " road_team_rest_days road_moneyline \\\n", - "0 3+ +127 \n", - "1 3+ +247 \n", - "13 3+ +124 \n", - "12 3+ +149 \n", - "11 3+ -261 \n", - "\n", - " road_starting_lineup crew_chief \\\n", - "0 Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M... James Capers \n", - "1 Lonnie Walker IV,LeBron James,Anthony Davis,Ru... Tony Brothers \n", - "13 Josh Hart,Jerami Grant,Jusuf Nurkic,Anfernee S... Courtney Kirkland \n", - "12 Reggie Bullock,Dorian Finney-Smith,JaVale McGe... Derek Richardson \n", - "11 Michael Porter Jr.,Aaron Gordon,Nikola Jokic,K... Tony Brothers \n", - "\n", - " referee_umpire day_of_season home_team_game_num \\\n", - "0 Brian Forte,Ray Acosta 1 1 \n", - "1 Rodney Mott,Scott Twardoski 1 1 \n", - "13 Brandon Adair,Justin Van Duyne 2 1 \n", - "12 Eric Lewis,Gediminas Petraitis 2 1 \n", - "11 Kevin Cutler,Lauren Holtkamp 2 1 \n", - "\n", - " road_team_game_num home_eFG% home_TOV% home_ORB% home_FT% road_eFG% \\\n", - "0 1 0.634146 0.104444 0.139535 0.268293 0.581250 \n", - "1 1 0.535354 0.141598 0.229167 0.171717 0.478723 \n", - "13 1 0.558824 0.146306 0.111111 0.152941 0.505682 \n", - "12 1 0.517647 0.112486 0.200000 0.223529 0.560000 \n", - "11 1 0.602410 0.171468 0.211538 0.277108 0.512048 \n", - "\n", - " road_TOV% road_ORB% road_FT% winner loser home_wins \\\n", - "0 0.131678 0.093023 0.300000 Boston Philadelphia 0 \n", - "1 0.173228 0.214286 0.202128 Golden State LA Lakers 0 \n", - "13 0.096899 0.239130 0.295455 Portland Sacramento 0 \n", - "12 0.117693 0.166667 0.280000 Phoenix Dallas 0 \n", - "11 0.187634 0.196078 0.204819 Utah Denver 0 \n", - "\n", - " home_losses home_win_pct road_wins road_losses road_win_pct \\\n", - "0 0 0.0 0 0 0.0 \n", - "1 0 0.0 0 0 0.0 \n", - "13 0 0.0 0 0 0.0 \n", - "12 0 0.0 0 0 0.0 \n", - "11 0 0.0 0 0 0.0 \n", - "\n", - " home_wins_l2w home_losses_l2w home_win_pct_l2w road_wins_l2w \\\n", - "0 0 0 0.0 0 \n", - "1 0 0 0.0 0 \n", - "13 0 0 0.0 0 \n", - "12 0 0 0.0 0 \n", - "11 0 0 0.0 0 \n", - "\n", - " road_losses_l2w road_win_pct_l2w \n", - "0 0 0.0 \n", - "1 0 0.0 \n", - "13 0 0.0 \n", - "12 0 0.0 \n", - "11 0 0.0 \n", - "\n", - "[5 rows x 106 columns]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_6.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "def calculate_stat_average(df, current_date, team, stat, last_2_weeks=False):\n", - " \"\"\"\n", - " Calculate the average of a given statistic for a team up to a specified date.\n", - " If last_2_weeks is True, the calculation is restricted to the last two weeks.\n", - "\n", - " :param df: DataFrame containing the NBA data.\n", - " :param current_date: The date of the current game.\n", - " :param team: The team for which the statistic is calculated.\n", - " :param stat: The statistic abbreviation (e.g., 'pts' for points).\n", - " :param last_2_weeks: Boolean, if True, calculate the average for the last two weeks.\n", - " :return: Average of the specified statistic.\n", - " \"\"\"\n", - " # Filter for games involving the team before the current date\n", - " relevant_games = df[\n", - " (df[\"date\"] < current_date)\n", - " & ((df[\"home_team\"] == team) | (df[\"road_team\"] == team))\n", - " ]\n", - "\n", - " # Consider only the last 2 weeks if required\n", - " if last_2_weeks:\n", - " two_weeks_ago = current_date - timedelta(days=14)\n", - " relevant_games = relevant_games[relevant_games[\"date\"] >= two_weeks_ago]\n", - "\n", - " # Calculate the total of the statistic\n", - " total_stat = relevant_games.apply(\n", - " lambda row: row[f\"home_{stat}\"]\n", - " if row[\"home_team\"] == team\n", - " else row[f\"road_{stat}\"],\n", - " axis=1,\n", - " ).sum()\n", - "\n", - " # Calculate the average\n", - " num_games = len(relevant_games)\n", - " return total_stat / num_games if num_games != 0 else 0\n", - "\n", - "\n", - "def add_stats_columns(df, stats):\n", - " \"\"\"\n", - " Adds columns for multiple statistics. For each stat, it creates columns for home_avg_stat,\n", - " road_avg_stat, home_avg_stat_l2w, and road_avg_stat_l2w.\n", - "\n", - " :param df: DataFrame containing the NBA data.\n", - " :param stats: List of statistic abbreviations (e.g., ['pts', 'or']).\n", - " :return: DataFrame with added columns for each statistic.\n", - " \"\"\"\n", - "\n", - " def apply_stats(row):\n", - " date = row[\"date\"]\n", - " home_team = row[\"home_team\"]\n", - " road_team = row[\"road_team\"]\n", - "\n", - " for stat in stats:\n", - " # Define new column names\n", - " home_avg_stat = f\"home_avg_{stat}\"\n", - " road_avg_stat = f\"road_avg_{stat}\"\n", - " home_avg_l2w = f\"home_avg_{stat}_l2w\"\n", - " road_avg_l2w = f\"road_avg_{stat}_l2w\"\n", - "\n", - " # Calculate and assign the averages\n", - " row[home_avg_stat] = calculate_stat_average(df, date, home_team, stat)\n", - " row[road_avg_stat] = calculate_stat_average(df, date, road_team, stat)\n", - " row[home_avg_l2w] = calculate_stat_average(\n", - " df, date, home_team, stat, last_2_weeks=True\n", - " )\n", - " row[road_avg_l2w] = calculate_stat_average(\n", - " df, date, road_team, stat, last_2_weeks=True\n", - " )\n", - "\n", - " return row\n", - "\n", - " # Apply the function to each row of the DataFrame\n", - " return df.apply(apply_stats, axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "stat_list = [\n", - " \"1q\",\n", - " \"2q\",\n", - " \"3q\",\n", - " \"4q\",\n", - " \"ot1\",\n", - " \"ot2\",\n", - " \"ot3\",\n", - " \"ot4\",\n", - " \"ot5\",\n", - " \"f\",\n", - " \"min\",\n", - " \"fg\",\n", - " \"fga\",\n", - " \"3p\",\n", - " \"3pa\",\n", - " \"ft\",\n", - " \"fta\",\n", - " \"or\",\n", - " \"dr\",\n", - " \"tot\",\n", - " \"a\",\n", - " \"pf\",\n", - " \"st\",\n", - " \"to\",\n", - " \"to_to\",\n", - " \"bl\",\n", - " \"pts\",\n", - " \"poss\",\n", - " \"pace\",\n", - " \"oeff\",\n", - " \"deff\",\n", - " \"eFG%\",\n", - " \"TOV%\",\n", - " \"ORB%\",\n", - " \"FT%\",\n", - "]\n", - "\n", - "df_7 = add_stats_columns(df_6, stat_list)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1...home_avg_st_l2wroad_avg_st_l2whome_avg_toroad_avg_tohome_avg_to_l2wroad_avg_to_l2whome_avg_to_toroad_avg_to_tohome_avg_to_to_l2wroad_avg_to_to_l2whome_avg_blroad_avg_blhome_avg_bl_l2wroad_avg_bl_l2whome_avg_ptsroad_avg_ptshome_avg_pts_l2wroad_avg_pts_l2whome_avg_possroad_avg_posshome_avg_poss_l2wroad_avg_poss_l2whome_avg_paceroad_avg_pacehome_avg_pace_l2wroad_avg_pace_l2whome_avg_oeffroad_avg_oeffhome_avg_oeff_l2wroad_avg_oeff_l2whome_avg_deffroad_avg_deffhome_avg_deff_l2wroad_avg_deff_l2whome_avg_eFG%road_avg_eFG%home_avg_eFG%_l2wroad_avg_eFG%_l2whome_avg_TOV%road_avg_TOV%home_avg_TOV%_l2wroad_avg_TOV%_l2whome_avg_ORB%road_avg_ORB%home_avg_ORB%_l2wroad_avg_ORB%_l2whome_avg_FT%road_avg_FT%home_avg_FT%_l2wroad_avg_FT%_l2w
0NBA 2022-2023 Regular Season222000012022-10-18Boston24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+-4.0213.5-3.0216.5-1502023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...Philadelphia29342529NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
1NBA 2022-2023 Regular Season222000022022-10-18Golden State25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+-6.0227.5-7.5223.5-3062023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...LA Lakers22301938NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
13NBA 2022-2023 Regular Season222000142022-10-19Sacramento23322924NaNNaNNaNNaNNaN108240.0398517441319437412725815165108101.731855101.731855106.161438113.0422713+-1.5223.5-3.0229.5-1492023-2023 Regular SeasonHarrison Barnes,KZ Okpala,Domantas Sabonis,Kev...Portland32193331NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
12NBA 2022-2023 Regular Season222000132022-10-19Phoenix24213131NaNNaNNaNNaNNaN107240.04085822192283240252941212510795.82903895.829038111.657178109.5701283+-5.5216.0-4.0218.0-1802023-2023 Regular SeasonMikal Bridges,Cameron Johnson,Deandre Ayton,De...Dallas32301924NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
11NBA 2022-2023 Regular Season222000122022-10-19Utah37381929NaNNaNNaNNaNNaN123240.042831638233111324327211019201123101.120258101.120258121.637348100.8699963+7.5218.07.0225.5+2132023-2023 Regular SeasonLauri Markkanen,Kelly Olynyk,Jarred Vanderbilt...Denver30232722NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
\n", - "

5 rows × 246 columns

\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston 24 \n", - "1 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State 25 \n", - "13 NBA 2022-2023 Regular Season 22200014 2022-10-19 Sacramento 23 \n", - "12 NBA 2022-2023 Regular Season 22200013 2022-10-19 Phoenix 24 \n", - "11 NBA 2022-2023 Regular Season 22200012 2022-10-19 Utah 37 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 39 35 28 NaN NaN NaN NaN \n", - "1 34 32 32 NaN NaN NaN NaN \n", - "13 32 29 24 NaN NaN NaN NaN \n", - "12 21 31 31 NaN NaN NaN NaN \n", - "11 38 19 29 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 126 240.0 46 82 12 35 22 \n", - "1 NaN 123 240.0 45 99 16 45 17 \n", - "13 NaN 108 240.0 39 85 17 44 13 \n", - "12 NaN 107 240.0 40 85 8 22 19 \n", - "11 NaN 123 240.0 42 83 16 38 23 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 28 6 30 36 24 24 8 10 \n", - "1 23 11 37 48 31 23 11 18 \n", - "13 19 4 37 41 27 25 8 15 \n", - "12 22 8 32 40 25 29 4 12 \n", - "11 31 11 32 43 27 21 10 19 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 11 3 126 98.680535 98.680535 127.684756 \n", - "1 18 4 123 114.091809 114.091809 107.807915 \n", - "13 16 5 108 101.731855 101.731855 106.161438 \n", - "12 12 5 107 95.829038 95.829038 111.657178 \n", - "11 20 1 123 101.120258 101.120258 121.637348 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 118.564416 3+ -4.0 213.5 \n", - "1 95.537095 3+ -6.0 227.5 \n", - "13 113.042271 3+ -1.5 223.5 \n", - "12 109.570128 3+ -5.5 216.0 \n", - "11 100.869996 3+ 7.5 218.0 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -3.0 216.5 -150 2023 \n", - "1 -7.5 223.5 -306 2023 \n", - "13 -3.0 229.5 -149 2023 \n", - "12 -4.0 218.0 -180 2023 \n", - "11 7.0 225.5 +213 2023 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2023 Regular Season Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "1 -2023 Regular Season Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "13 -2023 Regular Season Harrison Barnes,KZ Okpala,Domantas Sabonis,Kev... \n", - "12 -2023 Regular Season Mikal Bridges,Cameron Johnson,Deandre Ayton,De... \n", - "11 -2023 Regular Season Lauri Markkanen,Kelly Olynyk,Jarred Vanderbilt... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 ... \\\n", - "0 Philadelphia 29 34 25 29 NaN ... \n", - "1 LA Lakers 22 30 19 38 NaN ... \n", - "13 Portland 32 19 33 31 NaN ... \n", - "12 Dallas 32 30 19 24 NaN ... \n", - "11 Denver 30 23 27 22 NaN ... \n", - "\n", - " home_avg_st_l2w road_avg_st_l2w home_avg_to road_avg_to \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_to_l2w road_avg_to_l2w home_avg_to_to road_avg_to_to \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_to_to_l2w road_avg_to_to_l2w home_avg_bl road_avg_bl \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_bl_l2w road_avg_bl_l2w home_avg_pts road_avg_pts \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pts_l2w road_avg_pts_l2w home_avg_poss road_avg_poss \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_poss_l2w road_avg_poss_l2w home_avg_pace road_avg_pace \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pace_l2w road_avg_pace_l2w home_avg_oeff road_avg_oeff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_oeff_l2w road_avg_oeff_l2w home_avg_deff road_avg_deff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_deff_l2w road_avg_deff_l2w home_avg_eFG% road_avg_eFG% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_eFG%_l2w road_avg_eFG%_l2w home_avg_TOV% road_avg_TOV% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_TOV%_l2w road_avg_TOV%_l2w home_avg_ORB% road_avg_ORB% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ORB%_l2w road_avg_ORB%_l2w home_avg_FT% road_avg_FT% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_FT%_l2w road_avg_FT%_l2w \n", - "0 0.0 0.0 \n", - "1 0.0 0.0 \n", - "13 0.0 0.0 \n", - "12 0.0 0.0 \n", - "11 0.0 0.0 \n", - "\n", - "[5 rows x 246 columns]" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_7.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [], - "source": [ - "def calculate_pts_allowed_average(df, current_date, team, last_2_weeks=False):\n", - " \"\"\"\n", - " Calculate the average points allowed by a team up to a specified date.\n", - " Points allowed are the points scored by the opposing team.\n", - "\n", - " :param df: DataFrame containing the NBA data.\n", - " :param current_date: The date of the current game.\n", - " :param team: The team for which the points allowed is calculated.\n", - " :param last_2_weeks: Boolean, if True, calculate the average for the last two weeks.\n", - " :return: Average points allowed.\n", - " \"\"\"\n", - " # Filter for games involving the team before the current date\n", - " relevant_games = df[\n", - " (df[\"date\"] < current_date)\n", - " & ((df[\"home_team\"] == team) | (df[\"road_team\"] == team))\n", - " ]\n", - "\n", - " # Consider only the last 2 weeks if required\n", - " if last_2_weeks:\n", - " two_weeks_ago = current_date - timedelta(days=14)\n", - " relevant_games = relevant_games[relevant_games[\"date\"] >= two_weeks_ago]\n", - "\n", - " # Calculate the total points allowed by the team\n", - " total_points_allowed = relevant_games.apply(\n", - " lambda row: row[\"road_pts\"] if row[\"home_team\"] == team else row[\"home_pts\"],\n", - " axis=1,\n", - " ).sum()\n", - "\n", - " # Calculate the average points allowed\n", - " num_games = len(relevant_games)\n", - " return total_points_allowed / num_games if num_games != 0 else 0\n", - "\n", - "\n", - "def add_pts_allowed_columns(df):\n", - " \"\"\"\n", - " Adds columns for average points allowed for home and road teams, both overall and for the last two weeks.\n", - "\n", - " :param df: DataFrame containing the NBA data.\n", - " :return: DataFrame with added columns for points allowed.\n", - " \"\"\"\n", - "\n", - " def apply_pts_allowed(row):\n", - " date = row[\"date\"]\n", - " home_team = row[\"home_team\"]\n", - " road_team = row[\"road_team\"]\n", - "\n", - " # Define new column names\n", - " home_avg_pts_allowed = \"home_avg_pts_allowed\"\n", - " road_avg_pts_allowed = \"road_avg_pts_allowed\"\n", - " home_avg_pts_allowed_l2w = \"home_avg_pts_allowed_l2w\"\n", - " road_avg_pts_allowed_l2w = \"road_avg_pts_allowed_l2w\"\n", - "\n", - " # Calculate and assign the averages for points allowed\n", - " row[home_avg_pts_allowed] = calculate_pts_allowed_average(df, date, home_team)\n", - " row[road_avg_pts_allowed] = calculate_pts_allowed_average(df, date, road_team)\n", - " row[home_avg_pts_allowed_l2w] = calculate_pts_allowed_average(\n", - " df, date, home_team, last_2_weeks=True\n", - " )\n", - " row[road_avg_pts_allowed_l2w] = calculate_pts_allowed_average(\n", - " df, date, road_team, last_2_weeks=True\n", - " )\n", - "\n", - " return row\n", - "\n", - " # Apply the function to each row of the DataFrame\n", - " return df.apply(apply_pts_allowed, axis=1)" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "df_8 = add_pts_allowed_columns(df_7)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1...home_avg_to_l2wroad_avg_to_l2whome_avg_to_toroad_avg_to_tohome_avg_to_to_l2wroad_avg_to_to_l2whome_avg_blroad_avg_blhome_avg_bl_l2wroad_avg_bl_l2whome_avg_ptsroad_avg_ptshome_avg_pts_l2wroad_avg_pts_l2whome_avg_possroad_avg_posshome_avg_poss_l2wroad_avg_poss_l2whome_avg_paceroad_avg_pacehome_avg_pace_l2wroad_avg_pace_l2whome_avg_oeffroad_avg_oeffhome_avg_oeff_l2wroad_avg_oeff_l2whome_avg_deffroad_avg_deffhome_avg_deff_l2wroad_avg_deff_l2whome_avg_eFG%road_avg_eFG%home_avg_eFG%_l2wroad_avg_eFG%_l2whome_avg_TOV%road_avg_TOV%home_avg_TOV%_l2wroad_avg_TOV%_l2whome_avg_ORB%road_avg_ORB%home_avg_ORB%_l2wroad_avg_ORB%_l2whome_avg_FT%road_avg_FT%home_avg_FT%_l2wroad_avg_FT%_l2whome_avg_pts_allowedroad_avg_pts_allowedhome_avg_pts_allowed_l2wroad_avg_pts_allowed_l2w
0NBA 2022-2023 Regular Season222000012022-10-18Boston24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+-4.0213.5-3.0216.5-1502023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...Philadelphia29342529NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
1NBA 2022-2023 Regular Season222000022022-10-18Golden State25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+-6.0227.5-7.5223.5-3062023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...LA Lakers22301938NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
13NBA 2022-2023 Regular Season222000142022-10-19Sacramento23322924NaNNaNNaNNaNNaN108240.0398517441319437412725815165108101.731855101.731855106.161438113.0422713+-1.5223.5-3.0229.5-1492023-2023 Regular SeasonHarrison Barnes,KZ Okpala,Domantas Sabonis,Kev...Portland32193331NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
12NBA 2022-2023 Regular Season222000132022-10-19Phoenix24213131NaNNaNNaNNaNNaN107240.04085822192283240252941212510795.82903895.829038111.657178109.5701283+-5.5216.0-4.0218.0-1802023-2023 Regular SeasonMikal Bridges,Cameron Johnson,Deandre Ayton,De...Dallas32301924NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
11NBA 2022-2023 Regular Season222000122022-10-19Utah37381929NaNNaNNaNNaNNaN123240.042831638233111324327211019201123101.120258101.120258121.637348100.8699963+7.5218.07.0225.5+2132023-2023 Regular SeasonLauri Markkanen,Kelly Olynyk,Jarred Vanderbilt...Denver30232722NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0
\n", - "

5 rows × 250 columns

\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston 24 \n", - "1 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State 25 \n", - "13 NBA 2022-2023 Regular Season 22200014 2022-10-19 Sacramento 23 \n", - "12 NBA 2022-2023 Regular Season 22200013 2022-10-19 Phoenix 24 \n", - "11 NBA 2022-2023 Regular Season 22200012 2022-10-19 Utah 37 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 39 35 28 NaN NaN NaN NaN \n", - "1 34 32 32 NaN NaN NaN NaN \n", - "13 32 29 24 NaN NaN NaN NaN \n", - "12 21 31 31 NaN NaN NaN NaN \n", - "11 38 19 29 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 126 240.0 46 82 12 35 22 \n", - "1 NaN 123 240.0 45 99 16 45 17 \n", - "13 NaN 108 240.0 39 85 17 44 13 \n", - "12 NaN 107 240.0 40 85 8 22 19 \n", - "11 NaN 123 240.0 42 83 16 38 23 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 28 6 30 36 24 24 8 10 \n", - "1 23 11 37 48 31 23 11 18 \n", - "13 19 4 37 41 27 25 8 15 \n", - "12 22 8 32 40 25 29 4 12 \n", - "11 31 11 32 43 27 21 10 19 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 11 3 126 98.680535 98.680535 127.684756 \n", - "1 18 4 123 114.091809 114.091809 107.807915 \n", - "13 16 5 108 101.731855 101.731855 106.161438 \n", - "12 12 5 107 95.829038 95.829038 111.657178 \n", - "11 20 1 123 101.120258 101.120258 121.637348 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 118.564416 3+ -4.0 213.5 \n", - "1 95.537095 3+ -6.0 227.5 \n", - "13 113.042271 3+ -1.5 223.5 \n", - "12 109.570128 3+ -5.5 216.0 \n", - "11 100.869996 3+ 7.5 218.0 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -3.0 216.5 -150 2023 \n", - "1 -7.5 223.5 -306 2023 \n", - "13 -3.0 229.5 -149 2023 \n", - "12 -4.0 218.0 -180 2023 \n", - "11 7.0 225.5 +213 2023 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2023 Regular Season Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "1 -2023 Regular Season Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "13 -2023 Regular Season Harrison Barnes,KZ Okpala,Domantas Sabonis,Kev... \n", - "12 -2023 Regular Season Mikal Bridges,Cameron Johnson,Deandre Ayton,De... \n", - "11 -2023 Regular Season Lauri Markkanen,Kelly Olynyk,Jarred Vanderbilt... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 ... \\\n", - "0 Philadelphia 29 34 25 29 NaN ... \n", - "1 LA Lakers 22 30 19 38 NaN ... \n", - "13 Portland 32 19 33 31 NaN ... \n", - "12 Dallas 32 30 19 24 NaN ... \n", - "11 Denver 30 23 27 22 NaN ... \n", - "\n", - " home_avg_to_l2w road_avg_to_l2w home_avg_to_to road_avg_to_to \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_to_to_l2w road_avg_to_to_l2w home_avg_bl road_avg_bl \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_bl_l2w road_avg_bl_l2w home_avg_pts road_avg_pts \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pts_l2w road_avg_pts_l2w home_avg_poss road_avg_poss \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_poss_l2w road_avg_poss_l2w home_avg_pace road_avg_pace \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pace_l2w road_avg_pace_l2w home_avg_oeff road_avg_oeff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_oeff_l2w road_avg_oeff_l2w home_avg_deff road_avg_deff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_deff_l2w road_avg_deff_l2w home_avg_eFG% road_avg_eFG% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_eFG%_l2w road_avg_eFG%_l2w home_avg_TOV% road_avg_TOV% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_TOV%_l2w road_avg_TOV%_l2w home_avg_ORB% road_avg_ORB% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ORB%_l2w road_avg_ORB%_l2w home_avg_FT% road_avg_FT% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_FT%_l2w road_avg_FT%_l2w home_avg_pts_allowed \\\n", - "0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 \n", - "\n", - " road_avg_pts_allowed home_avg_pts_allowed_l2w road_avg_pts_allowed_l2w \n", - "0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 \n", - "\n", - "[5 rows x 250 columns]" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_8.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "def add_targets(df):\n", - " \"\"\"\n", - " Add various target columns to an NBA dataset for betting and game outcome analysis.\n", - "\n", - " This function calculates several targets based on game statistics and betting lines.\n", - "\n", - " Parameters:\n", - " df (pd.DataFrame): DataFrame containing NBA game statistics, including points, spreads, and totals.\n", - "\n", - " Returns:\n", - " pd.DataFrame: The original DataFrame with added target columns for analysis.\n", - "\n", - " The function adds the following columns:\n", - " - REG_TARGET: Point differential of the game (home points minus road points).\n", - " - CLS_TARGET: Boolean indicating if the home team beat the opening spread.\n", - " - CLS_TARGET_closing_spread: Boolean indicating if the home team beat the closing spread.\n", - " - REG_TARGET_OU: Total points scored in the game (home points plus road points).\n", - " - CLS_TARGET_OU_OPEN: Boolean indicating if the total points exceeded the opening total.\n", - " - CLS_TARGET_OU_CLOSE: Boolean indicating if the total points exceeded the closing total.\n", - " \"\"\"\n", - "\n", - " # REG_TARGET: Point differential (home points - road points)\n", - " # It represents the margin of victory or defeat for the home team.\n", - " df[\"REG_TARGET\"] = df[\"home_pts\"] - df[\"road_pts\"]\n", - "\n", - " # CLS_TARGET: Boolean indicating if the home team covered the opening spread.\n", - " # True if home team's win margin is greater than the negative of the opening spread.\n", - " # It's used to determine if the home team performed better than the pre-game expectations.\n", - " df[\"CLS_TARGET\"] = df[\"REG_TARGET\"] > -df[\"home_opening_spread\"]\n", - "\n", - " # CLS_TARGET_closing_spread: Similar to CLS_TARGET but using the closing spread.\n", - " # It reflects the home team's performance against the final betting line before the game.\n", - " df[\"CLS_TARGET_closing_spread\"] = df[\"REG_TARGET\"] > -df[\"home_closing_spread\"]\n", - "\n", - " # REG_TARGET_OU: Sum of home and road points, indicating total points scored in the game.\n", - " df[\"REG_TARGET_OU\"] = df[\"home_pts\"] + df[\"road_pts\"]\n", - "\n", - " # CLS_TARGET_OU_OPEN: Boolean indicating if total points scored exceeded the opening total line.\n", - " # It shows whether the game was higher-scoring than initially expected by bookmakers.\n", - " df[\"CLS_TARGET_OU_OPEN\"] = df[\"REG_TARGET_OU\"] > df[\"opening_total\"]\n", - "\n", - " # CLS_TARGET_OU_CLOSE: Similar to CLS_TARGET_OU_OPEN but with the closing total line.\n", - " # It reflects whether the game's total score surpassed the final total points line set before the game.\n", - " df[\"CLS_TARGET_OU_CLOSE\"] = df[\"REG_TARGET_OU\"] > df[\"closing_total\"]\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10903/1635828921.py:24: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"REG_TARGET\"] = df[\"home_pts\"] - df[\"road_pts\"]\n", - "/tmp/ipykernel_10903/1635828921.py:29: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"CLS_TARGET\"] = df[\"REG_TARGET\"] > -df[\"home_opening_spread\"]\n", - "/tmp/ipykernel_10903/1635828921.py:33: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"CLS_TARGET_closing_spread\"] = df[\"REG_TARGET\"] > -df[\"home_closing_spread\"]\n", - "/tmp/ipykernel_10903/1635828921.py:36: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"REG_TARGET_OU\"] = df[\"home_pts\"] + df[\"road_pts\"]\n", - "/tmp/ipykernel_10903/1635828921.py:40: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"CLS_TARGET_OU_OPEN\"] = df[\"REG_TARGET_OU\"] > df[\"opening_total\"]\n", - "/tmp/ipykernel_10903/1635828921.py:44: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"CLS_TARGET_OU_CLOSE\"] = df[\"REG_TARGET_OU\"] > df[\"closing_total\"]\n" - ] - } - ], - "source": [ - "df_9 = add_targets(df_8)" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1...home_avg_blroad_avg_blhome_avg_bl_l2wroad_avg_bl_l2whome_avg_ptsroad_avg_ptshome_avg_pts_l2wroad_avg_pts_l2whome_avg_possroad_avg_posshome_avg_poss_l2wroad_avg_poss_l2whome_avg_paceroad_avg_pacehome_avg_pace_l2wroad_avg_pace_l2whome_avg_oeffroad_avg_oeffhome_avg_oeff_l2wroad_avg_oeff_l2whome_avg_deffroad_avg_deffhome_avg_deff_l2wroad_avg_deff_l2whome_avg_eFG%road_avg_eFG%home_avg_eFG%_l2wroad_avg_eFG%_l2whome_avg_TOV%road_avg_TOV%home_avg_TOV%_l2wroad_avg_TOV%_l2whome_avg_ORB%road_avg_ORB%home_avg_ORB%_l2wroad_avg_ORB%_l2whome_avg_FT%road_avg_FT%home_avg_FT%_l2wroad_avg_FT%_l2whome_avg_pts_allowedroad_avg_pts_allowedhome_avg_pts_allowed_l2wroad_avg_pts_allowed_l2wREG_TARGETCLS_TARGETCLS_TARGET_closing_spreadREG_TARGET_OUCLS_TARGET_OU_OPENCLS_TARGET_OU_CLOSE
0NBA 2022-2023 Regular Season222000012022-10-18Boston24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+-4.0213.5-3.0216.5-1502023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...Philadelphia29342529NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.09TrueTrue243TrueTrue
1NBA 2022-2023 Regular Season222000022022-10-18Golden State25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+-6.0227.5-7.5223.5-3062023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...LA Lakers22301938NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.014TrueTrue232TrueTrue
13NBA 2022-2023 Regular Season222000142022-10-19Sacramento23322924NaNNaNNaNNaNNaN108240.0398517441319437412725815165108101.731855101.731855106.161438113.0422713+-1.5223.5-3.0229.5-1492023-2023 Regular SeasonHarrison Barnes,KZ Okpala,Domantas Sabonis,Kev...Portland32193331NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0-7FalseFalse223FalseFalse
12NBA 2022-2023 Regular Season222000132022-10-19Phoenix24213131NaNNaNNaNNaNNaN107240.04085822192283240252941212510795.82903895.829038111.657178109.5701283+-5.5216.0-4.0218.0-1802023-2023 Regular SeasonMikal Bridges,Cameron Johnson,Deandre Ayton,De...Dallas32301924NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.02FalseFalse212FalseFalse
11NBA 2022-2023 Regular Season222000122022-10-19Utah37381929NaNNaNNaNNaNNaN123240.042831638233111324327211019201123101.120258101.120258121.637348100.8699963+7.5218.07.0225.5+2132023-2023 Regular SeasonLauri Markkanen,Kelly Olynyk,Jarred Vanderbilt...Denver30232722NaN...0.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.021TrueTrue225TrueFalse
\n", - "

5 rows × 256 columns

\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston 24 \n", - "1 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State 25 \n", - "13 NBA 2022-2023 Regular Season 22200014 2022-10-19 Sacramento 23 \n", - "12 NBA 2022-2023 Regular Season 22200013 2022-10-19 Phoenix 24 \n", - "11 NBA 2022-2023 Regular Season 22200012 2022-10-19 Utah 37 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 39 35 28 NaN NaN NaN NaN \n", - "1 34 32 32 NaN NaN NaN NaN \n", - "13 32 29 24 NaN NaN NaN NaN \n", - "12 21 31 31 NaN NaN NaN NaN \n", - "11 38 19 29 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 126 240.0 46 82 12 35 22 \n", - "1 NaN 123 240.0 45 99 16 45 17 \n", - "13 NaN 108 240.0 39 85 17 44 13 \n", - "12 NaN 107 240.0 40 85 8 22 19 \n", - "11 NaN 123 240.0 42 83 16 38 23 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 28 6 30 36 24 24 8 10 \n", - "1 23 11 37 48 31 23 11 18 \n", - "13 19 4 37 41 27 25 8 15 \n", - "12 22 8 32 40 25 29 4 12 \n", - "11 31 11 32 43 27 21 10 19 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 11 3 126 98.680535 98.680535 127.684756 \n", - "1 18 4 123 114.091809 114.091809 107.807915 \n", - "13 16 5 108 101.731855 101.731855 106.161438 \n", - "12 12 5 107 95.829038 95.829038 111.657178 \n", - "11 20 1 123 101.120258 101.120258 121.637348 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 118.564416 3+ -4.0 213.5 \n", - "1 95.537095 3+ -6.0 227.5 \n", - "13 113.042271 3+ -1.5 223.5 \n", - "12 109.570128 3+ -5.5 216.0 \n", - "11 100.869996 3+ 7.5 218.0 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -3.0 216.5 -150 2023 \n", - "1 -7.5 223.5 -306 2023 \n", - "13 -3.0 229.5 -149 2023 \n", - "12 -4.0 218.0 -180 2023 \n", - "11 7.0 225.5 +213 2023 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2023 Regular Season Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "1 -2023 Regular Season Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "13 -2023 Regular Season Harrison Barnes,KZ Okpala,Domantas Sabonis,Kev... \n", - "12 -2023 Regular Season Mikal Bridges,Cameron Johnson,Deandre Ayton,De... \n", - "11 -2023 Regular Season Lauri Markkanen,Kelly Olynyk,Jarred Vanderbilt... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 ... \\\n", - "0 Philadelphia 29 34 25 29 NaN ... \n", - "1 LA Lakers 22 30 19 38 NaN ... \n", - "13 Portland 32 19 33 31 NaN ... \n", - "12 Dallas 32 30 19 24 NaN ... \n", - "11 Denver 30 23 27 22 NaN ... \n", - "\n", - " home_avg_bl road_avg_bl home_avg_bl_l2w road_avg_bl_l2w home_avg_pts \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_pts home_avg_pts_l2w road_avg_pts_l2w home_avg_poss \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_poss home_avg_poss_l2w road_avg_poss_l2w home_avg_pace \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_pace home_avg_pace_l2w road_avg_pace_l2w home_avg_oeff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_oeff home_avg_oeff_l2w road_avg_oeff_l2w home_avg_deff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_deff home_avg_deff_l2w road_avg_deff_l2w home_avg_eFG% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_eFG% home_avg_eFG%_l2w road_avg_eFG%_l2w home_avg_TOV% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_TOV% home_avg_TOV%_l2w road_avg_TOV%_l2w home_avg_ORB% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_ORB% home_avg_ORB%_l2w road_avg_ORB%_l2w home_avg_FT% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_FT% home_avg_FT%_l2w road_avg_FT%_l2w home_avg_pts_allowed \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_pts_allowed home_avg_pts_allowed_l2w road_avg_pts_allowed_l2w \\\n", - "0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 \n", - "13 0.0 0.0 0.0 \n", - "12 0.0 0.0 0.0 \n", - "11 0.0 0.0 0.0 \n", - "\n", - " REG_TARGET CLS_TARGET CLS_TARGET_closing_spread REG_TARGET_OU \\\n", - "0 9 True True 243 \n", - "1 14 True True 232 \n", - "13 -7 False False 223 \n", - "12 2 False False 212 \n", - "11 21 True True 225 \n", - "\n", - " CLS_TARGET_OU_OPEN CLS_TARGET_OU_CLOSE \n", - "0 True True \n", - "1 True True \n", - "13 False False \n", - "12 False False \n", - "11 True False \n", - "\n", - "[5 rows x 256 columns]" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_9.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [], - "source": [ - "def clean_odds_columns(df):\n", - " \"\"\"\n", - " Cleans and converts betting odds columns in the dataframe to numeric format.\n", - "\n", - " The function handles specific cases in the odds columns:\n", - " - Converts variations of 'even' or 'pk' (regardless of capitalization) to 0 in\n", - " 'home_opening_spread' and 'home_closing_spread' columns.\n", - " - Converts values like '+XXX' or '-XXX' to numeric in 'home_moneyline' and 'road_moneyline' columns,\n", - " also handling variations of 'even'.\n", - "\n", - " Parameters:\n", - " df (DataFrame): The input dataframe with the odds columns.\n", - "\n", - " Returns:\n", - " DataFrame: The dataframe with cleaned and numeric odds columns.\n", - " \"\"\"\n", - " # Handling 'home_opening_spread' and 'home_closing_spread'\n", - " # Replace variations of 'even' and 'pk' with 0, accounting for different capitalizations\n", - " df[\"home_opening_spread\"] = (\n", - " df[\"home_opening_spread\"]\n", - " .replace([\"even\", \"pk\", \"Even\", \"EVEN\", \"Pk\", \"PK\"], 0)\n", - " .astype(float)\n", - " )\n", - " df[\"home_closing_spread\"] = (\n", - " df[\"home_closing_spread\"]\n", - " .replace([\"even\", \"pk\", \"Even\", \"EVEN\", \"Pk\", \"PK\"], 0)\n", - " .astype(float)\n", - " )\n", - "\n", - " # Function to convert moneyline values to numeric\n", - " def moneyline_to_numeric(value):\n", - " if str(value).lower() in [\"even\", \"pk\"]:\n", - " return 0\n", - " elif isinstance(value, str) and (\n", - " value.startswith(\"+\") or value.startswith(\"-\")\n", - " ):\n", - " return int(value)\n", - " else:\n", - " return pd.to_numeric(value, errors=\"coerce\")\n", - "\n", - " # Apply the conversion function to 'home_moneyline' and 'road_moneyline'\n", - " df[\"home_moneyline\"] = df[\"home_moneyline\"].apply(moneyline_to_numeric)\n", - " df[\"road_moneyline\"] = df[\"road_moneyline\"].apply(moneyline_to_numeric)\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [], - "source": [ - "df_10 = clean_odds_columns(df_9)" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [], - "source": [ - "def encode_rest_days(df):\n", - " \"\"\"\n", - " Encode 'home_team_rest_days' and 'road_team_rest_days' in the DataFrame with ordinal values.\n", - "\n", - " This function maps the rest day categories to ordinal numbers based on the amount of rest.\n", - " '3+' indicating the most rest is mapped to the highest ordinal number, and '4IN5-B2B'\n", - " indicating the least rest is mapped to the lowest ordinal number.\n", - "\n", - " Parameters:\n", - " df (DataFrame): DataFrame containing 'home_team_rest_days' and 'road_team_rest_days' columns.\n", - "\n", - " Returns:\n", - " DataFrame: Modified DataFrame with encoded rest day columns.\n", - " \"\"\"\n", - " # Mapping from rest days categories to ordinal values\n", - " rest_days_mapping = {\n", - " \"3+\": 7, # Most Rest\n", - " 2: 6, # 2nd Most Rest\n", - " 1: 5, # 3rd Most Rest\n", - " \"3IN4\": 4, # 4th Most Rest\n", - " \"B2B\": 3, # 5th Most Rest\n", - " \"3IN4-B2B\": 2, # 6th Most Rest\n", - " \"4IN5-B2B\": 1, # 7th Most Rest\n", - " }\n", - "\n", - " # Apply the mapping to the DataFrame\n", - " df[\"home_team_rest\"] = df[\"home_team_rest_days\"].map(rest_days_mapping)\n", - " df[\"road_team_rest\"] = df[\"road_team_rest_days\"].map(rest_days_mapping)\n", - "\n", - " return df" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10903/3574651894.py:27: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"home_team_rest\"] = df[\"home_team_rest_days\"].map(rest_days_mapping)\n", - "/tmp/ipykernel_10903/3574651894.py:28: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"road_team_rest\"] = df[\"road_team_rest_days\"].map(rest_days_mapping)\n" - ] - } - ], - "source": [ - "df_11 = encode_rest_days(df_10)" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [], - "source": [ - "def encode_lineups_to_vectors(df, existing_mapping=None):\n", - " \"\"\"\n", - " Convert 'home_starting_lineup' and 'road_starting_lineup' in a DataFrame to 5-hot encoded vectors with an\n", - " additional flag for unknown players, using an existing mapping if provided.\n", - "\n", - " Parameters:\n", - " df (DataFrame): DataFrame containing 'home_starting_lineup' and 'road_starting_lineup' columns.\n", - " existing_mapping (dict, optional): Mapping from player names to indices. If None, a new mapping is created.\n", - "\n", - " Returns:\n", - " DataFrame, dict: Modified DataFrame with two new columns 'home_lineup_vector' and 'road_lineup_vector' containing\n", - " the 5-hot encoded vectors, and the mapping used for the encoding.\n", - " \"\"\"\n", - " if existing_mapping is None:\n", - " # Extract all unique player names from the lineups\n", - " all_lineups = (\n", - " df[\"home_starting_lineup\"].tolist() + df[\"road_starting_lineup\"].tolist()\n", - " )\n", - " all_players = set(\n", - " player for lineup in all_lineups for player in lineup.split(\",\")\n", - " )\n", - " all_players.add(\"unknown\") # Add 'unknown' player\n", - "\n", - " # Create a mapping from player names to indices\n", - " player_to_index = {player: i for i, player in enumerate(sorted(all_players))}\n", - " else:\n", - " player_to_index = existing_mapping\n", - "\n", - " def lineup_to_vector(lineup):\n", - " vector = np.zeros(len(player_to_index))\n", - " unknown = True\n", - "\n", - " for player in lineup.split(\",\"):\n", - " if player in player_to_index:\n", - " vector[player_to_index[player]] = 1\n", - " unknown = False\n", - "\n", - " if unknown:\n", - " vector[player_to_index[\"unknown\"]] = 1\n", - "\n", - " return vector\n", - "\n", - " df[\"home_lineup_vector\"] = df[\"home_starting_lineup\"].apply(lineup_to_vector)\n", - " df[\"road_lineup_vector\"] = df[\"road_starting_lineup\"].apply(lineup_to_vector)\n", - "\n", - " return df, player_to_index" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [], - "source": [ - "# Load the mapping from the file\n", - "with open(\"mapping.json\", \"r\") as file:\n", - " mapping = json.load(file)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_10903/3893493708.py:43: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"home_lineup_vector\"] = df[\"home_starting_lineup\"].apply(lineup_to_vector)\n", - "/tmp/ipykernel_10903/3893493708.py:44: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", - " df[\"road_lineup_vector\"] = df[\"road_starting_lineup\"].apply(lineup_to_vector)\n" - ] - } - ], - "source": [ - "# Usage during original training\n", - "# df_12, mapping = encode_lineups_to_vectors(df_11)\n", - "\n", - "# Usage during testing (using the saved mapping)\n", - "df_12, _ = encode_lineups_to_vectors(df_11, existing_mapping=mapping)" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": {}, - "outputs": [], - "source": [ - "# Convert the mapping to a JSON string and save it to a file\n", - "# with open('mapping.json', 'w') as file:\n", - "# json.dump(mapping, file)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_12.to_csv(\"../data/nba_ai/cleaned_data_2022-2023.csv\", index=False)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "nba_venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/baseline/mapping.json b/notebooks/baseline/mapping.json deleted file mode 100644 index 15897c1..0000000 --- a/notebooks/baseline/mapping.json +++ /dev/null @@ -1 +0,0 @@ -{"Aaron Gordon": 0, "Aaron Holiday": 1, "Aaron Nesmith": 2, "Aaron Wiggins": 3, "Admiral Schofield": 4, "Al Horford": 5, "Alec Burks": 6, "Aleksej Pokusevski": 7, "Alex Caruso": 8, "Alex Len": 9, "Alfonzo McKinnie": 10, "Alperen Sengun": 11, "Amir Coffey": 12, "Andre Drummond": 13, "Andrew Wiggins": 14, "Anfernee Simons": 15, "Anthony Davis": 16, "Anthony Edwards": 17, "Armoni Brooks": 18, "Austin Reaves": 19, "Austin Rivers": 20, "Avery Bradley": 21, "Ayo Dosunmu": 22, "Bam Adebayo": 23, "Ben McLemore": 24, "Bismack Biyombo": 25, "Blake Griffin": 26, "Bobby Portis": 27, "Bogdan Bogdanovic": 28, "Bojan Bogdanovic": 29, "Bones Hyland": 30, "Brad Wanamaker": 31, "Bradley Beal": 32, "Brandon Clarke": 33, "Brandon Goodwin": 34, "Brandon Ingram": 35, "Brandon Williams": 36, "Brook Lopez": 37, "Bruce Brown": 38, "Bryn Forbes": 39, "Buddy Hield": 40, "CJ Elleby": 41, "CJ McCollum": 42, "Cade Cunningham": 43, "Caleb Martin": 44, "Cam Reddish": 45, "Cam Thomas": 46, "Cameron Johnson": 47, "Cameron Payne": 48, "Caris LeVert": 49, "Carmelo Anthony": 50, "Cassius Stanley": 51, "Cedi Osman": 52, "Charlie Brown Jr.": 53, "Chaundee Brown Jr.": 54, "Chimezie Metu": 55, "Chris Boucher": 56, "Chris Chiozza": 57, "Chris Duarte": 58, "Chris Paul": 59, "Christian Wood": 60, "Chuma Okeke": 61, "Clint Capela": 62, "Coby White": 63, "Cody Martin": 64, "Cole Anthony": 65, "Collin Sexton": 66, "Corey Kispert": 67, "Cory Joseph": 68, "D'Angelo Russell": 69, "D.J. Augustin": 70, "D.J. Wilson": 71, "Dalano Banton": 72, "Damian Jones": 73, "Damian Lillard": 74, "Damion Lee": 75, "Daniel Gafford": 76, "Daniel Theis": 77, "Danilo Gallinari": 78, "Danny Green": 79, "Danuel House Jr.": 80, "Darius Bazley": 81, "Darius Garland": 82, "David Duke Jr.": 83, "David Nwaba": 84, "Davion Mitchell": 85, "Davon Reed": 86, "Day'Ron Sharpe": 87, "De'Aaron Fox": 88, "De'Andre Hunter": 89, "De'Anthony Melton": 90, "DeAndre Jordan": 91, "DeAndre' Bembry": 92, "DeMar DeRozan": 93, "DeMarcus Cousins": 94, "Dean Wade": 95, "Deandre Ayton": 96, "Dejounte Murray": 97, "Delon Wright": 98, "Deni Avdija": 99, "Dennis Schroder": 100, "Dennis Smith Jr.": 101, "Derrick Favors": 102, "Derrick Jones Jr.": 103, "Derrick Rose": 104, "Derrick Walton Jr.": 105, "Derrick White": 106, "Desmond Bane": 107, "Devin Booker": 108, "Devin Vassell": 109, "Devonte' Graham": 110, "Dewayne Dedmon": 111, "Didi Louzada": 112, "Dillon Brooks": 113, "Domantas Sabonis": 114, "Donovan Mitchell": 115, "Donte DiVincenzo": 116, "Dorian Finney-Smith": 117, "Doug McDermott": 118, "Draymond Green": 119, "Drew Eubanks": 120, "Duane Washington Jr.": 121, "Duncan Robinson": 122, "Dwight Howard": 123, "Dwight Powell": 124, "Ed Davis": 125, "Elfrid Payton": 126, "Elijah Hughes": 127, "Enes Freedom": 128, "Eric Bledsoe": 129, "Eric Gordon": 130, "Eric Paschall": 131, "Evan Fournier": 132, "Evan Mobley": 133, "Facundo Campazzo": 134, "Frank Jackson": 135, "Frank Ntilikina": 136, "Franz Wagner": 137, "Fred VanVleet": 138, "Freddie Gillespie": 139, "Furkan Korkmaz": 140, "Gabe Vincent": 141, "Garrett Temple": 142, "Garrison Mathews": 143, "Gary Clark": 144, "Gary Harris": 145, "Gary Payton II": 146, "Gary Trent Jr.": 147, "George Hill": 148, "Georges Niang": 149, "Georgios Kalaitzakis": 150, "Giannis Antetokounmpo": 151, "Goga Bitadze": 152, "Goran Dragic": 153, "Gordon Hayward": 154, "Gorgui Dieng": 155, "Grant Williams": 156, "Grayson Allen": 157, "Greg Brown III": 158, "Hamidou Diallo": 159, "Harrison Barnes": 160, "Hassan Whiteside": 161, "Hassani Gravett": 162, "Haywood Highsmith": 163, "Herbert Jones": 164, "Ignas Brazdeikis": 165, "Immanuel Quickley": 166, "Isaac Okoro": 167, "Isaiah Jackson": 168, "Isaiah Joe": 169, "Isaiah Livers": 170, "Isaiah Roby": 171, "Isaiah Stewart": 172, "Isaiah Thomas": 173, "Ish Smith": 174, "Ivica Zubac": 175, "Ja Morant": 176, "JaMychal Green": 177, "JaVale McGee": 178, "Jaden McDaniels": 179, "Jae Crowder": 180, "Jae'Sean Tate": 181, "Jake Layman": 182, "Jakob Poeltl": 183, "Jalen Brunson": 184, "Jalen Green": 185, "Jalen McDaniels": 186, "Jalen Smith": 187, "Jalen Suggs": 188, "James Harden": 189, "James Johnson": 190, "Jared Butler": 191, "Jaren Jackson Jr.": 192, "Jarred Vanderbilt": 193, "Jarrett Allen": 194, "Javonte Green": 195, "Javonte Smart": 196, "Jaxson Hayes": 197, "Jaylen Brown": 198, "Jaylen Hoard": 199, "Jaylen Nowell": 200, "Jayson Tatum": 201, "Jeff Green": 202, "Jerami Grant": 203, "Jeremiah Robinson-Earl": 204, "Jericho Sims": 205, "Jevon Carter": 206, "Jimmy Butler": 207, "Jock Landale": 208, "Joe Harris": 209, "Joe Ingles": 210, "Joel Embiid": 211, "John Collins": 212, "John Konchar": 213, "Jonas Valanciunas": 214, "Jonathan Kuminga": 215, "Jordan Clarkson": 216, "Jordan McLaughlin": 217, "Jordan Nwora": 218, "Jordan Poole": 219, "Jose Alvarado": 220, "Josh Christopher": 221, "Josh Giddey": 222, "Josh Green": 223, "Josh Hart": 224, "Josh Jackson": 225, "Josh Okogie": 226, "Josh Richardson": 227, "Joshua Primo": 228, "Jrue Holiday": 229, "Juan Toscano-Anderson": 230, "Juancho Hernangomez": 231, "Julius Randle": 232, "Justin Anderson": 233, "Justin Holiday": 234, "Justise Winslow": 235, "Jusuf Nurkic": 236, "Karl-Anthony Towns": 237, "Keifer Sykes": 238, "Keita Bates-Diop": 239, "Kelan Martin": 240, "Keldon Johnson": 241, "Keljin Blevins": 242, "Kelly Olynyk": 243, "Kelly Oubre Jr.": 244, "Kemba Walker": 245, "Kent Bazemore": 246, "Kentavious Caldwell-Pope": 247, "Kenyon Martin Jr.": 248, "Keon Johnson": 249, "Kessler Edwards": 250, "Kevin Durant": 251, "Kevin Huerter": 252, "Kevin Love": 253, "Kevin Pangos": 254, "Kevin Porter Jr.": 255, "Kevon Looney": 256, "Khem Birch": 257, "Khris Middleton": 258, "Killian Hayes": 259, "Killian Tillie": 260, "Klay Thompson": 261, "Kris Dunn": 262, "Kristaps Porzingis": 263, "Kyle Anderson": 264, "Kyle Kuzma": 265, "Kyle Lowry": 266, "Kyrie Irving": 267, "LaMarcus Aldridge": 268, "LaMelo Ball": 269, "Lamar Stevens": 270, "Lance Stephenson": 271, "Landry Shamet": 272, "Larry Nance Jr.": 273, "Lauri Markkanen": 274, "LeBron James": 275, "Leandro Bolmaro": 276, "Lindy Waters III": 277, "Lonnie Walker IV": 278, "Lonzo Ball": 279, "Luguentz Dort": 280, "Luka Doncic": 281, "Luka Garza": 282, "Luke Kennard": 283, "Malachi Flynn": 284, "Malcolm Brogdon": 285, "Malik Beasley": 286, "Malik Monk": 287, "Mamadi Diakite": 288, "Marcus Morris Sr.": 289, "Marcus Smart": 290, "Markelle Fultz": 291, "Markieff Morris": 292, "Marvin Bagley III": 293, "Mason Plumlee": 294, "Matisse Thybulle": 295, "Maurice Harkless": 296, "Max Strus": 297, "Maxi Kleber": 298, "Michael Porter Jr.": 299, "Mikal Bridges": 300, "Mike Conley": 301, "Miles Bridges": 302, "Miles McBride": 303, "Mitchell Robinson": 304, "Mo Bamba": 305, "Monte Morris": 306, "Montrezl Harrell": 307, "Moritz Wagner": 308, "Moses Brown": 309, "Moses Moody": 310, "Mychal Mulder": 311, "Myles Turner": 312, "Naji Marshall": 313, "Nassir Little": 314, "Nathan Knight": 315, "Naz Reid": 316, "Nerlens Noel": 317, "Nic Claxton": 318, "Nick Richards": 319, "Nickeil Alexander-Walker": 320, "Nicolas Batum": 321, "Nikola Jokic": 322, "Nikola Vucevic": 323, "Norman Powell": 324, "OG Anunoby": 325, "Obi Toppin": 326, "Olivier Sarr": 327, "Omer Yurtseven": 328, "Onyeka Okongwu": 329, "Oshae Brissett": 330, "Otto Porter Jr.": 331, "P.J. Tucker": 332, "P.J. Washington": 333, "Pascal Siakam": 334, "Pat Connaughton": 335, "Patrick Beverley": 336, "Patrick Williams": 337, "Patty Mills": 338, "Paul George": 339, "Paul Millsap": 340, "Paul Reed": 341, "Paul Watson": 342, "Payton Pritchard": 343, "Precious Achiuwa": 344, "Quentin Grimes": 345, "R.J. Hampton": 346, "RJ Barrett": 347, "Rajon Rondo": 348, "Raul Neto": 349, "Reggie Bullock": 350, "Reggie Jackson": 351, "Reggie Perry": 352, "Richaun Holmes": 353, "Ricky Rubio": 354, "Robert Covington": 355, "Robert Williams III": 356, "Robin Lopez": 357, "Rodney McGruder": 358, "Romeo Langford": 359, "Royce O'Neale": 360, "Rudy Gay": 361, "Rudy Gobert": 362, "Rui Hachimura": 363, "Russell Westbrook": 364, "Saddiq Bey": 365, "Sandro Mamukelashvili": 366, "Scottie Barnes": 367, "Serge Ibaka": 368, "Seth Curry": 369, "Shai Gilgeous-Alexander": 370, "Shake Milton": 371, "Skylar Mays": 372, "Solomon Hill": 373, "Spencer Dinwiddie": 374, "Stanley Johnson": 375, "Stephen Curry": 376, "Sterling Brown": 377, "Steven Adams": 378, "Svi Mykhailiuk": 379, "T.J. McConnell": 380, "Tacko Fall": 381, "Taj Gibson": 382, "Talen Horton-Tucker": 383, "Taurean Prince": 384, "Terance Mann": 385, "Terence Davis": 386, "Terry Rozier": 387, "Terry Taylor": 388, "Thaddeus Young": 389, "Thanasis Antetokounmpo": 390, "Theo Maledon": 391, "Thomas Bryant": 392, "Tim Frazier": 393, "Tim Hardaway Jr.": 394, "Timothe Luwawu-Cabarrot": 395, "Tobias Harris": 396, "Tomas Satoransky": 397, "Tony Bradley": 398, "Tony Snell": 399, "Torrey Craig": 400, "Trae Young": 401, "Tre Jones": 402, "Tre Mann": 403, "Trendon Watford": 404, "Trent Forrest": 405, "Trevor Ariza": 406, "Trey Lyles": 407, "Trey Murphy III": 408, "Tristan Thompson": 409, "Troy Brown Jr.": 410, "Ty Jerome": 411, "Tyler Cook": 412, "Tyler Herro": 413, "Tyrese Haliburton": 414, "Tyrese Maxey": 415, "Tyus Jones": 416, "Udoka Azubuike": 417, "Usman Garuba": 418, "Vernon Carey Jr.": 419, "Victor Oladipo": 420, "Vit Krejci": 421, "Vlatko Cancar": 422, "Wayne Ellington": 423, "Wendell Carter Jr.": 424, "Wenyen Gabriel": 425, "Wes Iwundu": 426, "Wesley Matthews": 427, "Will Barton": 428, "Willie Cauley-Stein": 429, "Willy Hernangomez": 430, "Xavier Tillman": 431, "Yuta Watanabe": 432, "Zach Collins": 433, "Zach LaVine": 434, "Zavier Simpson": 435, "Zeke Nnaji": 436, "Ziaire Williams": 437, "unknown": 438} \ No newline at end of file diff --git a/notebooks/baseline/model_eval.ipynb b/notebooks/baseline/model_eval.ipynb deleted file mode 100644 index 24c679a..0000000 --- a/notebooks/baseline/model_eval.ipynb +++ /dev/null @@ -1,2782 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# NBA AI - Model Evaluation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Imports and Global Settings" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-01-01 19:22:03.862703: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.\n", - "2024-01-01 19:22:03.911989: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.\n", - "2024-01-01 19:22:03.913159: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n", - "To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", - "2024-01-01 19:22:04.866418: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using TensorFlow backend\n" - ] - } - ], - "source": [ - "import pandas as pd\n", - "import numpy as np\n", - "import seaborn as sns\n", - "import matplotlib.pyplot as plt\n", - "from sklearn.model_selection import train_test_split\n", - "from pycaret import classification as pyc_cls\n", - "from pycaret import regression as pyc_reg\n", - "from tensorflow.keras.models import load_model\n", - "import autokeras as ak\n", - "import matplotlib.ticker as mtick\n", - "\n", - "pd.set_option(\"display.max_columns\", None)\n", - "pd.set_option(\"display.max_rows\", None)\n", - "sns.set_style(\"whitegrid\")\n", - "sns.set_context(\"notebook\")\n", - "sns.set_palette(sns.color_palette())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Loading Data" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "df_2021_2022 = pd.read_csv(\"../data/nba_ai/cleaned_data_2021-2022.csv\")\n", - "df_2022_2023 = pd.read_csv(\"../data/nba_ai/cleaned_data_2022-2023.csv\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
bigdataball_datasetgame_iddatehome_teamhome_1qhome_2qhome_3qhome_4qhome_ot1home_ot2home_ot3home_ot4home_ot5home_fhome_minhome_fghome_fgahome_3phome_3pahome_fthome_ftahome_orhome_drhome_tothome_ahome_pfhome_sthome_tohome_to_tohome_blhome_ptshome_posshome_pacehome_oeffhome_deffhome_team_rest_dayshome_opening_spreadopening_totalhome_closing_spreadclosing_totalhome_moneylineseasonseason_typehome_starting_lineuproad_teamroad_1qroad_2qroad_3qroad_4qroad_ot1road_ot2road_ot3road_ot4road_ot5road_froad_minroad_fgroad_fgaroad_3proad_3paroad_ftroad_ftaroad_orroad_drroad_totroad_aroad_pfroad_stroad_toroad_to_toroad_blroad_ptsroad_possroad_paceroad_oeffroad_deffroad_team_rest_daysroad_moneylineroad_starting_lineupcrew_chiefreferee_umpireday_of_seasonhome_team_game_numroad_team_game_numhome_eFG%home_TOV%home_ORB%home_FT%road_eFG%road_TOV%road_ORB%road_FT%winnerloserhome_winshome_losseshome_win_pctroad_winsroad_lossesroad_win_pcthome_wins_l2whome_losses_l2whome_win_pct_l2wroad_wins_l2wroad_losses_l2wroad_win_pct_l2whome_avg_1qroad_avg_1qhome_avg_1q_l2wroad_avg_1q_l2whome_avg_2qroad_avg_2qhome_avg_2q_l2wroad_avg_2q_l2whome_avg_3qroad_avg_3qhome_avg_3q_l2wroad_avg_3q_l2whome_avg_4qroad_avg_4qhome_avg_4q_l2wroad_avg_4q_l2whome_avg_ot1road_avg_ot1home_avg_ot1_l2wroad_avg_ot1_l2whome_avg_ot2road_avg_ot2home_avg_ot2_l2wroad_avg_ot2_l2whome_avg_ot3road_avg_ot3home_avg_ot3_l2wroad_avg_ot3_l2whome_avg_ot4road_avg_ot4home_avg_ot4_l2wroad_avg_ot4_l2whome_avg_ot5road_avg_ot5home_avg_ot5_l2wroad_avg_ot5_l2whome_avg_froad_avg_fhome_avg_f_l2wroad_avg_f_l2whome_avg_minroad_avg_minhome_avg_min_l2wroad_avg_min_l2whome_avg_fgroad_avg_fghome_avg_fg_l2wroad_avg_fg_l2whome_avg_fgaroad_avg_fgahome_avg_fga_l2wroad_avg_fga_l2whome_avg_3proad_avg_3phome_avg_3p_l2wroad_avg_3p_l2whome_avg_3paroad_avg_3pahome_avg_3pa_l2wroad_avg_3pa_l2whome_avg_ftroad_avg_fthome_avg_ft_l2wroad_avg_ft_l2whome_avg_ftaroad_avg_ftahome_avg_fta_l2wroad_avg_fta_l2whome_avg_orroad_avg_orhome_avg_or_l2wroad_avg_or_l2whome_avg_drroad_avg_drhome_avg_dr_l2wroad_avg_dr_l2whome_avg_totroad_avg_tothome_avg_tot_l2wroad_avg_tot_l2whome_avg_aroad_avg_ahome_avg_a_l2wroad_avg_a_l2whome_avg_pfroad_avg_pfhome_avg_pf_l2wroad_avg_pf_l2whome_avg_stroad_avg_sthome_avg_st_l2wroad_avg_st_l2whome_avg_toroad_avg_tohome_avg_to_l2wroad_avg_to_l2whome_avg_to_toroad_avg_to_tohome_avg_to_to_l2wroad_avg_to_to_l2whome_avg_blroad_avg_blhome_avg_bl_l2wroad_avg_bl_l2whome_avg_ptsroad_avg_ptshome_avg_pts_l2wroad_avg_pts_l2whome_avg_possroad_avg_posshome_avg_poss_l2wroad_avg_poss_l2whome_avg_paceroad_avg_pacehome_avg_pace_l2wroad_avg_pace_l2whome_avg_oeffroad_avg_oeffhome_avg_oeff_l2wroad_avg_oeff_l2whome_avg_deffroad_avg_deffhome_avg_deff_l2wroad_avg_deff_l2whome_avg_eFG%road_avg_eFG%home_avg_eFG%_l2wroad_avg_eFG%_l2whome_avg_TOV%road_avg_TOV%home_avg_TOV%_l2wroad_avg_TOV%_l2whome_avg_ORB%road_avg_ORB%home_avg_ORB%_l2wroad_avg_ORB%_l2whome_avg_FT%road_avg_FT%home_avg_FT%_l2wroad_avg_FT%_l2whome_avg_pts_allowedroad_avg_pts_allowedhome_avg_pts_allowed_l2wroad_avg_pts_allowed_l2wREG_TARGETCLS_TARGETCLS_TARGET_closing_spreadREG_TARGET_OUCLS_TARGET_OU_OPENCLS_TARGET_OU_CLOSE
0NBA 2022-2023 Regular Season222000012022-10-18Boston24393528NaNNaNNaNNaNNaN126240.046821235222863036242481011312698.68053598.680535127.684756118.5644163+-4.0213.5-3.0216.5-1502023-2023 Regular SeasonJaylen Brown,Jayson Tatum,Al Horford,Derrick W...Philadelphia29342529NaNNaNNaNNaNNaN117240.040801334242842731162581414311798.68053598.680535118.564416127.6847563++127Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M...James CapersBrian Forte,Ray Acosta1110.6341460.1044440.1395350.2682930.5812500.1316780.0930230.300000BostonPhiladelphia000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.09TrueTrue243TrueTrue
1NBA 2022-2023 Regular Season222000022022-10-18Golden State25343232NaNNaNNaNNaNNaN123240.045991645172311374831231118184123114.091809114.091809107.80791595.5370953+-6.0227.5-7.5223.5-3062023-2023 Regular SeasonAndrew Wiggins,Draymond Green,Kevon Looney,Kla...LA Lakers22301938NaNNaNNaNNaNNaN109240.04094104019259394823181221224109114.091809114.09180995.537095107.8079153++247Lonnie Walker IV,LeBron James,Anthony Davis,Ru...Tony BrothersRodney Mott,Scott Twardoski1110.5353540.1415980.2291670.1717170.4787230.1732280.2142860.202128Golden StateLA Lakers000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.014TrueTrue232TrueTrue
2NBA 2022-2023 Regular Season222000142022-10-19Sacramento23322924NaNNaNNaNNaNNaN108240.0398517441319437412725815165108101.731855101.731855106.161438113.0422713+-1.5223.5-3.0229.5-1492023-2023 Regular SeasonHarrison Barnes,KZ Okpala,Domantas Sabonis,Kev...Portland32193331NaNNaNNaNNaNNaN115240.039881128263311334420171111112115101.731855101.731855113.042271106.1614383++124Josh Hart,Jerami Grant,Jusuf Nurkic,Anfernee S...Courtney KirklandBrandon Adair,Justin Van Duyne2110.5588240.1463060.1111110.1529410.5056820.0968990.2391300.295455PortlandSacramento000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.0-7FalseFalse223FalseFalse
3NBA 2022-2023 Regular Season222000132022-10-19Phoenix24213131NaNNaNNaNNaNNaN107240.04085822192283240252941212510795.82903895.829038111.657178109.5701283+-5.5216.0-4.0218.0-1802023-2023 Regular SeasonMikal Bridges,Cameron Johnson,Deandre Ayton,De...Dallas32301924NaNNaNNaNNaNNaN105240.035751435213453540172261212410595.82903895.829038109.570128111.6571783++149Reggie Bullock,Dorian Finney-Smith,JaVale McGe...Derek RichardsonEric Lewis,Gediminas Petraitis2110.5176470.1124860.2000000.2235290.5600000.1176930.1666670.280000PhoenixDallas000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.02FalseFalse212FalseFalse
4NBA 2022-2023 Regular Season222000122022-10-19Utah37381929NaNNaNNaNNaNNaN123240.042831638233111324327211019201123101.120258101.120258121.637348100.8699963+7.5218.07.0225.5+2132023-2023 Regular SeasonLauri Markkanen,Kelly Olynyk,Jarred Vanderbilt...Denver30232722NaNNaNNaNNaNNaN102240.04083522171810253521231021213102101.120258101.120258100.869996121.6373483+-261Michael Porter Jr.,Aaron Gordon,Nikola Jokic,K...Tony BrothersKevin Cutler,Lauren Holtkamp2110.6024100.1714680.2115380.2771080.5120480.1876340.1960780.204819UtahDenver000.0000.0000.0000.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.021TrueTrue225TrueFalse
\n", - "
" - ], - "text/plain": [ - " bigdataball_dataset game_id date home_team home_1q \\\n", - "0 NBA 2022-2023 Regular Season 22200001 2022-10-18 Boston 24 \n", - "1 NBA 2022-2023 Regular Season 22200002 2022-10-18 Golden State 25 \n", - "2 NBA 2022-2023 Regular Season 22200014 2022-10-19 Sacramento 23 \n", - "3 NBA 2022-2023 Regular Season 22200013 2022-10-19 Phoenix 24 \n", - "4 NBA 2022-2023 Regular Season 22200012 2022-10-19 Utah 37 \n", - "\n", - " home_2q home_3q home_4q home_ot1 home_ot2 home_ot3 home_ot4 \\\n", - "0 39 35 28 NaN NaN NaN NaN \n", - "1 34 32 32 NaN NaN NaN NaN \n", - "2 32 29 24 NaN NaN NaN NaN \n", - "3 21 31 31 NaN NaN NaN NaN \n", - "4 38 19 29 NaN NaN NaN NaN \n", - "\n", - " home_ot5 home_f home_min home_fg home_fga home_3p home_3pa home_ft \\\n", - "0 NaN 126 240.0 46 82 12 35 22 \n", - "1 NaN 123 240.0 45 99 16 45 17 \n", - "2 NaN 108 240.0 39 85 17 44 13 \n", - "3 NaN 107 240.0 40 85 8 22 19 \n", - "4 NaN 123 240.0 42 83 16 38 23 \n", - "\n", - " home_fta home_or home_dr home_tot home_a home_pf home_st home_to \\\n", - "0 28 6 30 36 24 24 8 10 \n", - "1 23 11 37 48 31 23 11 18 \n", - "2 19 4 37 41 27 25 8 15 \n", - "3 22 8 32 40 25 29 4 12 \n", - "4 31 11 32 43 27 21 10 19 \n", - "\n", - " home_to_to home_bl home_pts home_poss home_pace home_oeff \\\n", - "0 11 3 126 98.680535 98.680535 127.684756 \n", - "1 18 4 123 114.091809 114.091809 107.807915 \n", - "2 16 5 108 101.731855 101.731855 106.161438 \n", - "3 12 5 107 95.829038 95.829038 111.657178 \n", - "4 20 1 123 101.120258 101.120258 121.637348 \n", - "\n", - " home_deff home_team_rest_days home_opening_spread opening_total \\\n", - "0 118.564416 3+ -4.0 213.5 \n", - "1 95.537095 3+ -6.0 227.5 \n", - "2 113.042271 3+ -1.5 223.5 \n", - "3 109.570128 3+ -5.5 216.0 \n", - "4 100.869996 3+ 7.5 218.0 \n", - "\n", - " home_closing_spread closing_total home_moneyline season \\\n", - "0 -3.0 216.5 -150 2023 \n", - "1 -7.5 223.5 -306 2023 \n", - "2 -3.0 229.5 -149 2023 \n", - "3 -4.0 218.0 -180 2023 \n", - "4 7.0 225.5 +213 2023 \n", - "\n", - " season_type home_starting_lineup \\\n", - "0 -2023 Regular Season Jaylen Brown,Jayson Tatum,Al Horford,Derrick W... \n", - "1 -2023 Regular Season Andrew Wiggins,Draymond Green,Kevon Looney,Kla... \n", - "2 -2023 Regular Season Harrison Barnes,KZ Okpala,Domantas Sabonis,Kev... \n", - "3 -2023 Regular Season Mikal Bridges,Cameron Johnson,Deandre Ayton,De... \n", - "4 -2023 Regular Season Lauri Markkanen,Kelly Olynyk,Jarred Vanderbilt... \n", - "\n", - " road_team road_1q road_2q road_3q road_4q road_ot1 road_ot2 \\\n", - "0 Philadelphia 29 34 25 29 NaN NaN \n", - "1 LA Lakers 22 30 19 38 NaN NaN \n", - "2 Portland 32 19 33 31 NaN NaN \n", - "3 Dallas 32 30 19 24 NaN NaN \n", - "4 Denver 30 23 27 22 NaN NaN \n", - "\n", - " road_ot3 road_ot4 road_ot5 road_f road_min road_fg road_fga road_3p \\\n", - "0 NaN NaN NaN 117 240.0 40 80 13 \n", - "1 NaN NaN NaN 109 240.0 40 94 10 \n", - "2 NaN NaN NaN 115 240.0 39 88 11 \n", - "3 NaN NaN NaN 105 240.0 35 75 14 \n", - "4 NaN NaN NaN 102 240.0 40 83 5 \n", - "\n", - " road_3pa road_ft road_fta road_or road_dr road_tot road_a road_pf \\\n", - "0 34 24 28 4 27 31 16 25 \n", - "1 40 19 25 9 39 48 23 18 \n", - "2 28 26 33 11 33 44 20 17 \n", - "3 35 21 34 5 35 40 17 22 \n", - "4 22 17 18 10 25 35 21 23 \n", - "\n", - " road_st road_to road_to_to road_bl road_pts road_poss road_pace \\\n", - "0 8 14 14 3 117 98.680535 98.680535 \n", - "1 12 21 22 4 109 114.091809 114.091809 \n", - "2 11 11 11 2 115 101.731855 101.731855 \n", - "3 6 12 12 4 105 95.829038 95.829038 \n", - "4 10 21 21 3 102 101.120258 101.120258 \n", - "\n", - " road_oeff road_deff road_team_rest_days road_moneyline \\\n", - "0 118.564416 127.684756 3+ +127 \n", - "1 95.537095 107.807915 3+ +247 \n", - "2 113.042271 106.161438 3+ +124 \n", - "3 109.570128 111.657178 3+ +149 \n", - "4 100.869996 121.637348 3+ -261 \n", - "\n", - " road_starting_lineup crew_chief \\\n", - "0 Tobias Harris,P.J. Tucker,Joel Embiid,Tyrese M... James Capers \n", - "1 Lonnie Walker IV,LeBron James,Anthony Davis,Ru... Tony Brothers \n", - "2 Josh Hart,Jerami Grant,Jusuf Nurkic,Anfernee S... Courtney Kirkland \n", - "3 Reggie Bullock,Dorian Finney-Smith,JaVale McGe... Derek Richardson \n", - "4 Michael Porter Jr.,Aaron Gordon,Nikola Jokic,K... Tony Brothers \n", - "\n", - " referee_umpire day_of_season home_team_game_num \\\n", - "0 Brian Forte,Ray Acosta 1 1 \n", - "1 Rodney Mott,Scott Twardoski 1 1 \n", - "2 Brandon Adair,Justin Van Duyne 2 1 \n", - "3 Eric Lewis,Gediminas Petraitis 2 1 \n", - "4 Kevin Cutler,Lauren Holtkamp 2 1 \n", - "\n", - " road_team_game_num home_eFG% home_TOV% home_ORB% home_FT% road_eFG% \\\n", - "0 1 0.634146 0.104444 0.139535 0.268293 0.581250 \n", - "1 1 0.535354 0.141598 0.229167 0.171717 0.478723 \n", - "2 1 0.558824 0.146306 0.111111 0.152941 0.505682 \n", - "3 1 0.517647 0.112486 0.200000 0.223529 0.560000 \n", - "4 1 0.602410 0.171468 0.211538 0.277108 0.512048 \n", - "\n", - " road_TOV% road_ORB% road_FT% winner loser home_wins \\\n", - "0 0.131678 0.093023 0.300000 Boston Philadelphia 0 \n", - "1 0.173228 0.214286 0.202128 Golden State LA Lakers 0 \n", - "2 0.096899 0.239130 0.295455 Portland Sacramento 0 \n", - "3 0.117693 0.166667 0.280000 Phoenix Dallas 0 \n", - "4 0.187634 0.196078 0.204819 Utah Denver 0 \n", - "\n", - " home_losses home_win_pct road_wins road_losses road_win_pct \\\n", - "0 0 0.0 0 0 0.0 \n", - "1 0 0.0 0 0 0.0 \n", - "2 0 0.0 0 0 0.0 \n", - "3 0 0.0 0 0 0.0 \n", - "4 0 0.0 0 0 0.0 \n", - "\n", - " home_wins_l2w home_losses_l2w home_win_pct_l2w road_wins_l2w \\\n", - "0 0 0 0.0 0 \n", - "1 0 0 0.0 0 \n", - "2 0 0 0.0 0 \n", - "3 0 0 0.0 0 \n", - "4 0 0 0.0 0 \n", - "\n", - " road_losses_l2w road_win_pct_l2w home_avg_1q road_avg_1q \\\n", - "0 0 0.0 0.0 0.0 \n", - "1 0 0.0 0.0 0.0 \n", - "2 0 0.0 0.0 0.0 \n", - "3 0 0.0 0.0 0.0 \n", - "4 0 0.0 0.0 0.0 \n", - "\n", - " home_avg_1q_l2w road_avg_1q_l2w home_avg_2q road_avg_2q \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_2q_l2w road_avg_2q_l2w home_avg_3q road_avg_3q \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_3q_l2w road_avg_3q_l2w home_avg_4q road_avg_4q \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_4q_l2w road_avg_4q_l2w home_avg_ot1 road_avg_ot1 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot1_l2w road_avg_ot1_l2w home_avg_ot2 road_avg_ot2 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot2_l2w road_avg_ot2_l2w home_avg_ot3 road_avg_ot3 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot3_l2w road_avg_ot3_l2w home_avg_ot4 road_avg_ot4 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot4_l2w road_avg_ot4_l2w home_avg_ot5 road_avg_ot5 \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ot5_l2w road_avg_ot5_l2w home_avg_f road_avg_f home_avg_f_l2w \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_f_l2w home_avg_min road_avg_min home_avg_min_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_min_l2w home_avg_fg road_avg_fg home_avg_fg_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_fg_l2w home_avg_fga road_avg_fga home_avg_fga_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_fga_l2w home_avg_3p road_avg_3p home_avg_3p_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_3p_l2w home_avg_3pa road_avg_3pa home_avg_3pa_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_3pa_l2w home_avg_ft road_avg_ft home_avg_ft_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_ft_l2w home_avg_fta road_avg_fta home_avg_fta_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_fta_l2w home_avg_or road_avg_or home_avg_or_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_or_l2w home_avg_dr road_avg_dr home_avg_dr_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_dr_l2w home_avg_tot road_avg_tot home_avg_tot_l2w \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_tot_l2w home_avg_a road_avg_a home_avg_a_l2w road_avg_a_l2w \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pf road_avg_pf home_avg_pf_l2w road_avg_pf_l2w home_avg_st \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " road_avg_st home_avg_st_l2w road_avg_st_l2w home_avg_to road_avg_to \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_to_l2w road_avg_to_l2w home_avg_to_to road_avg_to_to \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_to_to_l2w road_avg_to_to_l2w home_avg_bl road_avg_bl \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_bl_l2w road_avg_bl_l2w home_avg_pts road_avg_pts \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pts_l2w road_avg_pts_l2w home_avg_poss road_avg_poss \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_poss_l2w road_avg_poss_l2w home_avg_pace road_avg_pace \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_pace_l2w road_avg_pace_l2w home_avg_oeff road_avg_oeff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_oeff_l2w road_avg_oeff_l2w home_avg_deff road_avg_deff \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_deff_l2w road_avg_deff_l2w home_avg_eFG% road_avg_eFG% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_eFG%_l2w road_avg_eFG%_l2w home_avg_TOV% road_avg_TOV% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_TOV%_l2w road_avg_TOV%_l2w home_avg_ORB% road_avg_ORB% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_ORB%_l2w road_avg_ORB%_l2w home_avg_FT% road_avg_FT% \\\n", - "0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 0.0 \n", - "\n", - " home_avg_FT%_l2w road_avg_FT%_l2w home_avg_pts_allowed \\\n", - "0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 \n", - "\n", - " road_avg_pts_allowed home_avg_pts_allowed_l2w road_avg_pts_allowed_l2w \\\n", - "0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 \n", - "3 0.0 0.0 0.0 \n", - "4 0.0 0.0 0.0 \n", - "\n", - " REG_TARGET CLS_TARGET CLS_TARGET_closing_spread REG_TARGET_OU \\\n", - "0 9 True True 243 \n", - "1 14 True True 232 \n", - "2 -7 False False 223 \n", - "3 2 False False 212 \n", - "4 21 True True 225 \n", - "\n", - " CLS_TARGET_OU_OPEN CLS_TARGET_OU_CLOSE \n", - "0 True True \n", - "1 True True \n", - "2 False False \n", - "3 False False \n", - "4 True False " - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_2022_2023.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "## Data Preparation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Train Test Split" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "def prepare_datasets(train_df, cls_target, reg_target, test_df=None, test_size=0.3):\n", - " \"\"\"\n", - " Prepares datasets for training and testing for both classification and regression targets,\n", - " ensuring time-sensitive splitting based on a 'date' column.\n", - "\n", - " Parameters:\n", - " train_df (DataFrame): The training dataframe.\n", - " cls_target (str): The name of the classification target column.\n", - " reg_target (str): The name of the regression target column.\n", - " test_df (DataFrame, optional): An optional testing dataframe. If not provided, a portion of the training data is used.\n", - " test_size (float, optional): The proportion of the dataset to include in the test split (if test_df is not provided).\n", - "\n", - " Returns:\n", - " tuple: A tuple containing six dataframes:\n", - " (X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg).\n", - " \"\"\"\n", - "\n", - " # Sort the dataframe based on the 'date' column\n", - " train_df = train_df.sort_values(by=\"date\")\n", - "\n", - " # If a test dataframe is not provided, split the training dataframe\n", - " if test_df is None:\n", - " X_train, X_test, y_train, y_test = train_test_split(\n", - " train_df.drop([cls_target, reg_target], axis=1),\n", - " train_df[[cls_target, reg_target]],\n", - " test_size=test_size,\n", - " shuffle=False, # Important to maintain time order\n", - " )\n", - " else:\n", - " # If a test dataframe is provided, ensure it is also sorted by date\n", - " test_df = test_df.sort_values(by=\"date\")\n", - "\n", - " # Use provided test dataframe and separate features and targets\n", - " X_train = train_df.drop([cls_target, reg_target], axis=1)\n", - " y_train = train_df[[cls_target, reg_target]]\n", - " X_test = test_df.drop([cls_target, reg_target], axis=1)\n", - " y_test = test_df[[cls_target, reg_target]]\n", - "\n", - " # Separate classification and regression targets\n", - " y_train_cls = y_train[[cls_target]]\n", - " y_train_reg = y_train[[reg_target]]\n", - " y_test_cls = y_test[[cls_target]]\n", - " y_test_reg = y_test[[reg_target]]\n", - "\n", - " return X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "X_train, X_test, y_train_cls, y_test_cls, y_train_reg, y_test_reg = prepare_datasets(\n", - " df_2021_2022, \"CLS_TARGET\", \"REG_TARGET\", test_df=df_2022_2023\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Features" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "betting_feature_set = [\n", - " \"home_opening_spread\",\n", - " \"road_opening_spread\",\n", - " \"opening_total\",\n", - " \"home_closing_spread\",\n", - " \"road_closing_spread\",\n", - " \"closing_total\",\n", - " \"home_moneyline\",\n", - " \"road_moneyline\",\n", - "]\n", - "\n", - "base_feature_set = [\n", - " \"day_of_season\",\n", - " \"home_win_pct\",\n", - " \"road_win_pct\",\n", - " \"home_win_pct_l2w\",\n", - " \"road_win_pct_l2w\",\n", - " \"home_avg_pts\",\n", - " \"road_avg_pts\",\n", - " \"home_avg_pts_l2w\",\n", - " \"road_avg_pts_l2w\",\n", - " \"home_avg_oeff\",\n", - " \"road_avg_oeff\",\n", - " \"home_avg_oeff_l2w\",\n", - " \"road_avg_oeff_l2w\",\n", - " \"home_avg_deff\",\n", - " \"road_avg_deff\",\n", - " \"home_avg_deff_l2w\",\n", - " \"road_avg_deff_l2w\",\n", - " \"home_avg_eFG%\",\n", - " \"road_avg_eFG%\",\n", - " \"home_avg_eFG%_l2w\",\n", - " \"road_avg_eFG%_l2w\",\n", - " \"home_avg_TOV%\",\n", - " \"road_avg_TOV%\",\n", - " \"home_avg_TOV%_l2w\",\n", - " \"road_avg_TOV%_l2w\",\n", - " \"home_avg_ORB%\",\n", - " \"road_avg_ORB%\",\n", - " \"home_avg_ORB%_l2w\",\n", - " \"road_avg_ORB%_l2w\",\n", - " \"home_avg_FT%\",\n", - " \"road_avg_FT%\",\n", - " \"home_avg_FT%_l2w\",\n", - " \"road_avg_FT%_l2w\",\n", - " \"home_avg_pts_allowed\",\n", - " \"road_avg_pts_allowed\",\n", - " \"home_avg_pts_allowed_l2w\",\n", - " \"road_avg_pts_allowed_l2w\",\n", - "]\n", - "\n", - "features_to_prepare = [\n", - " \"home_team\",\n", - " \"road_team\",\n", - " \"home_team_rest_days\",\n", - " \"road_team_rest_days\",\n", - " \"home_team_starting_lineup\",\n", - " \"road_team_starting_lineup\",\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "X_train_all = X_train.copy()\n", - "X_test_all = X_test.copy()\n", - "\n", - "X_train = X_train[base_feature_set]\n", - "X_test = X_test[base_feature_set]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Model Predictions" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Transformation Pipeline and Model Successfully Loaded\n", - "Transformation Pipeline and Model Successfully Loaded\n" - ] - } - ], - "source": [ - "automl_cls = pyc_cls.load_model(\n", - " \"../models/AutoML/Classification_Ridge_58_49_2023-12-30_18-37-09\"\n", - ")\n", - "automl_reg = pyc_reg.load_model(\n", - " \"../models/AutoML/Regression_Ridge_11.16_12.54_2023-12-30_19-14-58\"\n", - ")\n", - "autodl_cls = load_model(\n", - " \"../models/AutoDL/Classification_AutoKeras_47_49_2023-12-30_19-32-24\",\n", - " custom_objects=ak.CUSTOM_OBJECTS,\n", - ")\n", - "autodl_reg = load_model(\n", - " \"../models/AutoDL/Regression_AutoKeras_10.24_12.07_2023-12-30_19-40-49\",\n", - " custom_objects=ak.CUSTOM_OBJECTS,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "42/42 [==============================] - 0s 2ms/step\n", - "42/42 [==============================] - 0s 2ms/step\n" - ] - } - ], - "source": [ - "automl_cls_predictions = pyc_cls.predict_model(automl_cls, data=X_test)[\n", - " \"prediction_label\"\n", - "]\n", - "automl_reg_predictions = pyc_reg.predict_model(automl_reg, data=X_test)[\n", - " \"prediction_label\"\n", - "]\n", - "autodl_cls_predictions = autodl_cls.predict(X_test).flatten().round()\n", - "autodl_reg_predictions = autodl_reg.predict(X_test).flatten()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Performance Graphs" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "# Combine Predictions with Original Data for Graphing\n", - "graph_df = X_test_all[[\"date\", \"season\", \"season_type\", \"game_id\"]].copy()\n", - "graph_df[\"actual_result_hv\"] = y_test_reg\n", - "graph_df[\"vegas_line_hv\"] = -X_test_all[\"home_opening_spread\"]\n", - "\n", - "# Adding predictions directly to the graph_df\n", - "graph_df[\"automl_reg_pred\"] = automl_reg_predictions\n", - "graph_df[\"automl_cls_pred\"] = automl_cls_predictions\n", - "graph_df[\"autodl_reg_pred\"] = autodl_reg_predictions\n", - "graph_df[\"autodl_cls_pred\"] = autodl_cls_predictions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Regression - MAE - Bar Chart" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "def mae_comparison_bar_chart(actual, vegas_predictions, model_predictions):\n", - " \"\"\"\n", - " Displays a vertical bar chart showing the Mean Absolute Error (MAE) of the Vegas\n", - " predictions compared to each of the model predictions, with MAE values displayed inside the bars.\n", - "\n", - " Parameters:\n", - " actual (list or array): Actual results of the games.\n", - " vegas_predictions (list or array): Vegas predictions.\n", - " model_predictions (dict): Dictionary of model predictions with model name as key.\n", - " \"\"\"\n", - " # Calculate MAE for Vegas predictions\n", - " vegas_mae = np.mean(np.abs(np.array(vegas_predictions) - np.array(actual)))\n", - "\n", - " # Calculate MAE for each model\n", - " model_maes = {}\n", - " for model_name, predictions in model_predictions.items():\n", - " model_mae = np.mean(np.abs(np.array(predictions) - np.array(actual)))\n", - " model_maes[model_name] = model_mae\n", - "\n", - " # Prepare data for bar chart\n", - " model_names = list(model_maes.keys()) + [\"Vegas\"]\n", - " maes = list(model_maes.values()) + [vegas_mae]\n", - "\n", - " # Create bar chart\n", - " plt.figure(figsize=(10, 6))\n", - " bars = sns.barplot(x=model_names, y=maes)\n", - "\n", - " # Add MAE values inside the bars\n", - " for bar in bars.patches:\n", - " bar_height = bar.get_height()\n", - " # Adjust the position of the annotation inside the bar\n", - " ypos = bar_height - (0.05 * bar_height) if bar_height > 0.1 else 0.01\n", - " plt.annotate(\n", - " format(bar_height, \".2f\"),\n", - " (bar.get_x() + bar.get_width() / 2, ypos),\n", - " ha=\"center\",\n", - " va=\"center\",\n", - " color=\"white\",\n", - " size=14,\n", - " xytext=(0, 0),\n", - " textcoords=\"offset points\",\n", - " )\n", - "\n", - " plt.ylabel(\"Mean Absolute Error\")\n", - " plt.title(\"MAE Comparison of Vegas Predictions vs. Model Predictions\")\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1cAAAIZCAYAAABK58VYAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABioklEQVR4nO3deVxU9f7H8feAgOCG4FKiuIO74q4o7rnhkpWWW96yMs1cMrO6ebW8LZaVW6Zpi/tSbiCaYampZZqmpea+4i4ioOyc3x/+mNsIKA4HYfT1fDx81JzznXM+M5z5wnvO93yPxTAMQwAAAACAbHHK7QIAAAAA4H5AuAIAAAAAExCuAAAAAMAEhCsAAAAAMAHhCgAAAABMQLgCAAAAABMQrgAAAADABIQrAAAAADAB4QoAAAAATEC4AvBAGjNmjFq3bp3bZWTbiRMn9Mwzz6hevXry9/dXeHh4bpf0QMroePL399fUqVNN20e/fv3Ur18/07YH+9n7sz1z5oz8/f21fPnyHKjKXLcebzlRe+vWrTVmzBjTtgfkBYQrwETLly+Xv7+//P39tXPnznTrDcNQixYt5O/vrxdeeCHDbURHR6tmzZry9/fX0aNHM2wzZswY635u/VezZs0s1ZqQkKCvv/5aTzzxhOrVq6eaNWuqffv2evvtt3X8+PGsv2jkqjFjxujQoUMaMWKEJk6cqBo1aqRr89VXX8nf31/btm3LdDtLly6Vv7+/NmzYkJPl5oi0P/rS/lWtWlUtW7bUkCFDdODAgdwu764cOXJEU6dO1ZkzZ3K7lDzPjP42r9q+fbvNMV29enW1adNGo0eP1unTp3O7vLuya9cuTZ06VdHR0bldCnBP5MvtAoD7kZubm0JDQ1W/fn2b5b/99pvOnz8vV1fXTJ+7bt06WSwWFS9eXKtXr9aIESMybOfq6qoJEyakW+7s7HzH+iIjIzVw4EDt27dPrVq1UnBwsDw8PHT8+HGFhYVp6dKl+uuvv+64HUf2zjvvyDCM3C4jW+Lj47V7924NGjRIffv2zbRdp06dNHHiRIWEhKhp06YZtgkJCZGnp6eCgoJyqtwcFxwcrKCgIKWmpuro0aNatGiRNm/erKVLl6pq1ar3vJ69e/dm6fP4T0eOHNG0adPUsGFDlS5d2mbdnDlzzCzvvpGd/jav69evn2rWrKnk5GTt379fS5Ys0aZNm7R69WqVLFnyntbi4+OjvXv3Kl++u/vTcffu3Zo2bZoeffRRFS5c2GZd2u874H5CuAJyQIsWLbRu3Tr9+9//tvlFFBoaqurVqysqKirT565evVotWrRQqVKlFBoammm4ypcvn7p162ZXfa+//roOHDigKVOmqH379jbrhg8frk8++cSu7TqCGzduyMPDQy4uLrldSrZFRkZKUro/WG5VsmRJNWrUSD/88IPGjx+f7o/NCxcuaOfOnerZs6dDvy/VqlWz+UzUrVtXL774ohYtWqS33347w+ekHQ85wc3NzdTtOXJIyEnZ6W/zuvr166tDhw6SpMcee0zlypXThAkTtHLlykzPxuXUMW2xWDimgSxgWCCQAzp37qyoqCht3brVuiwxMVHff/+9unTpkunzzp49q507d6pTp07q3Lmzzpw5o127dpla2549e7Rx40Y9/vjj6YKVdPOX3WuvvWaz7JdfflHv3r1Vp04d1a9fXy+++GK6IYtTp06Vv7+/jh8/rlGjRqlevXpq3LixPv30UxmGoXPnzunFF19U3bp1FRgYqC+//NLm+WnDYMLCwvTxxx8rMDBQderU0aBBg3Tu3Dmbtjt37tTLL7+sli1bqkaNGmrRooXeffddxcfH27QbM2aMAgICdOrUKT333HMKCAjQqFGjrOtuvUZmzZo16tGjhwICAlS3bl116dJF33zzjU2b06dP6+WXX1bDhg1Vu3Zt9ezZUxs3bsz0tcyYMUNBQUGqWbOmnn76aZ08eTKTn4yt/fv3a+DAgapbt64CAgL09NNP648//rB5v1u1aiVJmjhxovz9/W97DVnXrl0VExOTrta0152ammo9NlNTU/X111+rc+fOqlmzppo2baqxY8fq2rVrNs9LTU3V1KlT1axZM9WuXVv9+vXTkSNH0l1HERUVpQ8++EBdunSxvrcDBw7U33//na6WefPmqXPnzqpdu7YaNGigHj16KCQkJEvv2a0aN24sSdYhdmnDyH777TeNGzdOTZo0UYsWLaztN23aZD3OAwIC9Pzzz+vw4cPpthseHq7g4GDVrFlTwcHB+uGHHzLcf0bX5Vy4cEFvvPGGmjVrpho1aqh169b6z3/+o8TERC1fvlzDhg2TJPXv3986JGz79u2SMr7m6sqVK3rjjTfUtGlT1axZU127dtWKFSts2qQNm5wzZ46WLFmitm3bqkaNGnrssce0d+9em7aXLl3S66+/rqCgINWoUUPNmjXTiy++eNthinPmzJG/v78iIiLSrZs0aZJq1KhhPXZOnDihoUOHKjAwUDVr1lRQUJBGjBihmJiYTLd/J/b0tzdu3ND777+vFi1aqEaNGmrfvr3mzJmT7mx2YmKi3n33XTVu3FgBAQEaNGiQzp8/n+E2L1y4oNdff11NmzZVjRo11LlzZ3377bd2v66M3HpMp/W7R44c0SuvvKIGDRqod+/e1varVq1Sjx49VKtWLTVs2FAjRoxI159Ksh4XtWrV0uOPP57hMMvMrrk6evSohg0bpsaNG6tWrVpq37699Qu6qVOnauLEiZKkNm3aWI/ptPozuubK7D42J4454HY4cwXkAB8fH9WpU0dr1qyx/vG2efNmxcTEqFOnTpo3b16GzwsNDZW7u7tatWql/Pnzy9fXVyEhIapbt26G7dPOXPyTq6urChYsmGltP/74oyRl+azXtm3b9Nxzz6l06dJ66aWXFB8fr/nz5+upp57S8uXL0w1dGjFihCpWrKhXXnlFmzZt0owZM+Tp6anFixercePGGjVqlEJCQvTBBx+oZs2aatCggc3zZ8yYIYvFoueee05XrlzRN998owEDBmjVqlXKnz+/pJtDSeLj4/XUU0/J09NTe/fu1fz583X+/HlNmTLFZnvJycl69tlnVa9ePb322mvWbdxq69atGjlypJo0aWINYMeOHdOuXbv09NNPS5IuX76sJ598UnFxcerXr5+KFi2qFStW6MUXX9SUKVPUrl07m21+8cUXslgseuaZZxQbG6vZs2dr1KhRWrZs2W3f88OHD6tPnz4qUKCABg4cqHz58mnJkiXq16+f5s+fr9q1a6tdu3YqVKiQ3nvvPetwuAIFCmS6zUceeUTjxo1TaGioHnnkEZt1oaGh8vHxUb169SRJY8eO1YoVK9SjRw/169dPZ86c0YIFC7R//34tWrTIenZr0qRJmj17tlq1aqXmzZvr77//1rPPPquEhASb7Z8+fVrh4eHq0KGDSpcurcuXL2vJkiXq27ev1qxZYx3etHTpUk2YMEHt27dX//79lZCQoIMHD2rPnj23/VIiM6dOnZIkeXp62iwfP368vLy8NGTIEN24cUOStHLlSo0ZM0bNmjXTqFGjFBcXp0WLFql3795asWKF9TjfsmWLhg4d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wMBAubi46MSJE1q3bp3efPNNdejQQcWKFVP//v315ZdfatCgQWrevLkOHjyozZs3q2jRojbfVrds2VLTp0/X66+/roCAAB06dEghISHprul49tlnVaxYMdWtW1fe3t46duyY5s+frxYtWmQrUN5JwYIFNW7cOI0ePVo9evRQp06d5OXlpbNnz2rTpk2qW7euxo4dK0kaOXKkXnjhBfXu3VuPPfaYoqKiNH/+fFWuXNk6rXtmRo4cqa1bt6pfv37q2bOnKlasqEuXLmndunVauHChChcurKpVq8rZ2VlffPGFYmJi5OrqqsaNG1s/9//Uq1cvLVmyRGPGjNG+ffvk4+Oj77//Xrt27dIbb7xx1+/ZiRMnNGDAAHXo0EGVKlWSs7OzwsPDdfnyZXXu3PmOz/f29lajRo301Vdf6fr16+mGOP/66696++231aFDB5UrV04pKSlatWqVNZykee211/Tbb7/p4MGDd1W/dOf+VrrZnzZq1EiffPKJIiIi5O/vr61bt2rDhg16+umnrddYVa1aVcHBwVq4cKFiYmIUEBCgX3/9NcMzl6+88oq2b9+unj176oknnlClSpV07do17du3T7/88ot+++23u34t2eHr66vhw4dr0qRJioiIUNu2bVWgQAGdOXNG4eHh6tmzp5599lm5uLho+PDhGjt2rJ5++ml16tRJZ86c0fLly+94zZV0s9986qmn9Oijj6pXr14qXbq0IiIitHHjRuvvp+rVq0uSPvnkE3Xq1EkuLi5q1apVhr+jzO5js3rMAWYiXAG5LO1+GxkNn0nTqlUrffnll1qzZo369+8v6WYoGz16dIbtN2zYcNtwlfZN5cKFCxUWFqZPPvlESUlJ8vHxUevWra37kKSmTZtq9uzZmjJliqZMmaJ8+fKpQYMGevXVV7P0y/duDRo0SAcPHtSsWbN0/fp1NWnSRP/5z3+s3366uLjo888/t46bd3NzU7t27dSnT59sDZvs2rWrli5dqoULFyo6OlrFixdXx44dNXToUOsv9GLFimnx4sX68MMPNX/+fCUkJMjf31+ff/55uglHsqNy5cpasGCBJk2apJkzZ8owDNWqVUsffvhhhhd73620cOXq6prhHxhvv/22atSoocWLF+uTTz6Rs7OzfHx81LVrV5t7ro0aNUr58+fXsmXL9Msvv6hOnTqaM2eOevfubTOR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GcJy+qa7AAAAAJAX2R2uwsPD9f7771uDVZrSpUtrzJgxatOmTbaLAwAAAABHYVe42rRpk15++WWVKlVKI0aMsN4w+OjRo1q6dKmGDh2qzz//XEFBQaYWCwAAAAB5lV3h6rPPPpO/v78WLFggDw8P6/I2bdqob9++6t27t6ZPn064AgAAAPDAcLLnSQcPHlT37t1tglUaDw8PPfroozp48GC2iwMAAAAAR2FXuHJzc9O1a9cyXX/t2jW5ubnZXRQAAAAAOBq7wlWjRo00d+5c7d69O926PXv2aN68eWrSpEm2iwMAAAAAR2HXNVevvvqqnnzySfXu3Vu1atVS+fLlJUnHjx/X3r175e3trVGjRplaKAAAAADkZXaduSpTpoxWr16tfv366dq1awoLC1NYWJiuXbum/v37a9WqVSpdurTZtQIAAABAnmX3fa68vb31xhtv6I033jCzHgAAAABwSHaducrM6dOndfToUTM3CQAAAAAOwa5wNXfuXI0YMcJm2ZgxY/TII48oODhYPXr00JUrV0wpEAAAAAAcgV3hatmyZfL29rY+/vnnn7Vy5Ur17NlT//73v3XmzBlNmzbNtCIBAAAAIK+z65qrs2fPqmLFitbHa9euVenSpTV+/HhJ0uXLl7Vq1SpzKgQAAAAAB2DXmSvDMGweb926VUFBQdbHPj4+unz5cvYqAwAAAAAHYle4KleunMLDwyXdHBJ48eJFm3B1/vx5FS5c2JwKAQAAAMAB2DUs8Nlnn9Urr7yiBg0aKC4uThUrVlSzZs2s67dv364qVaqYViQAAAAA5HV2havOnTvL09NTmzZtUuHChdW7d2/ly3dzU1FRUSpSpIi6detmaqEAAAAAkJfZfRPhwMBABQYGplvu6enJTIEAAAAAHjh2hyvp5lmqbdu2KSIiQtLNiSyaNGmiokWLmlIcAAAAADgKu8PV1KlT9cUXXygxMdFmuYuLiwYOHKhhw4ZluzgAAAAAcBR2havp06dr+vTpatmypfr06aNy5cpJko4fP64FCxbo888/V758+TRkyBAzawUAAACAPMuucLV48WK1atVKM2bMsFlepkwZBQUFadCgQVq0aBHhCgAAAMADw677XMXGxqp58+aZrg8KCtL169ftLgoAAAAAHI1d4apu3brau3dvpuv37t2runXr2l0UAAAAADgau8LVuHHjtHv3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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "mae_comparison_bar_chart(\n", - " graph_df[\"actual_result_hv\"],\n", - " graph_df[\"vegas_line_hv\"],\n", - " {\"AutoML\": graph_df[\"automl_reg_pred\"], \"AutoDL\": graph_df[\"autodl_reg_pred\"]},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Regression - Time Series MAE - Line Chart" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "def mae_time_series_chart(\n", - " actual, vegas_predictions, model_predictions, dates, seasons, aggregate_by=\"month\"\n", - "):\n", - " \"\"\"\n", - " Displays a line chart showing the Mean Absolute Error (MAE) of the Vegas predictions and each of the model predictions over time.\n", - " Includes the overall MAE for each set in the legend.\n", - "\n", - " Parameters:\n", - " actual (list or array): Actual results of the games.\n", - " vegas_predictions (list or array): Vegas predictions.\n", - " model_predictions (dict): Dictionary of model predictions with model name as key.\n", - " dates (list or array): Dates of the games.\n", - " seasons (list or array): Seasons of the games.\n", - " aggregate_by (str): Aggregation method - 'month' or 'season'. Default is 'month'.\n", - " \"\"\"\n", - "\n", - " # Create a DataFrame for the data\n", - " data = pd.DataFrame(\n", - " {\n", - " \"Date\": pd.to_datetime(dates),\n", - " \"Season\": seasons,\n", - " \"Actual\": actual,\n", - " \"Vegas\": vegas_predictions,\n", - " }\n", - " )\n", - "\n", - " # Calculate MAE for Vegas predictions\n", - " data[\"Vegas_MAE\"] = np.abs(data[\"Vegas\"] - data[\"Actual\"])\n", - " overall_vegas_mae = np.mean(data[\"Vegas_MAE\"]).round(2)\n", - "\n", - " # Calculate MAE for each model and add to DataFrame\n", - " for model_name, predictions in model_predictions.items():\n", - " data[model_name + \"_MAE\"] = np.abs(np.array(predictions) - data[\"Actual\"])\n", - " model_predictions[model_name] = np.mean(data[model_name + \"_MAE\"]).round(2)\n", - "\n", - " # Aggregate by month or season\n", - " if aggregate_by == \"month\":\n", - " data[\"Month\"] = data[\"Date\"].dt.to_period(\"M\")\n", - " grouped = data.groupby(\"Month\").mean()\n", - " else: # aggregate_by == 'season'\n", - " grouped = data.groupby(\"Season\").mean()\n", - "\n", - " # Plotting\n", - " plt.figure(figsize=(15, 8))\n", - " plt.plot(\n", - " grouped.index.to_timestamp(),\n", - " grouped[\"Vegas_MAE\"],\n", - " label=f\"Vegas (Overall MAE: {overall_vegas_mae})\",\n", - " marker=\"o\",\n", - " )\n", - " for model_name, overall_mae in model_predictions.items():\n", - " plt.plot(\n", - " grouped.index.to_timestamp(),\n", - " grouped[model_name + \"_MAE\"],\n", - " label=f\"{model_name} (Overall MAE: {overall_mae})\",\n", - " marker=\"o\",\n", - " )\n", - "\n", - " plt.legend()\n", - " plt.xlabel(\"Date\" if aggregate_by == \"month\" else \"Season\")\n", - " plt.ylabel(\"Mean Absolute Error\")\n", - " plt.title(f\"MAE Over Time (Aggregated by {aggregate_by.capitalize()})\")\n", - " plt.xticks(rotation=45)\n", - " plt.grid(True)\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABNsAAALzCAYAAAAoOgGwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd1gUVxcG8HeX3puIVEEUBAV7A6yoWLDGaBJjS2wxRhM1iS3GGGOLibFrorEnauyAYq9gjxVQEQWlS+915/uDsJ/rUnVxAd/f8/DEvXPnzpndcY3He88VCYIggIiIiIiIiIiIiN6YWNkBEBERERERERER1RZMthERERERERERESkIk21EREREREREREQKwmQbERERERERERGRgjDZRkREREREREREpCBMthERERERERERESkIk21EREREREREREQKwmQbERERERERERGRgjDZRkREREREREREpCBMthERERGVwdHREatXr1Z2GHLGjRuHuXPnKjuMd1q3bt0wc+bMMvtERkbC0dERmzdvfktRVW+Ojo5YsGBBuf3+/vtvdOnSBXl5eW8hKiIiIsViso2IiKgaO3DgABwdHeHo6IgbN27IHRcEAZ07d4ajoyMmTJhQ4hhpaWlwcXGBo6MjwsLCSuwzc+ZM6XVe/XFxcalQrFlZWVi7di369euHZs2aoVWrVvjoo49w6NAhCIJQ8ZuuYi+/p2X9dOvWTdmhlurmzZsICAjAuHHjSjx+/vx5ODo6wsPDAxKJ5C1HV72cP3++WiZLFU0R3xWK8u+//2L16tVIS0t77TEGDx6M/Px87N69W4GRERERvR2qyg6AiIiIyqehoQFfX1+0bt1apv3atWuIjY2Furp6qef6+/tDJBLB1NQUR44cwVdffVViP3V1dSxcuFCuXUVFpdz4EhISMHr0aISFhaFPnz74+OOPkZubixMnTuDbb7/F+fPnsXz58gqNVdXatGmDZcuWybTNnTsXrq6uGDp0qLRNR0cHAHD37t1qEffLNm/ejA4dOqB+/folHj9y5AgsLS0RFRWFK1euwM3N7S1HWH2cP38eu3btwhdffKHsUN6KN/muUJRbt25hzZo1GDRoEPT19V9rDA0NDQwcOBBbt27FiBEjIBKJFBwlERFR1WGyjYiIqAbo3Lkz/P39MXfuXKiq/v+Pb19fXzRp0gQpKSmlnnvk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Kygo0MiRI7V9+3ZlZ2frj3/8o84991yrx4IFcnNzdf311yspKUm9evVS586d9eGHH+rAgQMaN27cKf9xRPVRUFCgP/7xj9q8ebP27dun6Ohode3alcINysvL0/Dhw5WUlKSrrrpKBQUF+uijjzRt2jTVrl1bbdu2tXpEnCXPfxuSk5P18MMPq7i4WCtWrNCLL76onj17qm7dunwPqOYaN26sZs2aKSwsTCNGjNCiRYso3KoZh8OhK664QqmpqZoyZYpq165t9UgIELNmzVLdunU1efJk1a9fX2FhJy/G9Pz3IxjeT/LdDD598803ys3N1fjx49WhQwdJ0sUXX6zu3bvr6aef1vHjx/Xggw/KZrNZPCmqitvt1ty5c2W323XZZZdp6dKlKigo0JgxY/gbqWrG6XRq/PjxSk5O1uTJk9WgQQNJUrNmzTR+/Hj99NNPlG3VlNvt1oIFC/Trr7/q6quv1meffabZs2fLNE1169aNwq0aczqdmjx5spKSkjRp0iTv94jmzZvrr3/9q7Zt26a2bduSjyBWXFysv/3tb6pZs6YmTZqkRo0aeT++bt06RUdHc22hdu3aafny5brhhhv00ksv6YYbbtArr7yipKQkud1u7w/XfC8ITYWFhbrhhhsUGRmpefPmeQvWvXv36uDBgyouLladOnX42aKa2rVrl9LS0pSSkqKwsDCtW7dOq1at0uHDh9WxY0cNHDhQ55xzTlC8n2TPNvhUXFysPXv2nLTnTp06dXTDDTfoySef1Ntvv62ZM2d6H2MPjtB36NAhffPNN2rRooWmTZumZ555Ru+++65mz56tnTt3Wj0eqtDOnTu1fv16XXzxxd6iTZKuueYaNW3aVKtWrbJwOljJbrdrz549SktL05NPPqnJkyfr+++/15w5c/TFF19IYt+m6io7O1sZGRlKT08/qYzv16+f2rZtq9WrVwf8G2ecnud947Bhw5Samurdi6tp06Zq0KCBnn32Wd13331auHChioqKLJ4WVmnevLlSUlLUt29fPfTQQyooKNCNN94oh8OhsLAwbdq0SZL4XhCitmzZouLiYknS+vXrFRYWppUrV+qmm27SbbfdpptvvlkjRozQq6++avGkqEput1vFxcX6+eefVa9ePUVGRurDDz/U6NGjtXnzZv388896+umn9dBDD2nt2rWSAv97BHe2wadatWqp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9tz5eOTIEX366adq1KiRatWqZeXLgJ+dKRc///yzRo4cqSeeeMJbuE2cOFGjRo1SeHi4PvzwQx06dCgo7nqnbKvGTNPUa6+9pp07d2r48OHavXu3pk6dKtM0NXjw4JP2S+nSpYtmzJih6dOn67nnnlNeXp5SU1N1/PhxzZs3T0lJSVa/HPjJmXJRWsniudNp79692rJli2w2mxYvXky5EuQqkgVJio+PV3FxsSZNmqRPPvlEixcvVps2bSx6FagsLpdLv/zyy0mHIdSvX18NGzbUc889p1dffVWmaWrkyJEn/cC0Z88eZWRkyGaz6fXXX/d+LUJHebLhKWBiY2NlGIaeeuopff7553zfCHAV/fO/fv16vfnmm/rvf/+rV155hb+sDUFlyYbL5dKNN96oq6++WgkJCRo+fPhJe/edePgSgteZsvDaa69JkkaOHCnpf0uMN27cqKVLlyozM1OLFi2SzWaz5gWgUpT1vx833HCDJkyYoHPPPVerV6/WnXfeqfr16ys+Pl7z588/6VTzQEXZVo0VFxeroKBAjRs31uOPP67Nmzfrueee07Rp0yTplMItJSVFTz/9tDZv3qwDBw4oMjJSrVu3Vr169Sx+JfCnsuSitJKluLhYq1ev1qeffqqFCxdStIWAimbB5XLp2LFjWrt2rd544w21bt3aqpeASmKaphwOh/bv36+IiJK3EsXFxQoPD1fbtm314IMPaurUqXrzzTd1zjnnePdtdLlc2rhxo7KysrRgwQKKthBU0WwUFxcrLy9PmzZt4o62AFfRa/zee+9p6tSpSkpK0sKFC71LghA6ypONc889V0OGDFHPnj1Vq1YtloqGmIp+n3jnnXc0depU2Ww2vfbaa9z5HmLKmou33npLKSkpGjhwoEaNGqXrrrtOv/zyi2JiYhQbGxs8+zmaqNays7NNu93u/fW6devMW265xezfv7+5YsWKkz7X5XJV9XiwSHlycaLNmzeb+/btq4oRUUUqkoWjR4+af/nLX8zvvvuuqsaERR599FGzf//+5k8//WSaZsl/J9xut2maprl161Zz0KBB5t13320WFhZ6v2bPnj3mgQMHrBgXVais2SgoKPB+zcSJE81vv/3WinFRARX58//JJ5+Y+/fvt2JcVKGyZOPOO+80i4qKLJwSVaG83ydyc3PNd955h58nQlxZc5GXl2eapul9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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "mae_time_series_chart(\n", - " graph_df[\"actual_result_hv\"],\n", - " graph_df[\"vegas_line_hv\"],\n", - " {\"AutoML\": graph_df[\"automl_reg_pred\"], \"AutoDL\": graph_df[\"autodl_reg_pred\"]},\n", - " graph_df[\"date\"],\n", - " graph_df[\"season\"],\n", - " aggregate_by=\"month\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Classification - Accuracy - Bar Chart" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "def accuracy_comparison_bar_chart(\n", - " actual, vegas_predictions, model_predictions, show_baselines=True\n", - "):\n", - " \"\"\"\n", - " Displays a vertical bar chart showing the accuracy of each model's binary predictions,\n", - " including optional baseline accuracies.\n", - "\n", - " Parameters:\n", - " actual (list or array): Actual results of the games.\n", - " vegas_predictions (list or array): Vegas predictions.\n", - " model_predictions (dict): Dictionary of model binary predictions with model name as key.\n", - " show_baselines (bool): Whether to show baseline accuracies. Default is True.\n", - " \"\"\"\n", - "\n", - " # Function to calculate accuracy\n", - " def calculate_accuracy(actual, predictions, vegas):\n", - " correct_predictions = (\n", - " predictions == (np.array(actual) > np.array(vegas))\n", - " ).astype(int)\n", - " return np.mean(correct_predictions)\n", - "\n", - " # Calculate accuracy for each model\n", - " model_accuracies = {\n", - " model_name: calculate_accuracy(actual, np.array(predictions), vegas_predictions)\n", - " * 100\n", - " for model_name, predictions in model_predictions.items()\n", - " } # Convert to percentages\n", - "\n", - " # Baselines\n", - " if show_baselines:\n", - " always_over_accuracy = (\n", - " np.mean(np.array(actual) > np.array(vegas_predictions)) * 100\n", - " ) # Convert to percentage\n", - " model_accuracies[\"Random Guess\"] = 50.0\n", - " model_accuracies[\"Profitable\"] = 52.4\n", - " model_accuracies[\"Always Over\"] = always_over_accuracy\n", - "\n", - " # Prepare data for bar chart\n", - " model_names = list(model_accuracies.keys())\n", - " accuracies = list(model_accuracies.values())\n", - "\n", - " # Create bar chart\n", - " plt.figure(figsize=(12, 6))\n", - " bars = sns.barplot(x=model_names, y=accuracies)\n", - "\n", - " # Add accuracy values inside the bars\n", - " for bar in bars.patches:\n", - " bar_height = bar.get_height()\n", - " # Adjust the position of the annotation inside the bar\n", - " ypos = bar_height - (0.05 * bar_height) if bar_height > 0.1 else 0.01\n", - " plt.annotate(\n", - " format(bar_height, \".2f\"),\n", - " (bar.get_x() + bar.get_width() / 2, ypos),\n", - " ha=\"center\",\n", - " va=\"center\",\n", - " color=\"white\",\n", - " size=14,\n", - " xytext=(0, 0),\n", - " textcoords=\"offset points\",\n", - " )\n", - "\n", - " plt.ylabel(\"Accuracy (%)\")\n", - " plt.title(\"Accuracy Comparison of Model Predictions\")\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA/IAAAIZCAYAAAAFlgTNAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB2BElEQVR4nO3deVxU9f7H8feAoIAC4pYKKmDgBuKK+4IL7luulbb4S800l8y00rLMbLuWS3a1PXPJpRIlzdS03Mot9xUXQMUF2ZF1fn94mRwBBURh5PV8PO7jOt/zPed8ZvjONO8533OOwWg0GgUAAAAAACyCVUEXAAAAAAAAco4gDwAAAACABSHIAwAAAABgQQjyAAAAAABYEII8AAAAAAAWhCAPAAAAAIAFIcgDAAAAAGBBCPIAAAAAAFgQgjwAAAAAABaEIA8AwEMuICBAkyZNKugy7tnWrVvVs2dP+fj4yNvbWzExMQVdUibe3t6aM2dOrtcLCwuTt7e3Vq1adR+qyl+DBw/W4MGDTY/vR+0Py5gFgPuFIA8AFuD777+Xt7e3+vXrV9ClWKSrV6/qvffeU6dOnVS3bl35+fmpT58++vTTTwtlGERm169f19ixY1WiRAlNnTpV77//vuzs7LLsu2rVKnl7e8vb21u7d+/OtNxoNKp169by9vbW8OHD73fp+WrXrl2m5+bt7a3atWurXbt2mjhxokJDQwu6vFzZu3ev5syZw3sQAPKgWEEXAAC4u6CgIFWuXFkHDhzQuXPnVLVq1YIuyWIcOHBAw4YNU0JCgnr06KHatWtLkg4dOqSFCxdq9+7d+vLLLwu4yvtr3bp1MhgMBV3GPTl48KDi4+M1ZswYNWvWLEfrFC9eXGvWrFHDhg3N2v/66y9dunRJtra296PUB2Lw4MHy8fFRamqqjhw5omXLlmnLli1avXq1KlSo8EBryfhsKlYsd18r9+3bp7lz56p3795ydHQ0W/YwjFkAuJ84Ig8AhVxoaKj27dunyZMny8XFRUFBQQVdUrYSEhIKugQzMTExGjVqlKytrfXjjz9q+vTpGjRokAYNGqR33nlHGzZsyBTyHhZGo1E3btyQJNna2srGxqaAK7o3kZGRkqRSpUrleJ3WrVtr3bp1Sk1NNWtfs2aNateurXLlyuVrjQ9Sw4YN1bNnTz322GOaMmWKXnnlFUVFRemnn37Kdp379f40GAwqXry4rK2t822bD8OYBYD7iSAPAIVcUFCQnJyc1Lp1awUGBmYb5GNiYjRjxgwFBASoTp06atWqlSZOnGgKQJKUlJSkOXPmKDAwUD4+PmrRooVGjRql8+fPS/p32u6uXbvMtp3VObCTJk1SvXr1dP78eT333HOqV6+eJkyYIEnavXu3XnzxRbVp00Z16tRR69atNWPGDFOwvNXp06c1ZswYNWnSRL6+vgoMDNSsWbMkSTt37pS3t7c2bNiQ5evi7e2tffv2ZfvaLV26VBEREZo0aZI8PT0zLS9btqxGjhxp1vb999+ra9euqlOnjlq0aKFp06Zlmvo7ePBgdevWTceOHdOTTz6punXrqkOHDlq3bp2km0d8+/XrZ3o+27dvN1t/zpw58vb2Nj33+vXry9/fX9OnT1dSUpJZ35UrV2rIkCFq2rSp6tSpoy5dumjx4sWZnktAQICGDx+uP/74Q3369JGvr6+WLl1qWnbr+cYpKSmaO3euOnbsKB8fH/n7+2vQoEHatm2b2TZ37Nihxx9/XH5+fmrYsKGef/55nT59Osvncu7cOU2aNEkNGzZUgwYNNHnyZCUmJmb5d7ndL7/8YqrZ399fEyZMUEREhNnr/corr0iS+vbtK29v7xydP921a1dFRUWZPa/k5GStX79e3bt3z3KdhIQEzZw5U61bt1adOnUUGBioL774Qkaj0axfcnKyZsyYoSZNmqhevXoaMWKELl26lOU2IyIiNHnyZDVr1kx16tRR165dtWLFirvWnxtNmjSRdPO9Kv37dzl16pReeuklNWrUSI8//rip/88//2x6zRs3bqxx48bp4sWLmba7bNkytW/fXr6+vurbt2+Wpypkd478nd7bc+bM0fvvvy9JateunelUgYz6szpHPjQ0VC+++KIaN26sunXrqn///vr999/N+mR8hgUHB2v+/Plq1aqVfHx89NRTT+ncuXNmfc+ePavRo0erefPm8vHxUatWrTRu3DjFxsbe9fUGgILG1HoAKOSCgoLUoUMH2draqlu3blqyZIkOHDggX19fU5/4+Hg98cQTOn36tB577DHVqlVL169f16ZNmxQRESEXFxelpaVp+PDh2rFjh7p27aohQ4YoPj5e27Zt04kTJ1SlSpVc15aamqqhQ4eqQYMGeuWVV1SiRAlJN6fF3rhxQ4MGDZKzs7MO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Zowee+wx1a5dW9HR0dq0aZOmTZumGjVqqFevXvrpp5/07rvv6sCBA2rQoIESExO1Y8cODRo0SO3bt1epUqXUqVMnLVq0SAaDQW5ubvr999917dq1HNfu4eGhKlWq6L333lNERIRKliyp9evXZ/nl//XXX9egQYPUu3dvDRgwQK6urgoPD9fvv/+un3/+2axvr1699OKLL0q6eUutnHByctK8efM0bNgw9erVSz169FDt2rUlSUeOHNGaNWtMt69zcXHR8OHDNXfuXP3f//2fAgICdObMGS1evFg+Pj53vbBeXoSFhWnEiBFq2bKl9u/fb7qdWcbRzubNm8vGxkYjRozQwIEDFR8fr+XLl6tMmTK6cuVKnvfbtWtXNW7cWLVr15azs7MOHjyo9evXm10IceLEiXruuec0YMAA9e3b13T7uVKlSmnUqFH3/Nylm+dbT5gwQZMnT9aTTz6prl27mm4/V7ly5Xy7NZukbKe23yogIED+/v6aNWuWwsPD5e3trW3btmnjxo166qmnTOfE16xZU926ddPixYsVGxurevXqaefOnZnuUy5JL730knbt2qX+/furX79+ql69uqKjo3X48GHt2LFDf/31V749x5yoUqWKxo4dq48++kjh4eFq3769HBwcFBYWpt9++039+/fX0KFDZWNjo7Fjx2rq1Kl66qmn1KVLF4WFhWnVqlV3PUdeytl7O+O9OGvWLHXp0kU2NjZq27ZtlrM9hg0bprVr1+q5557T4MGD5eTkpJ9++klhYWGaM2dOtlPps7Nz50699dZb6tSpk6pVq6a0tDT9/PPPph9fAKCwI8gDQCG0evVqFS9e3HRP79tZWVmpTZs2CgoK0vXr11W6dGl9//33mjNnjjZs2KAff/xRZcqUUdOmTVWhQgVJN48OLly4UPPnz9eaNWv066+/ytnZWfXr1zc7T/f1119Xamqqli5dKltbW3Xq1EkTJ06869T0DDY2Nvrss89M55sWL15cHTp00BNPPKGePXua9a1Ro4Z++OEHffLJJ1qyZImSkpJUqVKlLKfNt23bVk5OTkpPT1e7du1y+lKqbt26CgoK0hdffGEKEVZWVvLw8NCwYcPMwuvo0aPl4uKiRYsW6d1335WTk5P69++v8ePHZ3s6wr34+OOP9cknn+ijjz5SsWLF9OSTT2rixImm5R4eHpo9e7Y+/vhj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TRuNHDlSv/zyi4xGo1n/VatWydvbW5GRkXnaHwrGnDlzzP7O/v7+GjRokLZs2fLAa+nZs6cmTZr0wPeblX379unFF19UixYtVKdOHfn7+2vIkCFaunSpkpOTC7o8FBK3v3+aNGmiIUOG5Pkz+3aHDx9W//79VbduXXl7eysmJkYBAQF66623TH3u5XN+165d8vb21sGDB+/Yb86cOapXr16e9oHC7U7fM3I6Pgq706dP66WXXlLz5s1Vp04dtWvXTjNnzlRUVFRBl/bQIMgjx06ePKnjx49LkoKCgvK0jd9++01Lliy5pzrs7e0VHBycqX3NmjWyt7e/p20DhWWcjx8/XsuWLdMXX3yhcePGqVixYho7dqxGjhyp1NTUe9o2CocSJUpo2bJlWrZsmd5++20lJSVpxIgR2rt3b0GXViAWL16sxx9/XNevX9eECRP01Vdf6Z133lG1atX0zjvvaNWqVQVdIgqRW98/b775pqKiovT000/rxIkT97zt6dOnKy0tTf/973+1bNkyOTg4aO7cuXr22WdNffLjcx5FU358zyjs/v77b/Xt21enTp3SxIkT9eWXX+qpp57Szz//rAEDBujKlSsFXeJDgSCPHAsKCpKVlZX8/f21bt06paSkFEgd7dq104YNG5SUlGRqu3z5sv7++2+1b9++QGrCw6OwjPOqVavKz89PjRo1Us+ePTV79my99dZb2rRpkxYuXFggNSF/WVlZyc/PT35+furYsaPmz58vo9Gon376qaBLe+COHTumd955R7169dK3336rXr16qVGjRmrfvr3eeustBQUFqWrVqgVdJgqRW98/nTp10meffabU1FQtXbo0U1+j0ZirGR0hISFq1aqVmjRpIj8/P1lbW6tWrVpydXXNz6eAIqqwfM+4X27cuKHx48erUqVKWrJkiXr27KnGjRtryJAhWrRokS5cuGA2u+V+SktLe+he31sR5JEjRqNRa9asUZMmTfTMM88oKipKf/zxh2l5dtN7b52yOWnSJP344486efKkaTrcrdM5f/31V/Xs2VM+Pj5q0aKF3n33XbOwnqFVq1YyGAxmU1CDg4NVpUoV1a5dO7+fOoqQwjTOszJgwAD5+PjkeTonCrcKFSrIxcVFFy5cMLVdvnxZkydPVrt27eTr66uOHTvqP//5T6ZQ4u3trYULF2rOnDlq1qyZ/P39NXnyZCUkJJj127t3r/r06SMfHx9169Yt26n8dxunGVM///jjD40ZM0b16tVTmzZtTEeXvv32W7Vp00aNGzfWa6+9dtcQ9e2338rKykqvvPKKDAZDpuXVqlVT06ZNTY8HDx6s4cOHm/U5evSovL29tWvXLlOb0WjUF198ocDAQNPUzq+//tpsvUuXLmnMmDFq1qyZfHx8FBAQoBkzZuR4OQqHSpUqycXFRWFhYZo0aZJpfPfo0UM+Pj7atGmTpDuP7YxxHRUVpU8//VTe3t4aPHiwJJlNrb/T5/y+ffs0YsQItWjRQn5+furZs2e2P85FRkZq1KhR8vPzU4sWLfTZZ5/d9XnGxMTozTffNJ1+0qdPH/3555/3+vLhAbnb94ysvPrqq3r88cdNjyMjI1WjRg099thjprb4+HjVrl1bv/zyi6Sb09rHjRun1q1bq27duurSpYu+/PJLpaenm9bp06ePXnrppUz7++CDD9SiRQulpaVJkhYsWKAOHTrIx8dHTZo00dNPP63Q0NBs6123bp0uX76sESNGZJop6+npqZ49e2rDhg0KDw9XQkKC/Pz8sjwF9sUXX9SAAQNMj3My9jP+2/Djjz8qMDBQPj4+OnbsWLa1WrpiBV0ALMPevXsVHh6uF154QS1atJCzs7PWrFmjgICAHG9j5MiRioyMVEhIiD788ENJkouLiyRp48aNevHFF9W1a1e99NJLCgkJ0axZs3Tx4kXNnj3bbDu2trbq0KGD1qxZo44dO0q6Oa2+W7du+fRsUVQVpnGenebNm+uzzz5TeHi4KleunPsniUIrPj5e0dHRZkf9rl+/LmdnZ02ePFmOjo46e/as5syZoytXrujdd981W//7779XgwYNNHPmTJ09e1bvv/++ypQpowkTJkiSrly5oqFDh8rb21sff/yxYmJiNG3aNCUkJKhmzZqm7eRmnL755pvq3bu3+vfvrx9++EETJ07UsWPHdPLkSU2bNk2hoaGaOXOm3NzcNGLEiGyf+19//aU6derI2dk5H17Jf73zzjtavny5RowYobp162rv3r368MMPVbx4cQ0aNEiSNHHiRF2+fFmvv/66ypQpo4sXL+rQoUOmbdxtOQqHuLg4RUVFqXz58kpNTdXly5c1ffp0Pf/886pYsaIqVap017Fdu3ZtLVu2TM8884y6dOmifv36qWTJkpn2dafP+QsXLqh+/foaNGiQbG1ttXfvXr3++usyGo3q3bu32XamTJmirl27as6cOdq+fbtmzZolJycn09i8XXJysp555hldu3ZNY8eOVYUKFbR69WoNHz7c9EMzCre8fM9o1KiRgoKClJSUpOLFi2v37t2ytbXV0aNHFRcXp5IlS2rfvn1KTU1Vo0aNJN38Edjd3V3du3eXg4ODjh49qjlz5ighIUGjRo2SJPXr108zZ85UbGysSpUqJenmEeyff/5ZvXv3lrW1tX766Sd98sknevHFF+Xn56fY2Fjt2bNH8fHx2db7119/SZLatm2b5fKAgAAtX75ce/bsUY8ePRQQEKC1a9dq6NChpj5xcXH6/fff9fLLL0vK3dg/dOiQwsPDNWbMGDk6OqpixYo5+dNYJII8cmTNmjUqXry4OnbsKBsbGwUGBmr16tWKj4+Xg4NDjrZRpUoV09EmPz8/s2Vz586Vn5+fPvroI0k3j7rb2dlp6tSpOn78eKb/OHXr1k0jR45UfHy8rl27poMHD+qDDz4okAtF4eFR2MZ5VjL+g3T16lWC/EMg43oHly9f1gcffCAHBwcNGTLEtNzb21uvvPKK6XH9+vVlZ2enSZMmaerUqbKzszMtK1eunNnYOnLkiNavX28K8t98840MBoMWLlxo+tL2yCOP6OmnnzarKTfjtFOnTqYvhb6+vtqwYYPWrl2rDRs2yMbGRtLNL3Xr1q27Y5C/fPmyfH19s319pJtTqa2scj6R8Pz581q0aJGmTZtmOqrTrFkz3bhxQ/PmzdOAAQNkZWWlgwcPavz48erSpYtp3V69epn+fbflKDgZ4+PSpUt67733lJaWpsDAQK1du1bR0dFauHCh6tata+o/duzYu47tjKn0jzzySKbP8Ax3+pzv2rWr6d9Go1GNGjVSRESEli1blinIN2nSxPT+btmypa5du6b58+ebxubtgoKCdOzYMf3888+qXr26ab1z587p008/1SeffJK7FxAPXF6+ZzRs2FDJycn6559/1LhxY/3999/q0KGD/vzzT+3du1etWrXS33//rWrVqqls2bKSpKZNm5pmMRmNRjVo0EA3btzQokWLTJ/Z3bt313vvvaegoCDTEf8tW7boypUrpqP9Bw4ckLe3t9kMqLudxhoRESFHR8csfwSTbs6ekW6+b6Wb75mRI0fq7NmzqlatmqSb16BI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tZWLi4tq1aqlTz75RIGBgVlOG3v11VcztY0ZM0YjR47My0uCAuLh4aEuXbpoyZIlGj58uOl8wYzbvgUGBur111+/4xXgs1O+fHktXLhQ06dPN43fqVOnatasWWb97vfncXYef/xx1ahRQ1999ZU++OADRUVFycHBQTVq1NC4cePMroXSqlUrvfzyy1q0aJF+/PFHtWrVStOmTct0Bf7XX39d7u7uWrZsmebNmycHBwe5u7ubpo4WL15cXl5e+u6773Tx4kWVKFFCderU0RdffCEXFxclJyffcTksS36O7ew+5z/66CNNnTpVkyZNkrOzswYPHqyEhAR9+eWXmbbx1ltvadmyZVqyZIkcHBw0ZswY0ymDWbG1tdW3336rOXPm6LPPPtOVK1fk7OysWrVqmd1nHIVPTr9nnD9/PtttNGzYUOvXrze7qF2jRo20detWszbp5q0N33jjDb399tuys7NT79691aFDB9OPALdydnZW48aNdenSpUx3YahXr55++OEHLV++XImJiXJzc9PkyZPNzsfPSqNGjbRixQp9+umnmjlzpmJiYlS+fHl1795dI0aMyPJWo926ddN3330ne3v7TLeuY+xnzWDMuAQyAAAAAKDIiIuLU8uWLTV69Gg9++yzBV0OcoEj8gAAAABQhMTFxen06dNavHixDAaD+vTpU9AlIZcI8gAAAABQhBw+fFhDhgxRxYoV9d5772U53R2FG1PrAQAAAACwIFlfFhMAAAAAABRKBHkAAAAAACwIQR4AAAAAAAtCkAcAAAAAwIIQ5AEAAAAAsCAEeQAAAAAALAhBHgAAAAAAC0KQBwAAAADAghDkAQAAAACwIP8PdwpHgBq3TfEAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "accuracy_comparison_bar_chart(\n", - " graph_df[\"actual_result_hv\"],\n", - " graph_df[\"vegas_line_hv\"],\n", - " {\"AutoML\": graph_df[\"automl_cls_pred\"], \"AutoDL\": graph_df[\"autodl_cls_pred\"]},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Classification - Time Series Accuracy - Line Chart" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "def accuracy_time_series_chart(\n", - " actual,\n", - " vegas_predictions,\n", - " model_predictions,\n", - " dates,\n", - " seasons,\n", - " aggregate_by=\"month\",\n", - " show_baselines=True,\n", - "):\n", - " \"\"\"\n", - " Displays a line chart showing the accuracy of each model's binary predictions over time,\n", - " including overall accuracy in the legend and optional baseline accuracies.\n", - " The accuracy is displayed as a percentage. X-axis labels formatted as 'MM-YYYY'.\n", - "\n", - " Parameters:\n", - " actual (list or array): Actual results of the games.\n", - " vegas_predictions (list or array): Vegas predictions.\n", - " model_predictions (dict): Dictionary of model binary predictions with model name as key.\n", - " dates (list or array): Dates of the games.\n", - " seasons (list or array): Seasons of the games.\n", - " aggregate_by (str): Aggregation method - 'month' or 'season'. Default is 'month'.\n", - " show_baselines (bool): Whether to show baseline accuracies. Default is True.\n", - " \"\"\"\n", - "\n", - " # Create a DataFrame for the data\n", - " data = pd.DataFrame(\n", - " {\n", - " \"Date\": pd.to_datetime(dates),\n", - " \"Season\": seasons,\n", - " \"ActualGreaterThanVegas\": np.array(actual) > np.array(vegas_predictions),\n", - " }\n", - " )\n", - "\n", - " # Plotting\n", - " plt.figure(figsize=(15, 8))\n", - "\n", - " # Calculate and plot accuracy for each model\n", - " for model_name, predictions in model_predictions.items():\n", - " # Calculate accuracy\n", - " model_accuracy = (\n", - " np.array(predictions) == data[\"ActualGreaterThanVegas\"]\n", - " ).astype(int)\n", - " overall_accuracy = np.mean(model_accuracy).round(4)\n", - "\n", - " # Add to DataFrame for time series\n", - " data[model_name + \"_Accuracy\"] = model_accuracy\n", - "\n", - " # Group data by month or season\n", - " grouped = (\n", - " data.groupby(data[\"Date\"].dt.to_period(\"M\")).mean()\n", - " if aggregate_by.lower() == \"month\"\n", - " else data.groupby(\"Season\").mean()\n", - " )\n", - "\n", - " # Plot with overall accuracy in label\n", - " label = f\"{model_name} (Overall Accuracy: {overall_accuracy*100:.2f}%)\"\n", - " ax = grouped.plot(\n", - " y=model_name + \"_Accuracy\", label=label, marker=\"o\", ax=plt.gca()\n", - " )\n", - "\n", - " # Convert index to timestamp for plotting\n", - " ax.set_xticks([i.to_timestamp() for i in grouped.index])\n", - " ax.set_xticklabels(\n", - " [i.to_timestamp().strftime(\"%m-%Y\") for i in grouped.index], rotation=45\n", - " )\n", - "\n", - " # Baselines\n", - " if show_baselines:\n", - " # Random Guess at 50%, Profitable at 52.4%, Always Over\n", - " plt.axhline(y=0.5, color=\"gray\", linestyle=\"--\", label=\"Random Guess (50%)\")\n", - " plt.axhline(y=0.524, color=\"blue\", linestyle=\"--\", label=\"Profitable (52.4%)\")\n", - " always_over_accuracy = np.mean(data[\"ActualGreaterThanVegas\"]).round(4)\n", - " plt.axhline(\n", - " y=always_over_accuracy,\n", - " color=\"green\",\n", - " linestyle=\"--\",\n", - " label=f\"Always Over ({always_over_accuracy*100:.2f}%)\",\n", - " )\n", - "\n", - " # Set y-axis to display percentage\n", - " plt.gca().yaxis.set_major_formatter(mtick.PercentFormatter(1.0, decimals=0))\n", - " plt.legend()\n", - " plt.xlabel(\"Date\" if aggregate_by.lower() == \"month\" else \"Season\")\n", - " plt.ylabel(\"Accuracy (%)\")\n", - " plt.title(\n", - " f\"Model Prediction Accuracy Over Time (Aggregated by {aggregate_by.capitalize()})\"\n", - " )\n", - " plt.grid(True)\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABQUAAALzCAYAAACobGGKAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3zT1foH8E9W06Z770Fbugste09ZZasIbi+O63Xhus57vderolf9XRW3uBeislpAhgxB9u6ge6Z7z3RkfH9/hAZK07LaJqWf9+vVFyTfJ9+cJCenydPnnCMSBEEAERERERERERERDRhiUzeAiIiIiIiIiIiI+haTgkRERERERERERAMMk4JEREREREREREQDDJOCREREREREREREAwyTgkRERERERERERAMMk4JEREREREREREQDDJOCREREREREREREAwyTgkRERERERERERAMMk4JEREREREREREQDDJOCREQ0YISGhuL999+/4tsVFhYiNDQU69ev74VW9aw777wTd955p+Fyb7R92rRpeO6553rsfEQ94ciRIwgNDcWRI0dM3ZQOmpqaMHbsWMTHx5u6KQPW5Y6D69evR2hoKJKSkvqoZear/f20bdu2S8Y+8cQTWLFiRR+0ioiIehqTgkRE1Kfav3SFhobi+PHjnY4LgoDJkycjNDQUf/3rX03QwqvX/iWq/ScyMhLTp0/HM888A6VSaermXZGTJ0/i/fffR319vambYtQPP/yA0NBQLFmyxNRN6ZeKi4vx0ksvYdq0aYiKisLYsWPx0EMP4cSJE6ZuWgfPPfdch/dUVz/mnKT+9ttvYW1tjblz5xo9/uabbyI0NBSPP/543zbMDP3www/94o8v16q9Xw8bNgwtLS2djufl5Rn69hdffNGrbUlISMDXX399Tee4//77sWPHDqSlpfVMo4iIqM9ITd0AIiIamORyOTZv3owRI0Z0uP7o0aMoLS2FhYWFiVp27e68805ER0dDo9Hg7NmzWLt2Lf744w/Ex8fD3d29T9vi7e2NxMRESKVX9iv/1KlT+OCDD7B48WLY2dl1OLZt2zaIRKKebOYVS0hIMDy2/Px8+Pv7m7Q9/cmJEyfwwAMPAACWLFmCoKAgVFZWYsOGDbj99tvx4osvdqg2NaWlS5di7NixhsuFhYVYtWoVli5diuHDhxuu9/Pzw9ChQ5GYmAiZTGaKphqlVqvx7bff4p577oFEIul0XBAEbNmyBd7e3tizZw8aGxthY2NjgpaahzVr1sDR0RE33nijqZvS66RSKVpaWrB7927ExcV1OJaQkAC5XI7W1tZeb8fmzZuRmZmJe+6556rPERERgaioKHz55Zd48803e65xRETU61gpSEREJjF58mRs27YNGo2mw/WbN29GZGQkXF1dTdSyazdixAgsXLgQN910E/75z3/i2WefRW1tLTZu3NjlbVQqVa+0RSQSQS6XG01IXC0LCwuTJl6USiVOnTqF559/Hk5OTkhISDBZWy6lt17Xq1VXV4cVK1bA0tISGzZswHPPPYclS5bgb3/7G+Lj4zF8+HCsXLkSJ0+e7NN2tba2QqfTdbo+NjYWCxcuNPxMmjQJABATE9Ph+tjYWIjFYsjlcojF5vPxdu/evaiursacOXOMHj9y5AhKS0uxcuVKaLVa7Ny5s49beF5XrwH1DgsLC4wdOxZbtmzpdGzz5s2YMmVK3zfqGsyZMwc7d+5EU1OTqZtCRERXwHw+NRER0YAyd+5c1NbW4sCBA4br2trasH37dsyfP9/obVQqFd544w1MnjwZUVFRmDVrFr744gsIgtAhrq2tDStXrsSYMWMQGxuLBx98EKWlpUbPWVZWhueffx7jxo1DVFQU5s6di19//bXnHiiAMWPGANBXOQHA+++/j9DQUGRlZeGpp57CyJEjcdtttxniN23ahBtvvBFDhgzBqFGj8MQTT6CkpKTTedeuXYsbbrgBQ4YMwc0332x0OnZXa2llZ2djxYoVGDNmDIYMGYJZs2bhnXfeMbSvvdpj+vTphmls7e03tqagUqnEY489hlGjRmHo0KG45ZZbsHfv3g4x7dOrt27dio8//hiTJk1CdHQ07r77buTn51/285mQkAB7e3tMnjwZs2bN6jIpWF9fj5UrVxqmyE6aNAnPPPMMqqurDTGtra14//33MWvWLERHR2PChAl45JFHUFBQ0KHNF69TZ+x5fe655xAbG4uCggLcf//9iI2NxdNPPw0AOH78OB577DFMmTIFUVFRmDx5MlauXGl06mB3r83hw4cRGhpqNHmUkJCA0NBQnDp1qsvnbu3ataioqMDf//53+Pn5dThmaWmJN954AyKRCB9++CEAICkpCaGhodiwYUOnc+3fvx+hoaHYs2eP4brLeT+1P6dbtmzBO++8g4kTJ2Lo0KFobGzsst2Xw9hrdeedd2LevHlIS0vDHXfcgaFDh2LGjBmGddKOHj2KJUuWGJ7ngwcPdjrvtYwRv//+O7y9vTs91+0SEhIQHByMMWPGYOzYsV325aKiIjz44IOIiYnB2LFjsXLlSsPzf3Hf/OGHHzB9+vQO48LFa41e6jU4c+YM7r33XgwfPhxDhw7FHXfcYXRq+ZEjR3DjjTciOjoaN9xwA3766SfD+HahdevW4a677sLYsWMRFRWFuLg4/Pjjjx1ipk2bhszMTBw9etQw5lzY5vr6erz22muG8X/GjBn47LPPOiUy6+vr8dxzz2H48OEYMWIEnn32WTQ0NBh9XrvS0tKCl156CaNHj8awYcPwzDPPoK6uznD82WefxejRo6FWqzvddvny5Zg1a9Zl3c+8efOwb9++Dss0JCYmIi8vD/PmzTN6m54ca++8807s3bsXRUVFhud82rRpHc6l0+kua7weN24cVCqV0fcQERGZL04fJiIik/D29kZMTAy2bNmCyZMnAwD27duHhoYGxMXF4bvvvusQLwgC/va3v+HIkSO4+eabER4ejv379+PNN99EWVkZXnjhBUPsiy++iPj4eMybNw/Dhg3D4cOHDdMlL1RZWYlbbrkFIpEIt99+O5ycnLBv3z68+OKLaGxsvKbpVBdqTzA5ODh0uH7FihXw9/fHE088YUhsfvzxx3jvvfcwZ84c3Hzzzaiursb333+P22+/HRs3bjRM5f3ll1/w0ksvITY2FnfffTeUSiX+9re/wd7eHp6ent22Jy0tDbfffjukUimWLl0Kb29vFBQUYPfu3XjiiScwY8YM5OXlYfPmzXj++efh6OgIAHBycjJ6vsrKSixbtgzNzc2488474ejoiA0bNuBvf/sbVq1ahRkzZnSIX716NUQiEZYvX47GxkZ8/vnnePrpp/HLL79c1vOZkJCAGTNmwMLCAvPmzcOaNWuQmJiIIUOGGGKamppw++23Izs7GzfddBMiIiJQU1OD3bt3o6ysDE5OTtBqtfjrX/+KQ4cOYe7cubjrrrvQ1NSEAwcOICMjo8tETnc0Go0hmfLss8/C0tISgH7KdUtLC2699VY4ODggMTER33//PUpLS7Fq1SrD7S/12owePRqenp6G5+Di58XPzw+xsbFdtm/37t2Qy+Wdpiu28/X1xfDhw3HkyBG0tLQgOjoavr6++O2337B48eIOsVu3boW9vT0mTJgA4MrfTx999BFkMhnuvfdetLW19Vr1aV1dHR588EHExcVh9uzZWLNmDZ588knodDqsXLkSy5Ytw7x58/DFF1/gsccew969ew1TeK91jDh16hQiIyONHmtra8OOHTvwl7/8BYD+DyUvvPACKioqOlRKq1Qq3H333aioqMBdd90FFxcXbN682eiGKj/++CP+85//YMSIEbjnnntQVFSEhx9+GHZ2dvDw8OgUb+w1OHToEO6//35ERUXhkUcegUgkwvr163H33Xfjxx9/NLzPzp49i/vuuw+urq549NFHodPp8OGHHxodJ9asWYPBgwdj2rRpkEql2LNnD15++WUIgoDbb78dAPDCCy/glVdegUKhwIMPPggAcHFxAQA0NzfjjjvuQFlZGZYtWwZPT0+cOnUK//vf/1BRUYEXX3wRgP73RPvamMuWLUNQUBB27tyJZ599ttvX6WL/+c9/YGdnh0ceeQS5ublYs2YNiouL8d1330EkEmHhwoXYuHEj/vzzT0ydOtVwu4qKChw+fBgPP/zwZd3PjBkz8K9//Qs7duzAzTffDEBfJRgYGIiIiIhO8T091j744INoaGhAaWkpnn/+eQCAtbX1FZ2jXXBwMCwtLXHy5MlO7SAiIjMmEBER9aF169YJISEhQmJiovD9998LsbGxQnNzsyAIgvDYY48Jd955pyAIgjB16lThgQceMNxu586dQkhIiPDRRx91ON+jjz4qhIaGCvn5+YIgCEJqaqoQEhIi/Pvf/+4Q9+STTwohISHCqlWrDNe98MILwvjx44Xq6uoOsU888YQwfPhwQ7uUSqUQEhIirFu3rtvHdvjwYSEkJET49ddfhaqqKqGsrEzYu3evMHXqVCE0NFRITEwUBEEQVq1aJYSEhAhPPvlkh9sXFhYK4eHhwscff9zh+vT0dCEiIsJwfVtbmzB27Fhh4cKFQmtrqyFu7dq1QkhIiHDHHXcYrjPW9ttvv12IjY0VioqKOtyPTqcz/P/zzz8XQkJCBKVS2elxTp06VXj22WcNl1977TUhJCREOHbsmOG6xsZGYdq0acLUqVMFrVbb4fmZM2dOh3Z/8803QkhIiJCent7VU2uQlJQkhISECAcOHDC0edKkScKrr77aIe69994TQkJChB07dnQ6R/vj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BgbCz88P//3vf1FWVgYbGxts377daPLjcl6bdosWLTJUk61YseKy2uLo6IhVq1bhgQcewOLFi7FkyRIEBQWhsrISGzZsQH5+Pl588UUMGzas023j4uKwatUqyOVy3HzzzZ1eV3N7P/WEa31M06dPx6ZNmzqsEZiQkABBEDpsHHKhyZMnQyqVIiEhAUOHDsXSpUvx/fff46mnnsJdd90FV1dXJCQkGJYfaK/0srCwwKOPPopXXnkFd999N+bMmYOioiKsX7++0zqDXRGLxXj11Vdx//33Y968ebjxxhvh7u6OsrIyHDlyBDY2Nvjkk08AAI888gj+/PNP3Hrrrbj11luh0+nw/fffY/DgwUhNTTWcc/z48ZDJZHjwwQexbNkyNDU14ZdffoGzszMqKio63H9kZCTWrFmDjz76CP7+/oZ1Z++9917s3r0bDz74IBYvXozIyEg0NzcjIyMD27dvx65du+Dk5IRp06Zh2LBhhjE0ODgYO3bsuOKkl1qtxj333IM5c+YgNzcXP/74I4YPH97pNWtPzm/btg12dnaYMmXKFd3Phc/7Qw89dMm43hhrIyMjsXXrVrz++uuIjo6GQqG4qmq/gwcPwsrK6rqpFCYiGiiYFCQion5BLBbj448/xqpVq7B161asX78e3t7eeOaZZ7B8+fIOsStXroSjoyMSEhKwa9cujB49Gp999hkmT57cIc7FxQW//PILPvzwQ+zcuRNr1qyBg4MDgoOD8fTTT/flwzN44IEHEBAQgK+//hoffvghAP1GCePHj+/wRW3p0qXQarX44osv8OabbyIkJAQff/wx3nvvvUveR1hYGH7++We89957WLNmDVpbW+Hl5dVh+umQIUOwYsUK/PTTT9i/fz90Oh127dplNCno4uKCn376CW+99Ra+//57tLa2IjQ0FJ988slVf0m+WHx8PORyOcaPH2/0uFgsNuzcWlNTA0dHR/zwww94//33sXPnTmzYsAHOzs4YO3asYbdUiUSC1atX4+OPP8bmzZuxY8cOODg4YNiwYR2mWv7jH/+ARqPBTz/9BAsLC8yePRvPPPMM5s2bd1ltl8lk+OSTT/Dqq6/i008/hVwux4wZM3D77bd3msJ7Oa9Nu6lTp8Le3h46na7LBJMxI0aMQHx8PD799FNs27YNFRUVsLGxQWxsLF577bUOO5teKC4uDu+++y6a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ApxOJ9HR0cycOZNrr70WT09P/Pz88Pf35/nnn6eoqAgPDw/69+/PSy+9xNixY497vh07drBs2TIWLVp0zO2PPfYYf/3rX7nxxhtJTEzk0UcfPVMvUURERERERESkS4sOSQRMFFVmU1Vbhq9XoNGRuiSXKgXnzp3L3LlzT3o9ODiY559//pSfr3///qxZs+a42xMSEnjnnXdalFFERERERERERFrOx9OfsAArheVZ2IrT6NNtuNGRuiSX31NQREREREREREQ6l5iQpm3esuxpBifpulQKioiIiIiIiIjIGWW1NJWCh0v20+CoNzhN16RSUEREREREREREziiLX3d8PQNpcNSRV5pudJwuSaWgiIiIiIiIiIicUSaTCeuRJcQ2LSE2hEpBERERERERERE5444uIc6yp+F0Og1O0/WoFBQRERERERERkTMuKrgXZpM7FbV2SqsLjI7T5agUFBERERERERGRM87DzZOo4HgAbPbdBqfpelQKioiIiIiIiIiIIY7uK5ilfQXPOJWCIiIiIiIiIiJiiKP7CuaXZVDbUGVwmq5FpaCIiIiIiIiIiBgiwNtCsG8EThzkFO8zOk6XolJQREREREREREQMYw1JBrSE+ExTKSgiIiIiIiIiIoaxWhIByC7eg8PpMDhN16FSUEREREREREREDBMR2BNPN29qG6ooLM8yOk6XoVJQREREREREREQMYza5ER3SNFvQpiXEZ4xKQRERERERERERMdTRU4iz7LsNTtJ1qBQUERERERERERFDRYf0wYSJ4qpcKmtLjI7TJagUFBERERERERERQ3l7+BEe2APQKcRnikpBERERERERERExnDWkaQmx9hU8M1QKioiIiIiIiIiI4WIsyQAcLj1AQ2O9wWk6P5WCIiIiIiIiIiJiuGDfSPy8gmh01JNbesDoOJ2eSkERERERERERETGcyWTCGtI0W1D7CrY/lYIiIiIiIiIiIuISrJYj+woWp+F0Og1O07mpFBQREREREREREZcQFZSAm9mDytoSSqryjI7TqakUFBERERERERERl+Du5kFUUAKgJcTtTaWgiIiIiIiIiIi4jP8tId5tcJLOTaWgiIiIiIiIiIi4DGtIUylYUJZJTX2lwWk6L5WCIiIiIiIiIiLiMvy9gwnx7YYTJ9nFe42O02mpFBQREREREREREZfy01OIpX2oFBQREREREREREZcSY0kGINu+B4ez0eA0nZNKQRERERERERERcSlhATF4uftS11hDQVmm0XE6JZWCIiIiIiIiIiLiUswmM9EhiQBk2bWEuD2oFBQREREREREREZejfQXbl0pBERERERERERFxOdEhvTFhpqQqj/Iau9FxOh2VgiIiIiIiIiIi4nK83H2JCOwJgM2+x+A0nY9KQRERERERERERcUkxzUuIdxucpPNRKSgiIiIiIiIiIi7p6L6Ch0sOUt9YZ3CazkWloIiIiIiIiIiIuKQgnwj8vSw4nA0cLtlvdJxORaWgiIiIiIiIiIi4JJPJhNWSCOgU4ramUlBERERERERERFxWjCUZAJs9DafTaXCazkOloIiIiIiIiIiIuKzIoDjczR5U1ZVhrzxsdJxOQ6WgiIiIiIiIiIi4LHezB1HBvQGw2XUKcVtRKSgiIiIiIiIiIi4t5sgpxLbiPQYn6TxUCoqIiIiIiIiIiEuLDmk6bKSgPIua+gqD03QOKgVFRERERERERMSl+XkFYfHrDjix2TVbsC2oFBQREREREREREZf3vyXEaQYn6RxUCoqIiIiIiIiIiMuzHikFs4v34nA0Gpym41MpKCIiIiIiIiIiLi/M34q3hx/1jbXklR0yOk6Hp1JQRERERERERERcnslkbj5wREuIW0+loIiIiIiIiIiIdAgxlmQAsuwqBVtLpaCIiIiIiIiIiHQI3YN7YzKZKasuoKy60Og4HZpKQRERERERERER6RA83b3pFhgHgE2zBVtFpaCIiIiIiIiIiHQYR08hthXvMThJx6ZSUEREREREREREOoyjpWBu6UHqG2oNTtNxqRQUEREREREREZEOI8gnnADvUBzORnJK9hkdp8NSKSgiIiIiIiIiIh1KTPMSYu0r2FIqBUVEREREREREpENp3lfQvgen02Fwmo7JpUrB5cuXM3v2bEaOHEn//v2ZOHEiDz30EOXl5cfc79tvv+XCCy8kJSWFKVOm8NFHHx1zvaGhgfvuu4/hw4dz7rnnsnz58uN+1pw5c3j11Vfb8+WIiIiIiIiIiEg7iAyMw93Nk+r6cooqcoyO0yG5VClYUlJCamoq8+fP5+WXX+Y3v/kNn376KTfddFPzfTZs2MCNN97IwIEDeemllzjvvPO4++67Wbx4cfN9PvroI7799lseeeQRxo8fz6233kpJSUnz9UWLFlFYWMjs2bPP5MsTEREREREREZE24GZ2Jzq4N6AlxC3lbnSAn5oxY8Yxfx4xYgSenp7cc8895OXlERkZyfPPP09qair/+Mc/ABg5ciRZWVk888wzTJ06FYBVq1ZxxRVXcM455zB27Fg+/PBDtm7dyvjx46mpqeHRRx/l/vvvx93dpV6+iIiIiIiIiIicIqslmYyinWTZ0xjYY5LRcTocl5opeCLBwcEA1NfXU1dXx9q1a5vLv6OmTZvGgQMHsNlsANTV1eHt7Q2Au7s7np6e1NXVAbBgwQL69u3L6NGjz9yLEBERERERERGRNhUdkghAUYWNqrryX7i3/P9cshRsbGyktraWnTt38txzzzFhwgSsViuZmZnU19cTHx9/zP0TEhIAOHjwIAApKSl89tlnFBYW8umnn1JeXk5ycjLZ2dm8+eab3HnnnWf8NYmIiIiIiIiISNvx9QwgzN8KQLZdS4hPl0uunz3nnHPIy8sDYOzYsfzzn/8EoLS0FIDAwMBj7n/0z0evz5kzh+XLlzN69GhMJhN/+tOfsFqtzJs3j8svv5yYmJgz8jqqq6vPyM+Rzufo2NEYkpbSGJK2oHEkraUxJK2lMSStpTEkbUHjyLVFBiRQWGHjUOFOogP7GR3nhJxOJyaTyegYx3HJUnDBggVUV1ezf/9+nn/+ea6//nr++9//nvLjAwICeO+997DZbAQEBBAcHMyaNWvYvn07jz76KOnp6fztb38jLS2NpKQkHnjgAXr06NHmr+PQoUNt/pzStWgMSWtpDElb0DiS1tIYktbSGJLW0hiStqBx5JpqHR4AHC7Zz85dOzCb3AxOdGKenp5GRziOS5aCSUlJAAwaNIiUlBRmzJjB0qVL6dWrFwDl5ceuEy8rKwMgKCio+TaTydQ8I7ChoYEHHniAO+64Ax8fH26//XYGDhzIggULeOyxx7j99tt577332vx1xMbG4uPj0+bPK51fdXU1hw4d0hiSFtMYkragcSStpTEkraUxJK2lMSRtQePItTmdTmzbf6SmoYIwqy+RgfG//KAzbN++fUZHOCGXLAV/KjExEQ8PDzIzM5kwYQIeHh4cPHiQsWPHNt/n6F6C//9eg0e99dZbhISEMG3aNCoqKti+fTsPPvggPj4+zJo1iwsuuIDKykr8/PzaNLuPjw++vr5t+pzStWgMSWtpDElb0DiS1tIYktbSGJLW0hiStqBx5LpiQpPYl7eB/Kp04rr1NzrOcVxx6TC46EEjP7V161bq6+uxWq14enoyYsQIlixZcsx9Fi5cSEJCAlar9bjH2+12/v3vf3P33Xcfc3tNTQ3wvz0BnE5nO70CERERERERERFpL9aQphWnNnua+p3T4FIzBW+88Ub69+9PYmIi3t7epKWl8fLLL5OYmMikSZMAuOGGG5gzZw5///vfOe+881i7di1ffvklTz755Amf84knnmDatGnNS5L9/f3p168fTz/9NNdccw3/+c9/SElJwd/f/4y9ThERERERERERaRvdg3tjNrlRXlNEWXUhQb7hRkfqEFyqFExNTWXhwoUsWLAAp9NJdHQ0M2fO5Nprr23ekHHo0KE8++yzPPXUU3z44Yd0796d+++/n/POO++459uxYwfLli1j0aJFx9z+2GOP8de//pUbb7yRxMREHn300TPy+kREREREREREpG15uHvRLSienJJ9ZNl3qxQ8RS5VCs6dO5e5c+f+4v0mTpzIxIkTf/F+/fv3Z82aNcfdnpCQwDvvvNOijCIiIiIiIiIi4lqsliRySvZhK06jv3Wc0XE6BJffU1BEREREREREROTnHN1XMK/sEHUNNQan6RhUCoqIiIiIiIiISIcW6BNKkE84TqeDnJK9RsfpEFQKioiIiIiIiIhIh2e1NM0WzLKnGZykY1ApKCIiIiIiIiIiHd7RJcTZxXtwOB0Gp3F9KgVFRERERERERKTDiwyMxcPNm5r6SgrLbUbHcXkqBUVEREREREREpMMzm92IDukDgK1YS4h/iUpBERERERERERHpFKwhiQDY7LsNTuL6VAqKiIiIiIiIiEinYLUkAibslYeprC01Oo5LUykoIiIiIiIiIiKdgreHP+EBMYCWEP8SlYIiIiIiIiIiItJpWC1NpxDb7CoFf45KQRERERERERER6TSsIU2l4OGS/TQ46g1O47pUCoqIiIiIiIiISKdh8YvC1zOQBkc9uaUHjY7jslQKioiIiIiIiIhIp2EymbSE+BSoFBQRERERERERkU4lJuR/paDT6TQ4jWtSKSgiIiIiIiIiIp1Kt+BemE3uVNQWU1KVb3Qcl6RSUEREREREREREOhUPN0+ighMAsBVrCfGJqBQUzTpOygAAl81JREFUEREREREREZFOx9q8hHi3wUlck0pB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tkrzdK8ZOnv2LNnZ2ZSWlhIbG6uCoDRwrzk6d+4c2dnZeHl5sXHjRhUEBQB/f38WL17Mxx9/THZ2NiUlJcD1PNXU1BAaGspLL73E3/72N0pLS41RpyoIyo0amyO4Pr+pCoLi1NgM1dfXY7FYVBAUQ2MzZLLxLHKP7idHnp6eKgiKwcznIlMtNPJ9unTpwowZM7BYLGzZsoWKigry8/P58MMPefrpp13dPGkBlCFpCnfKUW5uLj4+Pq5uojQj0dHRpKSkMHnyZKxWK2PHjiUkJMQo/Lm7u1NXV4evr68xl5fIzRqTI5HbaUyG9Lqn3E5jMuTsVBW5mXIkD8qsGVJRkOuveXbt2pUZM2Zw9epV0tLSeOihh3jnnXdo3769q5snLcCdMtShQwdXN09aCJ2LpLH69+/Pn/70J1555RXOnTvHCy+8QJ8+ffj2228pKCigQ4cO2O12PD09Xd1UacaUI3lQypA8KGVImoJyJA/KjBky3ZyCd3LhwgXmzZvHyZMn2bZtG127dnV1k6SFUYakKShH0ljHjx9n6dKlnD59mrCwMCwWC8XFxWRkZGgif7lnypE8KGVIHpQyJE1BOZIHZaYMqSj4P9XV1SxevJj33nuPd9999//uDy0/PGVImoJyJPfr4sWLHDp0iKNHjxIUFMSgQYMICwtzdbOkhVGO5EEpQ/KglCFpCsqRPCizZEhFwRscOHCAtm3bEh4e7uqmSAulDElTUI5ERERERETkh6aioIiIiIiIiIiIiMloCTARERERERERERGTUVFQRERERERERETEZFQUFBERERERERERMRkVBUVERERERERERExGRUERERERERERERGTUVFQRERERERERETEZFQUFBERERERERERMRl3VzdARERERJqfd955hwULFhg/e3h48NBDDxEeHk5sbCyjR4/Gz8+v0fs9evQoBw8e5MUXX6R169ZN2WQRERERaQQVBUVERETke/3yl78kODgYu93OxYsX+fTTT1mxYgWbN28mNTWVbt26NWp/x44dY/369YwaNUpFQREREREXUlFQRERERL7XE088QVRUlPHzyy+/TF5eHtOnTycpKYm9e/fi5eXlwhaKiIiIyP3QnIIiIiIi0ij9+/cnKSmJM2fOsGvXLgC++uor5s+fzzPPPENUVBQDBgxgwYIFlJWVGd9bt24dr7/+OgDPPPMM4eHhhIeHU1RUZGyzc+dORo8eTXR0NP369eNXv/oVZ8+e/XEPUERERMQENFJQRERERBrtueeeY82aNRw4cIDnn3+eTz75hMLCQkaPHk3btm35z3/+w/bt2/n666/Zvn07FouFwYMHk5+fz549e1iwYAFt2rQBICAgAIANGzawdu1ann32WeLi4igtLeWtt94iPj6ed999V68bi4iIiDQhFQVFREREpNE6dOhAq1atKCwsBGD8+PFMmTKlwTa9evXi17/+NZ9//jl9+vShW7dudO/enT179jBo0CCCg4ONbc+cOcO6detITk5m+vTpxudDhgxh1KhRZGVlNfhcRERERB6MXh8WERERkfvi4+NDVVUVQIN5Ba9du0ZpaSk9e/YE4Msvv7zrvj766CPq6+t59tlnKS0tNf49/PDDdOrUicOHD/8wByEiIiJiUhopKCIiIiL35cqVKwQGBgJQXl7O+vXr2bt3L99++22D7SorK++6r/z8fBwOB0OGDLnt793dddsqIiIi0pR0dyUiIiIijXbu3DkqKysJDQ0FIDk5mWPHjpGYmEhERAQ+Pj7U19fz0ksv4XA47rq/+vp6LBYL6enpuLm53fJ7Hx+fJj8GERERETNTUVBEREREGm3nzp0APP7441y6dIm8vDxmzZrFzJkzjW3y8/Nv+Z7FYrnt/kJDQ3E4HAQHB9O5c+cfpM0iIiIi8h3NKSgiIiIijZKXl0dqairBwcGMHDnytiP7ADIyMm75zNvbG7j1leIhQ4bg5ubG+vXrbxlZ6HA4KCsra6LWi4iIiAhopKCIiIiI3MHf//53Tp8+TV1dHRcvXuTw4cMcPHiQoKAgNmzYgKenJ56envTt25dNmzZRW1tL+/btOXjwIEVFRbfsLzIyEoDf/e53DBs2DJvNxlNPPUVoaCjJycmsXr2aM2fOMGjQIHx9fSkqKmLfvn08//zzJCYm/tiHLyIiIvJ/S0VBEREREflef/jDHwCw2Wz4+/vz6KOPsnDhQkaPHo2fn5+x3erVq1m2bBlZWVk4HA4GDBhAeno6AwcObLC/6OhoZs+eTXZ2Nh9//DH19fXs378fHx8fpk2bRlhYGJs3byYlJQWADh06MGDAAJ5++ukf76BFRERETMDiuJeZn0VEREREREREROT/huYUFBERERERERERMRkVBUVERERERERERExGRUERERERERERERGTUVFQRERERERERETEZFQUFBERERERERERMRkVBUVERERERERERExGRUERERERERERERGTUVFQRERERERERETEZFQUFBERERERERERMRkVBUVERERERERERExGRUERERERERERERGTUVFQRERERERERETEZFQUFBERERERERERMZn/Ai+l6XBtrPUkAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "accuracy_time_series_chart(\n", - " graph_df[\"actual_result_hv\"],\n", - " graph_df[\"vegas_line_hv\"],\n", - " {\"AutoML\": graph_df[\"automl_cls_pred\"], \"AutoDL\": graph_df[\"autodl_cls_pred\"]},\n", - " graph_df[\"date\"],\n", - " graph_df[\"season\"],\n", - " aggregate_by=\"month\",\n", - " show_baselines=True,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "nba_venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/playground.ipynb b/notebooks/playground.ipynb deleted file mode 100644 index 5e46167..0000000 --- a/notebooks/playground.ipynb +++ /dev/null @@ -1,634 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from dotenv import load_dotenv\n", - "import os\n", - "import openai\n", - "from openai import OpenAI\n", - "import re\n", - "import pandas as pd\n", - "\n", - "load_dotenv()\n", - "OPEN_AI_API_KEY = os.getenv(\"OPEN_AI_API\")\n", - "\n", - "# Set options\n", - "pd.set_option(\"display.max_columns\", 100)\n", - "pd.set_option(\"display.max_rows\", 100)\n", - "pd.set_option(\"display.max_colwidth\", None)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "def merge_csv_files_to_dataframe(play_by_play_file, game_state_file):\n", - " # Load the CSV files into pandas DataFrames\n", - " play_by_play_df = pd.read_csv(play_by_play_file)\n", - " game_state_df = pd.read_csv(game_state_file)\n", - "\n", - " # Merge the DataFrames on the 'play_id' column\n", - " merged_df = pd.merge(play_by_play_df, game_state_df, on=\"play_id\", how=\"inner\")\n", - "\n", - " # Return the merged DataFrame\n", - " return merged_df" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "pbp_csv = \"../data/2021-2022/2021-2022_NBA_PbP_Logs/[2021-10-19]-0022100001-BKN@MIL.csv\"\n", - "game_states_csv = pbp_csv.replace(\".csv\", \"_game_states.csv\").replace(\n", - " \"NBA_PbP_Logs\", \"Game_States\"\n", - ")\n", - "\n", - "df = merge_csv_files_to_dataframe(pbp_csv, game_states_csv)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Index(['game_id', 'data_set', 'date', 'a1', 'a2', 'a3', 'a4', 'a5', 'h1', 'h2',\n", - " 'h3', 'h4', 'h5', 'period_x', 'away_score_x', 'home_score_x',\n", - " 'remaining_time_x', 'elapsed', 'play_length', 'play_id', 'team',\n", - " 'event_type', 'assist', 'away', 'home', 'block', 'entered', 'left',\n", - " 'num', 'opponent', 'outof', 'player', 'points', 'possession', 'reason',\n", - " 'result', 'steal', 'type', 'shot_distance', 'original_x', 'original_y',\n", - " 'converted_x', 'converted_y', 'description', 'home_score_y',\n", - " 'away_score_y', 'period_y', 'remaining_time_y', 'home_margin', 'total',\n", - " 'Nic_Claxton_pm', 'Kevin_Durant_pm', 'Blake_Griffin_pm',\n", - " 'James_Harden_pm', 'Joe_Harris_pm', 'Brook_Lopez_pm',\n", - " 'Giannis_Antetokounmpo_pm', 'Grayson_Allen_pm', 'Khris_Middleton_pm',\n", - " 'Jrue_Holiday_pm', 'Nic_Claxton_pts', 'Giannis_Antetokounmpo_pts',\n", - " 'Brook_Lopez_pts', 'Kevin_Durant_pts', 'Khris_Middleton_pts',\n", - " 'Blake_Griffin_pts', 'Pat_Connaughton_pm', 'Jrue_Holiday_pts',\n", - " 'Patty_Mills_pm', 'Pat_Connaughton_pts', 'James_Harden_pts',\n", - " 'George_Hill_pm', 'Paul_Millsap_pm', 'LaMarcus_Aldridge_pm',\n", - " 'Jevon_Carter_pm', 'Thanasis_Antetokounmpo_pm', 'Patty_Mills_pts',\n", - " 'Jordan_Nwora_pm', 'Thanasis_Antetokounmpo_pts', 'Jordan_Nwora_pts',\n", - " 'James_Johnson_pm', 'Sandro_Mamukelashvili_pm', 'Grayson_Allen_pts',\n", - " 'Joe_Harris_pts', 'Justin_Robinson_pm', 'LaMarcus_Aldridge_pts',\n", - " 'George_Hill_pts', 'DeAndre'_Bembry_pm', 'Cam_Thomas_pm',\n", - " 'Bruce_Brown_pm', 'James_Johnson_pts', 'Justin_Robinson_pts',\n", - " 'Cam_Thomas_pts', 'Georgios_Kalaitzakis_pm'],\n", - " dtype='object')" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.columns" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-01]-0022100769-ORL@CHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-21]-0022100014-DAL@ATL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-10]-0022100994-GSW@DEN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-23]-0022101085-ATL@DET_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-04]-0022100790-ATL@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-09]-0022100604-CLE@GSW_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-16]-0022101035-DEN@WAS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-14]-0022100858-DET@WAS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-15]-0022100641-TOR@MIL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-03]-0022100710-CHI@ATL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-08]-0022101209-POR@DAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-28]-0022101123-ORL@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-09]-0022100604-CLE@GSW_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-03]-0022100940-MEM@BOS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-21]-0042100173-DAL@UTA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-21]-0042100153-MEM@MIN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-10]-0022101218-MIL@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-24]-0022100274-MIA@MIN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-05]-0022100796-MEM@ORL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-26]-0022100722-MIL@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-01]-0022101159-MIN@DEN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-28]-0022100922-CHI@MIA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-08]-0022101209-POR@DAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-01]-0022100096-POR@PHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-08]-0022100150-NOP@DAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-17]-0022100655-CHA@NYK_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-23]-0022100487-SAS@LAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-25]-0022100897-OKC@IND_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-12]-0022100175-MIL@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-30]-0022100080-NYK@NOP_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-07]-0022100968-ATL@DET_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-12]-0022100398-MIL@NYK_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-08]-0022100823-ORL@POR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-01]-0022100327-SAC@LAC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-15]-0022101032-PHX@NOP_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-06]-0022100136-BOS@DAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-24]-0022100892-MEM@MIN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-22]-0022100697-IND@PHX_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-26]-0022101111-BKN@MIA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-05-19]-0042100302-BOS@MIA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-20]-0022100011-OKC@UTA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-28]-0022100065-DET@PHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-31]-0022100761-MEM@PHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-08]-0022100154-CHA@LAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-06]-0022100579-DET@MEM_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-09]-0022100599-NOP@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-09]-0022100599-NOP@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-22]-0022100471-CLE@BOS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-20]-0022100241-MIA@WAS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-27]-0022100299-NOP@UTA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-19]-0022100235-LAC@NOP_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-24]-0022100889-CLE@DET_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-07]-0022100969-CHI@PHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-25]-0022100900-MIA@NYK_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-02]-0022100103-MIL@DET_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-01]-0022100327-SAC@LAC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-04]-0022100951-MIN@OKC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-21]-0022100014-DAL@ATL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-13]-0022100411-WAS@DEN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-07]-0022100968-ATL@DET_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-20]-0022100011-OKC@UTA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-29]-0022100750-BKN@GSW_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-06]-0022100579-DET@MEM_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-22]-0022100025-PHX@LAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-28]-0022100739-POR@HOU_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-20]-0022100241-MIA@WAS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-01]-0022100928-BKN@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-08]-0022100154-CHA@LAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-03]-0022100782-MIN@DET_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-14]-0022100864-ORL@DEN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-24]-0022100271-CHI@HOU_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-20]-0022100463-SAS@LAC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-27]-0022100299-NOP@UTA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-29]-0022100308-OKC@HOU_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-27]-0022100061-LAL@OKC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-30]-0022100080-NYK@NOP_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-22]-0022100017-CHA@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-14]-0022100632-ORL@CHA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-20]-0022100463-SAS@LAC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-01]-0022100544-GSW@UTA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-14]-0022100638-CLE@SAS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-06-10]-0042100404-GSW@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-16]-0022100880-TOR@MIN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-18]-0022100447-WAS@UTA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-30]-0022100315-MEM@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-23]-0042100113-BOS@BKN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-13]-0022100188-MEM@NOP_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-23]-0022100263-MIA@DET_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-06]-0022100136-BOS@DAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-04]-0022100949-MIL@CHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-06]-0022100805-IND@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-27]-0022100504-UTA@SAS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-17]-0022100661-POR@ORL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-05-02]-0042100221-DAL@PHX_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-15]-0022101032-PHX@NOP_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-18]-0042100172-UTA@DAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-28]-0022100513-CLE@NOP_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-21]-0022100691-BKN@SAS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-21]-0042100153-MEM@MIN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-16]-0022100880-TOR@MIN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-21]-0042100163-GSW@DEN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-21]-0042100163-GSW@DEN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-24]-0022100889-CLE@DET_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-08]-0022100150-NOP@DAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-22]-0022100695-OKC@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-21]-0022100691-BKN@SAS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-03]-0022100710-CHI@ATL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-03]-0022100782-MIN@DET_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-26]-0022101111-BKN@MIA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-25]-0022100489-BOS@MIL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-06]-0022100356-MEM@MIA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-06]-0022100358-CLE@MIL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-27]-0022100298-WAS@DAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-06]-0022100356-MEM@MIA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-13]-0022101019-LAL@PHX_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-30]-0022101141-MIN@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-23]-0022101087-UTA@BOS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-06]-0022100133-HOU@DEN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-15]-0022100641-TOR@MIL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-23]-0022100477-NOP@ORL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-27]-0022100298-WAS@DAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-04]-0022100951-MIN@OKC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-06]-0022100361-ORL@GSW_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-27]-0022100058-IND@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-14]-0022100864-ORL@DEN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-29]-0022101133-DET@BKN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-23]-0022100481-MIL@DAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-10]-0022100160-TOR@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-17]-0022100661-POR@ORL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-23]-0022100265-DEN@POR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-29]-0022100750-BKN@GSW_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-23]-0022100481-MIL@DAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-20]-0022100682-PHX@DAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-13]-0052100131-SAS@NOP_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-26]-0022100282-PHX@NYK_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-12]-0022100398-MIL@NYK_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-15]-0022101029-MEM@IND_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-28]-0022100301-MIL@IND_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-01]-0022100928-BKN@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-08]-0022100817-BOS@BKN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-13]-0022101013-LAC@DET_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-26]-0022101109-IND@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-28]-0022100739-POR@HOU_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-22]-0022100262-PHI@SAC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-29]-0022101133-DET@BKN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-22]-0022100696-SAC@MIL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-05]-0022100127-CLE@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-09]-0022101215-GSW@SAS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-22]-0022100695-OKC@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-14]-0022100632-ORL@CHA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-11]-0022100844-ORL@UTA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-12]-0052100121-LAC@MIN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-20]-0042100112-BKN@BOS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-13]-0052100131-SAS@NOP_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-09]-0022101215-GSW@SAS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-06]-0022100358-CLE@MIL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-23]-0022100487-SAS@LAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-16]-0022100877-BKN@NYK_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-02]-0022101164-MIA@CHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-22]-0022101080-GSW@ORL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-27]-0022100504-UTA@SAS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-30]-0022100084-MIA@MEM_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-16]-0022101044-TOR@LAC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-05]-0022100796-MEM@ORL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-16]-0042100151-MIN@MEM_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-14]-0022100860-OKC@NYK_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-25]-0022100719-SAS@HOU_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-23]-0022100485-MEM@GSW_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-14]-0022100858-DET@WAS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-20]-0022100681-NOP@NYK_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-17]-0022100662-TOR@MIA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-13]-0022101019-LAL@PHX_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-02]-0022100932-CHA@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-22]-0042100103-MIA@ATL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-27]-0022100061-LAL@OKC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-19]-0022100001-BKN@MIL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-19]-0022100671-CHA@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-28]-0022100301-MIL@IND_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-13]-0022100188-MEM@NOP_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-23]-0022100477-NOP@ORL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-29]-0022100522-OKC@PHX_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-26]-0022100907-WAS@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-31]-0022100764-GSW@HOU_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-08]-0022100815-PHX@PHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-20]-0022100681-NOP@NYK_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-23]-0022101087-UTA@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-08]-0022100152-ATL@GSW_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-20]-0022100012-DEN@PHX_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-27]-0042100165-DEN@GSW_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-25]-0022100900-MIA@NYK_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-02]-0022100932-CHA@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-27]-0022100918-DEN@POR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-28]-0022101122-DEN@CHA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-01]-0022100769-ORL@CHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-08]-0022100817-BOS@BKN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-25]-0022100897-OKC@IND_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-17]-0022100662-TOR@MIA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-27]-0022100918-DEN@POR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-23]-0022100265-DEN@POR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-27]-0022100058-IND@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-10]-0022100831-MEM@DET_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-06]-0022100361-ORL@GSW_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-28]-0022100513-CLE@NOP_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-22]-0022100262-PHI@SAC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-27]-0022100733-LAL@PHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-18]-0042100172-UTA@DAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-22]-0022100017-CHA@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-27]-0042100165-DEN@GSW_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-19]-0022100235-LAC@NOP_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-11]-0022100415-DET@CHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-19]-0022100238-TOR@SAC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-16]-0042100151-MIN@MEM_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-22]-0042100103-MIA@ATL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-10]-0022100382-SAC@CHA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-12]-0022100175-MIL@BOS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-09]-0022100984-ATL@MIL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-17]-0022100655-CHA@NYK_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-31]-0022100764-GSW@HOU_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-04]-0022100120-UTA@ATL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-22]-0022100697-IND@PHX_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-10]-0022100389-LAL@OKC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-13]-0022101013-LAC@DET_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-28]-0022100922-CHI@MIA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-03]-0022101168-DEN@LAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-26]-0022100282-PHX@NYK_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-29]-0022100522-OKC@PHX_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-19]-0022100001-BKN@MIL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-24]-0022100892-MEM@MIN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-07]-0022100586-DAL@HOU_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-19]-0022100671-CHA@BOS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-03]-0022101168-DEN@LAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-01]-0022100096-POR@PHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-10]-0022100994-GSW@DEN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-20]-0022101066-PHX@SAC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-02]-0022100329-MIL@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-20]-0022100682-PHX@DAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-10]-0022101218-MIL@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-08]-0022100822-MIL@LAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-10]-0022100382-SAC@CHA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-10]-0022100831-MEM@DET_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-15]-0022100418-ATL@ORL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-22]-0022100025-PHX@LAL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-16]-0022101044-TOR@LAC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-26]-0022100907-WAS@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-06]-0022100802-PHI@CHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-09]-0022100605-SAC@POR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-29]-0022100311-WAS@SAS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-08]-0022100152-ATL@GSW_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-05]-0022100127-CLE@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-22]-0022101080-GSW@ORL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-01]-0022101159-MIN@DEN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-08]-0022100822-MIL@LAL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-04]-0022100949-MIL@CHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-08]-0022100823-ORL@POR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-06]-0022100967-NOP@DEN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-09]-0022100986-OKC@MIN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-15]-0022100418-ATL@ORL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-14]-0022100638-CLE@SAS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-06]-0022100802-PHI@CHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-11]-0022100616-DEN@LAC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-15]-0022101029-MEM@IND_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-11]-0022100415-DET@CHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-25]-0022100719-SAS@HOU_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-16]-0022100877-BKN@NYK_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-13]-0022100411-WAS@DEN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-05-02]-0042100221-DAL@PHX_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-06]-0022100135-PHI@CHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-23]-0022100485-MEM@GSW_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-01]-0022100320-DEN@ORL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-05]-0022100125-SAS@ORL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-10]-0022100160-TOR@BOS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-23]-0022101085-ATL@DET_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-03]-0022101171-NYK@ORL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-09]-0022100992-WAS@LAC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-21]-0042100173-DAL@UTA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-22]-0022100471-CLE@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-23]-0042100113-BOS@BKN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-05-19]-0042100302-BOS@MIA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-06]-0022100805-IND@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-07]-0022100586-DAL@HOU_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-28]-0022101122-DEN@CHA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-28]-0022100065-DET@PHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-26]-0022101109-IND@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-22]-0022100696-SAC@MIL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-19]-0022100238-TOR@SAC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-10]-0022100389-LAL@OKC_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-25]-0022100489-BOS@MIL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-06]-0022100133-HOU@DEN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-05]-0022100125-SAS@ORL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-24]-0022100270-BKN@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-01]-0022100544-GSW@UTA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-08]-0022100370-MIL@MIA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-11]-0022100616-DEN@LAC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-24]-0022100270-BKN@BOS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-28]-0022101123-ORL@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-31]-0022100761-MEM@PHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-30]-0022101141-MIN@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-06]-0022100967-NOP@DEN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-29]-0022100308-OKC@HOU_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-20]-0042100112-BKN@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-09]-0022100984-ATL@MIL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-27]-0022100295-ORL@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-12]-0052100121-LAC@MIN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-02]-0022100329-MIL@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-23]-0022100263-MIA@DET_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-09]-0022100986-OKC@MIN_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-16]-0022101041-CHI@UTA_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-08]-0022100370-MIL@MIA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-30]-0022100315-MEM@TOR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-04]-0022100120-UTA@ATL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-27]-0022100733-LAL@PHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-03]-0022100940-MEM@BOS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-04]-0022100790-ATL@TOR_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-06]-0022100135-PHI@CHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-20]-0022101066-PHX@SAC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-03]-0022101171-NYK@ORL_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-24]-0022100271-CHI@HOU_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-16]-0022101035-DEN@WAS_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-02]-0022100103-MIL@DET_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-08]-0022100815-PHX@PHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-20]-0022100012-DEN@PHX_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-09]-0022100605-SAC@POR_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-14]-0022100860-OKC@NYK_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-16]-0022101041-CHI@UTA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-10-30]-0022100084-MIA@MEM_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-04-02]-0022101164-MIA@CHI_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-01]-0022100320-DEN@ORL_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-01-26]-0022100722-MIL@CLE_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-07]-0022100969-CHI@PHI_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-27]-0022100295-ORL@CLE_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-03-09]-0022100992-WAS@LAC_final.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-02-11]-0022100844-ORL@UTA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-12-18]-0022100447-WAS@UTA_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-29]-0022100311-WAS@SAS_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2021-11-24]-0022100274-MIA@MIN_game_states.csv\n", - "Deleted file: ../data/2021-2022_NBA_PbP_Logs/[2022-06-10]-0042100404-GSW@BOS_final.csv\n" - ] - } - ], - "source": [ - "df = df.drop(\n", - " columns=[\n", - " \"data_set\",\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class ChatGPT:\n", - " def __init__(self, model=\"gpt-3.5-turbo\"):\n", - " self.client = OpenAI(api_key=OPEN_AI_API_KEY)\n", - " self.model = model\n", - " self.messages = [] # Initialize an empty list to store conversation history\n", - "\n", - " def chat(self, message):\n", - " # Append the user's message to the history\n", - " self.messages.append({\"role\": \"user\", \"content\": message})\n", - "\n", - " # Make the API request including the conversation history\n", - " response = self.client.chat.completions.create(\n", - " model=self.model, messages=self.messages\n", - " )\n", - "\n", - " # Extract the model's response\n", - " model_response = response.message[\"content\"]\n", - "\n", - " # Append the model's response to the history\n", - " self.messages.append({\"role\": \"system\", \"content\": model_response})\n", - "\n", - " return model_response" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "chatgpt = ChatGPT()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "response = chatgpt.chat(\"Your message here\")\n", - "response" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from nba_api.live.nba.endpoints import scoreboard, playbyplay\n", - "\n", - "# Today's Score Board\n", - "games = scoreboard.ScoreBoard()\n", - "\n", - "games.get_dict()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pbp = playbyplay.PlayByPlay(game_id=\"0022300686\").get_dict()\n", - "plays = pbp[\"game\"][\"actions\"]\n", - "pbp_df = pd.DataFrame(plays)\n", - "pbp_df.head(10)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pbp_df[[\"clock\", \"description\", \"scoreHome\", \"scoreAway\"]].head(20)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pbp_df[\"description\"].head(20)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".nba_ai_venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/src/NBAStats_game_states.py b/src/NBAStats_game_states.py new file mode 100644 index 0000000..3e35209 --- /dev/null +++ b/src/NBAStats_game_states.py @@ -0,0 +1,307 @@ +import json +import os +from copy import deepcopy + +import pandas as pd +from dotenv import load_dotenv +from nba_api.live.nba.endpoints import playbyplay +from tqdm import tqdm + +try: + from src.NBAStats_prior_states import get_prior_states + from src.utils import ( + game_id_to_season, + get_schedule, + lookup_basic_game_info, + validate_game_id, + ) +except ModuleNotFoundError: + from NBAStats_prior_states import get_prior_states + from utils import ( + game_id_to_season, + get_schedule, + lookup_basic_game_info, + validate_game_id, + ) + +load_dotenv() +PROJECT_ROOT = os.getenv("PROJECT_ROOT") + +pd.set_option("display.max_columns", None) + + +def get_pbp(game_id): + """ + This function retrieves the play-by-play logs for a given game ID. + + Parameters: + game_id (str): The ID of the game to retrieve the play-by-play logs for. + + Returns: + list: A list of dictionaries where each dictionary represents a play-by-play log, sorted by order number. + """ + + # Validate the game_id + validate_game_id(game_id) + + try: + # Use the PlayByPlay class to retrieve the play-by-play logs for the game + pbp = playbyplay.PlayByPlay(game_id=game_id) + # Convert the PlayByPlay object to a dictionary + pbp = pbp.get_dict() + except Exception as e: + # If an API call error occurs (e.g., the game ID is not found), print the error and return an empty list + print(f"API call error occurred for game ID {game_id}: {e}") + return [] + + try: + # Extract the play-by-play logs from the dictionary + pbp_logs = pbp["game"]["actions"] + # Sort the play-by-play logs by order number + pbp_logs_sorted = sorted(pbp_logs, key=lambda x: x["orderNumber"]) + except KeyError as e: + # If an API response format error occurs, print the error and return an empty list + print(f"API response format error occurred for game ID {game_id}: {e}") + return [] + + # Return the sorted play-by-play logs + return pbp_logs_sorted + + +def _create_game_states_and_final_state(pbp_logs, home, away, game_id, game_date): + """ + This function creates game states from play-by-play logs. + + Parameters: + pbp_logs (list): A list of dictionaries where each dictionary represents a play-by-play log. + home (str): The tricode of the home team. + away (str): The tricode of the away team. + game_id (str): The ID of the game. + game_date (str): The date of the game. + + Returns: + list, dict: A list of dictionaries where each dictionary represents a game state at a specific point in time, + and a dictionary representing the final game state. + """ + + # If there are no play-by-play logs, return an empty list and dictionary + if not pbp_logs: + return [], {} + + # Sort the play-by-play logs by order number + pbp_logs = sorted(pbp_logs, key=lambda x: x["orderNumber"]) + + # Filter out logs where 'description' is not a key + pbp_logs = [log for log in pbp_logs if "description" in log] + + # Initialize the list of game states and the players dictionary + game_states = [] + players = {"home": {}, "away": {}} + + # Iterate over the play-by-play logs + for row in pbp_logs: + # If the play involves a player, update the player's points + if row.get("personId") is not None and row.get("playerNameI") is not None: + # Determine the team of the player + team = "home" if row["teamTricode"] == home else "away" + + # Extract the player's ID and name + player_id = row["personId"] + player_name = row["playerNameI"] + + # If the player is not already in the game state, add them + if player_id not in players[team]: + players[team][player_id] = {"name": player_name, "points": 0} + + # If the play resulted in points, update the player's points + if row.get("pointsTotal") is not None: + points = int(row["pointsTotal"]) + players[team][player_id]["points"] = points + + # Create the current game state + current_game_state = { + "game_id": game_id, + "game_date": game_date, + "home": home, + "away": away, + "play_id": int(row["orderNumber"]), + "remaining_time": row["clock"], + "period": int(row["period"]), + "home_score": int(row["scoreHome"]), + "away_score": int(row["scoreAway"]), + "total": int(row["scoreHome"]) + int(row["scoreAway"]), + "home_margin": int(row["scoreHome"]) - int(row["scoreAway"]), + "players": deepcopy( + players + ), # Create a deep copy of the players dictionary + } + + # Add the current game state to the list of game states + game_states.append(current_game_state) + + # If the last play was the end of the game, set the final state to the last game state + if pbp_logs[-1]["description"] == "Game End": + final_state = game_states[-1] + else: + final_state = {} + + return game_states, final_state + + +def get_current_game_info(game_id, include_prior_states=False): + """ + This function retrieves the current game information given a game ID and a flag indicating whether to include prior states. + + Parameters: + game_id (str): The ID of the game to retrieve the information for. + include_prior_states (bool): A flag indicating whether to include prior states. Defaults to False. + + Returns: + dict: A dictionary containing the game information, including the game ID, game date, game time (EST), home team, away team, game status, play-by-play logs, game states, final state, and prior states (if requested). + """ + + # Validate the game_id + validate_game_id(game_id) + + # Look up the basic game information + game_info = lookup_basic_game_info(game_id) + + # Extract the game date, game time, home team, and away team from the game information + game_date = game_info["game_date"] + game_time_est = game_info["game_time_est"] + home = game_info["home"] + away = game_info["away"] + game_status_id = int(game_info["game_status_id"]) + + # Retrieve the play-by-play logs for the game + pbp_logs = get_pbp(game_id) + + # Create the game states from the play-by-play logs + game_states, final_state = _create_game_states_and_final_state( + pbp_logs, home, away, game_id, game_date + ) + + # Determine the game status based on the game status ID and the presence of final state and play-by-play logs + if final_state and game_status_id == 3: + game_status = "Completed" + elif pbp_logs and game_status_id == 2: + game_status = "In Progress" + elif game_status_id == 1: + game_status = "Not Started" + else: + game_status = "Unknown" + + # Create the base dictionary with the game information + game_info = { + "game_id": game_id, + "game_date": game_date, + "game_time_est": game_time_est, + "home": home, + "away": away, + "game_status": game_status, + "pbp_logs": pbp_logs, + "game_states": game_states, + "final_state": final_state, + } + + # If the include_prior_states flag is set to True, retrieve the prior states and handle any exceptions + if include_prior_states: + try: + game_info["prior_states"] = get_prior_states(game_id) + except Exception as e: + print(f"Failed to get prior states for game {game_id}. Error: {e}") + + return game_info + + +def update_database(game_data_dict, print_updated=True): + game_id = game_data_dict["game_id"] + game_date = game_data_dict["game_date"] + home = game_data_dict["home"] + away = game_data_dict["away"] + season = game_id_to_season(game_id) + + directory = f"{PROJECT_ROOT}/data/NBAStats/{season}/{game_date}" + filename = f"{directory}/{game_id}_{home}_{away}.json" + + # Create the directory if it does not exist + os.makedirs(directory, exist_ok=True) + + # If the file exists, load the existing data and update it + if os.path.exists(filename): + with open(filename, "r") as json_file: + existing_data = json.load(json_file) + existing_data.update(game_data_dict) + else: + existing_data = game_data_dict + + # Write the updated data back to the file + with open(filename, "w") as json_file: + json.dump(existing_data, json_file, indent=4) + + if print_updated: + print(f"Updated: {filename}") + + +def update_full_season_database( + season, + season_type="Regular Season", + include_prior_states=False, +): + schedule = get_schedule(season) + season_type_codes = { + "001": "Pre Season", + "002": "Regular Season", + "003": "All-Star", + "004": "Post Season", + } + + # Filter the game_ids to only include games that match the season type + games = [ + game + for game in schedule + if season_type_codes.get(game["gameId"][:3]) == season_type + ] + + # Sort the games by 'gameDateTimeEst' from least recent to most recent + games_sorted = sorted(games, key=lambda game: game["gameDateTimeEst"]) + + # Extract the game_ids from the sorted games + game_ids = [game["gameId"] for game in games_sorted] + + for game_id in tqdm(game_ids, desc="Updating database"): + try: + # Update the database for this game + if include_prior_states: + game_data_dict = get_current_game_info( + game_id, include_prior_states=True + ) + else: + game_data_dict = get_current_game_info(game_id) + update_database(game_data_dict, print_updated=False) + except Exception as e: + # Find the game that caused the exception + failed_game = next( + game for game in games_sorted if game["gameId"] == game_id + ) + print( + f"Failed to update game with ID: {failed_game['gameId']} and DateTime: {failed_game['gameDateTimeEst']}" + ) + print(f"Error: {str(e)}") + + +if __name__ == "__main__": + # game_id = "0022300001" + # game_info = get_current_game_info(game_id, include_prior_states=True) + # keys = ["game_id", "game_date", "game_time_est", "home", "away", "game_status"] + + # for key in keys: + # if key in game_info: + # print(f"{key}: {game_info[key]}") + # print(game_info["game_states"][-1] == game_info["final_state"]) + # print(game_info["final_state"]) + # print(game_info["prior_states"]) + + # update_database(game_info) + + update_full_season_database("2020-21", include_prior_states=True) diff --git a/src/NBAStats_prior_states.py b/src/NBAStats_prior_states.py new file mode 100644 index 0000000..2604380 --- /dev/null +++ b/src/NBAStats_prior_states.py @@ -0,0 +1,618 @@ +import json +import os +from datetime import datetime + +import numpy as np +import pandas as pd +from dotenv import load_dotenv + +from utils import ( + game_id_to_season, + get_schedule, + lookup_basic_game_info, + validate_date_format, + validate_game_id, +) + +load_dotenv() +PROJECT_ROOT = os.getenv("PROJECT_ROOT") + +pd.set_option("display.max_columns", None) + + +def get_prior_states(game_id): + validate_game_id(game_id) + game_info = lookup_basic_game_info(game_id) + game_date = game_info["game_date"] + home = game_info["home"] + away = game_info["away"] + original_prior_states = load_prior_states(game_id, game_date, home, away) + current_prior_states = update_prior_states( + game_id, game_date, home, away, original_prior_states + ) + return current_prior_states + + +def load_prior_states(game_id, game_date, home, away): + validate_game_id(game_id) + validate_date_format(game_date) + + season = game_id_to_season(game_id) + + filepath = f"{PROJECT_ROOT}/data/NBAStats/{season}/{game_date}/{game_id}_{home}_{away}.json" + + try: + with open(filepath, "r") as json_file: + data = json.load(json_file) + prior_states = data.get("prior_states", None) + + if prior_states is not None: + return prior_states + except Exception as e: + pass + + return {} + + +def update_prior_states(game_id, game_date, home, away, prior_states): + validate_game_id(game_id) + validate_date_format(game_date) + + # basic prior_states dict structure + if prior_states == {}: + prior_states = { + "prior_states_finalized": False, # True if game is completed, False otherwise + "home_prior_states_updated_to_date": None, # date of which prior games are included. Not a date of last update. + "away_prior_states_updated_to_date": None, # date of which prior games are included. Not a date of last update. + "home_prior_final_states": [], + "away_prior_final_states": [], + "feature_set": [], + } + # elif prior_states["prior_states_finalized"]: + # return prior_states # No update needed if prior states are finalized + + # Determine games to include in prior states + season = game_id_to_season(game_id, abbreviate=True) + file_system_season = game_id_to_season(game_id, abbreviate=False) + schedule = get_schedule(season) + home_prior_games, away_prior_games = _get_scheduled_prior_games( + game_date, home, away, schedule + ) + + # Filter prior games to include only games that are past the prior_states_updated_to_date + home_prior_games_to_add = _filter_prior_games( + home_prior_games, prior_states["home_prior_states_updated_to_date"] + ) + away_prior_games_to_add = _filter_prior_games( + away_prior_games, prior_states["away_prior_states_updated_to_date"] + ) + + # Load and add prior states from home_prior_games_to_add and away_prior_games_to_add + if home_prior_games_to_add: + prior_states = _add_prior_states( + home_prior_games_to_add, "home", prior_states, file_system_season + ) + # Find the most recent prior final state date for home + prior_states["home_prior_states_updated_to_date"] = prior_states[ + "home_prior_final_states" + ][0]["game_date"] + # Extract the gameIds from home_prior_games + needed_home_game_ids = [game["gameId"] for game in home_prior_games] + # Extract the game_ids from home_prior_states + existing_home_game_ids = [ + game["game_id"] for game in prior_states["home_prior_final_states"] + ] + # Check if all needed home game ids are in existing_home_game_ids + home_prior_states_finalized = all( + game_id in existing_home_game_ids for game_id in needed_home_game_ids + ) + else: + home_prior_states_finalized = True + + if away_prior_games_to_add: + prior_states = _add_prior_states( + away_prior_games_to_add, "away", prior_states, file_system_season + ) + # Find the most recent prior final state date for away + prior_states["away_prior_states_updated_to_date"] = prior_states[ + "away_prior_final_states" + ][0]["game_date"] + # Extract the gameIds from away_prior_games + needed_away_game_ids = [game["gameId"] for game in away_prior_games] + # Extract the game_ids from away_prior_states + existing_away_game_ids = [ + game["game_id"] for game in prior_states["away_prior_final_states"] + ] + # Check if all needed away game ids are in existing_away_game_ids + away_prior_states_finalized = all( + game_id in existing_away_game_ids for game_id in needed_away_game_ids + ) + else: + away_prior_states_finalized = True + + # Check if both home and away prior states are finalized + prior_states["prior_states_finalized"] = ( + home_prior_states_finalized and away_prior_states_finalized + ) + + # Generate feature set + if prior_states["prior_states_finalized"] and not prior_states["feature_set"]: + prior_states["feature_set"] = _generate_feature_set( + prior_states, home, away, game_date + ) + + return prior_states + + +def _get_scheduled_prior_games( + game_date, home, away, schedule, season_type="Regular Season" +): + """ + This function returns the prior games for the home and away teams based on the season type. + + Parameters: + game_date (str): The date of the game in the format "YYYY-MM-DD". + home (str): The name of the home team. + away (str): The name of the away team. + schedule (list): The list of games. Each game is a dictionary with keys "gameDateTimeEst", "homeTeam", "awayTeam", and "GameId". + season_type (str): The type of the season. It can be "Pre Season", "Regular Season", "All-Star", or "Post Season". Default is "Regular Season". + + Returns: + tuple: A tuple containing two lists. The first list contains the prior games of the home team and the second list contains the prior games of the away team. + """ + + # Convert the game_date from string to datetime object + game_date = datetime.strptime(game_date, "%Y-%m-%d") + home_prior_games = [] + away_prior_games = [] + + # Mapping of season type codes to season types + season_type_codes = { + "001": "Pre Season", + "002": "Regular Season", + "003": "All-Star", + "004": "Post Season", + } + + # Loop through each game in the schedule + for game in schedule: + # Convert the game date from string to datetime object + game_datetime = datetime.strptime(game["gameDateTimeEst"][:10], "%Y-%m-%d") + + # Check if the game date is before the input game_date + if game_datetime < game_date: + # Check if the game's season type matches the input season_type + if season_type_codes.get(game["gameId"][:3]) == season_type: + # If the home team played in this game, add it to home_prior_games + if home in (game["homeTeam"], game["awayTeam"]): + home_prior_games.append(game) + # If the away team played in this game, add it to away_prior_games + if away in (game["homeTeam"], game["awayTeam"]): + away_prior_games.append(game) + + # Return the prior games of the home and away teams + return home_prior_games, away_prior_games + + +def _filter_prior_games(prior_games, prior_states_updated_to_date_str): + # Convert prior_states_updated_to_date to datetime for comparison + prior_states_updated_to_date = None + if prior_states_updated_to_date_str: + prior_states_updated_to_date = datetime.strptime( + prior_states_updated_to_date_str, "%Y-%m-%d" + ) + + # Only include games that are past the prior_states_updated_to_date + prior_games_to_add = [ + game + for game in prior_games + if not prior_states_updated_to_date + or datetime.strptime(game["gameDateTimeEst"][:10], "%Y-%m-%d") + > prior_states_updated_to_date + ] + + return prior_games_to_add + + +def _add_prior_states(games_to_add, team_type, prior_states, season): + # Add the final state from the filepath to the prior_final_states + for game in games_to_add: + date_str = game["gameDateTimeEst"][:10] + filepath = f"{PROJECT_ROOT}/data/NBAStats/{season}/{date_str}/{game['gameId']}_{game['homeTeam']}_{game['awayTeam']}.json" + + # Check if the file exists + if not os.path.exists(filepath): + continue + + with open(filepath, "r") as f: + game_data = json.load(f) + + # Check if the file is empty or the final_state is an empty dict + if not game_data or not game_data.get("final_state"): + continue + + final_state = game_data["final_state"] + + prior_states[f"{team_type}_prior_final_states"].append(final_state) + + # Sort prior states by date most recent to least recent + prior_states[f"{team_type}_prior_final_states"].sort( + key=lambda x: datetime.strptime(x["game_date"], "%Y-%m-%d"), reverse=True + ) + + return prior_states + + +def _generate_feature_set(prior_states, home_team, away_team, game_date): + # Create DataFrames from prior_states + home_df = pd.DataFrame(prior_states["home_prior_final_states"]) + away_df = pd.DataFrame(prior_states["away_prior_final_states"]) + + if home_df.empty or away_df.empty: + return [] + + # Sort the datasets by 'game_date' to ensure chronological order + home_df_sorted = home_df.sort_values(by="game_date") + away_df_sorted = away_df.sort_values(by="game_date") + + # Basic Features ####### + home_features_basic = calculate_team_features(home_df_sorted, home_team) + away_features_basic = calculate_team_features(away_df_sorted, away_team) + + # Mapping the recalculated features to the new structure for the upcoming game's DataFrame + basic_features_df = pd.DataFrame( + { + "Home_Winning_Percentage": [home_features_basic["Winning_Percentage"]], + "Home_PPG": [home_features_basic["PPG"]], + "Home_OPP_PPG": [home_features_basic["OPP_PPG"]], + "Home_PPG_Diff": [home_features_basic["PPG_Diff"]], + "Home_WL_Streak": [home_features_basic["WL_Streak"]], + "Away_Winning_Percentage": [away_features_basic["Winning_Percentage"]], + "Away_PPG": [away_features_basic["PPG"]], + "Away_OPP_PPG": [away_features_basic["OPP_PPG"]], + "Away_PPG_Diff": [away_features_basic["PPG_Diff"]], + "Away_WL_Streak": [away_features_basic["WL_Streak"]], + "Winning_Percentage_Diff": [ + home_features_basic["Winning_Percentage"] + - away_features_basic["Winning_Percentage"] + ], + "PPG_Diff": [home_features_basic["PPG"] - away_features_basic["PPG"]], + "OPP_PPG_Diff": [ + home_features_basic["OPP_PPG"] - away_features_basic["OPP_PPG"] + ], + "PPG_Diff_Diff": [ + home_features_basic["PPG_Diff"] - away_features_basic["PPG_Diff"] + ], + "WL_Streak_Diff": [ + home_features_basic["WL_Streak"] - away_features_basic["WL_Streak"] + ], + } + ) + + # Context-Specific Features ####### + home_features_home_context = calculate_context_specific_features( + home_df_sorted, home_team, "home" + ) + away_features_away_context = calculate_context_specific_features( + away_df_sorted, away_team, "away" + ) + + # Construct the features DataFrame + contextual_features_df = pd.DataFrame( + { + "Home_Winning_Percentage_Home": [ + home_features_home_context["Winning_Percentage"] + ], + "Home_PPG_Home": [home_features_home_context["PPG"]], + "Home_OPP_PPG_Home": [home_features_home_context["OPP_PPG"]], + "Home_PPG_Diff_Home": [home_features_home_context["PPG_Diff"]], + "Home_WL_Streak_Home": [home_features_home_context["WL_Streak"]], + "Away_Winning_Percentage_Away": [ + away_features_away_context["Winning_Percentage"] + ], + "Away_PPG_Away": [away_features_away_context["PPG"]], + "Away_OPP_PPG_Away": [away_features_away_context["OPP_PPG"]], + "Away_PPG_Diff_Away": [away_features_away_context["PPG_Diff"]], + "Away_WL_Streak_Away": [away_features_away_context["WL_Streak"]], + "Winning_Percentage_Home_Away_Diff": [ + home_features_home_context["Winning_Percentage"] + - away_features_away_context["Winning_Percentage"] + ], + "PPG_Home_Away_Diff": [ + home_features_home_context["PPG"] - away_features_away_context["PPG"] + ], + "OPP_PPG_Home_Away_Diff": [ + home_features_home_context["OPP_PPG"] + - away_features_away_context["OPP_PPG"] + ], + "PPG_Diff_Home_Away_Diff": [ + home_features_home_context["PPG_Diff"] + - away_features_away_context["PPG_Diff"] + ], + "WL_Streak_Home_Away_Diff": [ + home_features_home_context["WL_Streak"] + - away_features_away_context["WL_Streak"] + ], + } + ) + + # Time Decay Features ####### + home_features_time_decay = calculate_time_decayed_features( + home_df_sorted, home_team, game_date + ) + away_features_time_decay = calculate_time_decayed_features( + away_df_sorted, away_team, game_date + ) + + # Construct the features DataFrame + time_decay_features_df = pd.DataFrame( + { + "Home_Time_Decayed_Winning_Percentage": [ + home_features_time_decay["Time_Decayed_Winning_Percentage"] + ], + "Home_Time_Decayed_PPG": [home_features_time_decay["Time_Decayed_PPG"]], + "Home_Time_Decayed_OPP_PPG": [ + home_features_time_decay["Time_Decayed_OPP_PPG"] + ], + "Home_Time_Decayed_PPG_Diff": [ + home_features_time_decay["Time_Decayed_PPG_Diff"] + ], + "Away_Time_Decayed_Winning_Percentage": [ + away_features_time_decay["Time_Decayed_Winning_Percentage"] + ], + "Away_Time_Decayed_PPG": [away_features_time_decay["Time_Decayed_PPG"]], + "Away_Time_Decayed_OPP_PPG": [ + away_features_time_decay["Time_Decayed_OPP_PPG"] + ], + "Away_Time_Decayed_PPG_Diff": [ + away_features_time_decay["Time_Decayed_PPG_Diff"] + ], + "Time_Decayed_Winning_Percentage_Diff": [ + home_features_time_decay["Time_Decayed_Winning_Percentage"] + - away_features_time_decay["Time_Decayed_Winning_Percentage"] + ], + "Time_Decayed_PPG_Diff": [ + home_features_time_decay["Time_Decayed_PPG"] + - away_features_time_decay["Time_Decayed_PPG"] + ], + "Time_Decayed_OPP_PPG_Diff": [ + home_features_time_decay["Time_Decayed_OPP_PPG"] + - away_features_time_decay["Time_Decayed_OPP_PPG"] + ], + "Time_Decayed_PPG_Diff_Diff": [ + home_features_time_decay["Time_Decayed_PPG_Diff"] + - away_features_time_decay["Time_Decayed_PPG_Diff"] + ], + } + ) + + # Rest Days and Day of Season Features ####### + + home_rest_days, home_day_of_season, home_avg_rest_play_count = ( + calculate_rest_and_season_day(home_df_sorted, game_date) + ) + + away_rest_days, away_day_of_season, away_avg_rest_play_count = ( + calculate_rest_and_season_day(away_df_sorted, game_date) + ) + + rest_and_day_of_season_features_df = pd.DataFrame( + { + "Home_Rest_Days": [home_rest_days], + "Home_Day_of_Season": [home_day_of_season], + "Home_Avg_Rest_Play_Count": [home_avg_rest_play_count], + "Away_Rest_Days": [away_rest_days], + "Away_Day_of_Season": [away_day_of_season], + "Away_Avg_Rest_Play_Count": [away_avg_rest_play_count], + "Rest_Days_Diff": [home_rest_days - away_rest_days], + "Avg_Rest_Play_Count_Diff": home_avg_rest_play_count + - away_avg_rest_play_count, + } + ) + + features_df = pd.concat( + [ + basic_features_df, + contextual_features_df, + time_decay_features_df, + rest_and_day_of_season_features_df, + ], + axis=1, + ) + + # Convert NaN values to None + features_df = features_df.where(pd.notnull(features_df), None) + + # Convert DataFrame to dictionary + features_dict = features_df.to_dict(orient="records") + + return features_dict + + +def calculate_team_features(df, team_abbr): + # Determine if the team was playing at home or away, then calculate wins, losses, and scores + df["team_score"] = df.apply( + lambda x: x["home_score"] if x["home"] == team_abbr else x["away_score"], axis=1 + ) + df["opponent_score"] = df.apply( + lambda x: x["away_score"] if x["home"] == team_abbr else x["home_score"], axis=1 + ) + df["win"] = df["team_score"] > df["opponent_score"] + df["loss"] = df["team_score"] < df["opponent_score"] + + # Calculate features + wins = df["win"].sum() + losses = df["loss"].sum() + winning_percentage = wins / (wins + losses) if (wins + losses) > 0 else 0 + ppg = df["team_score"].mean() + opp_ppg = df["opponent_score"].mean() + ppg_diff = ppg - opp_ppg + + # Calculate WL_Streak + streak = 0 + for s in df["win"]: + if s: + streak = streak + 1 if streak > 0 else 1 + else: + streak = streak - 1 if streak < 0 else -1 + wl_streak = streak + + return { + "Wins": wins, + "Losses": losses, + "Winning_Percentage": winning_percentage, + "PPG": ppg, + "OPP_PPG": opp_ppg, + "PPG_Diff": ppg_diff, + "WL_Streak": wl_streak, + } + + +def calculate_context_specific_features(df, team_abbr, context): + # Filter the DataFrame based on the context (home/away games) + if context == "home": + df_filtered = df[df["home"] == team_abbr].copy() + elif context == "away": + df_filtered = df[df["away"] == team_abbr].copy() + else: + raise ValueError("Context must be 'home' or 'away'") + + # Define team_score and opponent_score based on the context after filtering + if context == "home": + df_filtered["team_score"] = df_filtered["home_score"] + df_filtered["opponent_score"] = df_filtered["away_score"] + else: # context == 'away' + df_filtered["team_score"] = df_filtered["away_score"] + df_filtered["opponent_score"] = df_filtered["home_score"] + + # Proceed with calculations + df_filtered["win"] = df_filtered["team_score"] > df_filtered["opponent_score"] + df_filtered["loss"] = df_filtered["team_score"] < df_filtered["opponent_score"] + wins = df_filtered["win"].sum() + losses = df_filtered["loss"].sum() + winning_percentage = wins / (wins + losses) if (wins + losses) > 0 else 0 + ppg = df_filtered["team_score"].mean() + opp_ppg = df_filtered["opponent_score"].mean() + ppg_diff = ppg - opp_ppg + + # Calculate streak with context-specific games + streak = 0 + for s in df_filtered["win"]: + if s: + streak = streak + 1 if streak > 0 else 1 + else: + streak = streak - 1 if streak < 0 else -1 + wl_streak = streak + + return { + "Winning_Percentage": winning_percentage, + "PPG": ppg, + "OPP_PPG": opp_ppg, + "PPG_Diff": ppg_diff, + "WL_Streak": wl_streak, + } + + +def calculate_time_decayed_features(df, team_abbr, game_date, half_life=10): + # Convert game_date to datetime and calculate days before the game + df["game_date"] = pd.to_datetime(df["game_date"]) + target_date = pd.to_datetime(game_date) + df["days_before_game"] = (target_date - df["game_date"]).dt.days + + # Calculate decay rate from half-life + lambda_decay = np.log(2) / half_life + + # Calculate decay weights using the half-life + df["decay_weight"] = np.exp(-lambda_decay * df["days_before_game"]) + + # Assign team and opponent scores based on home/away status + df["team_score"] = df.apply( + lambda x: x["home_score"] if x["home"] == team_abbr else x["away_score"], axis=1 + ) + df["opponent_score"] = df.apply( + lambda x: x["away_score"] if x["home"] == team_abbr else x["home_score"], axis=1 + ) + + # Calculate win/loss and apply weights + df["win"] = df["team_score"] > df["opponent_score"] + weighted_wins = (df["win"] * df["decay_weight"]).sum() + total_weight = df["decay_weight"].sum() + + # Calculate time-decayed metrics + time_decayed_winning_percentage = ( + weighted_wins / total_weight if total_weight > 0 else 0 + ) + time_decayed_ppg = ( + (df["team_score"] * df["decay_weight"]).sum() / total_weight + if total_weight > 0 + else 0 + ) + time_decayed_opp_ppg = ( + (df["opponent_score"] * df["decay_weight"]).sum() / total_weight + if total_weight > 0 + else 0 + ) + time_decayed_ppg_diff = time_decayed_ppg - time_decayed_opp_ppg + + return { + "Time_Decayed_Winning_Percentage": time_decayed_winning_percentage, + "Time_Decayed_PPG": time_decayed_ppg, + "Time_Decayed_OPP_PPG": time_decayed_opp_ppg, + "Time_Decayed_PPG_Diff": time_decayed_ppg_diff, + } + + +def calculate_rest_and_season_day(df, game_date): + # Ensure 'game_date' is in datetime format + df["game_date"] = pd.to_datetime(df["game_date"]) + target_date = pd.to_datetime(game_date) + + # Sort the dataframe by game_date to ensure chronological order + df_sorted = df.sort_values(by="game_date") + + # Find the team's first game of the season and last game before the game_date + team_season_start = df_sorted["game_date"].min() + previous_games = df_sorted[df_sorted["game_date"] < target_date] + + if previous_games.empty: + # If no previous games, use the season start as the last game (implies first game of the season) + last_game_date = team_season_start + rest_days = ( + (target_date - last_game_date).days + if target_date != team_season_start + else 0 + ) + else: + # Calculate rest days using the last game date + last_game_date = previous_games["game_date"].max() + rest_days = (target_date - last_game_date).days + + # Calculate the day of the season + day_of_season = (target_date - team_season_start).days + + # Calculate rest/play count for the last 5, 10, and 30 days + rest_play_counts = [] + for days in [5, 10, 30]: + start_date = max(target_date - pd.Timedelta(days=days), team_season_start) + date_range = pd.date_range( + start=start_date, end=target_date - pd.Timedelta(days=1) + ) + rest_play_count = 0 + for day in date_range: + if day in previous_games["game_date"].values: + rest_play_count += 1 # Add 1 for game days + else: + rest_play_count -= 1 # Subtract 1 for rest days + rest_play_counts.append(rest_play_count) + + # Average the rest/play counts + avg_rest_play_count = sum(rest_play_counts) / len(rest_play_counts) + + return rest_days, day_of_season, avg_rest_play_count + + +if __name__ == "__main__": + game_id = "0022300122" + prior_states = get_prior_states(game_id) + for key, value in prior_states.items(): + print(key, value) diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/evaluation.py b/src/evaluation.py new file mode 100644 index 0000000..baef126 --- /dev/null +++ b/src/evaluation.py @@ -0,0 +1,147 @@ +final_state_evaluations = { + "winning_team": { + "description": "Identifier of the winning team, using a tricode of three capital letters.", + "examples": ["TOR", "GSW", "LAL", "ATL"], + "datatype": "string", + "format": "3 capital letter string", + "validation_checks": { + "format_validation": "Ensure the output is a 3 capital letter string", + "valid_team_tricode": "Check if the tricode matches one of the 30 NBA team tricodes", + "valid_team_tricode_for_game": "Check if the tricode matches one of the 2 teams playing in the specific game", + "classification_accuracy": "Accuracy of the prediction in choosing the winning team out of the two", + }, + }, + "winning_team_probability": { + "description": "Probability of the winning team to win, expressed as a float between 0 and 1.", + "examples": [0.75, 0.65, 0.85], + "datatype": "float", + "format": "float between 0 and 1", + "validation_checks": { + "format_validation": "Ensure the output is a float within the valid range of 0 to 1", + "logical_check": "The probability should not be below 0.5, as it indicates the team with a higher chance of winning", + "probability_accuracy": "Assess how closely the probability matches the outcome", + }, + }, + "home_score": { + "description": "Predicted score for the home team", + "examples": [102, 110, 95], + "datatype": "int", + "format": "integer", + "validation_checks": { + "format_validation": "Ensure the output is an integer", + "score_range": "The score must be no less than 0 and no more than 200", + "score_accuracy": "Evaluate the error, or difference, between the predicted score and the actual score", + }, + }, + "away_score": { + "description": "Predicted score for the away team", + "examples": [97, 105, 100], + "datatype": "int", + "format": "integer", + "validation_checks": { + "format_validation": "Ensure the output is an integer", + "score_range": "The score must be no less than 0 and no more than 200", + "score_accuracy": "Evaluate the error, or difference, between the predicted score and the actual score", + }, + }, + "home_margin_direct": { + "description": "Directly predicted difference in scores from the home team perspective.", + "examples": [5, -10, 15], + "datatype": "int", + "format": "integer", + "validation_checks": { + "format_validation": "Ensure the output is an integer", + "logical_check": "The margin must be no less than -100 and no more than 100", + "margin_accuracy": "Evaluate the error, or difference, between the predicted margin and the actual margin", + }, + }, + "home_margin_derived": { + "description": "Difference in scores derived from combining home and away score predictions.", + "examples": [5, -10, 15], + "datatype": "int", + "format": "integer", + "validation_checks": { + "format_validation": "Ensure the derived output is an integer", + "logical_check": "The derived margin must be no less than -100 and no more than 100", + "margin_accuracy": "Evaluate the error, or difference, between the derived margin and the actual margin", + }, + }, + "total_points_direct": { + "description": "Directly predicted total points scored in the game.", + "examples": [204, 215, 190], + "datatype": "int", + "format": "integer", + "validation_checks": { + "format_validation": "Ensure the output is an integer", + "score_range": "The total points must be no less than 0 and no more than 400", + "total_points_accuracy": "Evaluate the error, or difference, between the predicted total points and the actual total points", + }, + }, + "total_points_derived": { + "description": "Total points derived from combining home and away score predictions.", + "examples": [204, 215, 190], + "datatype": "int", + "format": "integer", + "validation_checks": { + "format_validation": "Ensure the derived output is an integer", + "score_range": "The derived total points must be no less than 0 and no more than 400", + "total_points_accuracy": "Evaluate the error, or difference, between the derived total points and the actual total points", + }, + }, + "home_players_point_totals": { + "description": "Predicted point totals for each home player", + "examples": {"player_id_1": 20, "player_id_2": 18}, + "datatype": "dict", + "format": "dictionary of player_id to integer points", + "validation_checks": { + "format_validation": "Ensure each point total is an integer and each player_id is an int or a string that converts to an int", + "points_range": "Each player's points must be no less than 0 and no more than 100", + "points_accuracy": "Evaluate the error, or difference, between the predicted points and the actual points for each player", + "id_format": "Check if the player_id is an integer or a string representing an integer", + "id_in_league": "Check if the player_id is part of the set of all player IDs in the league", + "id_in_game": "Check if the player_id is part of the set of all player IDs in the game", + "id_in_team": "Check if the player_id is in the set of all player IDs in the game for the home team", + }, + }, + "away_players_point_totals": { + "description": "Predicted point totals for each away player", + "examples": {"player_id_3": 22, "player_id_4": 15}, + "datatype": "dict", + "format": "dictionary of player_id to integer points", + "validation_checks": { + "format_validation": "Ensure each point total is an integer and each player_id is an int or a string that converts to an int", + "points_range": "Each player's points must be no less than 0 and no more than 100", + "points_accuracy": "Evaluate the error, or difference, between the predicted points and the actual points for each player", + "id_format": "Check if the player_id is an integer or a string representing an integer", + "id_in_league": "Check if the player_id is part of the set of all player IDs in the league", + "id_in_game": "Check if the player_id is part of the set of all player IDs in the game", + "id_in_team": "Check if the player_id is in the set of all player IDs in the game for the away team", + }, + }, + "overall_player_identification": { + "description": "Evaluation of player identification.", + "evaluations": { + "precision_evaluation": "The proportion of predicted player IDs that were correct (actual scorers).", + "recall_evaluation": "The proportion of actual scoring players that were correctly identified by the model.", + "F1_score_evaluation": "The harmonic mean of precision and recall, providing a single metric to assess the overall identification accuracy.", + }, + }, + "overall_player_score_prediction": { + "description": "Evaluation of player score predictions.", + "evaluations": { + "inner_join_evaluation": "Match actual scorers with predicted scorers, and calculate the metric for the matching pairs.", + "left_join_evaluation": "Keep all actual scorers and match to predicted scorers where available. If a predicted scorer is missing, fill with 0 for the prediction. Calculate the metric for resulting pairs.", + "right_join_evaluation": "Keep all predicted scorers and match to actual scorers where available. If an actual scorer is missing, fill with 0 for the actual score. Calculate the metric for resulting pairs.", + "outer_join_evaluation": "Keep all actual scorers and predicted scorers and match if available. If a predicted scorer is missing, fill with 0 for the prediction. If an actual scorer is missing, fill with 0 for the actual score. Calculate the metric for resulting pairs.", + }, + }, + "overall_model_output_completeness": { + "description": "Check if the model output is complete for all required fields and if extra fields are returned.", + "evaluations": { + "completeness_evaluation": "Percentage of fields that are present in the model output compared to the required fields.", + "unnecessary_fields_evaluation": "Count of fields that are present in the model output but are not required.", + }, + }, +} + +game_state_evaluations = {} diff --git a/src/game_states.py b/src/game_states.py deleted file mode 100644 index efd9ee2..0000000 --- a/src/game_states.py +++ /dev/null @@ -1,363 +0,0 @@ -import glob -import os -import re -from datetime import datetime - -import pandas as pd -import tqdm -from dotenv import load_dotenv - -load_dotenv() -PROJECT_ROOT = os.getenv("PROJECT_ROOT") -import traceback - -from player_mapper import NBAPlayerMapper - -# Initialize NBAPlayerMapper -csv_path = os.path.join(PROJECT_ROOT, "data", "all_players.csv") -player_mapper = NBAPlayerMapper(csv_path) - -pd.set_option("display.max_columns", None) - - -def initialize_game_base_state(df): - # Assuming df is already sorted by play_id; if not, do so - df_sorted = df.sort_values(by="play_id") - first_row = df_sorted.iloc[0] # Get the first row to identify starting lineups - - # Initialize base state with starting lineup column identifiers and player IDs - base_state = { - "play_id": first_row["play_id"], - "home_score": first_row["home_score"], - "away_score": first_row["away_score"], - "period": first_row["period"], - "remaining_time": first_row["remaining_time"], - "home_margin": first_row["home_score"] - first_row["away_score"], - "total": first_row["home_score"] + first_row["away_score"], - } - - # Assign starting players to their respective column identifiers and fetch their IDs - for i, position in enumerate(["h1", "h2", "h3", "h4", "h5"], start=1): - player_name = first_row[position] - player_id = player_mapper.name_to_id( - player_name - ) # Use player_mapper to get player ID - base_state[f"p{i}h_name"] = player_name - base_state[f"p{i}h_id"] = player_id # Include player ID - base_state[f"p{i}h_pts"] = 0 # Initialize points - base_state[f"p{i}h_pm"] = 0 # Initialize plus-minus - - for i, position in enumerate(["a1", "a2", "a3", "a4", "a5"], start=1): - player_name = first_row[position] - player_id = player_mapper.name_to_id( - player_name - ) # Use player_mapper to get player ID - base_state[f"p{i}a_name"] = player_name - base_state[f"p{i}a_id"] = player_id # Include player ID - base_state[f"p{i}a_pts"] = 0 # Initialize points - base_state[f"p{i}a_pm"] = 0 # Initialize plus-minus - - return base_state - - -def update_game_state_iteratively(df, base_state): - # Initialize the DataFrame to store the updated game states - updated_game_states = [base_state] # Start with the base state - - # Initialize dictionaries to track mappings and next available identifiers - # Maps player names to their column identifiers (e.g., 'LeBron James': 'p1h') - # Initialized with starting lineup mappings from the base state - player_column_mappings = {} - for i in range(1, 6): - player_column_mappings[base_state[f"p{i}h_name"]] = f"p{i}h" - player_column_mappings[base_state[f"p{i}a_name"]] = f"p{i}a" - - # Tracks the next available column identifier for home and away. - # Starts at 6 since 1 thru 5 are assigned to starting lineups in the base state - next_column_identifiers = { - "h": 6, - "a": 6, - } - - # Iterate through each row in the DataFrame (excluding the first row, already in base_state) - for index, row in df.iloc[1:].iterrows(): - previous_state = updated_game_states[-1].copy() # Get the previous state - current_state = previous_state.copy() # Start with the previous state - - # Part 1: Game State Metrics - # Update the current state with the game state metrics - current_state.update( - { - "play_id": row["play_id"], - "home_score": row["home_score"], - "away_score": row["away_score"], - "period": row["period"], - "remaining_time": row["remaining_time"], - "home_margin": row["home_score"] - row["away_score"], - "total": row["home_score"] + row["away_score"], - } - ) - - # Part 2: Player Names and IDs along with Column Assigning - - # Iterate through home and away team player positions - for team_prefix in ["h", "a"]: - for pos in range(1, 6): # For positions 1-5 - player_name = row[f"{team_prefix}{pos}"] - if pd.notna(player_name): - # Check if the player already has a column identifier - if player_name not in player_column_mappings: - # Assign next available column identifier - column_identifier = next_column_identifiers[team_prefix] - player_column_mappings[player_name] = ( - f"p{column_identifier}{team_prefix}" - ) - next_column_identifiers[team_prefix] += 1 - - # Get player ID from the mapper - player_id = player_mapper.name_to_id(player_name) - - # Update current state with player info - current_state[player_column_mappings[player_name] + "_name"] = ( - player_name - ) - current_state[player_column_mappings[player_name] + "_id"] = ( - player_id - ) - current_state[player_column_mappings[player_name] + "_pts"] = 0 - current_state[player_column_mappings[player_name] + "_pm"] = 0 - else: - # Ensure existing players are represented even if not active in the current play - column_identifier = player_column_mappings[player_name] - current_state[column_identifier + "_name"] = player_name - current_state[column_identifier + "_id"] = previous_state[ - column_identifier + "_id" - ] - - # Part 3: Player Cumulative Points - if pd.notna(row["player"]) and pd.notna(row["points"]) and row["points"] > 0: - scoring_player = row["player"] - points_scored = row["points"] - scoring_player_points_column = ( - player_column_mappings[scoring_player] + "_pts" - ) - current_state[scoring_player_points_column] += points_scored - - # Part 4: Player Cumulative Plus-Minus - previous_score_diff = ( - previous_state["home_score"] - previous_state["away_score"] - ) - current_score_diff = row["home_score"] - row["away_score"] - score_diff_change = current_score_diff - previous_score_diff - home_player_pm_adjustment = score_diff_change - away_player_pm_adjustment = -score_diff_change - - players_on_court_home = [ - row[f"h{i}"] for i in range(1, 6) if pd.notna(row[f"h{i}"]) - ] - players_on_court_away = [ - row[f"a{i}"] for i in range(1, 6) if pd.notna(row[f"a{i}"]) - ] - - players_on_court_home_column_identifiers = [ - player_column_mappings[player] for player in players_on_court_home - ] - players_on_court_away_column_identifiers = [ - player_column_mappings[player] for player in players_on_court_away - ] - - for column_identifier in players_on_court_home_column_identifiers: - current_state[column_identifier + "_pm"] += home_player_pm_adjustment - for column_identifier in players_on_court_away_column_identifiers: - current_state[column_identifier + "_pm"] += away_player_pm_adjustment - - # Append the current_state to the list of updated game states - updated_game_states.append(current_state) - - # Convert the list of game states into a DataFrame for final output - final_game_state_df = pd.DataFrame(updated_game_states) - return final_game_state_df - - -def create_game_states(df): - # Initialize the base game state - base_state = initialize_game_base_state(df) - - # Update the game state iteratively - final_game_state_df = update_game_state_iteratively(df, base_state) - - return final_game_state_df - - -def create_game_state_files(input_csv_path): - # Ensure the input path is absolute - input_csv_path = os.path.abspath(input_csv_path) - - # Load the CSV file into a DataFrame - df = pd.read_csv(input_csv_path) - - # Extract year1 and year2 from the folder name - folder_path = os.path.dirname(input_csv_path) - parent_folder_name = os.path.basename(folder_path) - year_pattern = re.compile(r"(\d{4})-(\d{4})_.*") - match = year_pattern.search(parent_folder_name) - if match: - year1, year2 = match.groups() - else: - # If year1 and year2 are not in the folder name, raise an error - raise ValueError("Could not extract year1 and year2 from the folder name.") - - # Extract game_id, dataset, and date from the input DataFrame - game_id = df["game_id"].iloc[0] - dataset = df["data_set"].iloc[0] - date = df["date"].iloc[0] - - # Extract home_team and away_team from the filename - filename_pattern = re.compile(r"-(\w+)@(\w+)\.csv$") - match = filename_pattern.search(input_csv_path) - if match: - away_team, home_team = match.groups() - else: - away_team, home_team = "Unknown", "Unknown" - - # Assume calculate_game_state is a function you've defined elsewhere - detailed_game_state_df = create_game_states(df) - - # Directory handling for Game_States - parent_dir = os.path.dirname(folder_path) - game_states_folder = os.path.join(parent_dir, f"{year1}-{year2}_Game_States") - os.makedirs(game_states_folder, exist_ok=True) - base_filename = os.path.splitext(os.path.basename(input_csv_path))[0] - game_states_csv_file = os.path.join( - game_states_folder, f"{base_filename}_game_states.csv" - ) - detailed_game_state_df.to_csv(game_states_csv_file, index=False) - - # Preparing the final game state DataFrame - final_state_df = detailed_game_state_df.iloc[[-1]].drop( - ["play_id", "period", "remaining_time"], axis=1 - ) - new_columns_df = pd.DataFrame( - { - "game_id": [game_id], - "data_set": [dataset], - "date": [date], - "home_team": [home_team], - "away_team": [away_team], - } - ) - final_state_ordered_df = pd.concat( - [new_columns_df, final_state_df.reset_index(drop=True)], axis=1 - ) - - # Directory handling for Final_States - final_states_folder = os.path.join(parent_dir, f"{year1}-{year2}_Final_States") - os.makedirs(final_states_folder, exist_ok=True) - final_state_csv_file = os.path.join( - final_states_folder, f"{base_filename}_final_state.csv" - ) - final_state_ordered_df.to_csv(final_state_csv_file, index=False) - - # New logic for Prior_States - # Directory handling for Prior_States - prior_states_folder = os.path.join(parent_dir, f"{year1}-{year2}_Prior_States") - os.makedirs(prior_states_folder, exist_ok=True) - prior_state_csv_file = os.path.join( - prior_states_folder, f"{base_filename}_prior_state.csv" - ) - - final_state_filename_pattern = re.compile( - r"\[(\d{4}-\d{2}-\d{2})\]-(\d{10})-(\w{3})@(\w{3})(?:_final_state)?\.csv$" - ) - - pattern = os.path.join(final_states_folder, "*.csv") - - # Convert current game date string to datetime object for comparison - try: - current_game_date_dt = datetime.strptime(date, "%Y-%m-%d") - except ValueError: - current_game_date_dt = datetime.strptime(date, "%m/%d/%Y") - - all_prior_states_df = pd.DataFrame() - - for team in [home_team, away_team]: - team_prior_games_final_states = [] - for file in glob.glob(pattern): - filename = os.path.basename(file) - match = final_state_filename_pattern.match(filename) - if match: - # Extract the components from the filename - date_str, game_id, away, home = match.groups() - game_date_dt = datetime.strptime(date_str, "%Y-%m-%d") - - # Check if the file includes the specified team and predates the current game - if (team in [home, away]) and (game_date_dt < current_game_date_dt): - df = pd.read_csv(file) - df["current_focus_team"] = team - team_prior_games_final_states.append(df) - - if len(team_prior_games_final_states) == 0: - team_prior_states_df = pd.DataFrame() - else: - team_prior_states_df = pd.concat( - team_prior_games_final_states, ignore_index=True - ) - - all_prior_states_df = pd.concat([all_prior_states_df, team_prior_states_df]) - - all_prior_states_df.to_csv(prior_state_csv_file, index=False) - - return game_states_csv_file, final_state_csv_file, prior_state_csv_file - - -def create_game_state_files_by_folder(folder_path): - # List all CSV files in the given folder, excluding any with "combined-stats" in the filename - csv_files = [ - file - for file in os.listdir(folder_path) - if file.endswith(".csv") and "combined-stats" not in file - ] - - # Sort files by date extracted from the filename - csv_files_sorted = sorted( - csv_files, key=lambda x: datetime.strptime(x.split("]")[0][1:], "%Y-%m-%d") - ) - - # Total number of files to process - total_files = len(csv_files_sorted) - print(f"Total files to process: {total_files}") - - # Iterate over each CSV file and process it - for csv_file in tqdm.tqdm(csv_files_sorted, desc="Processing files", unit="file"): - full_file_path = os.path.join(folder_path, csv_file) - try: - create_game_state_files( - full_file_path - ) # Assume this is a predefined function elsewhere - except Exception as e: - print(f"Failed to process file: {csv_file}") - print(f"Error: {str(e)}") - traceback.print_exc() - - if total_files == 0: - print("No files to process.") - else: - print("All files processed successfully.") - - -if __name__ == "__main__": - # game_state_df = create_game_states( - # pd.read_csv( - # "../data/2021-2022/2021-2022_NBA_PbP_Logs/[2021-10-19]-0022100001-BKN@MIL.csv" - # ) - # ) - - # print(game_state_df.info(verbose=True)) - # print(game_state_df.head()) - # print(game_state_df.tail()) - - # create_game_state_files( - # "../data/2021-2022/2021-2022_NBA_PbP_Logs/[2021-10-23]-0022100029-DAL@TOR.csv" - # ) - - create_game_state_files_by_folder("../data/2022-2023/2022-2023_NBA_PbP_Logs") diff --git a/src/linear_model.py b/src/linear_model.py new file mode 100644 index 0000000..0784826 --- /dev/null +++ b/src/linear_model.py @@ -0,0 +1,218 @@ +import os +from datetime import datetime + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import seaborn as sns +from dotenv import load_dotenv +from joblib import dump +from sklearn.linear_model import Ridge +from sklearn.metrics import brier_score_loss, mean_absolute_error, r2_score +from sklearn.model_selection import RandomizedSearchCV +from sklearn.preprocessing import StandardScaler +from wandb.sklearn import plot_regressor + +import wandb +from utils import load_featurized_data + +load_dotenv() +PROJECT_ROOT = os.getenv("PROJECT_ROOT") + +pd.set_option("display.max_columns", None) +sns.set_context("notebook") + +if __name__ == "__main__": + # Choose Seasons + training_seasons = ["2021-2022"] + testing_seasons = ["2022-2023"] + + # Load data + training_df = load_featurized_data(training_seasons) + testing_df = load_featurized_data(testing_seasons) + + # Remove nans + training_df = training_df.dropna() + testing_df = testing_df.dropna() + + # Create X and y + X_train = training_df.drop( + columns=[ + "game_id", + "game_date", + "home_margin", + "home_score", + "away_score", + "total_score", + ] + ) + y_train = training_df[["home_score", "away_score"]] + X_test = testing_df.drop( + columns=[ + "game_id", + "game_date", + "home_margin", + "home_score", + "away_score", + "total_score", + ] + ) + y_test = testing_df[["home_score", "away_score"]] + + # Save the feature names for later use + feature_names = X_train.columns.tolist() + + # Initialize the scaler + scaler = StandardScaler() + + # Fit on the training data and transform both the training and testing data + X_train = scaler.fit_transform(X_train) + X_test = scaler.transform(X_test) + + # Hyperparameter Tuning + # Define the hyperparameters and their distributions + param_distributions = { + "alpha": np.logspace(-4, 4, 20), # Logarithmically spaced values + "fit_intercept": [True, False], + } + + # Initialize the RandomizedSearchCV for Ridge Regression + random_search = RandomizedSearchCV( + Ridge(), param_distributions, n_iter=10, cv=5, random_state=42 + ) + + # Fit the RandomizedSearchCV + random_search.fit(X_train, y_train) + + # Get the best parameters + best_params = random_search.best_params_ + + # Initialize wandb + run = wandb.init(project="NBA AI", config=best_params) + + # Update the configuration + model_type = "Ridge Regression" + run_datetime = datetime.now().isoformat() + wandb.config.update( + { + "model_type": model_type, + "train_seasons": training_seasons, + "test_seasons": testing_seasons, + "train_shape": X_train.shape, + "test_shape": X_test.shape, + "targets": ["home_score", "away_score"], + "features": feature_names, + "run_datetime": run_datetime, + } + ) + + # Setup the model with the best parameters found + model = Ridge(**best_params) + + # Fit the model + model.fit(X_train, y_train) + + # Create predictions + y_pred = model.predict(X_test) + y_pred_train = model.predict(X_train) + + y_pred_home_score = y_pred[:, 0] + y_pred_away_score = y_pred[:, 1] + y_pred_home_margin = y_pred_home_score - y_pred_away_score + + y_pred_train_home_score = y_pred_train[:, 0] + y_pred_train_away_score = y_pred_train[:, 1] + y_pred_train_home_margin = y_pred_train_home_score - y_pred_train_away_score + + def sigmoid(x): + sensitivity_factor = ( + 0.1 # Adjust this value to change the steepness of the curve + ) + return 1 / (1 + np.exp(-x * sensitivity_factor)) + + y_pred_home_win_prob = sigmoid(y_pred_home_margin) + y_pred_train_home_win_prob = sigmoid(y_pred_train_home_margin) + + # Compute metrics + train_mae_home_score = mean_absolute_error( + y_train["home_score"], y_pred_train_home_score + ) + test_mae_home_score = mean_absolute_error(y_test["home_score"], y_pred_home_score) + train_r2_home_score = r2_score(y_train["home_score"], y_pred_train_home_score) + test_r2_home_score = r2_score(y_test["home_score"], y_pred_home_score) + + train_mae_away_score = mean_absolute_error( + y_train["away_score"], y_pred_train_away_score + ) + test_mae_away_score = mean_absolute_error(y_test["away_score"], y_pred_away_score) + train_r2_away_score = r2_score(y_train["away_score"], y_pred_train_away_score) + test_r2_away_score = r2_score(y_test["away_score"], y_pred_away_score) + + y_train_home_margin = y_train["home_score"] - y_train["away_score"] + y_test_home_margin = y_test["home_score"] - y_test["away_score"] + + train_mae_home_margin = mean_absolute_error( + y_train_home_margin, y_pred_train_home_margin + ) + test_mae_home_margin = mean_absolute_error(y_test_home_margin, y_pred_home_margin) + train_r2_home_margin = r2_score(y_train_home_margin, y_pred_train_home_margin) + test_r2_home_margin = r2_score(y_test_home_margin, y_pred_home_margin) + + y_train_home_win = y_train_home_margin > 0 + y_test_home_win = y_test_home_margin > 0 + + train_brier_score = brier_score_loss(y_train_home_win, y_pred_train_home_win_prob) + test_brier_score = brier_score_loss(y_test_home_win, y_pred_home_win_prob) + + # Log the metrics + wandb.log( + { + "train_mae_home_score": train_mae_home_score, + "test_mae_home_score": test_mae_home_score, + "train_r2_home_score": train_r2_home_score, + "test_r2_home_score": test_r2_home_score, + "train_mae_away_score": train_mae_away_score, + "test_mae_away_score": test_mae_away_score, + "train_r2_away_score": train_r2_away_score, + "test_r2_away_score": test_r2_away_score, + "train_mae_home_margin": train_mae_home_margin, + "test_mae_home_margin": test_mae_home_margin, + "train_r2_home_margin": train_r2_home_margin, + "test_r2_home_margin": test_r2_home_margin, + "train_brier_score": train_brier_score, + "test_brier_score": test_brier_score, + } + ) + + # Log model info + wandb.log({"intercept": model.intercept_}) + # Log the coefficients as a dictionary + wandb.log({"coefficients": dict(zip(feature_names, model.coef_))}) + + # Create and log the plots + plot_regressor(model, X_train, X_test, y_train, y_test, model_name=model_type) + + # Feature importances + feature_importances = dict(zip(feature_names, model.coef_)) + sorted_features = sorted( + feature_importances.items(), key=lambda x: abs(x[1]), reverse=True + ) + features, importances = zip(*sorted_features) + + plt.figure(figsize=(10, 6)) + sns.barplot(x=importances, y=features, palette="viridis") + plt.xlabel("Importance") + plt.ylabel("Feature") + plt.title("Feature Importances") + + wandb.log({"feature_importances": plt}) + + # Save the model + model_filename = f"{PROJECT_ROOT}/models/{model_type}_{run_datetime}_train_mae_{train_mae:.2f}_test_mae_{test_mae:.2f}.joblib" + + # Save the model using joblib + dump(model, model_filename) + wandb.save(model_filename, base_path=PROJECT_ROOT) + + # Finish the wandb run + run.finish() diff --git a/src/mlp_model.py b/src/mlp_model.py new file mode 100644 index 0000000..e69de29 diff --git a/src/player_mapper.py b/src/player_mapper.py deleted file mode 100644 index 8bfeb62..0000000 --- a/src/player_mapper.py +++ /dev/null @@ -1,59 +0,0 @@ -import re -import warnings - -import pandas as pd - - -class NBAPlayerMapper: - def __init__(self, csv_path): - self.data = pd.read_csv(csv_path) - # Normalize names and create mappings - self.data["normalized_name"] = self.data["full_name"].apply(self.normalize_name) - # Mapping player names and normalized names to IDs, considering possible duplicates - self.name_to_ids_map = ( - self.data.groupby("full_name")["id"].apply(list).to_dict() - ) - self.normalized_name_to_ids_map = ( - self.data.groupby("normalized_name")["id"].apply(list).to_dict() - ) - self.id_to_names_map = ( - self.data.groupby("id")["full_name"].apply(list).to_dict() - ) - - @staticmethod - def normalize_name(name): - """Normalize a name for matching.""" - return re.sub(r"\W", "", name).lower() - - def name_to_id(self, player_name): - """Find player ID(s) using a multi-level matching system, with error handling for duplicates.""" - # Exact match - if player_name in self.name_to_ids_map: - ids = self.name_to_ids_map[player_name] - if len(ids) > 1: - print(f"Duplicate IDs found for '{player_name}': {ids}") - return None - return ids[0] - - # Normalized match - normalized_name = self.normalize_name(player_name) - if normalized_name in self.normalized_name_to_ids_map: - ids = self.normalized_name_to_ids_map[normalized_name] - if len(ids) > 1: - print( - f"Duplicate IDs found for '{player_name} > {normalized_name}': {ids}" - ) - return None - return ids[0] - - # If no match found - return None - - def id_to_name(self, player_id): - """Return player's full name(s) given their ID, with error handling for inconsistencies.""" - if player_id in self.id_to_names_map: - names = self.id_to_names_map[player_id] - if len(names) > 1: - raise ValueError(f"Duplicate names found for ID '{player_id}': {names}") - return names[0] - raise ValueError(f"No player found for ID '{player_id}'") diff --git a/src/predictions.py b/src/predictions.py new file mode 100644 index 0000000..22a2654 --- /dev/null +++ b/src/predictions.py @@ -0,0 +1,50 @@ +import numpy as np + + +def random_predictions(game): + output_game = game.copy() + + # Generate random home and away scores from a normal distribution + home_score = int(max(80, min(140, abs(np.random.normal(110, 15))))) + away_score = int(max(80, min(140, abs(np.random.normal(110, 15))))) + + # Determine the winning team and winning percentage + if home_score > away_score: + winning_team = game["home"] + winning_team_pct = (home_score + (home_score - away_score)) / ( + home_score + away_score + ) + else: + winning_team = game["away"] + winning_team_pct = (away_score + (away_score - home_score)) / ( + home_score + away_score + ) + + winning_team_pct = f"{winning_team_pct:.0%}" + + predictions = { + "home_score": home_score, + "away_score": away_score, + "winning_team": winning_team, + "winning_team_pct": winning_team_pct, + "players": {"home": {}, "away": {}}, + } + + # Generate random player predictions + if game["game_states"]: + current_players = game["game_states"][-1]["players"] + for player in current_players["home"]: + predictions["players"]["home"][player] = { + "name": current_players["home"][player]["name"], + "points": int(max(0, min(40, abs(np.random.normal(20, 5))))), + } + # Add this loop for away team players + for player in current_players["away"]: + predictions["players"]["away"][player] = { + "name": current_players["away"][player]["name"], + "points": int(max(0, min(40, abs(np.random.normal(20, 5))))), + } + + output_game["predictions"] = predictions + + return output_game diff --git a/src/team_mapper.py b/src/team_mapper.py deleted file mode 100644 index a8a91cd..0000000 --- a/src/team_mapper.py +++ /dev/null @@ -1,563 +0,0 @@ -class NBATeamConverter: - """ - A class to convert between various identifiers of NBA teams such as team ID, abbreviation, - short name, full name, or any of the alternatives. - """ - - # Team data with details for each team. Include abbreviation, full name, short name, and alternatives. - teams_data = { - "1610612737": { - "abbreviation": "ATL", - "full_name": "Atlanta Hawks", - "short_name": "Hawks", - "alternatives": [ - "STL", - "Tri-Cities Blackhawks", - "MLH", - "TRI", - "atlanta-hawks", - "Milwaukee Hawks", - "St. Louis Hawks", - ], - }, - "1610612751": { - "abbreviation": "BKN", - "full_name": "Brooklyn Nets", - "short_name": "Nets", - "alternatives": [ - "NJA", - "BK", - "brooklyn-nets", - "NYN", - "NJN", - "New York Nets", - "BRK", - "New Jersey Americans", - "New Jersey Nets", - ], - }, - "1610612738": { - "abbreviation": "BOS", - "full_name": "Boston Celtics", - "short_name": "Celtics", - "alternatives": ["boston-celtics"], - }, - "1610612766": { - "abbreviation": "CHA", - "full_name": "Charlotte Hornets", - "short_name": "Hornets", - "alternatives": [ - "charlotte-hornets", - "Charlotte Bobcats", - "CHH", - "CHO", - ], - }, - "1610612739": { - "abbreviation": "CLE", - "full_name": "Cleveland Cavaliers", - "short_name": "Cavaliers", - "alternatives": ["cleveland-cavaliers"], - }, - "1610612741": { - "abbreviation": "CHI", - "full_name": "Chicago Bulls", - "short_name": "Bulls", - "alternatives": ["chicago-bulls"], - }, - "1610612742": { - "abbreviation": "DAL", - "full_name": "Dallas Mavericks", - "short_name": "Mavericks", - "alternatives": ["dallas-mavericks"], - }, - "1610612743": { - "abbreviation": "DEN", - "full_name": "Denver Nuggets", - "short_name": "Nuggets", - "alternatives": ["Denver Rockets", "DNR", "denver-nuggets"], - }, - "1610612765": { - "abbreviation": "DET", - "full_name": "Detroit Pistons", - "short_name": "Pistons", - "alternatives": ["FTW", "Fort Wayne Pistons", "detroit-pistons"], - }, - "1610612744": { - "abbreviation": "GSW", - "full_name": "Golden State Warriors", - "short_name": "Warriors", - "alternatives": [ - "PHW", - "GS", - "Philadelphia Warriors", - "San Francisco Warriors", - "SFW", - "golden-state-warriors", - ], - }, - "1610612745": { - "abbreviation": "HOU", - "full_name": "Houston Rockets", - "short_name": "Rockets", - "alternatives": ["SDR", "San Diego Rockets", "houston-rockets"], - }, - "1610612754": { - "abbreviation": "IND", - "full_name": "Indiana Pacers", - "short_name": "Pacers", - "alternatives": ["indiana-pacers"], - }, - "1610612746": { - "abbreviation": "LAC", - "full_name": "Los Angeles Clippers", - "short_name": "Clippers", - "alternatives": [ - "SDC", - "los-angeles-clippers", - "Buffalo Braves", - "San Diego Clippers", - "LA Clippers", - "BUF", - ], - }, - "1610612747": { - "abbreviation": "LAL", - "full_name": "Los Angeles Lakers", - "short_name": "Lakers", - "alternatives": ["MNL", "los-angeles-lakers", "Minneapolis Lakers"], - }, - "1610612763": { - "abbreviation": "MEM", - "full_name": "Memphis Grizzlies", - "short_name": "Grizzlies", - "alternatives": ["memphis-grizzlies", "VAN", "Vancouver Grizzlies"], - }, - "1610612748": { - "abbreviation": "MIA", - "full_name": "Miami Heat", - "short_name": "Heat", - "alternatives": ["miami-heat"], - }, - "1610612749": { - "abbreviation": "MIL", - "full_name": "Milwaukee Bucks", - "short_name": "Bucks", - "alternatives": ["milwaukee-bucks"], - }, - "1610612750": { - "abbreviation": "MIN", - "full_name": "Minnesota Timberwolves", - "short_name": "Timberwolves", - "alternatives": ["minnesota-timberwolves"], - }, - "1610612752": { - "abbreviation": "NYK", - "full_name": "New York Knicks", - "short_name": "Knicks", - "alternatives": ["new-york-knicks", "NY"], - }, - "1610612740": { - "abbreviation": "NOP", - "full_name": "New Orleans Pelicans", - "short_name": "Pelicans", - "alternatives": [ - "new-orleans-pelicans", - "NOH", - "New Orleans Hornets", - "NOK", - "NO", - "New Orleans/Oklahoma City Hornets", - ], - }, - "1610612760": { - "abbreviation": "OKC", - "full_name": "Oklahoma City Thunder", - "short_name": "Thunder", - "alternatives": ["Seattle SuperSonics", "oklahoma-city-thunder", "SEA"], - }, - "1610612753": { - "abbreviation": "ORL", - "full_name": "Orlando Magic", - "short_name": "Magic", - "alternatives": ["orlando-magic"], - }, - "1610612755": { - "abbreviation": "PHI", - "full_name": "Philadelphia 76ers", - "short_name": "76ers", - "alternatives": [ - "PHL", - "Syracuse Nationals", - "SYR", - "philadelphia-76ers", - ], - }, - "1610612756": { - "abbreviation": "PHX", - "full_name": "Phoenix Suns", - "short_name": "Suns", - "alternatives": ["phoenix-suns", "PHO"], - }, - "1610612757": { - "abbreviation": "POR", - "full_name": "Portland Trail Blazers", - "short_name": "Trail Blazers", - "alternatives": ["portland-trail-blazers"], - }, - "1610612759": { - "abbreviation": "SAS", - "full_name": "San Antonio Spurs", - "short_name": "Spurs", - "alternatives": [ - "DLC", - "SAN", - "Dallas Chaparrals", - "SA", - "san-antonio-spurs", - ], - }, - "1610612758": { - "abbreviation": "SAC", - "full_name": "Sacramento Kings", - "short_name": "Kings", - "alternatives": [ - "KCO", - "KCK", - "Kansas City-Omaha Kings", - "Kansas City Kings", - "sacramento-kings", - "Cincinnati Royals", - "CIN", - "Rochester Royals", - "ROC", - ], - }, - "1610612761": { - "abbreviation": "TOR", - "full_name": "Toronto Raptors", - "short_name": "Raptors", - "alternatives": ["toronto-raptors"], - }, - "1610612762": { - "abbreviation": "UTA", - "full_name": "Utah Jazz", - "short_name": "Jazz", - "alternatives": ["NOJ", "utah-jazz", "New Orleans Jazz", "UTAH"], - }, - "1610612764": { - "abbreviation": "WAS", - "full_name": "Washington Wizards", - "short_name": "Wizards", - "alternatives": [ - "WSH", - "Washington Bullets", - "CAP", - "BAL", - "Baltimore Bullets", - "CHP", - "CHZ", - "Chicago Packers", - "WSB", - "washington-wizards", - "Capital Bullets", - "Chicago Zephyrs", - ], - }, - } - - # Lookup dictionary for quick ID retrieval based on any identifier. - lookup_dict = { - "1610612737": "1610612737", - "ATL": "1610612737", - "Hawks": "1610612737", - "Atlanta Hawks": "1610612737", - "STL": "1610612737", - "Tri-Cities Blackhawks": "1610612737", - "MLH": "1610612737", - "TRI": "1610612737", - "atlanta-hawks": "1610612737", - "Milwaukee Hawks": "1610612737", - "St. Louis Hawks": "1610612737", - "1610612751": "1610612751", - "BKN": "1610612751", - "Nets": "1610612751", - "Brooklyn Nets": "1610612751", - "NJA": "1610612751", - "BK": "1610612751", - "brooklyn-nets": "1610612751", - "NYN": "1610612751", - "NJN": "1610612751", - "New York Nets": "1610612751", - "BRK": "1610612751", - "New Jersey Americans": "1610612751", - "New Jersey Nets": "1610612751", - "1610612738": "1610612738", - "BOS": "1610612738", - "Celtics": "1610612738", - "Boston Celtics": "1610612738", - "boston-celtics": "1610612738", - "1610612766": "1610612766", - "CHA": "1610612766", - "Hornets": "1610612766", - "Charlotte Hornets": "1610612766", - "charlotte-hornets": "1610612766", - "Charlotte Bobcats": "1610612766", - "CHH": "1610612766", - "CHO": "1610612766", - "1610612739": "1610612739", - "CLE": "1610612739", - "Cavaliers": "1610612739", - "Cleveland Cavaliers": "1610612739", - "cleveland-cavaliers": "1610612739", - "1610612741": "1610612741", - "CHI": "1610612741", - "Bulls": "1610612741", - "Chicago Bulls": "1610612741", - "chicago-bulls": "1610612741", - "1610612742": "1610612742", - "DAL": "1610612742", - "Mavericks": "1610612742", - "Dallas Mavericks": "1610612742", - "dallas-mavericks": "1610612742", - "1610612743": "1610612743", - "DEN": "1610612743", - "Nuggets": "1610612743", - "Denver Nuggets": "1610612743", - "Denver Rockets": "1610612743", - "DNR": "1610612743", - "denver-nuggets": "1610612743", - "1610612765": "1610612765", - "DET": "1610612765", - "Pistons": "1610612765", - "Detroit Pistons": "1610612765", - "FTW": "1610612765", - "Fort Wayne Pistons": "1610612765", - "detroit-pistons": "1610612765", - "1610612744": "1610612744", - "GSW": "1610612744", - "Warriors": "1610612744", - "Golden State Warriors": "1610612744", - "PHW": "1610612744", - "GS": "1610612744", - "Philadelphia Warriors": "1610612744", - "San Francisco Warriors": "1610612744", - "SFW": "1610612744", - "golden-state-warriors": "1610612744", - "1610612745": "1610612745", - "HOU": "1610612745", - "Rockets": "1610612745", - "Houston Rockets": "1610612745", - "SDR": "1610612745", - "San Diego Rockets": "1610612745", - "houston-rockets": "1610612745", - "1610612754": "1610612754", - "IND": "1610612754", - "Pacers": "1610612754", - "Indiana Pacers": "1610612754", - "indiana-pacers": "1610612754", - "1610612746": "1610612746", - "LAC": "1610612746", - "Clippers": "1610612746", - "Los Angeles Clippers": "1610612746", - "SDC": "1610612746", - "los-angeles-clippers": "1610612746", - "Buffalo Braves": "1610612746", - "San Diego Clippers": "1610612746", - "LA Clippers": "1610612746", - "BUF": "1610612746", - "1610612747": "1610612747", - "LAL": "1610612747", - "Lakers": "1610612747", - "Los Angeles Lakers": "1610612747", - "MNL": "1610612747", - "los-angeles-lakers": "1610612747", - "Minneapolis Lakers": "1610612747", - "1610612763": "1610612763", - "MEM": "1610612763", - "Grizzlies": "1610612763", - "Memphis Grizzlies": "1610612763", - "memphis-grizzlies": "1610612763", - "VAN": "1610612763", - "Vancouver Grizzlies": "1610612763", - "1610612748": "1610612748", - "MIA": "1610612748", - "Heat": "1610612748", - "Miami Heat": "1610612748", - "miami-heat": "1610612748", - "1610612749": "1610612749", - "MIL": "1610612749", - "Bucks": "1610612749", - "Milwaukee Bucks": "1610612749", - "milwaukee-bucks": "1610612749", - "1610612750": "1610612750", - "MIN": "1610612750", - "Timberwolves": "1610612750", - "Minnesota Timberwolves": "1610612750", - "minnesota-timberwolves": "1610612750", - "1610612752": "1610612752", - "NYK": "1610612752", - "Knicks": "1610612752", - "New York Knicks": "1610612752", - "new-york-knicks": "1610612752", - "NY": "1610612752", - "1610612740": "1610612740", - "NOP": "1610612740", - "Pelicans": "1610612740", - "New Orleans Pelicans": "1610612740", - "new-orleans-pelicans": "1610612740", - "NOH": "1610612740", - "New Orleans Hornets": "1610612740", - "NOK": "1610612740", - "NO": "1610612740", - "New Orleans/Oklahoma City Hornets": "1610612740", - "1610612760": "1610612760", - "OKC": "1610612760", - "Thunder": "1610612760", - "Oklahoma City Thunder": "1610612760", - "Seattle SuperSonics": "1610612760", - "oklahoma-city-thunder": "1610612760", - "SEA": "1610612760", - "1610612753": "1610612753", - "ORL": "1610612753", - "Magic": "1610612753", - "Orlando Magic": "1610612753", - "orlando-magic": "1610612753", - "1610612755": "1610612755", - "PHI": "1610612755", - "76ers": "1610612755", - "Philadelphia 76ers": "1610612755", - "PHL": "1610612755", - "Syracuse Nationals": "1610612755", - "SYR": "1610612755", - "philadelphia-76ers": "1610612755", - "1610612756": "1610612756", - "PHX": "1610612756", - "Suns": "1610612756", - "Phoenix Suns": "1610612756", - "phoenix-suns": "1610612756", - "PHO": "1610612756", - "1610612757": "1610612757", - "POR": "1610612757", - "Trail Blazers": "1610612757", - "Portland Trail Blazers": "1610612757", - "portland-trail-blazers": "1610612757", - "1610612759": "1610612759", - "SAS": "1610612759", - "Spurs": "1610612759", - "San Antonio Spurs": "1610612759", - "DLC": "1610612759", - "SAN": "1610612759", - "Dallas Chaparrals": "1610612759", - "SA": "1610612759", - "san-antonio-spurs": "1610612759", - "1610612758": "1610612758", - "SAC": "1610612758", - "Kings": "1610612758", - "Sacramento Kings": "1610612758", - "KCO": "1610612758", - "KCK": "1610612758", - "Kansas City-Omaha Kings": "1610612758", - "Kansas City Kings": "1610612758", - "sacramento-kings": "1610612758", - "Cincinnati Royals": "1610612758", - "CIN": "1610612758", - "Rochester Royals": "1610612758", - "ROC": "1610612758", - "1610612761": "1610612761", - "TOR": "1610612761", - "Raptors": "1610612761", - "Toronto Raptors": "1610612761", - "toronto-raptors": "1610612761", - "1610612762": "1610612762", - "UTA": "1610612762", - "Jazz": "1610612762", - "Utah Jazz": "1610612762", - "NOJ": "1610612762", - "utah-jazz": "1610612762", - "New Orleans Jazz": "1610612762", - "UTAH": "1610612762", - "1610612764": "1610612764", - "WAS": "1610612764", - "Wizards": "1610612764", - "Washington Wizards": "1610612764", - "WSH": "1610612764", - "Washington Bullets": "1610612764", - "CAP": "1610612764", - "BAL": "1610612764", - "Baltimore Bullets": "1610612764", - "CHP": "1610612764", - "CHZ": "1610612764", - "Chicago Packers": "1610612764", - "WSB": "1610612764", - "washington-wizards": "1610612764", - "Capital Bullets": "1610612764", - "Chicago Zephyrs": "1610612764", - } - - @classmethod - def get_team_id(cls, identifier): - """ - Retrieve the team ID based on any given identifier. - - Args: - identifier (str|int): The identifier to lookup (team ID, abbreviation, short name, full name, or alternatives). - - Returns: - int|str: The corresponding team ID or a message indicating an unknown identifier. - """ - return cls.lookup_dict.get(identifier, "Unknown ID") - - @classmethod - def get_abbreviation(cls, identifier): - """ - Retrieve the team abbreviation based on any given identifier. - - Args: - identifier (str|int): The identifier to lookup. - - Returns: - str: The corresponding team abbreviation or a message indicating an unknown identifier. - """ - team_id = cls.get_team_id(identifier) - return ( - cls.teams_data[team_id]["abbreviation"] - if team_id != "Unknown ID" - else "Unknown Abbreviation" - ) - - @classmethod - def get_short_name(cls, identifier): - """ - Retrieve the team short name based on any given identifier. - - Args: - identifier (str|int): The identifier to lookup. - - Returns: - str: The corresponding team short name or a message indicating an unknown identifier. - """ - team_id = cls.get_team_id(identifier) - return ( - cls.teams_data[team_id]["short_name"] - if team_id != "Unknown ID" - else "Unknown Short Name" - ) - - @classmethod - def get_full_name(cls, identifier): - """ - Retrieve the team full name based on any given identifier. - - Args: - identifier (str|int): The identifier to lookup. - - Returns: - str: The corresponding team full name or a message indicating an unknown identifier. - """ - team_id = cls.get_team_id(identifier) - return ( - cls.teams_data[team_id]["full_name"] - if team_id != "Unknown ID" - else "Unknown Full Name" - ) diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..e1d628a --- /dev/null +++ b/src/utils.py @@ -0,0 +1,679 @@ +import json +import os +import re + +import pandas as pd +import requests +from dotenv import load_dotenv +from tqdm import tqdm + +load_dotenv() +PROJECT_ROOT = os.getenv("PROJECT_ROOT") + + +def load_featurized_data(seasons): + dfs = [] + for season in seasons: + dfs.append(pd.read_csv(f"{PROJECT_ROOT}/data/featurized_NBAStats/{season}.csv")) + return pd.concat(dfs) + + +def create_featurized_data_csv(season): + """ + This function creates a CSV file of featurized data for a given NBA season. + Saves time loading in data from core data files for basic featurized modeling. + + Parameters: + season (str): The season for which to create the featurized data CSV. + + Returns: + pd.DataFrame: A DataFrame of the featurized data for the given season. + """ + # Define the path to the season folder + season_folder = f"{PROJECT_ROOT}/data/NBAStats/{season}" + # Initialize an empty list to store the season data + season_data = [] + # Convert os.scandir() object to a list to get the count + date_folders = list(os.scandir(season_folder)) + # Iterate over all date subfolders in the season folder + for date_folder in tqdm( + date_folders, total=len(date_folders), desc="Loading season data" + ): + # Iterate over all JSON files in the date folder + for game_file in os.scandir(date_folder.path): + try: + # Open and load the JSON file + with open(game_file.path, "r") as f: + game = json.load(f) + # Check if the game has a feature set + if game["prior_states"]["feature_set"]: + # Extract the game ID, date, feature set, and home margin + game_id = game["game_id"] + game_date = game["game_date"] + feature_set = game["prior_states"]["feature_set"][0] + home_score = game["final_state"]["home_score"] + away_score = game["final_state"]["away_score"] + home_margin = game["final_state"]["home_margin"] + total_score = home_score + away_score + # Append the extracted data to the season data list + season_data.append( + { + "game_id": game_id, + "game_date": game_date, + "home_score": home_score, + "away_score": away_score, + "home_margin": home_margin, + "total_score": total_score, + **feature_set, + } + ) + except Exception as e: + # Print an error message if an exception occurs + print(f"Error processing file: {game_file.path}") + print(f"Error: {str(e)}") + # Convert the list of dictionaries to a DataFrame + season_data = pd.DataFrame(season_data) + # Write the DataFrame to a CSV file + season_data.to_csv( + f"{PROJECT_ROOT}/data/featurized_NBAStats/{season}.csv", index=False + ) + + # Return the DataFrame + return season_data + + +def lookup_basic_game_info(game_id): + """ + This function looks up basic game information given a game_id. + + Parameters: + game_id (str): The ID of the game to look up. + + Returns: + dict: A dictionary containing the game_id, home team, away team, game date, game time, and game status. + + Raises: + ValueError: If multiple games are found for the given game_id or if no game is found for the given game_id. + """ + + # Validate the game_id + validate_game_id(game_id) + + # Convert the game_id to a season string + season = game_id_to_season(game_id, abbreviate=True) + + # Get the schedule for the extracted season + schedule = get_schedule(season) + + # Find the game in the schedule using the game_id + game = [g for g in schedule if g["gameId"] == game_id] + + # Ensure a single game is returned + if len(game) > 1: + raise ValueError(f"""Multiple games found for Game ID {game_id}.""") + + # Raise an error if the game is not found + if not game: + raise ValueError(f"""Game ID {game_id} not found in the schedule.""") + + # Extract the first (and only) game from the list + game = game[0] + + # Extract the home team, away team, game date, and game time from the game dictionary + home = game["homeTeam"] + away = game["awayTeam"] + game_date = game["gameDateTimeEst"][:10] + game_time_est = game["gameDateTimeEst"][11:19] + game_status_id = game["gameStatus"] + + # Return the game information as a dictionary + return { + "game_id": game_id, + "home": home, + "away": away, + "game_date": game_date, + "game_time_est": game_time_est, + "game_status_id": game_status_id, + } + + +def get_games_for_date(date, season): + """ + Fetches the NBA games for a given date and season. + + Parameters: + date (str): The date to fetch the games for, formatted as 'YYYY-MM-DD'. + season (str): The season to fetch the games for, formatted as 'XXXX-XX'. + + Returns: + list: A list of dictionaries, each representing a game. Each dictionary contains the game ID, status, date/time, and the home and away teams. + """ + # Validate the date and season formats + validate_date_format(date) + validate_season_format(season, abbreviated=True) + + # Fetch the schedule for the entire season + season_schedule = get_schedule(season) + + # Filter the schedule to only include games on the specified date + # Note: This assumes that game["gameDateTimeEst"] is a string starting with the date in 'YYYY-MM-DD' format + games_on_date = [ + game for game in season_schedule if game["gameDateTimeEst"].startswith(date) + ] + + return games_on_date + + +def get_schedule(season): + """ + Fetches the NBA schedule for a given season. + + Parameters: + season (str): The season to fetch the schedule for, formatted as 'XXXX-XX' (e.g., '2020-21'). + + Returns: + list: A list of dictionaries, each representing a game. Each dictionary contains the game ID, status, date/time, and the home and away teams. + """ + # Check if the season format is correct + validate_season_format(season, abbreviated=True) + + # Define the endpoint URL, including the season + endpoint = ( + f"https://stats.nba.com/stats/scheduleleaguev2?Season={season}&LeagueID=00" + ) + + # Define the headers to be sent with the request + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3", + "Referer": "https://stats.nba.com/schedule/", + "Accept-Language": "en-US,en;q=0.9", + } + + try: + # Send the request and get the response + response = requests.get(endpoint, headers=headers) + response.raise_for_status() # Raise an exception if the response indicates an error + except requests.exceptions.RequestException as e: + print(f"Error occurred: {e}") + raise + + # Parse the JSON response + game_dates = response.json()["leagueSchedule"]["gameDates"] + + # Extract all games from the response + all_games = [game for date in game_dates for game in date["games"]] + + # Define the keys to be kept in the game dictionaries + keys_needed = [ + "gameId", + "gameStatus", + "gameDateTimeEst", + "homeTeam", + "awayTeam", + ] + + # Filter the game dictionaries to only include the needed keys + all_games = [{key: game[key] for key in keys_needed} for game in all_games] + + # Replace the homeTeam and awayTeam dictionaries with their teamTricode values + for game in all_games: + game["homeTeam"] = game["homeTeam"]["teamTricode"] + game["awayTeam"] = game["awayTeam"]["teamTricode"] + + return all_games + + +def game_id_to_season(game_id, abbreviate=False): + """ + Converts a game ID to a season. + + The season is determined based on the third and fourth characters of the game ID. + If these characters represent a number less than 40, the season is in the 2000s, otherwise it's in the 1900s. + + Args: + game_id (str): The game ID to convert. + abbreviate (bool): Whether to abbreviate the second year of the season. + + Returns: + str: The season corresponding to the game ID. + """ + # Validate the game ID + validate_game_id(game_id) + + # Extract the season from the game ID + season = game_id[3:5] + + # Determine the prefix based on the season + prefix = "20" if int(season) < 40 else "19" + + # Construct the years for the season + year1 = prefix + season + year2 = str(int(year1) + 1) + + # Return the season in the appropriate format + if abbreviate: + return year1 + "-" + year2[2:] + return year1 + "-" + year2 + + +def validate_game_id(game_id): + """ + Validates a game ID. + + The game ID must be a 10-character string that starts with '00'. + + Args: + game_id (str): The game ID to validate. + + Raises: + ValueError: If the game ID is not valid. + """ + if not ( + game_id + and isinstance(game_id, str) + and len(game_id) == 10 + and game_id.startswith("00") + ): + raise ValueError( + """Invalid game ID. + Game ID must be a 10-digit string starting with '00'. + Example: '0022100001'. + Offical NBA.com Game ID""" + ) + + +def validate_date_format(date): + """ + Validates if the given date is in the format "YYYY-MM-DD". + + Args: + date (str): The date string to validate. + + Raises: + ValueError: If the date is not in the correct format or if the month or day is not valid. + """ + # Check the overall format + if len(date) != 10 or date[4] != "-" or date[7] != "-": + raise ValueError("Invalid date format. Please use YYYY-MM-DD format.") + + year, month, day = date.split("-") + + # Check if year, month and day are all digits + if not year.isdigit() or not month.isdigit() or not day.isdigit(): + raise ValueError("Invalid date format. Please use YYYY-MM-DD format.") + + year, month, day = int(year), int(month), int(day) + + # Check if month is between 1 and 12 + if month < 1 or month > 12: + raise ValueError( + "Invalid month. Please use MM format with a value between 01 and 12." + ) + + # Check if day is between 1 and the maximum day of the month + if month in [4, 6, 9, 11] and day > 30: + raise ValueError( + "Invalid day. Please use DD format with a value between 01 and 30 for this month." + ) + elif month == 2: + if (year % 4 == 0 and year % 100 != 0 or year % 400 == 0) and day > 29: + raise ValueError( + "Invalid day. Please use DD format with a value between 01 and 29 for this month." + ) + elif day > 28: + raise ValueError( + "Invalid day. Please use DD format with a value between 01 and 28 for this month." + ) + elif day < 1 or day > 31: + raise ValueError( + "Invalid day. Please use DD format with a value between 01 and 31." + ) + + +def validate_season_format(season, abbreviated=False): + """ + Validates the format of a season string. + + Parameters: + season (str): The season string to validate, formatted as 'XXXX-XX' or 'XXXX-XXXX'. + abbreviated (bool): Whether the second year in the season string is abbreviated. + + Raises: + ValueError: If the season string does not match the required format or if the second year does not logically follow the first year. + """ + # Define the regex pattern based on abbreviated flag + pattern = r"^(\d{4})-(\d{2,4})$" if abbreviated else r"^(\d{4})-(\d{4})$" + + # Attempt to match the pattern to the season string + match = re.match(pattern, season) + if not match: + raise ValueError("Season does not match the required format.") + + year1, year2_suffix = map(int, match.groups()) + + # Handle the year2 based on whether it's abbreviated or not + year2 = year2_suffix if not abbreviated else year1 // 100 * 100 + year2_suffix + + # Check if year2 logically follows year1 + if year1 + 1 != year2: + raise ValueError("Second year does not logically follow the first year.") + + +class NBATeamConverter: + """ + A class to convert between various identifiers of NBA teams such as team ID, + abbreviation, short name, and full name along with any historical identifiers. + """ + + @classmethod + def __generate_lookup_dict(cls): + """ + Generate a lookup dictionary from teams_data. The dictionary maps each identifier + (team ID, abbreviation, full name, short name, and alternatives) to the corresponding team ID. + """ + lookup_dict = {} + for team_id, details in cls.teams_data.items(): + # Directly map the team's ID + lookup_dict[team_id] = team_id + + # Map other identifiers, normalized to lowercase + identifiers = [ + details["abbreviation"], + details["full_name"], + details["short_name"], + ] + details["alternatives"] + + for identifier in identifiers: + normalized_identifier = identifier.lower().replace(" ", "-") + lookup_dict[normalized_identifier] = team_id + return lookup_dict + + # Call the generate_lookup_dict method to initialize lookup_dict + __lookup_dict = __generate_lookup_dict() + + @classmethod + def __get_team_id(cls, identifier): + """ + Get the team ID corresponding to the given identifier. + If the identifier is unknown, raise a ValueError. + """ + identifier_normalized = str(identifier).lower().replace(" ", "-") + if identifier_normalized not in cls.__lookup_dict: + raise ValueError(f"Unknown team identifier: {identifier}") + return cls.__lookup_dict[identifier_normalized] + + @classmethod + def get_abbreviation(cls, identifier): + """ + Get the abbreviation of the team corresponding to the given identifier. + """ + team_id = cls.__get_team_id(identifier) + return cls.teams_data[team_id]["abbreviation"].upper() + + @classmethod + def get_short_name(cls, identifier): + """ + Get the short name of the team corresponding to the given identifier. + """ + team_id = cls.__get_team_id(identifier) + return cls.teams_data[team_id]["short_name"].title() + + @classmethod + def get_full_name(cls, identifier): + """ + Get the full name of the team corresponding to the given identifier. + """ + team_id = cls.__get_team_id(identifier) + return cls.teams_data[team_id]["full_name"].title() + + # Team data with details for each team. Including abbreviation, full name, short name, and alternatives. + teams_data = { + "1610612737": { + "abbreviation": "ATL", + "full_name": "Atlanta Hawks", + "short_name": "Hawks", + "alternatives": [ + "STL", + "Tri-Cities Blackhawks", + "MLH", + "TRI", + "Milwaukee Hawks", + "St. Louis Hawks", + ], + }, + "1610612751": { + "abbreviation": "BKN", + "full_name": "Brooklyn Nets", + "short_name": "Nets", + "alternatives": [ + "NJA", + "BK", + "NYN", + "NJN", + "New York Nets", + "BRK", + "New Jersey Americans", + "New Jersey Nets", + ], + }, + "1610612738": { + "abbreviation": "BOS", + "full_name": "Boston Celtics", + "short_name": "Celtics", + "alternatives": [], + }, + "1610612766": { + "abbreviation": "CHA", + "full_name": "Charlotte Hornets", + "short_name": "Hornets", + "alternatives": [ + "Charlotte Bobcats", + "CHH", + "CHO", + ], + }, + "1610612739": { + "abbreviation": "CLE", + "full_name": "Cleveland Cavaliers", + "short_name": "Cavaliers", + "alternatives": [], + }, + "1610612741": { + "abbreviation": "CHI", + "full_name": "Chicago Bulls", + "short_name": "Bulls", + "alternatives": [], + }, + "1610612742": { + "abbreviation": "DAL", + "full_name": "Dallas Mavericks", + "short_name": "Mavericks", + "alternatives": [], + }, + "1610612743": { + "abbreviation": "DEN", + "full_name": "Denver Nuggets", + "short_name": "Nuggets", + "alternatives": ["Denver Rockets", "DNR"], + }, + "1610612765": { + "abbreviation": "DET", + "full_name": "Detroit Pistons", + "short_name": "Pistons", + "alternatives": ["FTW", "Fort Wayne Pistons"], + }, + "1610612744": { + "abbreviation": "GSW", + "full_name": "Golden State Warriors", + "short_name": "Warriors", + "alternatives": [ + "PHW", + "GS", + "Philadelphia Warriors", + "San Francisco Warriors", + "SFW", + ], + }, + "1610612745": { + "abbreviation": "HOU", + "full_name": "Houston Rockets", + "short_name": "Rockets", + "alternatives": ["SDR", "San Diego Rockets"], + }, + "1610612754": { + "abbreviation": "IND", + "full_name": "Indiana Pacers", + "short_name": "Pacers", + "alternatives": [], + }, + "1610612746": { + "abbreviation": "LAC", + "full_name": "Los Angeles Clippers", + "short_name": "Clippers", + "alternatives": [ + "SDC", + "Buffalo Braves", + "San Diego Clippers", + "LA Clippers", + "BUF", + ], + }, + "1610612747": { + "abbreviation": "LAL", + "full_name": "Los Angeles Lakers", + "short_name": "Lakers", + "alternatives": ["MNL", "Minneapolis Lakers"], + }, + "1610612763": { + "abbreviation": "MEM", + "full_name": "Memphis Grizzlies", + "short_name": "Grizzlies", + "alternatives": ["VAN", "Vancouver Grizzlies"], + }, + "1610612748": { + "abbreviation": "MIA", + "full_name": "Miami Heat", + "short_name": "Heat", + "alternatives": [], + }, + "1610612749": { + "abbreviation": "MIL", + "full_name": "Milwaukee Bucks", + "short_name": "Bucks", + "alternatives": [], + }, + "1610612750": { + "abbreviation": "MIN", + "full_name": "Minnesota Timberwolves", + "short_name": "Timberwolves", + "alternatives": [], + }, + "1610612752": { + "abbreviation": "NYK", + "full_name": "New York Knicks", + "short_name": "Knicks", + "alternatives": ["NY"], + }, + "1610612740": { + "abbreviation": "NOP", + "full_name": "New Orleans Pelicans", + "short_name": "Pelicans", + "alternatives": [ + "NOH", + "New Orleans Hornets", + "NOK", + "NO", + "New Orleans/Oklahoma City Hornets", + ], + }, + "1610612760": { + "abbreviation": "OKC", + "full_name": "Oklahoma City Thunder", + "short_name": "Thunder", + "alternatives": ["Seattle SuperSonics", "SEA"], + }, + "1610612753": { + "abbreviation": "ORL", + "full_name": "Orlando Magic", + "short_name": "Magic", + "alternatives": [], + }, + "1610612755": { + "abbreviation": "PHI", + "full_name": "Philadelphia 76ers", + "short_name": "76ers", + "alternatives": [ + "PHL", + "Syracuse Nationals", + "SYR", + ], + }, + "1610612756": { + "abbreviation": "PHX", + "full_name": "Phoenix Suns", + "short_name": "Suns", + "alternatives": ["PHO"], + }, + "1610612757": { + "abbreviation": "POR", + "full_name": "Portland Trail Blazers", + "short_name": "Trail Blazers", + "alternatives": [], + }, + "1610612759": { + "abbreviation": "SAS", + "full_name": "San Antonio Spurs", + "short_name": "Spurs", + "alternatives": [ + "DLC", + "SAN", + "Dallas Chaparrals", + "SA", + ], + }, + "1610612758": { + "abbreviation": "SAC", + "full_name": "Sacramento Kings", + "short_name": "Kings", + "alternatives": [ + "KCO", + "KCK", + "Kansas City-Omaha Kings", + "Kansas City Kings", + "Cincinnati Royals", + "CIN", + "Rochester Royals", + "ROC", + ], + }, + "1610612761": { + "abbreviation": "TOR", + "full_name": "Toronto Raptors", + "short_name": "Raptors", + "alternatives": [], + }, + "1610612762": { + "abbreviation": "UTA", + "full_name": "Utah Jazz", + "short_name": "Jazz", + "alternatives": ["NOJ", "New Orleans Jazz", "UTAH"], + }, + "1610612764": { + "abbreviation": "WAS", + "full_name": "Washington Wizards", + "short_name": "Wizards", + "alternatives": [ + "WSH", + "Washington Bullets", + "CAP", + "BAL", + "Baltimore Bullets", + "CHP", + "CHZ", + "Chicago Packers", + "WSB", + "Capital Bullets", + "Chicago Zephyrs", + ], + }, + } diff --git a/src/xgb_model.py b/src/xgb_model.py new file mode 100644 index 0000000..e69de29 diff --git a/web_app/app.py b/web_app/app.py index 7ad6299..ae8209b 100644 --- a/web_app/app.py +++ b/web_app/app.py @@ -1,43 +1,259 @@ -import math import os import sys from datetime import datetime, timedelta +import numpy as np import pandas as pd +import pytz +from dotenv import load_dotenv from flask import Flask, jsonify, render_template, request +from tzlocal import get_localzone # Add the parent directory to sys.path to allow imports from there parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if parent_dir not in sys.path: sys.path.append(parent_dir) -from team_mapper import NBATeamConverter +from src.NBAStats_game_states import get_current_game_info +from src.predictions import get_predictions +from src.utils import NBATeamConverter, get_games_for_date + +load_dotenv() +PROJECT_ROOT = os.getenv("PROJECT_ROOT") app = Flask(__name__) -game_records = pd.read_csv("../data/test_game_records.csv").to_dict(orient="records") +def process_game_data(game, current_date_et): + game_data = { + "game_id": game["game_id"], + "game_date": game["game_date"], + "home": game["home"], + "away": game["away"], + } + + # Full team names + home_full_name = NBATeamConverter.get_full_name(game_data["home"]) + away_full_name = NBATeamConverter.get_full_name(game_data["away"]) + game_data["home_full_name"] = home_full_name + game_data["away_full_name"] = away_full_name + + def format_team_name(full_name): + if "Trail Blazers" in full_name: + city, team = full_name.split(" Trail ") + return f"{city}
Trail {team}" + else: + city, team = full_name.rsplit(" ", 1) + return f"{city}
{team}" + + game_data["home_team_display"] = format_team_name(home_full_name) + game_data["away_team_display"] = format_team_name(away_full_name) + + # Team Logo URLs + def generate_logo_url(team_name): + # Example inbound team_name: "Phoenix Suns" + # Example url: web_app/static/img/team_logos/nba-phoenix-suns-logo.png + formatted_team_name = team_name.lower().replace(" ", "-") + logo_url = f"static/img/team_logos/nba-{formatted_team_name}-logo.png" + return logo_url + + game_data["home_logo_url"] = generate_logo_url(home_full_name) + game_data["away_logo_url"] = generate_logo_url(away_full_name) + + # Time Display + # Add a time_display field to show the time and period/quarter/overtime + # or the date and start time if the game is not in progress + if game["game_status"] == "In Progress": + period = game["game_states"][-1]["period"] + # Parse the time_remaining string + # Parse the time_remaining string + time_remaining = game["game_states"][-1]["remaining_time"] + minutes, seconds = time_remaining.lstrip("PT").rstrip("S").split("M") + minutes = int(minutes) # Remove leading zero from minutes + seconds = int(seconds.split(".")[0]) # Only take the whole seconds + + # Format the time_remaining string + time_remaining = f"{minutes}:{seconds:02}" + + period_display_dict = { + 1: "1st Quarter", + 2: "2nd Quarter", + 3: "3rd Quarter", + 4: "4th Quarter", + 5: "Overtime", + 6: "2nd Overtime", + 7: "3rd Overtime", + 8: "4th Overtime", + 9: "5th Overtime", + 10: "Crazy Overtime", + } + period_display = period_display_dict[period] + game_data["time_display"] = f"{time_remaining} - {period_display}" + elif game["game_status"] == "Completed": + game_date = datetime.strptime(game["game_date"], "%Y-%m-%d") + if game_date.date() == current_date_et.date(): + date_display = "Today" + elif game_date.date() == (current_date_et - timedelta(days=1)).date(): + date_display = "Yesterday" + else: + date_display = game_date.strftime("%b %d") + game_data["time_display"] = f"{date_display} - Final" + elif game["game_status"] == "Not Started": + game_date = datetime.strptime(game["game_date"], "%Y-%m-%d") + if game_date.date() == current_date_et.date(): + date_display = "Today" + elif game_date.date() == (current_date_et + timedelta(days=1)).date(): + date_display = "Tomorrow" + elif game_date.date() == (current_date_et - timedelta(days=1)).date(): + date_display = "Yesterday" + else: + date_display = game_date.strftime("%b %d") + + # Parse the game date and time as Eastern Time + eastern = pytz.timezone("US/Eastern") + game_date = datetime.strptime(game["game_date"], "%Y-%m-%d").date() + game_hour, game_minute = map(int, game["game_time_et"].split(":")) + game_time_et = datetime( + game_date.year, game_date.month, game_date.day, game_hour, game_minute + ) + game_time_et = eastern.localize(game_time_et) + + # Convert to the user's local timezone + user_timezone = get_localzone() + game_time_local = game_time_et.astimezone(user_timezone) + + # Format the local game time as a string + game_time_local_str = game_time_local.strftime("%I:%M %p").lstrip("0") + + # Update the time display + game_data["time_display"] = f"{date_display} - {game_time_local_str}" + + # Score Display + if game["game_states"]: + game_data["home_score"] = game["game_states"][-1]["home_score"] + game_data["away_score"] = game["game_states"][-1]["away_score"] + else: + game_data["home_score"] = "" + game_data["away_score"] = "" + + # Play by Play Logs + pbp = sorted(game["pbp_logs"], key=lambda x: x["orderNumber"], reverse=True) + condensed_pbp = [] + for play in pbp: + # Parse the clock value + time_remaining = play["clock"] + minutes, seconds = time_remaining.lstrip("PT").rstrip("S").split("M") + minutes = int(minutes) # Remove leading zero from minutes + seconds = int(seconds.split(".")[0]) # Only take the whole seconds + + # Combine clock and period into a single item + time_info = f"{minutes}:{seconds:02} Q{play['period']}" + + # Add the play to condensed_pbp with the new time_info key + condensed_pbp.append( + { + "time_info": time_info, + "home_score": play["scoreHome"], + "away_score": play["scoreAway"], + "description": play["description"], + } + ) + + game_data["pbp"] = condensed_pbp + + # Pass through predictions + game_data["predictions"] = game["predictions"] + + # Players to show + home_players = [] + away_players = [] + + # Iterate over home players + for player_id, player_data in game["predictions"]["players"]["home"].items(): + # Get all files in the directory + files = os.listdir("static/img/player_images") + + # Find the file that ends with {player_id}.png + player_image_file = next( + (f for f in files if f.endswith(f"_{player_id}.png")), None + ) + + if player_image_file: + player_headshot_url = f"static/img/player_images/{player_image_file}" + else: + player_headshot_url = "static/img/basketball-player.png" # Replace with your default image URL + + player = { + "player_id": player_id, + "name": player_data["name"], + "player_headshot_url": player_headshot_url, + "predicted_points": player_data["points"], + } + home_players.append(player) + + # Iterate over away players + for player_id, player_data in game["predictions"]["players"]["away"].items(): + # Get all files in the directory + files = os.listdir("static/img/player_images") + + # Find the file that ends with {player_id}.png + player_image_file = next( + (f for f in files if f.endswith(f"_{player_id}.png")), None + ) + + if player_image_file: + player_headshot_url = f"static/img/player_images/{player_image_file}" + else: + player_headshot_url = "static/img/basketball-player.png" # Replace with your default image URL + + player = { + "player_id": player_id, + "name": player_data["name"], + "player_headshot_url": player_headshot_url, + "predicted_points": player_data["points"], + } + away_players.append(player) + + # Sort players by predicted points in descending order + home_players = sorted( + home_players, key=lambda x: x["predicted_points"], reverse=True + ) + away_players = sorted( + away_players, key=lambda x: x["predicted_points"], reverse=True + ) + + game_data["home_players"] = home_players + game_data["away_players"] = away_players + + return game_data + -play_by_play_records = pd.read_csv("../data/test_play_by_play.csv").to_dict( - orient="records" -) +def get_user_datetime(as_eastern_tz=False): + user_timezone = get_localzone() + user_datetime = datetime.now(user_timezone) + if as_eastern_tz: + eastern_timezone = pytz.timezone("US/Eastern") + user_datetime_eastern = user_datetime.astimezone(eastern_timezone) + return user_datetime_eastern + return user_datetime @app.route("/") def home(): # Set the current date, or use the date provided in the query parameters + current_date_et = get_user_datetime(as_eastern_tz=True) # current_date = datetime.now() - current_date = datetime(2024, 2, 25) - current_date_str = current_date.strftime("%m/%d/%Y") + # current_date = datetime(2024, 2, 25) + current_date_str = current_date_et.strftime("%Y-%m-%d") query_date_str = request.args.get("date", current_date_str) - query_date = datetime.strptime(query_date_str, "%m/%d/%Y") + query_date = datetime.strptime(query_date_str, "%Y-%m-%d") query_date_display_str = query_date.strftime("%b %d") # Calculate previous and next dates for navigation links next_date = query_date + timedelta(days=1) prev_date = query_date - timedelta(days=1) - next_date_str = next_date.strftime("%m/%d/%Y") - prev_date_str = prev_date.strftime("%m/%d/%Y") + next_date_str = next_date.strftime("%Y-%m-%d") + prev_date_str = prev_date.strftime("%Y-%m-%d") # Render the template, passing necessary data for initial page setup return render_template( @@ -51,158 +267,49 @@ def home(): @app.route("/get-games") def get_games(): - current_date = datetime(2024, 2, 25) - # current_date = datetime.now() + current_date_et = get_user_datetime(as_eastern_tz=True) # Fetch the date parameter from the request, default to current date if not provided query_date_str = request.args.get("date") if query_date_str is None or query_date_str == "": - query_date_str = current_date.strftime("%m/%d/%Y") - query_date = datetime.strptime(query_date_str, "%m/%d/%Y") + query_date_str = current_date_et.strftime("%Y-%m-%d") - # Filter game_records for games on the requested date - games_on_date = [game for game in game_records if game["date"] == query_date_str] + # Load Games + games = get_games_for_date(query_date_str)[ + 0:2 + ] # Limit to 2 games for now for testing + games = [get_current_game_info(game["game_id"]) for game in games] - # Sort games by time - games_on_date_sorted = sorted( - games_on_date, key=lambda x: datetime.strptime(x["time"], "%I:%M %p") - ) + # Create Predictions + games = [get_predictions(game) for game in games] + + outbound_game_data = [] + + for game in games: + game_data = process_game_data(game, current_date_et) + outbound_game_data.append(game_data) + + return jsonify(outbound_game_data) - def process_game_data(sorted_games): - # Helper function to format team names - def format_team_name(team_name): - if team_name == "Portland Trail Blazers": - return "Portland
Trail Blazers" - else: - parts = team_name.rsplit(" ", 1) - return f"{parts[0]}
{parts[1]}" if len(parts) == 2 else team_name - - # Select the current date for testing or use the current date - # current_date = datetime.now() - current_date = datetime(2024, 2, 25) - for game in sorted_games: - # Add a time_display field to show the time and period/quarter/overtime - # or the date and start time if the game is not in progress - if game["game_state"] == "In Progress": - period = game["period"] - period_display_dict = { - 1: "1st Quarter", - 2: "2nd Quarter", - 3: "3rd Quarter", - 4: "4th Quarter", - 5: "Overtime", - 6: "2nd Overtime", - 7: "3rd Overtime", - 8: "4th Overtime", - 9: "5th Overtime", - 10: "Crazy Overtime", - } - period_display = period_display_dict[period] - game["time_display"] = f"{game['time_remaining']} - {period_display}" - elif game["game_state"] == "Completed": - game_date = datetime.strptime(game["date"], "%m/%d/%Y") - if game_date.date() == current_date.date(): - date_display = "Today" - elif game_date.date() == (current_date - timedelta(days=1)).date(): - date_display = "Yesterday" - else: - date_display = game_date.strftime("%b %d") - game["time_display"] = f"{date_display} - Final" - elif game["game_state"] == "Not Started": - game_date = datetime.strptime(game["date"], "%m/%d/%Y") - if game_date.date() == current_date.date(): - date_display = "Today" - elif game_date.date() == (current_date + timedelta(days=1)).date(): - date_display = "Tomorrow" - elif game_date.date() == (current_date - timedelta(days=1)).date(): - date_display = "Yesterday" - game["time_display"] = f"{date_display} - {game['time']}" - - # Abbreviate the predicted_winner field - game["predicted_winner_abbr"] = NBATeamConverter.get_abbreviation( - game["predicted_winner"] - ) - - # Convert predicted_win_pct to a rounded percentage string - game["predicted_win_pct_str"] = f"{round(game['predicted_win_pct'] * 100)}%" - - # Format team names - game["home_team_display"] = format_team_name(game["home"]) - game["away_team_display"] = format_team_name(game["away"]) - - def generate_logo_url(team_name): - # Example inbound team_name: "Phoenix Suns" - # Example url: web_app/static/img/team_logos/nba-phoenix-suns-logo.png - formatted_team_name = team_name.lower().replace(" ", "-") - logo_url = f"static/img/team_logos/nba-{formatted_team_name}-logo.png" - return logo_url - - game["home_logo_url"] = generate_logo_url(game["home"]) - game["away_logo_url"] = generate_logo_url(game["away"]) - - def generate_headshot_url(player_id, player_name): - # Example inbound player_id: 203078 - # Example url: "static/img/player_images/Bradley Beal_203078.png" - headshot_url = f"static/img/player_images/{player_name}_{player_id}.png" - return headshot_url - - for player in range(1, 6): - game[f"player_{player}H_headshot_url"] = generate_headshot_url( - game[f"player_{player}H_id"], game[f"player_{player}H_name"] - ) - game[f"player_{player}R_headshot_url"] = generate_headshot_url( - game[f"player_{player}R_id"], game[f"player_{player}R_name"] - ) - - return sorted_games - - games = process_game_data(games_on_date_sorted) - - def clean_data(games): - for game in games: - for key, value in game.items(): - if isinstance(value, float) and math.isnan(value): - game[key] = None # Replace NaN with None - return games - - cleaned_games = clean_data(games) # Clean the data - return jsonify(cleaned_games) - - -@app.route("/game-details/") + +@app.route("/game-details/") def game_details(game_id): - # Find the game by game_id - game_detail = next( - (game for game in game_records if game["game_id"] == game_id), None - ) + # Load game data + game = get_current_game_info(game_id) - # Check if the game was found - if game_detail: - # Sort play_by_play_records by play_id descending - sorted_play_by_play_records = sorted( - play_by_play_records, key=lambda x: x["play_id"], reverse=True - ) + # Create Predictions + game = get_predictions(game) - def clean_play_by_play_records(play_by_play_records): - for record in play_by_play_records: - for key, value in record.items(): - if isinstance(value, float) and math.isnan(value): - record[key] = None - return play_by_play_records + # Process game data + current_date_et = get_user_datetime(as_eastern_tz=True) + outbound_game_data = process_game_data(game, current_date_et) - sorted_play_by_play_records = clean_play_by_play_records( - sorted_play_by_play_records - ) + return jsonify(outbound_game_data) - # Return the game details and sorted play by play records as JSON - return jsonify( - { - "game_detail": game_detail, - "play_by_play": sorted_play_by_play_records, - } - ) - else: - # If no game was found, return an error message and a 404 status code - return jsonify({"error": "Game not found"}), 404 + +@app.after_request +def add_header(response): + response.headers["Cache-Control"] = "no-store" + return response if __name__ == "__main__": diff --git a/web_app/static/img/team_logos/nba-la-clippers-logo.png b/web_app/static/img/team_logos/nba-los-angeles-clippers-logo.png similarity index 100% rename from web_app/static/img/team_logos/nba-la-clippers-logo.png rename to web_app/static/img/team_logos/nba-los-angeles-clippers-logo.png diff --git a/web_app/templates/index.html b/web_app/templates/index.html index afcbca5..063815b 100644 --- a/web_app/templates/index.html +++ b/web_app/templates/index.html @@ -92,14 +92,13 @@