wsba-hockey 1.0.1__py3-none-any.whl → 1.0.3__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/tools/plotting.py +15 -9
- wsba_hockey/tools/xg_model.py +42 -15
- wsba_hockey/workspace.py +94 -31
- wsba_hockey/wsba_main.py +32 -7
- {wsba_hockey-1.0.1.dist-info → wsba_hockey-1.0.3.dist-info}/METADATA +2 -2
- {wsba_hockey-1.0.1.dist-info → wsba_hockey-1.0.3.dist-info}/RECORD +9 -9
- {wsba_hockey-1.0.1.dist-info → wsba_hockey-1.0.3.dist-info}/WHEEL +0 -0
- {wsba_hockey-1.0.1.dist-info → wsba_hockey-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {wsba_hockey-1.0.1.dist-info → wsba_hockey-1.0.3.dist-info}/top_level.txt +0 -0
wsba_hockey/tools/plotting.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
import matplotlib.pyplot as plt
|
2
|
-
import matplotlib.image as
|
2
|
+
import matplotlib.image as mpimg
|
3
3
|
import numpy as np
|
4
4
|
import pandas as pd
|
5
5
|
from scipy.interpolate import griddata
|
6
6
|
from scipy.ndimage import gaussian_filter
|
7
|
+
import urllib.request
|
8
|
+
import PIL
|
7
9
|
from .xg_model import *
|
8
10
|
from hockey_rink import NHLRink
|
9
11
|
from hockey_rink import CircularImage
|
@@ -12,9 +14,9 @@ event_markers = {
|
|
12
14
|
'faceoff':'X',
|
13
15
|
'hit':'P',
|
14
16
|
'blocked-shot':'v',
|
15
|
-
'missed-shot':'
|
17
|
+
'missed-shot':'o',
|
16
18
|
'shot-on-goal':'D',
|
17
|
-
'goal':'
|
19
|
+
'goal':'*',
|
18
20
|
'giveaway':'1',
|
19
21
|
'takeaway':'2',
|
20
22
|
}
|
@@ -109,7 +111,7 @@ def plot_skater_shots(pbp, player, season, team, strengths, title = None, marker
|
|
109
111
|
|
110
112
|
return fig
|
111
113
|
|
112
|
-
def plot_game_events(pbp,game_id,events,strengths,marker_dict=event_markers,legend=False,xg='moneypuck'):
|
114
|
+
def plot_game_events(pbp,game_id,events,strengths,marker_dict=event_markers,team_colors={'away':'secondary','home':'primary'},legend=False,xg='moneypuck'):
|
113
115
|
pbp = prep_plot_data(pbp,events,strengths,marker_dict,xg)
|
114
116
|
pbp = pbp.loc[pbp['game_id'].astype(str)==game_id]
|
115
117
|
|
@@ -119,17 +121,21 @@ def plot_game_events(pbp,game_id,events,strengths,marker_dict=event_markers,lege
|
|
119
121
|
season = f'{game_id[0:4]}{int(game_id[0:4])+1}'
|
120
122
|
|
121
123
|
team_data = pd.read_csv('teaminfo/nhl_teaminfo.csv')
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
124
|
+
team_info ={
|
125
|
+
'away_color':'#000000' if list(team_data.loc[team_data['WSBA']==f'{away_abbr}{season}','Secondary Color'])[0]=='#FFFFFF' else list(team_data.loc[team_data['WSBA']==f'{away_abbr}{season}',f'{team_colors['away'].capitalize()} Color'])[0],
|
126
|
+
'home_color': list(team_data.loc[team_data['WSBA']==f'{home_abbr}{season}',f'{team_colors['home'].capitalize()} Color'])[0],
|
127
|
+
'away_logo': f'tools/logos/png/{away_abbr}{season}.png',
|
128
|
+
'home_logo': f'tools/logos/png/{home_abbr}{season}.png',
|
129
|
+
}
|
130
|
+
|
131
|
+
pbp['color'] = np.where(pbp['event_team_abbr']==away_abbr,team_info['away_color'],team_info['home_color'])
|
126
132
|
|
127
133
|
fig, ax = plt.subplots()
|
128
134
|
wsba_rink(display_range='full')
|
129
135
|
|
130
136
|
for event in events:
|
131
137
|
plays = pbp.loc[pbp['event_type']==event]
|
132
|
-
ax.scatter(plays['x_adj'],plays['y_adj'],plays['size'],plays['color'],marker=event_markers[event],label=event,zorder=5)
|
138
|
+
ax.scatter(plays['x_adj'],plays['y_adj'],plays['size'],plays['color'],marker=event_markers[event],edgecolors='white',label=event,zorder=5)
|
133
139
|
|
134
140
|
ax.set_title(f'{away_abbr} @ {home_abbr} - {date}')
|
135
141
|
ax.legend(bbox_to_anchor =(0.5,-0.35), loc='lower center',ncol=1).set_visible(legend)
|
wsba_hockey/tools/xg_model.py
CHANGED
@@ -38,6 +38,7 @@ def prep_xG_data(pbp):
|
|
38
38
|
data.sort_values(['season','game_id','period','seconds_elapsed','event_num'],inplace=True)
|
39
39
|
data['score_state'] = np.where(data['away_team_abbr']==data['event_team_abbr'],data['away_score']-data['home_score'],data['home_score']-data['away_score'])
|
40
40
|
data['strength_diff'] = np.where(data['away_team_abbr']==data['event_team_abbr'],data['away_skaters']-data['home_skaters'],data['home_skaters']-data['away_skaters'])
|
41
|
+
data['strength_state_venue'] = data['away_skaters'].astype(str)+'v'+data['home_skaters'].astype(str)
|
41
42
|
data['fenwick_state'] = np.where(data['away_team_abbr']==data['event_team_abbr'],data['away_fenwick']-data['home_fenwick'],data['home_fenwick']-data['away_fenwick'])
|
42
43
|
data['distance_from_last'] = np.sqrt((data['x_fixed'] - data['x_fixed_last'])**2 + (data['y_fixed'] - data['y_fixed_last'])**2)
|
43
44
|
|
@@ -107,7 +108,7 @@ def wsba_xG(pbp, train = False, overwrite = False, model_path = "tools/xg_model/
|
|
107
108
|
'prior_faceoff']
|
108
109
|
|
109
110
|
#Prep Data
|
110
|
-
|
111
|
+
pbp_prep = prep_xG_data(pbp)
|
111
112
|
#Filter unwanted date:
|
112
113
|
#Shots must occur in specified events and strength states, occur before the shootout, and have valid coordinates
|
113
114
|
events = ['faceoff','hit','giveaway','takeaway','blocked-shot','missed-shot','shot-on-goal','goal']
|
@@ -126,12 +127,12 @@ def wsba_xG(pbp, train = False, overwrite = False, model_path = "tools/xg_model/
|
|
126
127
|
'6v4',
|
127
128
|
'6v5']
|
128
129
|
|
129
|
-
data =
|
130
|
-
(
|
131
|
-
(
|
132
|
-
(
|
133
|
-
(
|
134
|
-
~((
|
130
|
+
data = pbp_prep.loc[(pbp_prep['event_type'].isin(events))&
|
131
|
+
(pbp_prep['strength_state'].isin(strengths))&
|
132
|
+
(pbp_prep['period'] < 5)&
|
133
|
+
(pbp_prep['x_fixed'].notna())&
|
134
|
+
(pbp_prep['y_fixed'].notna())&
|
135
|
+
~((pbp_prep['x_fixed']==0)&(pbp_prep['y_fixed']==0)&(pbp_prep['x_fixed'].isin(fenwick_events))&(pbp_prep['event_distance']!=90))]
|
135
136
|
|
136
137
|
#Convert to sparse
|
137
138
|
data_sparse = sp.csr_matrix(data[[target]+continous+boolean])
|
@@ -198,11 +199,11 @@ def wsba_xG(pbp, train = False, overwrite = False, model_path = "tools/xg_model/
|
|
198
199
|
best_all = best_all.sort_values(by="auc", ascending=False)
|
199
200
|
|
200
201
|
if overwrite == True:
|
201
|
-
best_all.to_csv("xg_model/testing/xg_model_training_runs.csv",index=False)
|
202
|
+
best_all.to_csv("tools/xg_model/testing/xg_model_training_runs.csv",index=False)
|
202
203
|
else:
|
203
|
-
best_old = pd.read_csv("xg_model/testing/xg_model_training_runs.csv")
|
204
|
+
best_old = pd.read_csv("tools/xg_model/testing/xg_model_training_runs.csv")
|
204
205
|
best_comb = pd.concat([best_old,best_all])
|
205
|
-
best_comb.to_csv("xg_model/testing/xg_model_training_runs.csv",index=False)
|
206
|
+
best_comb.to_csv("tools/xg_model/testing/xg_model_training_runs.csv",index=False)
|
206
207
|
|
207
208
|
# Final parameters
|
208
209
|
param_7_EV = {
|
@@ -249,11 +250,11 @@ def wsba_xG(pbp, train = False, overwrite = False, model_path = "tools/xg_model/
|
|
249
250
|
# Clean results and sort to find the number of rounds to use and seed
|
250
251
|
cv_final = cv_test.sort_values(by="AUC", ascending=False)
|
251
252
|
if overwrite == True:
|
252
|
-
cv_final.to_csv("xg_model/testing/xg_model_cv_runs.csv",index=False)
|
253
|
+
cv_final.to_csv("tools/xg_model/testing/xg_model_cv_runs.csv",index=False)
|
253
254
|
else:
|
254
|
-
cv_old = pd.read_csv("xg_model/testing/xg_model_cv_runs.csv")
|
255
|
+
cv_old = pd.read_csv("tools/xg_model/testing/xg_model_cv_runs.csv")
|
255
256
|
cv_comb = pd.concat([cv_old,cv_final])
|
256
|
-
cv_comb.to_csv("xg_model/testing/xg_model_cv_runs.csv")
|
257
|
+
cv_comb.to_csv("tools/xg_model/testing/xg_model_cv_runs.csv")
|
257
258
|
cv_final.loc[len(cv_final)] = cv_test.mean()
|
258
259
|
|
259
260
|
# Train the final model
|
@@ -275,8 +276,34 @@ def wsba_xG(pbp, train = False, overwrite = False, model_path = "tools/xg_model/
|
|
275
276
|
|
276
277
|
else:
|
277
278
|
model = joblib.load(model_path)
|
278
|
-
|
279
|
-
|
279
|
+
|
280
|
+
#Predict goal
|
281
|
+
data['xG'] = model.predict(xgb_matrix)
|
282
|
+
data['xG'] = np.where(data['event_type'].isin(fenwick_events),data['xG'],np.nan)
|
283
|
+
|
284
|
+
#Avoid merging errors
|
285
|
+
merge_col = ['game_id','period','seconds_elapsed','event_type','event_team_abbr','event_player_1_id']
|
286
|
+
|
287
|
+
for df in [pbp,data]:
|
288
|
+
df = df.astype({
|
289
|
+
'game_id':'int',
|
290
|
+
'period':'int',
|
291
|
+
'seconds_elapsed':'int',
|
292
|
+
'event_type':'str',
|
293
|
+
'event_team_abbr':'str',
|
294
|
+
'event_player_1_id':'float'
|
295
|
+
})
|
296
|
+
|
297
|
+
#Drop previous xG if it exists
|
298
|
+
try: pbp = pbp.drop(columns=['xG'])
|
299
|
+
except KeyError:
|
300
|
+
''
|
301
|
+
|
302
|
+
#Merge
|
303
|
+
data = data[merge_col+['xG']]
|
304
|
+
pbp_xg = pd.merge(pbp,data,how='left')
|
305
|
+
|
306
|
+
return pbp_xg
|
280
307
|
|
281
308
|
def moneypuck_xG(pbp,repo_path = "tools/xg_model/moneypuck/shots_2007-2023.zip"):
|
282
309
|
#Given play-by-play, return itself with xG column sourced from MoneyPuck.com
|
wsba_hockey/workspace.py
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
import numpy as np
|
2
2
|
import pandas as pd
|
3
3
|
import matplotlib
|
4
|
+
matplotlib.use('agg')
|
4
5
|
import matplotlib.pyplot as plt
|
5
6
|
import wsba_hockey as wsba
|
7
|
+
from gspread_pandas import Spread, Client
|
8
|
+
import urllib.request
|
9
|
+
from wand.color import Color
|
10
|
+
from wand.image import Image
|
11
|
+
from tools.xg_model import wsba_xG
|
6
12
|
|
7
13
|
season_load = wsba.repo_load_seasons()
|
8
14
|
|
9
|
-
def workspace(seasons,type):
|
15
|
+
def workspace(seasons,type,arg = '',start='',end=''):
|
10
16
|
if type == 'pbp':
|
11
17
|
#Scrape pbp
|
12
18
|
errors=[]
|
@@ -16,52 +22,109 @@ def workspace(seasons,type):
|
|
16
22
|
data['pbp'].to_csv(f'pbp/nhl_pbp_{season}.csv',index=False)
|
17
23
|
print(f'Errors: {errors}')
|
18
24
|
|
25
|
+
elif type == 'pbp_xg':
|
26
|
+
#Add xG to pbp
|
27
|
+
for season in seasons:
|
28
|
+
print(f'WSBA xG for {season}')
|
29
|
+
data = pd.read_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet')
|
30
|
+
wsba_xG(data).to_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet',index=False)
|
31
|
+
|
19
32
|
elif type == 'convert':
|
20
|
-
for season in seasons
|
33
|
+
for season in seasons:
|
21
34
|
data = pd.read_csv(f"pbp/nhl_pbp_{season}.csv")
|
22
35
|
data = wsba.wsba_main.moneypuck_xG(data)
|
23
36
|
data.to_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet',index=False)
|
24
37
|
|
25
|
-
elif type == '
|
26
|
-
#Scrape
|
27
|
-
stand = [wsba.nhl_scrape_standings(season) for season in
|
28
|
-
pd.concat(stand)
|
38
|
+
elif type == 'team_info':
|
39
|
+
#Scrape team info
|
40
|
+
stand = [wsba.nhl_scrape_standings(season) for season in seasons]
|
41
|
+
standings = pd.concat(stand)
|
42
|
+
|
43
|
+
colors = pd.read_csv('teaminfo/nhl_colors.csv')
|
44
|
+
data = pd.merge(colors,standings,how='right',left_on='triCode',right_on='teamAbbrev.default').sort_values(by=['seasonId','triCode'])
|
45
|
+
data['WSBA'] = data['teamAbbrev.default']+data['seasonId'].astype(str)
|
46
|
+
|
47
|
+
data.to_csv('teaminfo/nhl_teaminfo.csv',index=False)
|
29
48
|
|
30
49
|
elif type == 'stats':
|
31
50
|
#Stats building
|
32
|
-
|
33
|
-
|
34
|
-
for
|
51
|
+
for group in ['skater','team']:
|
52
|
+
stats = []
|
53
|
+
for season in seasons:
|
35
54
|
pbp = pd.read_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet')
|
36
55
|
stat = wsba.nhl_calculate_stats(pbp,group,[2],['5v5'],shot_impact=True)
|
37
56
|
stat.to_csv(f'stats/{group}/wsba_nhl_{season}_{group}.csv',index=False)
|
38
|
-
stats.append(stat)
|
39
|
-
|
40
|
-
|
41
|
-
|
57
|
+
stats.append(stat)
|
58
|
+
|
59
|
+
if arg:
|
60
|
+
pd.concat(stats).to_csv(f'stats/db/wsba_nhl_{group}_db.csv',index=False)
|
42
61
|
|
43
|
-
|
62
|
+
elif type == 'xg_model':
|
63
|
+
data = pd.concat([pd.read_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet') for season in seasons])
|
64
|
+
wsba.wsba_main.wsba_xG(data,True,True)
|
65
|
+
|
66
|
+
elif type == 'plot_game':
|
67
|
+
for season in seasons:
|
68
|
+
pbp = wsba.nhl_scrape_season(season,remove=[],start=start,end=end)
|
44
69
|
|
45
|
-
|
70
|
+
plots = wsba.nhl_plot_games(pbp,wsba.wsba_main.fenwick_events,['5v5'],'all',team_colors=arg,legend=False,xg='wsba')
|
46
71
|
|
47
|
-
|
48
|
-
|
49
|
-
|
72
|
+
games = list(pbp['game_id'].astype(str).drop_duplicates())
|
73
|
+
i = 1
|
74
|
+
for plot, game_id in zip(plots,games):
|
75
|
+
plot.savefig(f'plots/games/{game_id[0:4]}{int(game_id[0:4])+1}/{game_id[5:6]}/{game_id}_shotplot.png',bbox_inches='tight',transparent=True)
|
76
|
+
i += 1
|
77
|
+
|
78
|
+
elif type == 'plot_skater':
|
79
|
+
for season in seasons:
|
80
|
+
pbp = pd.read_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet')
|
81
|
+
|
82
|
+
skaters={}
|
83
|
+
|
84
|
+
for shooter,season,team in zip(pbp['event_player_1_name'],pbp['season'].astype(str),pbp['event_team_abbr']):
|
85
|
+
if shooter is None:
|
86
|
+
continue
|
87
|
+
else:
|
88
|
+
skaters.update({
|
89
|
+
shooter:[season,team]
|
90
|
+
})
|
91
|
+
|
92
|
+
plots = wsba.nhl_plot_skaters_shots(pbp,skaters,['5v5'],onice='indv',title=False,legend=True)
|
93
|
+
|
94
|
+
items = list(skaters.items())
|
95
|
+
for plot,skater in zip(plots,items):
|
96
|
+
plot.savefig(f'plots/{skater[1][0]}/{skater[0]}{skater[1][0]}{skater[1][1]}_indv.png',bbox_inches='tight',transparent=True)
|
97
|
+
|
98
|
+
elif type == 'logos':
|
99
|
+
data = pd.read_csv('teaminfo/nhl_teaminfo.csv')
|
100
|
+
for url, id in zip(data['teamLogo'],data['WSBA']):
|
101
|
+
print(url)
|
102
|
+
urllib.request.urlretrieve(url,f'tools/logos/svg/{id}.svg')
|
103
|
+
with Image(filename=f'tools/logos/svg/{id}.svg') as img:
|
104
|
+
img.format = 'png32'
|
105
|
+
img.background_color = Color('transparent')
|
106
|
+
img.alpha_channel = 'activate'
|
107
|
+
img.save(filename=f'tools/logos/png/{id}.png')
|
108
|
+
|
50
109
|
else:
|
51
|
-
|
52
|
-
|
53
|
-
|
110
|
+
print('Nothing here.')
|
111
|
+
|
112
|
+
def push_to_sheet():
|
113
|
+
spread = Spread('WSBA - NHL 5v5 Shooting Metrics Public v2.0')
|
54
114
|
|
55
|
-
|
115
|
+
#Tables
|
116
|
+
skater = pd.read_csv('stats/db/wsba_nhl_skater_db.csv')
|
117
|
+
team = pd.read_csv('stats/db/wsba_nhl_team_db.csv')
|
118
|
+
team_info = pd.read_csv('teaminfo/nhl_teaminfo.csv')
|
119
|
+
country = pd.read_csv('teaminfo/nhl_countryinfo.csv')
|
120
|
+
schedule = pd.read_csv('schedule/schedule.csv')
|
56
121
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
122
|
+
spread.df_to_sheet(skater,index=False,sheet='Skaters DB')
|
123
|
+
spread.df_to_sheet(team,index=False,sheet='Teams DB')
|
124
|
+
spread.df_to_sheet(team_info,index=False,sheet='Team Info')
|
125
|
+
spread.df_to_sheet(country,index=False,sheet='Country Info')
|
126
|
+
spread.df_to_sheet(schedule,index=False,sheet='Schedule')
|
61
127
|
|
62
|
-
|
128
|
+
print('Done.')
|
63
129
|
|
64
|
-
|
65
|
-
for plot in plots:
|
66
|
-
plot.savefig(f'plots/20242025_03_{i}_shotplot.png',bbox_inches='tight',transparent=True)
|
67
|
-
i += 1
|
130
|
+
workspace(season_load[6:18],,Tru)
|
wsba_hockey/wsba_main.py
CHANGED
@@ -541,15 +541,15 @@ def nhl_shooting_impacts(agg,team=False):
|
|
541
541
|
pos['RushF/60'] = (pos['RushF']/pos['TOI'])*60
|
542
542
|
pos['RushA/60'] = (pos['RushA']/pos['TOI'])*60
|
543
543
|
pos['Rushes FF'] = pos['RushF/60'].rank(pct=True)
|
544
|
-
pos['Rushes FA'] = pos['RushA/60'].rank(pct=True)
|
544
|
+
pos['Rushes FA'] = 1 - pos['RushA/60'].rank(pct=True)
|
545
545
|
pos['RushFxG/60'] = (pos['RushFxG']/pos['TOI'])*60
|
546
546
|
pos['RushAxG/60'] = (pos['RushAxG']/pos['TOI'])*60
|
547
547
|
pos['Rushes xGF'] = pos['RushFxG/60'].rank(pct=True)
|
548
|
-
pos['Rushes xGA'] = pos['RushAxG/60'].rank(pct=True)
|
548
|
+
pos['Rushes xGA'] = 1 - pos['RushAxG/60'].rank(pct=True)
|
549
549
|
pos['RushFG/60'] = (pos['RushFG']/pos['TOI'])*60
|
550
550
|
pos['RushAG/60'] = (pos['RushAG']/pos['TOI'])*60
|
551
551
|
pos['Rushes GF'] = pos['RushFG/60'].rank(pct=True)
|
552
|
-
pos['Rushes GA'] = pos['RushAG/60'].rank(pct=True)
|
552
|
+
pos['Rushes GA'] = 1 - pos['RushAG/60'].rank(pct=True)
|
553
553
|
|
554
554
|
#Flip against metric percentiles
|
555
555
|
pos['ODEF-SR'] = 1-pos['ODEF-SR']
|
@@ -642,7 +642,7 @@ def nhl_calculate_stats(pbp,type,season_types,game_strength,roster_path="rosters
|
|
642
642
|
# param 'xg' - xG model to apply to pbp for aggregation
|
643
643
|
# param 'shot_impact' - boolean determining if the shot impact model will be applied to the dataset
|
644
644
|
|
645
|
-
print(f"Calculating statistics for all games in the provided play-by-play data...\nSeasons included: {pbp['season'].drop_duplicates().to_list()}...")
|
645
|
+
print(f"Calculating statistics for all games in the provided play-by-play data for {type}s...\nSeasons included: {pbp['season'].drop_duplicates().to_list()}...")
|
646
646
|
start = time.perf_counter()
|
647
647
|
|
648
648
|
#Add extra data and apply team changes
|
@@ -660,6 +660,12 @@ def nhl_calculate_stats(pbp,type,season_types,game_strength,roster_path="rosters
|
|
660
660
|
#Filter by season types and remove shootouts
|
661
661
|
pbp = pbp.loc[(pbp['season_type'].isin(season_types)) & (pbp['period'] < 5)]
|
662
662
|
|
663
|
+
#Convert all columns with player ids to float in order to avoid merging errors
|
664
|
+
for col in get_col():
|
665
|
+
if "_id" in col:
|
666
|
+
try: pbp[col] = pbp[col].astype(float)
|
667
|
+
except KeyError: continue
|
668
|
+
|
663
669
|
# Filter by game strength if not "all"
|
664
670
|
if game_strength != "all":
|
665
671
|
pbp = pbp.loc[pbp['strength_state'].isin(game_strength)]
|
@@ -668,6 +674,9 @@ def nhl_calculate_stats(pbp,type,season_types,game_strength,roster_path="rosters
|
|
668
674
|
if type == 'team':
|
669
675
|
complete = calc_team(pbp)
|
670
676
|
|
677
|
+
#WSBA
|
678
|
+
complete['WSBA'] = complete['Team']+complete['Season'].astype(str)
|
679
|
+
|
671
680
|
#Set TOI to minute
|
672
681
|
complete['TOI'] = complete['TOI']/60
|
673
682
|
|
@@ -675,6 +684,14 @@ def nhl_calculate_stats(pbp,type,season_types,game_strength,roster_path="rosters
|
|
675
684
|
for stat in per_sixty[7:13]:
|
676
685
|
complete[f'{stat}/60'] = (complete[stat]/complete['TOI'])*60
|
677
686
|
|
687
|
+
#Rank per 60 stats
|
688
|
+
for stat in per_sixty[7:13]:
|
689
|
+
complete[f'{stat}/60 Percentile'] = complete[f'{stat}/60'].rank(pct=True)
|
690
|
+
|
691
|
+
#Flip percentiles for against stats
|
692
|
+
for stat in ['FA','xGA','GA']:
|
693
|
+
complete[f'{stat}/60 Percentile'] = 1-complete[f'{stat}/60 Percentile']
|
694
|
+
|
678
695
|
end = time.perf_counter()
|
679
696
|
length = end-start
|
680
697
|
print(f'...finished in {(length if length <60 else length/60):.2f} {'seconds' if length <60 else 'minutes'}.')
|
@@ -729,6 +746,14 @@ def nhl_calculate_stats(pbp,type,season_types,game_strength,roster_path="rosters
|
|
729
746
|
for stat in per_sixty:
|
730
747
|
complete[f'{stat}/60'] = (complete[stat]/complete['TOI'])*60
|
731
748
|
|
749
|
+
#Rank per 60 stats
|
750
|
+
for stat in per_sixty:
|
751
|
+
complete[f'{stat}/60 Percentile'] = complete[f'{stat}/60'].rank(pct=True)
|
752
|
+
|
753
|
+
#Flip percentiles for against stats
|
754
|
+
for stat in ['FA','xGA','GA']:
|
755
|
+
complete[f'{stat}/60 Percentile'] = 1-complete[f'{stat}/60 Percentile']
|
756
|
+
|
732
757
|
#Add player age
|
733
758
|
complete['Birthday'] = pd.to_datetime(complete['Birthday'])
|
734
759
|
complete['season_year'] = complete['Season'].astype(str).str[4:8].astype(int)
|
@@ -762,7 +787,7 @@ def nhl_calculate_stats(pbp,type,season_types,game_strength,roster_path="rosters
|
|
762
787
|
"GF","FF","xGF","xGF/FF","GF/xGF","FshF%",
|
763
788
|
"GA","FA","xGA","xGA/FA","GA/xGA","FshA%",
|
764
789
|
'Rush',"Rush xG",'Rush G',"GC%","AC%","GI%","FC%","xGC%",
|
765
|
-
]+[f'{stat}/60' for stat in per_sixty]+type_metrics].fillna(0).sort_values(['Player','Season','Team','ID'])
|
790
|
+
]+[f'{stat}/60' for stat in per_sixty]+[f'{stat}/60 Percentile' for stat in per_sixty]+type_metrics].fillna(0).sort_values(['Player','Season','Team','ID'])
|
766
791
|
|
767
792
|
print(f'...finished in {(length if length <60 else length/60):.2f} {'seconds' if length <60 else 'minutes'}.')
|
768
793
|
#Apply shot impacts if necessary (Note: this will remove skaters with fewer than 150 minutes of TOI due to the shot impact TOI rule)
|
@@ -794,7 +819,7 @@ def nhl_plot_skaters_shots(pbp,skater_dict,strengths,marker_dict=event_markers,o
|
|
794
819
|
#Return: list of plotted skater shot charts
|
795
820
|
return skater_plots
|
796
821
|
|
797
|
-
def nhl_plot_games(pbp,events,strengths,game_ids='all',marker_dict=event_markers,legend=False,xg='moneypuck'):
|
822
|
+
def nhl_plot_games(pbp,events,strengths,game_ids='all',marker_dict=event_markers,team_colors={'away':'primary','home':'primary'},legend=False,xg='moneypuck'):
|
798
823
|
#Returns list of plots for specified games
|
799
824
|
# param 'pbp' - pbp to plot data
|
800
825
|
# param 'events' - type of events to plot
|
@@ -811,7 +836,7 @@ def nhl_plot_games(pbp,events,strengths,game_ids='all',marker_dict=event_markers
|
|
811
836
|
print(f'Plotting the following games: {game_ids}...')
|
812
837
|
|
813
838
|
#Iterate through games, adding plot to list
|
814
|
-
game_plots = [plot_game_events(pbp,game,events,strengths,marker_dict,legend,xg) for game in game_ids]
|
839
|
+
game_plots = [plot_game_events(pbp,game,events,strengths,marker_dict,team_colors,legend,xg) for game in game_ids]
|
815
840
|
|
816
841
|
#Return: list of plotted game events
|
817
842
|
return game_plots
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: wsba_hockey
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.3
|
4
4
|
Summary: WeakSide Breakout's complete Python package of access to hockey data, primairly including the scraping of National Hockey League schedule, play-by-play, and shifts information.
|
5
5
|
Author-email: Owen Singh <owenbksingh@gmail.com>
|
6
6
|
Project-URL: Homepage, https://github.com/owensingh38/wsba_hockey/
|
@@ -69,7 +69,7 @@ wsba.nhl_scrape_prospects('BOS')
|
|
69
69
|
### Stat Aggregation
|
70
70
|
```python
|
71
71
|
pbp = wsba.nhl_scrape_season('20232024',remove=[], local = True)
|
72
|
-
wsba.nhl_calculate_stats(pbp,'skater',[2],['5v5','4v4','3v3'],xg='
|
72
|
+
wsba.nhl_calculate_stats(pbp,'skater',[2],['5v5','4v4','3v3'],xg='wsba',shot_impact = True)
|
73
73
|
```
|
74
74
|
### Shot Plotting (Plots, Heatmaps, etc.)
|
75
75
|
```python
|
@@ -1,19 +1,19 @@
|
|
1
1
|
wsba_hockey/__init__.py,sha256=QXyc8FFlCDWQuECyyEbj80ASHEbTFj4R13DOFOY9nJg,353
|
2
|
-
wsba_hockey/workspace.py,sha256=
|
3
|
-
wsba_hockey/wsba_main.py,sha256=
|
2
|
+
wsba_hockey/workspace.py,sha256=YQMHCAiCCsJiaSs_MZI_2tKuQp6dmQImIZw-RvjBEhA,5395
|
3
|
+
wsba_hockey/wsba_main.py,sha256=xyZLnOZxzPAi1-n9mBsQ8ThyGJkVotRg5I39SiM_CYs,37886
|
4
4
|
wsba_hockey/stats/calculate_viz/shot_impact.py,sha256=7zxf64yt87YDucUBG31W75u951AUbMC7a3x5ClNIxYI,39
|
5
5
|
wsba_hockey/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
wsba_hockey/tools/agg.py,sha256=WRfuxJt0OgDSEkqHtuGql9nZrQnLGzkm6DUFVbDXayE,9560
|
7
|
-
wsba_hockey/tools/plotting.py,sha256=
|
7
|
+
wsba_hockey/tools/plotting.py,sha256=olPGYJtVFIjBwrOQyz1CNNqaCM6iOJTKjWXBA3egJHM,6022
|
8
8
|
wsba_hockey/tools/scraping.py,sha256=hZv1XMtQjsVSSVwN6Fzw7FuT94zSQGyX1WupTjUjUuU,48548
|
9
|
-
wsba_hockey/tools/xg_model.py,sha256=
|
9
|
+
wsba_hockey/tools/xg_model.py,sha256=ItVWGvYzi3zHA8mPCkUbpTU7NUcQFSw3Xm_nV0E6ypQ,16004
|
10
10
|
wsba_hockey/tools/archive/old_scraping.py,sha256=hEjMI1RtfeZnf0RBiJFI38oXkLZ3WofeH5xqcF4pzgM,49585
|
11
11
|
wsba_hockey/tools/utils/__init__.py,sha256=vccXhOtzARoR99fmEWU1OEI3qCIdQ9Z42AlRA_BUhrs,114
|
12
12
|
wsba_hockey/tools/utils/config.py,sha256=D3Uk05-YTyrhfReMTTLfNI3HN_rON2uo_CDE9oER3Lg,351
|
13
13
|
wsba_hockey/tools/utils/save_pages.py,sha256=CsyL_0n-b-4pJoUauwU3HpnCO6n69-RlBMJQBd_qGDc,4979
|
14
14
|
wsba_hockey/tools/utils/shared.py,sha256=dH_JwZfia5fib8rksy5sW-mBp0pluBPvw37Vdr8Kap0,14211
|
15
|
-
wsba_hockey-1.0.
|
16
|
-
wsba_hockey-1.0.
|
17
|
-
wsba_hockey-1.0.
|
18
|
-
wsba_hockey-1.0.
|
19
|
-
wsba_hockey-1.0.
|
15
|
+
wsba_hockey-1.0.3.dist-info/licenses/LICENSE,sha256=Nr_Um1Pd5FQJTWWgm7maZArdtYMbDhzXYSwyJIZDGik,1114
|
16
|
+
wsba_hockey-1.0.3.dist-info/METADATA,sha256=Cds728R4Mz7RqNuOA0mnMciv4wPUjkNShlPScxaKclw,3542
|
17
|
+
wsba_hockey-1.0.3.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
18
|
+
wsba_hockey-1.0.3.dist-info/top_level.txt,sha256=acU7s3x-RZC1zGiqCOmO0g267iqCg34lzIfdmYxxGmQ,12
|
19
|
+
wsba_hockey-1.0.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|