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.
@@ -1,9 +1,11 @@
1
1
  import matplotlib.pyplot as plt
2
- import matplotlib.image as img
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':'H',
17
+ 'missed-shot':'o',
16
18
  'shot-on-goal':'D',
17
- 'goal':'o',
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
- away_color = list(team_data.loc[team_data['WSBA']==f'{away_abbr}{season}','Primary Color'])[0]
123
- home_color = list(team_data.loc[team_data['WSBA']==f'{home_abbr}{season}','Primary Color'])[0]
124
-
125
- pbp['color'] = np.where(pbp['event_team_abbr']==away_abbr,away_color,home_color)
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)
@@ -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
- pbp = prep_xG_data(pbp)
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 = pbp.loc[(pbp['event_type'].isin(events))&
130
- (pbp['strength_state'].isin(strengths))&
131
- (pbp['period'] < 5)&
132
- (pbp['x_fixed'].notna())&
133
- (pbp['y_fixed'].notna())&
134
- ~((pbp['x_fixed']==0)&(pbp['y_fixed']==0)&(pbp['x_fixed'].isin(fenwick_events))&(pbp['event_distance']!=90))]
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
- pbp['xG'] = np.where(pbp['event_type'].isin(fenwick_events),model.predict(xgb_matrix),"")
279
- return pbp
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[6:12]:
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 == 'standings':
26
- #Scrape standings
27
- stand = [wsba.nhl_scrape_standings(season) for season in season]
28
- pd.concat(stand).to_csv('teaminfo/nhl_standings.csv',index=False)
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
- stats = []
33
- for season in seasons[6:18]:
34
- for group in ['skater','team']:
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
- pd.concat(stats).to_csv(f'stats/db/wsba_nhl_{group}_db.csv',index=False)
40
- else:
41
- print('Nothing here.')
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
- pbp = pd.read_parquet('pbp/parquet/nhl_pbp_20242025.parquet')
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
- skaters={}
70
+ plots = wsba.nhl_plot_games(pbp,wsba.wsba_main.fenwick_events,['5v5'],'all',team_colors=arg,legend=False,xg='wsba')
46
71
 
47
- for shooter,season,team in zip(pbp['event_player_1_name'],pbp['season'].astype(str),pbp['event_team_abbr']):
48
- if shooter is None:
49
- continue
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
- skaters.update({
52
- shooter:[season,team]
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
- plots = wsba.nhl_plot_skaters_shots(pbp,skaters,['5v5'],onice='indv',legend=True)
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
- items = list(skaters.items())
58
- for plot,skater in zip(plots,items):
59
- plt.title('')
60
- plot.savefig(f'plots/{skater[0]}{skater[1][0]}{skater[1][1]}_indv.png',bbox_inches='tight',transparent=True)
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
- plots = wsba.nhl_plot_games(wsba.nhl_scrape_season('20242025',start='04-19',end='04-19'),wsba.wsba_main.fenwick_events,['5v5'],'all',legend=True)
128
+ print('Done.')
63
129
 
64
- i = 1
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.1
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='moneypuck',shot_impact = True)
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=XS4ppyjUWY_NmMA47ge_idvgcFp8E7vkXrTxB5T5wNI,2478
3
- wsba_hockey/wsba_main.py,sha256=YGisPPO2GJo1e-53ppb_nYB-ujbSkn7WljY-maKQBEI,36783
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=RZ5lx2H8MlUu72fP2sOM36ceorpDqYpRA7Afv-m1Frw,5557
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=qjScRMzLOkVsv6zVzJJxHBlbBPF6xg6_koncMCyYGYE,15052
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.1.dist-info/licenses/LICENSE,sha256=Nr_Um1Pd5FQJTWWgm7maZArdtYMbDhzXYSwyJIZDGik,1114
16
- wsba_hockey-1.0.1.dist-info/METADATA,sha256=IVhe2jlgHHLLeVXL4tITxUsBrCT3eiT-4mHoeZryxJk,3547
17
- wsba_hockey-1.0.1.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
18
- wsba_hockey-1.0.1.dist-info/top_level.txt,sha256=acU7s3x-RZC1zGiqCOmO0g267iqCg34lzIfdmYxxGmQ,12
19
- wsba_hockey-1.0.1.dist-info/RECORD,,
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,,