wsba-hockey 1.1.3__py3-none-any.whl → 1.1.5__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.
@@ -0,0 +1,245 @@
1
+ import pandas as pd
2
+ import pyarrow.dataset as ds
3
+ import plotly.express as px
4
+ import plot as wsba_plt
5
+ import numpy as np
6
+ from urllib.parse import *
7
+ from shiny import *
8
+ from shinywidgets import output_widget, render_widget
9
+
10
+ app_ui = ui.page_fluid(
11
+ ui.tags.link(
12
+ rel='stylesheet',
13
+ href='https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap'
14
+ ),
15
+ ui.tags.style(
16
+ """
17
+ body {
18
+ background-color: #09090b;
19
+ color: white;
20
+ font-family: 'Bebas Neue', sans-serif;
21
+ }
22
+
23
+ .custom-input input.form-control,
24
+ .custom-input .selectize-control,
25
+ .custom-input .selectize-input {
26
+ background-color: #09090b !important; /* black background */
27
+ color: white !important; /* white font color */
28
+ border-radius: 4px;
29
+ border: 1px solid #444;
30
+ }
31
+
32
+ .custom-input .selectize-dropdown,
33
+ .custom-input .selectize-dropdown-content {
34
+ background-color: #09090b !important;
35
+ color: white !important;
36
+ }
37
+
38
+ .custom-input .selectize-control.multi .item {
39
+ background-color: #09090b !important;
40
+ color: white !important;
41
+ border-radius: 4px;
42
+ padding: 2px 6px;
43
+ margin: 2px 4px 2px 0;
44
+ }
45
+
46
+ label.control-label {
47
+ color: white !important;
48
+ }
49
+
50
+ .selectize-control.multi {
51
+ width: 300px !important;
52
+ }
53
+
54
+ .form-row {
55
+ display: flex;
56
+ gap: 12px;
57
+ flex-wrap: wrap;
58
+ justify-content: center;
59
+ }
60
+
61
+ .submit-button {
62
+ display: flex;
63
+ justify-content: center;
64
+ }
65
+
66
+ .hide {
67
+ display: none;
68
+ }
69
+
70
+ .table thead tr {
71
+ white-space: nowrap;
72
+ text-align: center;
73
+ color: white;
74
+ background-color: #09090b;
75
+ }
76
+
77
+ .table thead th {
78
+ white-space: nowrap;
79
+ text-align: center;
80
+ color: #09090b;
81
+ }
82
+
83
+ .table tbody tr {
84
+ --bs-table-bg: #09090b;
85
+ --bs-table-color-state: white;
86
+ }
87
+
88
+ .table tbody tr td {
89
+ white-space: nowrap;
90
+ text-align: center;
91
+ overflow: hidden;
92
+ text-overflow: ellipsis;
93
+ color: white;
94
+ background-color: #09090b;
95
+ }
96
+ """
97
+ ),
98
+ output_widget("line_combos"),
99
+ ui.output_data_frame("with_out")
100
+ )
101
+
102
+ def server(input, output, session):
103
+ queries = reactive.Value({})
104
+ team_data = reactive.Value(pd.DataFrame())
105
+ player_data = reactive.Value(pd.DataFrame())
106
+
107
+ col = [
108
+ 'season','season_type','game_id','game_date',
109
+ 'away_team_abbr','home_team_abbr','event_num','period',
110
+ 'seconds_elapsed',"strength_state","strength_state_venue",
111
+ "event_type","description",
112
+ "penalty_duration",
113
+ "event_team_abbr","event_team_venue",
114
+ "x_adj","y_adj",
115
+ "event_distance","event_angle","event_length","seconds_since_last",
116
+ "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",
117
+ "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",
118
+ 'rush','rebound','empty_net','xG'
119
+ ]
120
+
121
+ @output()
122
+ @render_widget
123
+ def line_combos():
124
+ #Retreive query parameters
125
+ search = session.input[".clientdata_url_search"]()
126
+ query = parse_qs(urlparse(search).query)
127
+
128
+ print(query)
129
+ #If no input data is provided automatically provide a select skater and plot all 5v5 fenwick shots
130
+ defaults = {
131
+ 'season':['20182019'],
132
+ 'team':['BOS'],
133
+ 'strength_state':['5v5'],
134
+ 'season_type':['2'],
135
+ 'skaters':['8473419,8470638']
136
+ }
137
+
138
+ for key in defaults.keys():
139
+ if key not in query.keys():
140
+ query.update({key:defaults[key]})
141
+
142
+ #Iterate through query and parse params with multiple selections
143
+ for param in query.keys():
144
+ q_string = query[param][0]
145
+ query[param] = q_string.split(',')
146
+
147
+ print(query)
148
+ #Determine which season to load based on the input
149
+ season = query['season'][0]
150
+ queries.set(query)
151
+
152
+ #Load appropriate dataframe
153
+ dataset = ds.dataset(f's3://weakside-breakout/pbp/parquet/nhl_pbp_{season}.parquet', format='parquet')
154
+ filter_expr = ((ds.field('away_team_abbr') == query['team'][0]) | (ds.field('home_team_abbr') == query['team'][0])) & ((ds.field('season_type') == int(query['season_type'][0])))
155
+
156
+ table = dataset.to_table(columns=col,filter=filter_expr)
157
+ df = table.to_pandas()
158
+
159
+ #Prepare dataframe for plotting based on URL parameters
160
+ team_data.set(df[(df['away_team_abbr']==query['team'][0]) | (df['home_team_abbr']==query['team'][0])])
161
+ player_data.set(wsba_plt.player_events(df,query['skaters']))
162
+
163
+ #Return empty rink if no data exists else continue
164
+ if df.empty:
165
+ return wsba_plt.wsba_rink()
166
+ else:
167
+ rink = wsba_plt.wsba_rink()
168
+
169
+ try:
170
+ for_plot = wsba_plt.heatmap(df,team=query['team'][0],skaters=query['skaters'],events=['missed-shot','shot-on-goal','goal'],strengths=query['strength_state'],onice='for')
171
+ against_plot = wsba_plt.heatmap(df,team=query['team'][0],skaters=query['skaters'],events=['missed-shot','shot-on-goal','goal'],strengths=query['strength_state'],onice='against')
172
+
173
+ for trace in for_plot[1].data:
174
+ rink.add_trace(trace)
175
+
176
+ for trace in against_plot[1].data:
177
+ rink.add_trace(trace)
178
+
179
+ season = int(season[0:4])
180
+
181
+ return rink.add_annotation(
182
+ text='Lower xG',
183
+ xref="paper",
184
+ yref="paper",
185
+ xanchor='right',
186
+ yanchor='top',
187
+ font=dict(color='white'),
188
+ x=0.3,
189
+ y=0.04,
190
+ showarrow=False
191
+ ).add_annotation(
192
+ text='Higher xG',
193
+ xref="paper",
194
+ yref="paper",
195
+ xanchor='right',
196
+ yanchor='top',
197
+ font=dict(color='white'),
198
+ x=0.76,
199
+ y=0.04,
200
+ showarrow=False
201
+ )
202
+ except:
203
+ return wsba_plt.wsba_rink()
204
+
205
+ @output()
206
+ @render.data_frame
207
+ def with_out():
208
+ query_load = queries.get()
209
+
210
+ ids = query_load['skaters']
211
+ team = query_load['team'][0]
212
+ team_pbp = team_data.get()
213
+ team_pbp['home_on_ice'] = team_pbp['home_on_1_id'].astype(str) + ";" + team_pbp['home_on_2_id'].astype(str) + ";" + team_pbp['home_on_3_id'].astype(str) + ";" + team_pbp['home_on_4_id'].astype(str) + ";" + team_pbp['home_on_5_id'].astype(str) + ";" + team_pbp['home_on_6_id'].astype(str)
214
+ team_pbp['away_on_ice'] = team_pbp['away_on_1_id'].astype(str) + ";" + team_pbp['away_on_2_id'].astype(str) + ";" + team_pbp['away_on_3_id'].astype(str) + ";" + team_pbp['away_on_4_id'].astype(str) + ";" + team_pbp['away_on_5_id'].astype(str) + ";" + team_pbp['away_on_6_id'].astype(str)
215
+
216
+ team_pbp['onice'] = team_pbp['away_on_ice']+';'+team_pbp['home_on_ice']
217
+
218
+ if len(ids)>2:
219
+ mask = ((team_pbp['onice'].str.contains(ids[0])) & (team_pbp['onice'].str.contains(ids[1])) & (team_pbp['onice'].str.contains(ids[2])))
220
+ else:
221
+ mask = ((team_pbp['onice'].str.contains(ids[0])) & (team_pbp['onice'].str.contains(ids[1])))
222
+
223
+ team_pbp = team_pbp.loc[~mask]
224
+
225
+ if 'Other' in query_load['strength_state']:
226
+ strength_state = query_load['strength_state'] + team_pbp.loc[~(team_pbp['strength_state'].isin(['5v5','5v4','4v5'])),'strength_state'].drop_duplicates().to_list()
227
+ else:
228
+ strength_state = query_load['strength_state']
229
+
230
+ with_stats = wsba_plt.calculate_stats(player_data.get(), query_load['team'][0], strength_state).replace({team:f'{team} With'})
231
+ without_stats = wsba_plt.calculate_stats(team_pbp, query_load['team'][0], strength_state).replace({team:f'{team} Without'})
232
+
233
+ total = pd.concat([with_stats, without_stats])[['Team',
234
+ 'TOI',
235
+ 'GF/60','GA/60',
236
+ 'SF/60','SA/60',
237
+ 'FF/60','FA/60',
238
+ 'xGF/60','xGA/60',
239
+ 'GF%','SF%',
240
+ 'FF%','xGF%',
241
+ 'GSAx']].round(2)
242
+
243
+ return render.DataTable(total)
244
+
245
+ app = App(app_ui, server)
@@ -0,0 +1,275 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+ import matplotlib.pyplot as plt
5
+ import rink_plot
6
+ from scipy.interpolate import griddata
7
+ from scipy.ndimage import gaussian_filter
8
+
9
+ def wsba_rink(setting='full', vertical=False):
10
+ return rink_plot.rink(setting=setting, vertical=vertical)
11
+
12
+ def player_events(df,skaters):
13
+ df['onice'] = df['home_on_1_id'].astype(str) + ";" + df['home_on_2_id'].astype(str) + ";" + df['home_on_3_id'].astype(str) + ";" + df['home_on_4_id'].astype(str) + ";" + df['home_on_5_id'].astype(str) + ";" + df['home_on_6_id'].astype(str) + ";" + df['away_on_1_id'].astype(str) + ";" + df['away_on_2_id'].astype(str) + ";" + df['away_on_3_id'].astype(str) + ";" + df['away_on_4_id'].astype(str) + ";" + df['away_on_5_id'].astype(str) + ";" + df['away_on_6_id'].astype(str)
14
+
15
+ if len(skaters)>2:
16
+ mask = ((df['onice'].str.contains(skaters[0])) & (df['onice'].str.contains(skaters[1])) & (df['onice'].str.contains(skaters[2])))
17
+ else:
18
+ mask = ((df['onice'].str.contains(skaters[0])) & (df['onice'].str.contains(skaters[1])))
19
+
20
+ return df[mask]
21
+
22
+ def heatmap(df,team,skaters,events,strengths,onice):
23
+ df = df.copy()
24
+ df = df.loc[df['event_type'].isin(['missed-shot','shot-on-goal','goal'])].replace({np.nan: None})
25
+
26
+ df['event_team_abbr_2'] = np.where(df['home_team_abbr']==df['event_team_abbr'],df['away_team_abbr'],df['home_team_abbr'])
27
+ df['strength_state_2'] = df['strength_state'].str[::-1]
28
+
29
+ df = df.fillna(0)
30
+ df = df.loc[(df['event_type'].isin(events))&(df['x_adj'].notna())&(df['y_adj'].notna())]
31
+ if onice == 'for':
32
+ df['x'] = abs(df['x_adj'])
33
+ df['y'] = np.where(df['x_adj']<0,-df['y_adj'],df['y_adj'])
34
+ df['event_distance'] = abs(df['event_distance'].fillna(0))
35
+ df = df.loc[(df['event_distance']<=89)&(df['x']<=89)&(df['empty_net']==0)]
36
+
37
+ x_min = 0
38
+ x_max = 100
39
+ else:
40
+ df['x'] = -abs(df['x_adj'])
41
+ df['y'] = np.where(df['x_adj']>0,-df['y_adj'],df['y_adj'])
42
+ df['event_distance'] = -abs(df['event_distance'])
43
+ df = df.loc[(df['event_distance']>-89)&(df['x']>-89)&(df['empty_net']==0)]
44
+
45
+ x_min = -100
46
+ x_max = 0
47
+
48
+ df['home_on_ice'] = df['home_on_1_id'].astype(str) + ";" + df['home_on_2_id'].astype(str) + ";" + df['home_on_3_id'].astype(str) + ";" + df['home_on_4_id'].astype(str) + ";" + df['home_on_5_id'].astype(str) + ";" + df['home_on_6_id'].astype(str)
49
+ df['away_on_ice'] = df['away_on_1_id'].astype(str) + ";" + df['away_on_2_id'].astype(str) + ";" + df['away_on_3_id'].astype(str) + ";" + df['away_on_4_id'].astype(str) + ";" + df['away_on_5_id'].astype(str) + ";" + df['away_on_6_id'].astype(str)
50
+
51
+ df['onice_for'] = np.where(df['home_team_abbr']==df['event_team_abbr'],df['home_on_ice'],df['away_on_ice'])
52
+ df['onice_against'] = np.where(df['away_team_abbr']==df['event_team_abbr'],df['home_on_ice'],df['away_on_ice'])
53
+
54
+ df['strength_state'] = np.where(df['strength_state'].isin(['5v5','5v4','4v5']),df['strength_state'],'Other')
55
+ df['strength_state_2'] = np.where(df['strength_state_2'].isin(['5v5','5v4','4v5']),df['strength_state_2'],'Other')
56
+
57
+ if strengths != 'all':
58
+ if onice == 'against':
59
+ df = df.loc[((df['strength_state_2'].isin(strengths)))]
60
+ else:
61
+ df = df.loc[((df['strength_state'].isin(strengths)))]
62
+
63
+ [x,y] = np.round(np.meshgrid(np.linspace(x_min,x_max,(x_max-x_min)),np.linspace(-42.5,42.5,85)))
64
+ xgoals = griddata((df['x'],df['y']),df['xG'],(x,y),method='cubic',fill_value=0)
65
+ xgoals = np.where(xgoals < 0,0,xgoals)
66
+ xgoals_smooth = gaussian_filter(xgoals,sigma=3)
67
+
68
+ if len(skaters)>2:
69
+ mask = ((df['onice'].str.contains(skaters[0])) & (df['onice'].str.contains(skaters[1])) & (df['onice'].str.contains(skaters[2])))
70
+ else:
71
+ mask = ((df['onice'].str.contains(skaters[0])) & (df['onice'].str.contains(skaters[1])))
72
+
73
+ if onice == 'for':
74
+ player_shots = df.loc[(df['event_team_abbr']==team)&mask]
75
+ else:
76
+ player_shots = df.loc[(df['event_team_abbr_2']==team)&mask]
77
+
78
+ [x,y] = np.round(np.meshgrid(np.linspace(x_min,x_max,(x_max-x_min)),np.linspace(-42.5,42.5,85)))
79
+ xgoals_player = griddata((player_shots['x'],player_shots['y']),player_shots['xG'],(x,y),method='cubic',fill_value=0)
80
+ xgoals_player = np.where(xgoals_player < 0,0,xgoals_player)
81
+
82
+ difference = (gaussian_filter(xgoals_player,sigma = 3)) - xgoals_smooth
83
+ data_min= difference.min()
84
+ data_max= difference.max()
85
+
86
+ if abs(data_min) > data_max:
87
+ data_max = data_min * -1
88
+ elif data_max > abs(data_min):
89
+ data_min = data_max * -1
90
+
91
+ fig = go.Figure(
92
+ data = go.Contour( x=np.linspace(x_min,x_max,(x_max-x_min)),
93
+ y=np.linspace(-42.5,42.5,85),
94
+ z=difference,
95
+ colorscale=[[0.0,'red'],[0.5,'#09090b'],[1.0,'blue']],
96
+ connectgaps=True,
97
+ contours=dict(
98
+ type='levels',
99
+ start = data_min,
100
+ end = data_max,
101
+ size=(data_max-data_min)/11
102
+ ),
103
+ colorbar=dict(
104
+ len = 0.7,
105
+ orientation='h',
106
+ showticklabels=False,
107
+ thickness=15,
108
+ yref='paper',
109
+ yanchor='top',
110
+ y=0
111
+ ))
112
+ )
113
+
114
+ return player_shots, fig
115
+
116
+ def calc_team(pbp,game_strength):
117
+ teams = []
118
+ fenwick_events = ['missed-shot','shot-on-goal','goal']
119
+
120
+ for team in [('away','home'),('home','away')]:
121
+ #Flip strength state (when necessary) and filter by game strength if not "all"
122
+ if game_strength != "all":
123
+ if game_strength not in ['3v3','4v4','5v5']:
124
+ for strength in game_strength:
125
+ pbp['strength_state'] = np.where(np.logical_and(pbp['event_team_venue']==team[1],pbp['strength_state']==strength[::-1]),strength,pbp['strength_state'])
126
+
127
+ pbp = pbp.loc[pbp['strength_state'].isin(game_strength)]
128
+
129
+ pbp['xGF'] = np.where(pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'], pbp['xG'], 0)
130
+ pbp['xGA'] = np.where(pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'], pbp['xG'], 0)
131
+ pbp['GF'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
132
+ pbp['GA'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
133
+ pbp['SF'] = np.where((pbp['event_type'].isin(['shot-on-goal','goal'])) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
134
+ pbp['SA'] = np.where((pbp['event_type'].isin(['shot-on-goal','goal'])) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
135
+ pbp['FF'] = np.where((pbp['event_type'].isin(fenwick_events)) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
136
+ pbp['FA'] = np.where((pbp['event_type'].isin(fenwick_events)) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
137
+ pbp['CF'] = np.where((pbp['event_type'].isin(fenwick_events+['blocked-shot'])) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
138
+ pbp['CA'] = np.where((pbp['event_type'].isin(fenwick_events+['blocked-shot'])) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
139
+ pbp['HF'] = np.where((pbp['event_type']=='hit') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
140
+ pbp['HA'] = np.where((pbp['event_type']=='hit') & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
141
+ pbp['Penl'] = np.where((pbp['event_type']=='penalty') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
142
+ pbp['Penl2'] = np.where((pbp['event_type']=='penalty') & (pbp['penalty_duration']==2) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
143
+ pbp['Penl5'] = np.where((pbp['event_type']=='penalty') & (pbp['penalty_duration']==5) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
144
+ pbp['PIM'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), pbp['penalty_duration'], 0)
145
+ pbp['Draw'] = np.where((pbp['event_type']=='penalty') & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
146
+ pbp['Give'] = np.where((pbp['event_type']=='giveaway') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
147
+ pbp['Take'] = np.where((pbp['event_type']=='takeaway') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
148
+ pbp['Block'] = pbp['CA'] - pbp['FA']
149
+ pbp['RushF'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), 1, 0)
150
+ pbp['RushA'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), 1, 0)
151
+ pbp['RushFxG'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), pbp['xG'], 0)
152
+ pbp['RushAxG'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), pbp['xG'], 0)
153
+ pbp['RushFG'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), 1, 0)
154
+ pbp['RushAG'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), 1, 0)
155
+
156
+ stats = pbp.groupby([f'{team[0]}_team_abbr','season']).agg(
157
+ GP=('game_id','nunique'),
158
+ TOI=('event_length','sum'),
159
+ FF=('FF', 'sum'),
160
+ FA=('FA', 'sum'),
161
+ GF=('GF', 'sum'),
162
+ GA=('GA', 'sum'),
163
+ SF=('SF','sum'),
164
+ SA=('SA','sum'),
165
+ xGF=('xGF', 'sum'),
166
+ xGA=('xGA', 'sum'),
167
+ CF=('CF','sum'),
168
+ CA=('CA','sum'),
169
+ HF=('HF','sum'),
170
+ HA=('HA','sum'),
171
+ Penl=('Penl','sum'),
172
+ Penl2=('Penl2','sum'),
173
+ Penl5=('Penl5','sum'),
174
+ PIM=('PIM','sum'),
175
+ Draw=('Draw','sum'),
176
+ Give=('Give','sum'),
177
+ Take=('Take','sum'),
178
+ Block=('Block','sum'),
179
+ RushF=('RushF','sum'),
180
+ RushA=('RushA','sum'),
181
+ RushFxG=('RushFxG','sum'),
182
+ RushAxG=('RushAxG','sum'),
183
+ RushFG=('RushFG','sum'),
184
+ RushAG=('RushAG','sum'),
185
+ ).reset_index().rename(columns={f'{team[0]}_team_abbr':"Team",'season':"Season",'game_id':'Game'})
186
+ teams.append(stats)
187
+
188
+ onice_stats = pd.concat(teams).groupby(['Team','Season']).agg(
189
+ GP=('GP','sum'),
190
+ TOI=('TOI','sum'),
191
+ FF=('FF', 'sum'),
192
+ FA=('FA', 'sum'),
193
+ GF=('GF', 'sum'),
194
+ GA=('GA', 'sum'),
195
+ SF=('SF','sum'),
196
+ SA=('SA','sum'),
197
+ xGF=('xGF', 'sum'),
198
+ xGA=('xGA', 'sum'),
199
+ CF=('CF','sum'),
200
+ CA=('CA','sum'),
201
+ HF=('HF','sum'),
202
+ HA=('HA','sum'),
203
+ Penl=('Penl','sum'),
204
+ Penl2=('Penl2','sum'),
205
+ Penl5=('Penl5','sum'),
206
+ PIM=('PIM','sum'),
207
+ Draw=('Draw','sum'),
208
+ Give=('Give','sum'),
209
+ Take=('Take','sum'),
210
+ Block=('Block','sum'),
211
+ RushF=('RushF','sum'),
212
+ RushA=('RushA','sum'),
213
+ RushFxG=('RushFxG','sum'),
214
+ RushAxG=('RushAxG','sum'),
215
+ RushFG=('RushFG','sum'),
216
+ RushAG=('RushAG','sum'),
217
+ ).reset_index()
218
+
219
+ for col in onice_stats.columns.to_list()[2:30]:
220
+ onice_stats[col] = onice_stats[col].astype(float)
221
+
222
+ onice_stats['ShF%'] = onice_stats['GF']/onice_stats['SF']
223
+ onice_stats['xGF/FF'] = onice_stats['xGF']/onice_stats['FF']
224
+ onice_stats['GF/xGF'] = onice_stats['GF']/onice_stats['xGF']
225
+ onice_stats['FshF%'] = onice_stats['GF']/onice_stats['FF']
226
+ onice_stats['ShA%'] = onice_stats['GA']/onice_stats['SA']
227
+ onice_stats['xGA/FA'] = onice_stats['xGA']/onice_stats['FA']
228
+ onice_stats['GA/xGA'] = onice_stats['GA']/onice_stats['xGA']
229
+ onice_stats['FshA%'] = onice_stats['GA']/onice_stats['FA']
230
+ onice_stats['PM%'] = onice_stats['Take']/(onice_stats['Give']+onice_stats['Take'])
231
+ onice_stats['HF%'] = onice_stats['HF']/(onice_stats['HF']+onice_stats['HA'])
232
+ onice_stats['PENL%'] = onice_stats['Draw']/(onice_stats['Draw']+onice_stats['Penl'])
233
+ onice_stats['GSAx'] = onice_stats['xGA']/onice_stats['GA']
234
+
235
+ return onice_stats
236
+
237
+
238
+ def calculate_stats(pbp,team,game_strength):
239
+ per_sixty = ['Fi','xGi','Gi','A1','A2','P1','P','Si','OZF','NZF','DZF','FF','FA','xGF','xGA','GF','GA','SF','SA','CF','CA','HF','HA','Give','Take','Penl','Penl2','Penl5','Draw','Block']
240
+
241
+ complete = calc_team(pbp,game_strength)
242
+
243
+ #WSBA
244
+ complete['WSBA'] = complete['Team']+complete['Season'].astype(str)
245
+
246
+ #Set TOI to minute
247
+ complete['TOI'] = complete['TOI']/60
248
+
249
+ #Add per 60 stats
250
+ for stat in per_sixty[11:len(per_sixty)]:
251
+ complete[f'{stat}/60'] = (complete[stat]/complete['TOI'])*60
252
+
253
+ complete['GF%'] = complete['GF']/(complete['GF']+complete['GA'])
254
+ complete['SF%'] = complete['SF']/(complete['SF']+complete['SA'])
255
+ complete['xGF%'] = complete['xGF']/(complete['xGF']+complete['xGA'])
256
+ complete['FF%'] = complete['FF']/(complete['FF']+complete['FA'])
257
+ complete['CF%'] = complete['CF']/(complete['CF']+complete['CA'])
258
+
259
+ head = ['Team','Game'] if 'Game' in complete.columns else ['Team']
260
+ complete = complete[head+[
261
+ 'Season','WSBA',
262
+ 'GP','TOI',
263
+ "GF","SF","FF","xGF","xGF/FF","GF/xGF","ShF%","FshF%",
264
+ "GA","SA","FA","xGA","xGA/FA","GA/xGA","ShA%","FshA%",
265
+ 'CF','CA',
266
+ 'GF%','SF%','FF%','xGF%','CF%',
267
+ 'HF','HA','HF%',
268
+ 'Penl','Penl2','Penl5','PIM','Draw','PENL%',
269
+ 'Give','Take','PM%',
270
+ 'Block',
271
+ 'RushF','RushA','RushFxG','RushAxG','RushFG','RushAG',
272
+ 'GSAx'
273
+ ]+[f'{stat}/60' for stat in per_sixty[11:len(per_sixty)]]]
274
+
275
+ return complete.loc[complete['Team']==team]