Spaces:
Running on Zero
Running on Zero
File size: 14,494 Bytes
1f62eb2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 | import pickle as pickle
import requests
import numpy as np
import pandas as pd
import operator
import sys
# Add github location of github file location (keep it as local file)
# import custom functions written for the data processing
from NHL_gameData_process_functions import *
from NHL_gameData_process_sequence import *
import os
dir_data = os.getcwd() # Current working directory of the server
# Object to pull data from NHL official API
class nhl_api_datapull:
def __init__(self, year, dir_data):
self.year = year
def datacall(self, postseason=False):
if postseason:
# By default don't include
ssn = ['02']
else:
ssn = ['02', '03']; # Regular season and playoff, respectively
max_game_ID = 3000; # just to be conservative in case there are data not accounted
for season_type in ssn:
game_data = []
for i in range(0,max_game_ID):
r = requests.get(url='http://statsapi.web.nhl.com/api/v1/game/'
+ self.year + season_type +str(i).zfill(4)+'/feed/live')
data = r.json()
game_data.append(data)
# Save as pickle file
with open(dir_data + '/raw/' + self.year + '_' + season_type + 'FullDataset.pkl', 'wb') as f:
pickle.dump(game_data, f, pickle.HIGHEST_PROTOCOL)
# Data pull done, let me know.
print('Done! - year ' + self.year + ' and season type: ' + season_type)
# no object to return
return None
class nhl_dataproc_baseline:
def __init__(self, year, dir_data):
# Year and season to process
self.str_year = year
self.dir_data = dir_data
def dataproc(self, idx_seasontype='02'):
# idx_season 02 is regular, 03 postseason
# Define some data location source and
str_dirc = self.dir_data + '/raw/' # Raw data location
str_data = self.str_year + '_' + idx_seasontype + 'FullDataset.pkl' # name data file to pull
str_dirc_save = self.dir_data + '/cleaned/' # cleaned data
with open(str_dirc + str_data, 'rb') as f:
game_data = pickle.load(f)
dat = pd.DataFrame(game_data)
# Process data
# only including with those of data points
dat_data = dat.dropna(subset = ["gameData"]).reset_index();
# Total computing game
num_tot_games = dat_data.shape[0] - 1
#data pull and enumerate - initialize entries
df_game, df_player, df_box = process_game(dat_data.iloc[0, :]['liveData'], dat_data.iloc[0, :]['gameData'])
for index, row in dat_data.iterrows(): # The first game has been recorded
# Skip the first observation, already have it
if index == 0:
None
else:
# Check to see if the data is valid
# There are posponed games that still have placeholders:
if row['liveData']['boxscore']['officials'] == []:
# If there is no information on the officials,
# we assume there was no games played
continue
# move on to the next row
else:
None
# Process the data
df1, df2, df3 = process_game(row['liveData'], row['gameData'])
# Append the necessary data
df_game = df_game.append(df1);
df_player = df_player.append(df2);
df_box = df_box.append(df3);
#print(f"Row {index} of {num_tot_games} completed", end='\r')
#progressBar(index, num_tot_games, barLength = 20)
# Save the data
df_game.to_csv(str_dirc_save + str_data[:7] + '_game.csv', index = False)
df_player.to_csv(str_dirc_save + str_data[:7] + '_player.csv', index = False)
df_box.to_csv(str_dirc_save + str_data[:7] + '_box.csv', index = True)
return df_game, df_player, df_box
# Team success measurement
class nhl_dataproc_teamsuccess:
def __init__(self, year, dir_data):
self.str_year = year
self.dir_data = dir_data
def dataproc(self, idx_seasontype='02'):
# idx_season 02 is regular, 03 postseason
# inital name directiory of data
str_dirc = self.dir_data + f"/cleaned/{self.str_year}"
str_dirc_save = self.dir_data + f"/processed/{self.str_year}"
# Pull the latest data for process
df_box = pd.read_csv(str_dirc + f"_{idx_seasontype}_box.csv",
parse_dates = ['gameDate'],
index_col = 'gameIdx')
# Unpack data
df_box_exnt = df_unpack(df_box)
#df_box_exnt.columns
# Calculate metrics
df_box_exnt = df_metrics(df_box_exnt)
# Aggregate up for team-level statistics
dfteams = df_box_exnt.groupby(['team_tri_for'])\
.agg(
{
"fenwick_lvl":"sum",
"corsi_lvl":"sum",
"rpe":"last",
"rwin":"last",
"rgame":"last"
}
)
dfteams.sort_values(by = ["rwin","fenwick_lvl", "corsi_lvl"], inplace = True)
# Join with season-pythagorean expectation
#dfteams = dfteams.join(df_pe_season)
# Normalize KPIs for measurement comparison
dfteams['wp'] = dfteams.rwin / dfteams.rgame
dfteams['kpi_corsi'] = kpinorm(dfteams.corsi_lvl)
dfteams['kpi_fenwick'] = kpinorm(dfteams.fenwick_lvl)
dfteams['kpi_pe'] = kpinorm(dfteams.rpe)
# Coefficient estimates from 2010-2020, calculate fitted win percentage
beta_pyth_old = 0.00809623 * 100
alpah_pyth_old = 0.042901
x_starting = 0.3
x_end = x_starting + 0.45
y_starting = (x_starting) * beta_pyth_old + alpah_pyth_old
y_end = (x_end) * beta_pyth_old + alpah_pyth_old
dfteams['rwin_fittedPE'] = (dfteams['rpe'] * beta_pyth_old + alpah_pyth_old) * 82
# drop the first game,
df_box_exnt.loc[df_box_exnt.gameDate_del_against.notnull(), :].head(3)
time_games_all = np.sort(df_box_exnt.gameDate_for.unique()); # by date existing
# Capture the last date.
iter_time = time_games_all[-1];
# NOTE: while it may be exciting to compute the measure in real time, it takes
# some time to compute it all. I focus on looking at upto the latest available
# data
# Head to head statistics
dff_h2h_com = df_gen_h2h_common(df_box_exnt, iter_time)
# Game pairwise - RPI statistics
dfteams = dfteams.join(pairwise_h2h(df_box_exnt, dff_h2h_com))
# More head-to-head statistics
dff_h2h_com_win = dff_h2h_com\
.groupby(['team_tri_for_ow', 'team_tri_for_oow']) \
.agg(
{
'game_won_h2h_ow':'sum',
'game_played_h2h_ow':'sum',
'game_won_h2h_oow':'sum',
'game_played_h2h_oow':'sum'
}
)
# for each opponent, compare who has winning record as common opponent
# I use win ratio
dff_h2h_com_win['h2h_win'] = (\
(dff_h2h_com_win.game_won_h2h_ow / dff_h2h_com_win.game_played_h2h_ow) > \
(dff_h2h_com_win.game_won_h2h_oow / dff_h2h_com_win.game_played_h2h_oow)
) * 1
# For each team, count how many opponents the team has winning record against
rank_pairwise_win = dff_h2h_com_win.groupby(['team_tri_for_ow']) \
.agg({'h2h_win':'sum'}) \
.sort_values(by = 'h2h_win', ascending = False)
rank_pairwise_win.columns = ['pairwise_win']
# Here the rank can even be more continuous (how much they win instead of count in games)
# Join with the existing team stats
dfteams = dfteams.join(rank_pairwise_win)
# Normalize
dfteams['kpi_pairwise'] = kpinorm(dfteams.pairwise_win)
dfteams['kpi_rpi'] = kpinorm(dfteams.rpi)
dfteams['kpi_wp'] = kpinorm(dfteams.wp)
dfteams['Final'] = dfteams\
.sort_values(by = 'kpi_wp', ascending = False) \
.reset_index().index + 1
dfteams['Final'] = kpinorm(dfteams.Final)
# Data append
# Add season statistics for and against
df_box_exnt = df_box_exnt.merge(dfteams.reset_index(),
on = ['team_tri_for'], suffixes=('', '_for')) \
.merge(dfteams.reset_index(),
left_on = ['team_tri_against'],
right_on = ['team_tri_for'],
suffixes=('', '_against'))
# Check wins against better opponent
# Is the team-for facing eventual better opponent?
# Using pairwise here - alternatively can use RPI
# ...........................................................................
# Observing win streaks
# Is opponent better than average?
df_box_exnt['idx_OppoGood'] = df_box_exnt.rpi_against > \
np.quantile(df_box_exnt.rpi_against, 0.75)
# Playing against worse team, thus leading to expected outcome?
df_box_exnt['idx_OppoBetter'] = df_box_exnt.pairwise_win_against < \
df_box_exnt.pairwise_win
df_box_exnt['idx_OppoBetter'] = df_box_exnt.rpi_against < \
df_box_exnt.rpi
df_box_exnt['win_for_better'] = df_box_exnt.win_for * df_box_exnt.idx_OppoGood
df_box_exnt['win_for_expected'] = df_box_exnt.win_for * df_box_exnt.idx_OppoBetter
pivot_teamcumwin = df_box_exnt \
.groupby(['team_tri_for', 'rgame']) \
.agg(
win = ("win_for", "sum"),
win_better = ("win_for_better", "sum"),
win_expected = ("win_for_expected", "sum")
) \
.reset_index()
for iter_lag in np.arange(1,3):
pivot_teamcumwin[f'win_lag{iter_lag}'] = pivot_teamcumwin \
.groupby(['team_tri_for']).win.shift(iter_lag)
pivot_teamcumwin[f'win_better_lag{iter_lag}'] = pivot_teamcumwin \
.groupby(['team_tri_for']).win_better.shift(iter_lag)
pivot_teamcumwin[f'win_expected_lag{iter_lag}'] = pivot_teamcumwin \
.groupby(['team_tri_for']).win_expected.shift(iter_lag)
# Identify all win streak
pivot_teamcumwin['idx_WinStreak'] = 0
pivot_teamcumwin.loc[(np.sum(pivot_teamcumwin[['win','win_lag1', 'win_lag2']], axis = 1) == 3), 'idx_WinStreak'] = 1
# Identify win streak with games all expected to win
pivot_teamcumwin['idx_WinStreak_expected'] = 0
pivot_teamcumwin.loc[\
(np.sum(pivot_teamcumwin[['win_expected','win_expected_lag1', 'win_expected_lag2']], axis = 1) >= 2) \
& pivot_teamcumwin['idx_WinStreak'] == 1 \
, 'idx_WinStreak_expected'] = 1
# Identify win streak against good team: 75%tile and above RPI
pivot_teamcumwin['idx_HotStreak'] = 0
pivot_teamcumwin.loc[(pivot_teamcumwin.idx_WinStreak == 1) & \
(np.sum(pivot_teamcumwin[['win_better', 'win_better_lag1', 'win_better_lag2']], axis = 1) >= 1), \
'idx_HotStreak'] = 1
df_streak = pivot_teamcumwin[['team_tri_for',
'idx_WinStreak',
'idx_WinStreak_expected',
'idx_HotStreak']] \
.groupby('team_tri_for').sum()
df_streak['ratio'] = df_streak.idx_HotStreak / df_streak.idx_WinStreak
df_streak['ratio_viz'] = np.int0(df_streak['ratio']*100)
df_streak['ratio_viz'] = [str(iter) + '%' for iter in df_streak['ratio_viz']]
# Sort value for future ease of use
# Finally join with data
dfteams = dfteams.join(df_streak)
# ===========================================================================
# Some plotting
#exec(open(str_dir_sourceCode + "/evaluation_winStreak_plot.py").read())
# ...........................................................................
# output data
# (a) game-level stats
df_box_exnt['yr_season'] = self.str_year
df_box_exnt.to_csv(str_dirc_save + "_02_box.csv")
# (b) team-level stats for each season
dfteams['yr_season'] = self.str_year
dfteams.to_csv(str_dirc_save + "_02_team.csv")
# Data sequence creation
# NOTE: This process use parallel process. Make sure to adjust your available process
class nhl_dataproc_sequence:
def __init__(self, year, dir_data):
# Year and season to process
self.str_year = year
self.dir_data = dir_data
def dataproc(self, idx_seasontype='02'):
# inital name directiory of data
str_dirc = self.dir_data + f"/cleaned/{self.str_year}"
str_dirc_save = self.dir_data + f"/processed/{self.str_year}"
# Pull processed data
df = pd.read_csv(str_dirc+f"/_{idx_seasontype}_game.csv")
# ==================== Data processing step ====================
# Convert time to python-datetime format
df["periodTime"] = [datetime.strptime(iterconvert,'%M:%S') for iterconvert in df.periodTime]
# Calculating total seconds require python datetime format to be timedelta
df["periodTime"] = df["periodTime"] - datetime.strptime("00:00",'%M:%S')
# Convert the period information to be in seconds
df["periodTime"] = df.periodTime.dt.total_seconds()
# Identify games to be evaluated
list_gameix = df.gameIdx.unique()
# ==================== Extract features ====================
# For each year of data process,
# Parallel process the data
outdf_stack = Parallel(n_jobs=num_worker)(delayed(process_sequence)(iter_game, df) for iter_game in list_gameix)
# Combine dataframe
df_test = pd.concat(outdf_stack)
# ==================== Save data ====================
df_test.to_csv(str_dirc_save+f"/_{idx_seasontype}_possession_sequence.csv", index=False) |