wsba-hockey 1.0.6__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. wsba_hockey/api/api/index.py +129 -0
  2. wsba_hockey/api/api/main.py +4 -0
  3. wsba_hockey/api/api/tools/__init__.py +0 -0
  4. wsba_hockey/api/api/tools/agg.py +374 -0
  5. wsba_hockey/api/api/tools/archive/old_scraping.py +1104 -0
  6. wsba_hockey/api/api/tools/plotting.py +144 -0
  7. wsba_hockey/api/api/tools/scraping.py +1000 -0
  8. wsba_hockey/api/api/tools/utils/__init__.py +1 -0
  9. wsba_hockey/api/api/tools/utils/config.py +14 -0
  10. wsba_hockey/api/api/tools/utils/save_pages.py +133 -0
  11. wsba_hockey/api/api/tools/utils/shared.py +450 -0
  12. wsba_hockey/api/api/tools/xg_model.py +455 -0
  13. wsba_hockey/api/api/wsba_main.py +1213 -0
  14. wsba_hockey/data_pipelines.py +71 -8
  15. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/app.py +6 -5
  16. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/app.py +101 -0
  17. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/plot.py +71 -0
  18. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/rink_plot.py +245 -0
  19. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/app.py +1 -1
  20. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/plot.py +2 -0
  21. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/rink_plot.py +1 -1
  22. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/app.py +3 -3
  23. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/plot.py +2 -0
  24. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/rink_plot.py +1 -1
  25. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/app.py +44 -28
  26. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/plot.py +12 -3
  27. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/rink_plot.py +1 -1
  28. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/app.py +1 -1
  29. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/plot.py +5 -4
  30. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/rink_plot.py +1 -1
  31. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/app.py +103 -0
  32. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/plot.py +95 -0
  33. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/rink_plot.py +245 -0
  34. wsba_hockey/flask/app.py +77 -0
  35. wsba_hockey/tools/plotting.py +3 -3
  36. wsba_hockey/tools/scraping.py +7 -3
  37. wsba_hockey/tools/xg_model.py +3 -3
  38. wsba_hockey/workspace.py +28 -12
  39. wsba_hockey/wsba_main.py +10 -17
  40. {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/METADATA +1 -1
  41. {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/RECORD +44 -24
  42. {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/WHEEL +0 -0
  43. {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/licenses/LICENSE +0 -0
  44. {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,129 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import wsba_main as wsba
4
+ import requests as rs
5
+ from fastapi import FastAPI
6
+ from datetime import datetime
7
+ import pytz
8
+
9
+ app = FastAPI()
10
+
11
+ @app.get("/")
12
+ def read_root():
13
+ return {"WeakSide Breakout Analysis": "Welcome to the API!"}
14
+
15
+ @app.get("/nhl/players/{player_id}")
16
+ def player(player_id: int):
17
+ player = rs.get(f'https://api-web.nhle.com/v1/player/{player_id}/landing').json()
18
+
19
+ return player
20
+
21
+ @app.get("/nhl/schedule/{date}")
22
+ def schedule_info(date: str):
23
+ data = rs.get(f'https://api-web.nhle.com/v1/schedule/{date}').json()
24
+
25
+ eastern = pytz.timezone('US/Eastern')
26
+ for game in data['gameWeek'][0]['games']:
27
+ game['startTimeEST'] = datetime.strptime(game['startTimeUTC'],'%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=pytz.UTC).astimezone(eastern).strftime('%I:%M %p')
28
+
29
+ return data
30
+
31
+ @app.get("/nhl/games/{game_id}")
32
+ def pbp(game_id: int):
33
+ df = wsba.nhl_apply_xG(wsba.nhl_scrape_game([game_id],remove=[]))
34
+
35
+ skater = {}
36
+ goalie = {}
37
+ team_stats = {}
38
+ other = df.loc[~df['strength_state'].isin(['5v5','5v4','4v5']),'strength_state'].drop_duplicates().to_list()
39
+ for strength in [['5v5'],['5v4'],['4v5'],
40
+ other,
41
+ 'all']:
42
+
43
+ s = wsba.nhl_calculate_stats(df,'skater',[2,3],strength,True).replace([np.inf, -np.inf], np.nan).fillna('').to_dict(orient='records')
44
+ g = wsba.nhl_calculate_stats(df,'goalie',[2,3],strength,True).replace([np.inf, -np.inf], np.nan).fillna('').to_dict(orient='records')
45
+ t = wsba.nhl_calculate_stats(df,'team',[2,3],strength,True).replace([np.inf, -np.inf], np.nan).fillna('').to_dict(orient='records')
46
+
47
+ if strength != 'all':
48
+ if len(strength)>1:
49
+ add = 'Other'
50
+ else:
51
+ add = strength[0]
52
+ else:
53
+ add = 'All'
54
+
55
+ skater.update({add:s})
56
+ goalie.update({add:g})
57
+ team_stats.update({add:t})
58
+
59
+ df = df.fillna('')
60
+
61
+ team_data = pd.read_csv('https://weakside-breakout.s3.us-east-2.amazonaws.com/info/nhl_teaminfo.csv')[['triCode','seasonId','teamName.default','teamLogo','Primary Color','Secondary Color','WSBA']]
62
+
63
+ info = df[['season','season_type','game_id','game_date',
64
+ 'venue','venue_location']].drop_duplicates().to_dict(orient='records')[0]
65
+
66
+ info.update({'notice':'All data and materials are from the National Hockey League.'})
67
+
68
+ teams = {}
69
+ for team in ['away','home']:
70
+ df = pd.merge(df,team_data,how='left',left_on=[f'{team}_team_abbr','season'],right_on=['triCode','seasonId']).fillna('')
71
+ mod = '' if team == 'away' else '_y'
72
+ teams.update({team: df[[f'{team}_team_abbr'
73
+ ,f'{team}_coach',
74
+ f'teamName.default{mod}',
75
+ f'teamLogo{mod}',
76
+ f'Primary Color{mod}',
77
+ f'Secondary Color{mod}',
78
+ f'WSBA{mod}']].rename(columns={f'{team}_team_abbr':'team_abbr',f'{team}_coach':'coach',
79
+ f'teamName.default{mod}':'team_name',
80
+ f'teamLogo{mod}':'team_logo',
81
+ f'Primary Color{mod}':'primary_color',
82
+ f'Secondary Color{mod}':'secondary_color',
83
+ f'WSBA{mod}':'WSBA'
84
+ }).drop_duplicates().to_dict(orient='records')[0]})
85
+
86
+ play_col = [
87
+ 'event_num','period','period_type',
88
+ 'seconds_elapsed','period_time','game_time',"strength_state","strength_state_venue","home_team_defending_side",
89
+ "event_type_code","event_type","description","event_reason",
90
+ "penalty_type","penalty_duration","penalty_attribution",
91
+ "event_team_abbr","event_team_venue",
92
+ 'num_on', 'players_on','ids_on','num_off','players_off','ids_off','shift_type',
93
+ "event_player_1_name","event_player_2_name","event_player_3_name",
94
+ "event_player_1_id","event_player_2_id","event_player_3_id",
95
+ "event_player_1_pos","event_player_2_pos","event_player_3_pos",
96
+ "event_goalie_name","event_goalie_id",
97
+ "shot_type","zone_code","x","y","x_fixed","y_fixed","x_adj","y_adj",
98
+ "event_skaters","away_skaters","home_skaters",
99
+ "event_distance","event_angle","event_length","seconds_since_last",
100
+ "away_score","home_score", "away_fenwick", "home_fenwick",
101
+ "away_on_1","away_on_2","away_on_3","away_on_4","away_on_5","away_on_6","away_goalie",
102
+ "home_on_1","home_on_2","home_on_3","home_on_4","home_on_5","home_on_6","home_goalie",
103
+ "away_on_1_id","away_on_2_id","away_on_3_id","away_on_4_id","away_on_5_id","away_on_6_id","away_goalie_id",
104
+ "home_on_1_id","home_on_2_id","home_on_3_id","home_on_4_id","home_on_5_id","home_on_6_id","home_goalie_id",
105
+ "event_coach",'xG'
106
+ ]
107
+
108
+ def sanitize(value):
109
+ if isinstance(value, (np.generic, np.ndarray)):
110
+ return value.item()
111
+ return value
112
+
113
+ plays = [
114
+ {k: sanitize(v) for k, v in row.items() if v != ''}
115
+ for row in df[[col for col in play_col if col in df.columns]].to_dict(orient='records')
116
+ ]
117
+
118
+ plays = [
119
+ {k: sanitize(v) for k, v in row.items() if v != ''}
120
+ for row in df[[col for col in play_col if col in df.columns]].to_dict(orient='records')
121
+ ]
122
+
123
+ return {'info': info,
124
+ 'teams': teams,
125
+ 'skater_stats':skater,
126
+ 'goalie_stats':goalie,
127
+ 'team_stats':team_stats,
128
+ 'plays': plays
129
+ }
@@ -0,0 +1,4 @@
1
+ import uvicorn
2
+
3
+ if __name__ == "__main__":
4
+ uvicorn.run("index:app", reload=True)
File without changes
@@ -0,0 +1,374 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from wsba_hockey.tools.xg_model import *
4
+
5
+ ## AGGREGATE FUNCTIONS ##
6
+
7
+ ## GLOBAL VARIABLES ##
8
+ shot_types = ['wrist','deflected','tip-in','slap','backhand','snap','wrap-around','poke','bat','cradle','between-legs']
9
+ fenwick_events = ['missed-shot','shot-on-goal','goal']
10
+
11
+ def calc_indv(pbp,game_strength,second_group):
12
+ # Filter by game strength if not "all"
13
+ if game_strength != "all":
14
+ pbp = pbp.loc[pbp['strength_state'].isin(game_strength)]
15
+
16
+ #Add second event-team column for necessary situations
17
+ pbp['event_team_abbr_2'] = np.where(pbp['event_team_abbr'].notna(),
18
+ np.where(pbp['event_team_abbr']==pbp['home_team_abbr'],pbp['away_team_abbr'],pbp['home_team_abbr']),np.nan)
19
+
20
+ #Change second event team to goal-scoring team for goal events
21
+ pbp['event_team_abbr_2'] = np.where(pbp['event_type']=='goal',pbp['event_team_abbr'],pbp['event_team_abbr_2'])
22
+
23
+ #Determine how to group
24
+ raw_group_1 = ['event_player_1_id','event_team_abbr']+second_group
25
+ raw_group_2 = ['event_player_2_id','event_team_abbr_2']+second_group
26
+ raw_group_3 = ['event_player_3_id','event_team_abbr']+second_group
27
+ clean_group = ['ID','Team','Season']+(['Game'] if 'game_id' in second_group else [])
28
+
29
+ #First event player stats
30
+ ep1 = (
31
+ pbp.loc[pbp['event_type'].isin(["goal", "shot-on-goal", "missed-shot","blocked-shot",'hit','giveaway','takeaway','faceoff','penalty'])].groupby(raw_group_1).agg(
32
+ Gi=('event_type', lambda x: (x == "goal").sum()),
33
+ Fi=('event_type', lambda x: (x.isin(fenwick_events)).sum()),
34
+ Ci=('event_type', lambda x: (x.isin(fenwick_events+['blocked-shot'])).sum()),
35
+ xGi=('xG', 'sum'),
36
+ HF=('event_type',lambda x: (x=='hit').sum()),
37
+ Give=('event_type',lambda x: (x=='giveaway').sum()),
38
+ Take=('event_type',lambda x: (x=='takeaway').sum()),
39
+ Penl=('event_type',lambda x: (x=='penalty').sum()),
40
+ Penl2=('penalty_duration',lambda x: (x==2).sum()),
41
+ Penl5=('penalty_duration',lambda x: (x==5).sum()),
42
+ PIM=('penalty_duration','sum'),
43
+ FW=('event_type',lambda x: (x=='faceoff').sum())
44
+ ).reset_index().rename(columns={'event_player_1_id': 'ID', 'event_team_abbr': 'Team', 'season': 'Season', 'game_id':'Game'})
45
+ )
46
+
47
+ #Second event player stats
48
+ ep2 = (
49
+ pbp.loc[(pbp['event_type'].isin(['goal','blocked-shot','hit','faceoff','penalty']))&~(pbp['description'].str.lower().str.contains('blocked by teammate',na=False))].groupby(raw_group_2).agg(
50
+ A1=('event_type',lambda x: (x=='goal').sum()),
51
+ HA=('event_type',lambda x: (x=='hit').sum()),
52
+ Draw=('event_type',lambda x: (x=='penalty').sum()),
53
+ FL=('event_type',lambda x: (x=='faceoff').sum()),
54
+ Block=('event_type',lambda x:(x=='blocked-shot').sum())
55
+ ).reset_index().rename(columns={'event_player_2_id': 'ID', 'event_team_abbr_2': 'Team', 'season': 'Season', 'game_id':'Game'})
56
+ )
57
+
58
+ #Third event player stats
59
+ ep3 = (
60
+ pbp.loc[pbp['event_type'].isin(["goal"])].groupby(raw_group_3).agg(
61
+ A2=('event_type', 'count')
62
+ ).reset_index().rename(columns={'event_player_3_id': 'ID', 'event_team_abbr': 'Team', 'season': 'Season', 'game_id':'Game'})
63
+ )
64
+
65
+ #Rush events
66
+ rush = (
67
+ pbp.loc[(pbp['event_type'].isin(fenwick_events))&(pbp['rush']>0)].groupby(raw_group_1).agg(
68
+ Rush=('event_type','count'),
69
+ Rush_G=('event_type',lambda x: (x == 'goal').sum()),
70
+ Rush_xG=('xG','sum')
71
+ ).reset_index().rename(columns={'event_player_1_id': 'ID', 'event_team_abbr': 'Team', 'season': 'Season', 'game_id':'Game', 'Rush_G': 'Rush G', 'Rush_xG': 'Rush xG'})
72
+ )
73
+
74
+ indv = pd.merge(ep1,ep2,how='outer',on=clean_group)
75
+ indv = pd.merge(indv,ep3,how='outer',on=clean_group)
76
+ indv = pd.merge(indv,rush,how='outer',on=clean_group)
77
+
78
+ #Shot Types
79
+ for type in shot_types:
80
+ shot = (
81
+ pbp.loc[(pbp['event_type'].isin(["goal", "shot-on-goal", "missed-shot"])&(pbp['shot_type']==type))].groupby(raw_group_1).agg(
82
+ Gi=('event_type', lambda x: (x == "goal").sum()),
83
+ Fi=('event_type', lambda x: (x != "blocked-shot").sum()),
84
+ xGi=('xG', 'sum'),
85
+ ).reset_index().rename(columns={'event_player_1_id': 'ID', 'event_team_abbr': 'Team', 'season': 'Season', 'game_id':'Game'})
86
+ )
87
+
88
+ shot = shot.rename(columns={
89
+ 'Gi':f'{type.capitalize()}Gi',
90
+ 'Fi':f'{type.capitalize()}Fi',
91
+ 'xGi':f'{type.capitalize()}xGi',
92
+ })
93
+ indv = pd.merge(indv,shot,how='outer',on=clean_group)
94
+
95
+ indv[['Gi','A1','A2','Penl','Draw','FW','FL']] = indv[['Gi','A1','A2','Penl','Draw','FW','FL']].fillna(0)
96
+
97
+ indv['P1'] = indv['Gi']+indv['A1']
98
+ indv['P'] = indv['P1']+indv['A2']
99
+ indv['xGi/Fi'] = indv['xGi']/indv['Fi']
100
+ indv['Gi/xGi'] = indv['Gi']/indv['xGi']
101
+ indv['Fshi%'] = indv['Gi']/indv['Fi']
102
+ indv['F'] = indv['FW']+indv['FL']
103
+ indv['F%'] = indv['FW']/indv['F']
104
+ indv['PM%'] = indv['Take']/(indv['Give']+indv['Take'])
105
+ indv['HF%'] = indv['HF']/(indv['HF']+indv['HA'])
106
+ indv['PENL%'] = indv['Draw']/(indv['Draw']+indv['Penl'])
107
+
108
+ return indv
109
+
110
+ def calc_onice(pbp,game_strength,second_group):
111
+ #Convert player on-ice columns to vectors
112
+ pbp['home_on_ice'] = pbp['home_on_1_id'].astype(str) + ";" + pbp['home_on_2_id'].astype(str) + ";" + pbp['home_on_3_id'].astype(str) + ";" + pbp['home_on_4_id'].astype(str) + ";" + pbp['home_on_5_id'].astype(str) + ";" + pbp['home_on_6_id'].astype(str)
113
+ pbp['away_on_ice'] = pbp['away_on_1_id'].astype(str) + ";" + pbp['away_on_2_id'].astype(str) + ";" + pbp['away_on_3_id'].astype(str) + ";" + pbp['away_on_4_id'].astype(str) + ";" + pbp['away_on_5_id'].astype(str) + ";" + pbp['away_on_6_id'].astype(str)
114
+
115
+ #Remove NA players
116
+ pbp['home_on_ice'] = pbp['home_on_ice'].str.replace(';nan', '', regex=True)
117
+ pbp['away_on_ice'] = pbp['away_on_ice'].str.replace(';nan', '', regex=True)
118
+
119
+ def process_team_stats(df, on_ice_col, team_col, opp_col, game_strength):
120
+ df = df[['season','game_id','strength_state','event_num', team_col, opp_col, 'event_type', 'event_team_venue','event_team_abbr', on_ice_col,'ids_on','shift_type','event_length','zone_code','xG']].copy()
121
+
122
+ #Flip strength state (when necessary) and filter by game strength if not "all"
123
+ if game_strength != "all":
124
+ if game_strength not in ['3v3','4v4','5v5']:
125
+ for strength in game_strength:
126
+ df['strength_state'] = np.where(np.logical_and(df['event_team_venue']==opp_col[0:4],df['strength_state']==strength[::-1]),strength,df['strength_state'])
127
+
128
+ df = df.loc[df['strength_state'].isin(game_strength)]
129
+
130
+ df[on_ice_col] = df[on_ice_col].str.split(';')
131
+ df = df.explode(on_ice_col)
132
+ df = df.rename(columns={on_ice_col: 'ID', 'season': 'Season'})
133
+ df['xGF'] = np.where(df['event_team_abbr'] == df[team_col], df['xG'], 0)
134
+ df['xGA'] = np.where(df['event_team_abbr'] == df[opp_col], df['xG'], 0)
135
+ df['GF'] = np.where((df['event_type'] == "goal") & (df['event_team_abbr'] == df[team_col]), 1, 0)
136
+ df['GA'] = np.where((df['event_type'] == "goal") & (df['event_team_abbr'] == df[opp_col]), 1, 0)
137
+ df['FF'] = np.where((df['event_type'].isin(fenwick_events)) & (df['event_team_abbr'] == df[team_col]), 1, 0)
138
+ df['FA'] = np.where((df['event_type'].isin(fenwick_events)) & (df['event_team_abbr'] == df[opp_col]), 1, 0)
139
+ df['CF'] = np.where((df['event_type'].isin(fenwick_events+['blocked-shot'])) & (df['event_team_abbr'] == df[team_col]), 1, 0)
140
+ df['CA'] = np.where((df['event_type'].isin(fenwick_events+['blocked-shot'])) & (df['event_team_abbr'] == df[opp_col]), 1, 0)
141
+ df['OZF'] = np.where((df['event_type']=='faceoff') & ((df['zone_code']=='O')&((df['event_team_abbr'] == df[team_col])) | (df['zone_code']=='D')&((df['event_team_abbr'] == df[opp_col]))), 1, 0)
142
+ df['NZF'] = np.where((df['zone_code']=='N') & (df['event_team_abbr']==df[team_col]),1,0)
143
+ df['DZF'] = np.where((df['event_type']=='faceoff') & ((df['zone_code']=='D')&((df['event_team_abbr'] == df[team_col])) | (df['zone_code']=='O')&((df['event_team_abbr'] == df[opp_col]))), 1, 0)
144
+
145
+ stats = df.groupby(['ID',team_col,'Season']+(['game_id'] if 'game_id' in second_group else [])).agg(
146
+ GP=('game_id','nunique'),
147
+ TOI=('event_length','sum'),
148
+ FF=('FF', 'sum'),
149
+ FA=('FA', 'sum'),
150
+ GF=('GF', 'sum'),
151
+ GA=('GA', 'sum'),
152
+ xGF=('xGF', 'sum'),
153
+ xGA=('xGA', 'sum'),
154
+ CF=('CF','sum'),
155
+ CA=('CA','sum'),
156
+ OZF=('OZF','sum'),
157
+ NZF=('NZF','sum'),
158
+ DZF=('DZF','sum')
159
+ ).reset_index()
160
+
161
+ return stats.rename(columns={team_col:"Team", 'game_id':'Game'})
162
+
163
+ home_stats = process_team_stats(pbp, 'home_on_ice', 'home_team_abbr', 'away_team_abbr',game_strength)
164
+ away_stats = process_team_stats(pbp, 'away_on_ice', 'away_team_abbr', 'home_team_abbr',game_strength)
165
+
166
+ onice_stats = pd.concat([home_stats,away_stats]).groupby(['ID','Team','Season']+(['Game'] if 'game_id' in second_group else [])).agg(
167
+ GP=('GP','sum'),
168
+ TOI=('TOI','sum'),
169
+ FF=('FF', 'sum'),
170
+ FA=('FA', 'sum'),
171
+ GF=('GF', 'sum'),
172
+ GA=('GA', 'sum'),
173
+ xGF=('xGF', 'sum'),
174
+ xGA=('xGA', 'sum'),
175
+ CF=('CF','sum'),
176
+ CA=('CA','sum'),
177
+ OZF=('OZF','sum'),
178
+ NZF=('NZF','sum'),
179
+ DZF=('DZF','sum')
180
+ ).reset_index()
181
+
182
+ onice_stats['xGF/FF'] = onice_stats['xGF']/onice_stats['FF']
183
+ onice_stats['GF/xGF'] = onice_stats['GF']/onice_stats['xGF']
184
+ onice_stats['FshF%'] = onice_stats['GF']/onice_stats['FF']
185
+ onice_stats['xGA/FA'] = onice_stats['xGA']/onice_stats['FA']
186
+ onice_stats['GA/xGA'] = onice_stats['GA']/onice_stats['xGA']
187
+ onice_stats['FshA%'] = onice_stats['GA']/onice_stats['FA']
188
+ onice_stats['OZF%'] = onice_stats['OZF']/(onice_stats['OZF']+onice_stats['NZF']+onice_stats['DZF'])
189
+ onice_stats['NZF%'] = onice_stats['NZF']/(onice_stats['OZF']+onice_stats['NZF']+onice_stats['DZF'])
190
+ onice_stats['DZF%'] = onice_stats['DZF']/(onice_stats['OZF']+onice_stats['NZF']+onice_stats['DZF'])
191
+
192
+ return onice_stats
193
+
194
+ def calc_team(pbp,game_strength,second_group):
195
+ teams = []
196
+ for team in [('away','home'),('home','away')]:
197
+ #Flip strength state (when necessary) and filter by game strength if not "all"
198
+ if game_strength != "all":
199
+ if game_strength not in ['3v3','4v4','5v5']:
200
+ for strength in game_strength:
201
+ pbp['strength_state'] = np.where(np.logical_and(pbp['event_team_venue']==team[1],pbp['strength_state']==strength[::-1]),strength,pbp['strength_state'])
202
+
203
+ pbp = pbp.loc[pbp['strength_state'].isin(game_strength)]
204
+
205
+ pbp['xGF'] = np.where(pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'], pbp['xG'], 0)
206
+ pbp['xGA'] = np.where(pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'], pbp['xG'], 0)
207
+ pbp['GF'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
208
+ pbp['GA'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
209
+ pbp['FF'] = np.where((pbp['event_type'].isin(fenwick_events)) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
210
+ pbp['FA'] = np.where((pbp['event_type'].isin(fenwick_events)) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
211
+ pbp['CF'] = np.where((pbp['event_type'].isin(fenwick_events+['blocked-shot'])) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
212
+ pbp['CA'] = np.where((pbp['event_type'].isin(fenwick_events+['blocked-shot'])) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
213
+ pbp['HF'] = np.where((pbp['event_type']=='hit') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
214
+ pbp['HA'] = np.where((pbp['event_type']=='hit') & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
215
+ pbp['Penl'] = np.where((pbp['event_type']=='penalty') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
216
+ pbp['Penl2'] = np.where((pbp['event_type']=='penalty') & (pbp['penalty_duration']==2) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
217
+ pbp['Penl5'] = np.where((pbp['event_type']=='penalty') & (pbp['penalty_duration']==5) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
218
+ pbp['PIM'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), pbp['penalty_duration'], 0)
219
+ pbp['Draw'] = np.where((pbp['event_type']=='penalty') & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
220
+ pbp['Give'] = np.where((pbp['event_type']=='giveaway') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
221
+ pbp['Take'] = np.where((pbp['event_type']=='takeaway') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
222
+ pbp['Block'] = pbp['CA'] - pbp['FA']
223
+ pbp['RushF'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), 1, 0)
224
+ pbp['RushA'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), 1, 0)
225
+ pbp['RushFxG'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), pbp['xG'], 0)
226
+ pbp['RushAxG'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), pbp['xG'], 0)
227
+ pbp['RushFG'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), 1, 0)
228
+ pbp['RushAG'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), 1, 0)
229
+
230
+ stats = pbp.groupby([f'{team[0]}_team_abbr']+second_group).agg(
231
+ GP=('game_id','nunique'),
232
+ TOI=('event_length','sum'),
233
+ FF=('FF', 'sum'),
234
+ FA=('FA', 'sum'),
235
+ GF=('GF', 'sum'),
236
+ GA=('GA', 'sum'),
237
+ xGF=('xGF', 'sum'),
238
+ xGA=('xGA', 'sum'),
239
+ CF=('CF','sum'),
240
+ CA=('CA','sum'),
241
+ HF=('HF','sum'),
242
+ HA=('HA','sum'),
243
+ Penl=('Penl','sum'),
244
+ Penl2=('Penl2','sum'),
245
+ Penl5=('Penl5','sum'),
246
+ PIM=('PIM','sum'),
247
+ Draw=('Draw','sum'),
248
+ Give=('Give','sum'),
249
+ Take=('Take','sum'),
250
+ Block=('Block','sum'),
251
+ RushF=('RushF','sum'),
252
+ RushA=('RushA','sum'),
253
+ RushFxG=('RushFxG','sum'),
254
+ RushAxG=('RushAxG','sum'),
255
+ RushFG=('RushFG','sum'),
256
+ RushAG=('RushAG','sum'),
257
+ ).reset_index().rename(columns={f'{team[0]}_team_abbr':"Team",'season':"Season",'game_id':'Game'})
258
+ teams.append(stats)
259
+
260
+ onice_stats = pd.concat(teams).groupby(['Team','Season']+(['Game'] if 'game_id' in second_group else [])).agg(
261
+ GP=('GP','sum'),
262
+ TOI=('TOI','sum'),
263
+ FF=('FF', 'sum'),
264
+ FA=('FA', 'sum'),
265
+ GF=('GF', 'sum'),
266
+ GA=('GA', 'sum'),
267
+ xGF=('xGF', 'sum'),
268
+ xGA=('xGA', 'sum'),
269
+ CF=('CF','sum'),
270
+ CA=('CA','sum'),
271
+ HF=('HF','sum'),
272
+ HA=('HA','sum'),
273
+ Penl=('Penl','sum'),
274
+ Penl2=('Penl2','sum'),
275
+ Penl5=('Penl5','sum'),
276
+ PIM=('PIM','sum'),
277
+ Draw=('Draw','sum'),
278
+ Give=('Give','sum'),
279
+ Take=('Take','sum'),
280
+ Block=('Block','sum'),
281
+ RushF=('RushF','sum'),
282
+ RushA=('RushA','sum'),
283
+ RushFxG=('RushFxG','sum'),
284
+ RushAxG=('RushAxG','sum'),
285
+ RushFG=('RushFG','sum'),
286
+ RushAG=('RushAG','sum'),
287
+ ).reset_index()
288
+
289
+ onice_stats['xGF/FF'] = onice_stats['xGF']/onice_stats['FF']
290
+ onice_stats['GF/xGF'] = onice_stats['GF']/onice_stats['xGF']
291
+ onice_stats['FshF%'] = onice_stats['GF']/onice_stats['FF']
292
+ onice_stats['xGA/FA'] = onice_stats['xGA']/onice_stats['FA']
293
+ onice_stats['GA/xGA'] = onice_stats['GA']/onice_stats['xGA']
294
+ onice_stats['FshA%'] = onice_stats['GA']/onice_stats['FA']
295
+ onice_stats['PM%'] = onice_stats['Take']/(onice_stats['Give']+onice_stats['Take'])
296
+ onice_stats['HF%'] = onice_stats['HF']/(onice_stats['HF']+onice_stats['HA'])
297
+ onice_stats['PENL%'] = onice_stats['Draw']/(onice_stats['Draw']+onice_stats['Penl'])
298
+
299
+ return onice_stats
300
+
301
+ def calc_goalie(pbp,game_strength,second_group):
302
+ teams=[]
303
+ for team in [('away','home'),('home','away')]:
304
+ #Flip strength state (when necessary) and filter by game strength if not "all"
305
+ if game_strength != "all":
306
+ if game_strength not in ['3v3','4v4','5v5']:
307
+ for strength in game_strength:
308
+ pbp['strength_state'] = np.where(np.logical_and(pbp['event_team_venue']==team[1],pbp['strength_state']==strength[::-1]),strength,pbp['strength_state'])
309
+
310
+ pbp = pbp.loc[pbp['strength_state'].isin(game_strength)]
311
+
312
+ pbp['xGF'] = np.where(pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'], pbp['xG'], 0)
313
+ pbp['xGA'] = np.where(pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'], pbp['xG'], 0)
314
+ pbp['GF'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
315
+ pbp['GA'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
316
+ pbp['FF'] = np.where((pbp['event_type'].isin(fenwick_events)) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
317
+ pbp['FA'] = np.where((pbp['event_type'].isin(fenwick_events)) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
318
+ pbp['CF'] = np.where((pbp['event_type'].isin(fenwick_events+['blocked-shot'])) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
319
+ pbp['CA'] = np.where((pbp['event_type'].isin(fenwick_events+['blocked-shot'])) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
320
+ pbp['RushF'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), 1, 0)
321
+ pbp['RushA'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), 1, 0)
322
+ pbp['RushFxG'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), pbp['xG'], 0)
323
+ pbp['RushAxG'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), pbp['xG'], 0)
324
+ pbp['RushFG'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), 1, 0)
325
+ pbp['RushAG'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), 1, 0)
326
+
327
+ stats = pbp.groupby([f'{team[0]}_goalie_id',f'{team[0]}_team_abbr']+second_group).agg(
328
+ GP=('game_id','nunique'),
329
+ TOI=('event_length','sum'),
330
+ FF=('FF', 'sum'),
331
+ FA=('FA', 'sum'),
332
+ GF=('GF', 'sum'),
333
+ GA=('GA', 'sum'),
334
+ xGF=('xGF', 'sum'),
335
+ xGA=('xGA', 'sum'),
336
+ CF=('CF','sum'),
337
+ CA=('CA','sum'),
338
+ RushF=('RushF','sum'),
339
+ RushA=('RushA','sum'),
340
+ RushFxG=('RushFxG','sum'),
341
+ RushAxG=('RushAxG','sum'),
342
+ RushFG=('RushFG','sum'),
343
+ RushAG=('RushAG','sum'),
344
+ ).reset_index().rename(columns={f'{team[0]}_goalie_id':"ID",f'{team[0]}_team_abbr':"Team",'season':"Season",'game_id':'Game'})
345
+ teams.append(stats)
346
+
347
+ onice_stats = pd.concat(teams).groupby(['ID','Team','Season']+(['Game'] if 'game_id' in second_group else [])).agg(
348
+ GP=('GP','sum'),
349
+ TOI=('TOI','sum'),
350
+ FF=('FF', 'sum'),
351
+ FA=('FA', 'sum'),
352
+ GF=('GF', 'sum'),
353
+ GA=('GA', 'sum'),
354
+ xGF=('xGF', 'sum'),
355
+ xGA=('xGA', 'sum'),
356
+ CF=('CF','sum'),
357
+ CA=('CA','sum'),
358
+ RushF=('RushF','sum'),
359
+ RushA=('RushA','sum'),
360
+ RushFxG=('RushFxG','sum'),
361
+ RushAxG=('RushAxG','sum'),
362
+ RushFG=('RushFG','sum'),
363
+ RushAG=('RushAG','sum'),
364
+ ).reset_index()
365
+
366
+ onice_stats['xGF/FF'] = onice_stats['xGF']/onice_stats['FF']
367
+ onice_stats['GF/xGF'] = onice_stats['GF']/onice_stats['xGF']
368
+ onice_stats['FshF%'] = onice_stats['GF']/onice_stats['FF']
369
+ onice_stats['xGA/FA'] = onice_stats['xGA']/onice_stats['FA']
370
+ onice_stats['GA/xGA'] = onice_stats['GA']/onice_stats['xGA']
371
+ onice_stats['FshA%'] = onice_stats['GA']/onice_stats['FA']
372
+ onice_stats['GSAx'] = onice_stats['xGA']-onice_stats['GA']
373
+
374
+ return onice_stats