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)