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.
- wsba_hockey/api/api/index.py +129 -0
- wsba_hockey/api/api/main.py +4 -0
- wsba_hockey/api/api/tools/__init__.py +0 -0
- wsba_hockey/api/api/tools/agg.py +374 -0
- wsba_hockey/api/api/tools/archive/old_scraping.py +1104 -0
- wsba_hockey/api/api/tools/plotting.py +144 -0
- wsba_hockey/api/api/tools/scraping.py +1000 -0
- wsba_hockey/api/api/tools/utils/__init__.py +1 -0
- wsba_hockey/api/api/tools/utils/config.py +14 -0
- wsba_hockey/api/api/tools/utils/save_pages.py +133 -0
- wsba_hockey/api/api/tools/utils/shared.py +450 -0
- wsba_hockey/api/api/tools/xg_model.py +455 -0
- wsba_hockey/api/api/wsba_main.py +1213 -0
- wsba_hockey/data_pipelines.py +71 -8
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/app.py +6 -5
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/app.py +101 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/plot.py +71 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/rink_plot.py +245 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/app.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/plot.py +2 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/rink_plot.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/app.py +3 -3
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/plot.py +2 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/rink_plot.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/app.py +44 -28
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/plot.py +12 -3
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/rink_plot.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/app.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/plot.py +5 -4
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/rink_plot.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/app.py +103 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/plot.py +95 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/rink_plot.py +245 -0
- wsba_hockey/flask/app.py +77 -0
- wsba_hockey/tools/plotting.py +3 -3
- wsba_hockey/tools/scraping.py +7 -3
- wsba_hockey/tools/xg_model.py +3 -3
- wsba_hockey/workspace.py +28 -12
- wsba_hockey/wsba_main.py +10 -17
- {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/METADATA +1 -1
- {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/RECORD +44 -24
- {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/WHEEL +0 -0
- {wsba_hockey-1.0.6.dist-info → wsba_hockey-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
}
|
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
|