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.
@@ -1,17 +1,70 @@
1
1
  import pandas as pd
2
+ import pyarrow.dataset as ds
2
3
  import numpy as np
3
- import wsba_hockey as wsba
4
4
  import requests as rs
5
5
  from fastapi import FastAPI
6
+ from fastapi.middleware.cors import CORSMiddleware
6
7
  from datetime import datetime
7
8
  import pytz
9
+ import feedparser
8
10
 
9
11
  app = FastAPI()
10
12
 
13
+ app.add_middleware(
14
+ CORSMiddleware,
15
+ allow_origins=["*"],
16
+ allow_credentials=True,
17
+ allow_methods=["*"],
18
+ allow_headers=["*"],
19
+ )
20
+
11
21
  @app.get("/")
12
22
  def read_root():
13
23
  return {"WeakSide Breakout Analysis": "Welcome to the API!"}
14
24
 
25
+ @app.get("/articles")
26
+ def articles():
27
+ rss = 'https://wsba.substack.com/feed'
28
+ feed = feedparser.parse(rss)
29
+
30
+ output = {}
31
+ output['articles'] = []
32
+
33
+ for entry in feed['entries']:
34
+ year = entry['published_parsed'][0]
35
+ month = entry['published_parsed'][1]
36
+ day = entry['published_parsed'][2]
37
+
38
+ date = f'{year}-{month:02}-{day:02}'
39
+
40
+ if len(entry['links']) > 0:
41
+ for link in entry['links']:
42
+ if 'image' in link['type']:
43
+ image = link['href']
44
+ else:
45
+ image = ''
46
+ else:
47
+ if 'image' in link['type']:
48
+ image = link['href']
49
+ else:
50
+ image = ''
51
+
52
+ output['articles'].append(
53
+ {
54
+ 'title': entry['title'],
55
+ 'summary': entry['summary'],
56
+ 'link': entry['link'],
57
+ 'guid': entry['id'].replace('https://wsba.substack.com/p/',''),
58
+ 'author': entry['author'],
59
+ 'published': {'datetime': entry['published'],
60
+ 'date': date},
61
+ 'thumbnail': image,
62
+ 'content': entry['content'][0]['value']
63
+ }
64
+ )
65
+
66
+ return output
67
+
15
68
  @app.get("/nhl/players/{player_id}")
16
69
  def player(player_id: int):
17
70
  player = rs.get(f'https://api-web.nhle.com/v1/player/{player_id}/landing').json()
@@ -28,33 +81,23 @@ def schedule_info(date: str):
28
81
 
29
82
  return data
30
83
 
84
+ @app.get("/nhl/bracket/{season}")
85
+ def schedule_info(season: int):
86
+ year = str(season)[4:8]
87
+ data = rs.get(f'https://api-web.nhle.com/v1/playoff-bracket/{year}').json()
88
+
89
+ return data
90
+
31
91
  @app.get("/nhl/games/{game_id}")
32
92
  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'
93
+ info = rs.get(f'https://api-web.nhle.com/v1/gamecenter/{game_id}/play-by-play').json()
94
+
95
+ season = info['season']
96
+ dataset = ds.dataset(f's3://weakside-breakout/pbp/parquet/nhl_pbp_{season}.parquet', format='parquet')
97
+ filter_expr = (ds.field('game_id')==game_id)
54
98
 
55
- skater.update({add:s})
56
- goalie.update({add:g})
57
- team_stats.update({add:t})
99
+ table = dataset.to_table(use_threads=True,filter=filter_expr)
100
+ df = table.to_pandas()
58
101
 
59
102
  df = df.fillna('')
60
103
 
@@ -122,8 +165,5 @@ def pbp(game_id: int):
122
165
 
123
166
  return {'info': info,
124
167
  'teams': teams,
125
- 'skater_stats':skater,
126
- 'goalie_stats':goalie,
127
- 'team_stats':team_stats,
128
168
  'plays': plays
129
169
  }
@@ -16,8 +16,9 @@ def pbp(seasons):
16
16
  for season in seasons:
17
17
  data = wsba.nhl_scrape_season(season,remove=[],local=True,sources=True,errors=True)
18
18
  errors.append(data['errors'])
19
- data['pbp'].to_csv(f'pbp/nhl_pbp_{season}.csv',index=False)
20
- pd.read_csv(f'pbp/nhl_pbp_{season}.csv').to_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet',index=False)
19
+ data['pbp'].to_csv('temp.csv',index=False)
20
+ pd.read_csv('temp.csv').to_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet',index=False)
21
+ os.remove('temp.csv')
21
22
  print(f'Errors: {errors}')
22
23
 
23
24
  def pbp_db(seasons):
@@ -0,0 +1,210 @@
1
+ import pandas as pd
2
+ import pyarrow.dataset as ds
3
+ import numpy as np
4
+ import calc
5
+ import requests as rs
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
+ ui.output_data_frame("duos")
99
+ )
100
+
101
+ def server(input, output, session):
102
+ col = [
103
+ 'season','season_type','game_id','game_date',
104
+ 'away_team_abbr','home_team_abbr','event_num','period',
105
+ 'seconds_elapsed',"strength_state","strength_state_venue",
106
+ "event_type","description",
107
+ "penalty_duration",
108
+ "event_team_abbr","event_team_venue",
109
+ "x_adj","y_adj",
110
+ "event_distance","event_angle","event_length","seconds_since_last",
111
+ "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",
112
+ "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",
113
+ 'rush','rebound','empty_net','xG'
114
+ ]
115
+
116
+ @output()
117
+ @render.data_frame
118
+ def duos():
119
+ #Retreive query parameters
120
+ search = session.input[".clientdata_url_search"]()
121
+ query = parse_qs(urlparse(search).query)
122
+
123
+ print(query)
124
+ #If no input data is provided automatically provide a select skater and plot all 5v5 fenwick shots
125
+ defaults = {
126
+ 'season':['20182019'],
127
+ 'team':['BOS'],
128
+ 'strength_state':['5v5'],
129
+ 'season_type':['2'],
130
+ 'skaters':['8473419,8470638']
131
+ }
132
+
133
+ for key in defaults.keys():
134
+ if key not in query.keys():
135
+ query.update({key:defaults[key]})
136
+
137
+ #Iterate through query and parse params with multiple selections
138
+ for param in query.keys():
139
+ q_string = query[param][0]
140
+ query[param] = q_string.split(',')
141
+
142
+ print(query)
143
+ #Determine which season to load based on the input
144
+ season = query['season'][0]
145
+
146
+ #Load appropriate dataframe
147
+ dataset = ds.dataset(f's3://weakside-breakout/pbp/parquet/nhl_pbp_{season}.parquet', format='parquet')
148
+ 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])))
149
+
150
+ table = dataset.to_table(columns=col,filter=filter_expr)
151
+ df = table.to_pandas()
152
+
153
+ #Prepare dataframe
154
+ 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)
155
+ 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)
156
+
157
+ df['onice_for'] = np.where(df['home_team_abbr']==df['event_team_abbr'],df['home_on_ice'],df['away_on_ice'])
158
+ df['onice_against'] = np.where(df['away_team_abbr']==df['event_team_abbr'],df['home_on_ice'],df['away_on_ice'])
159
+
160
+ df['onice'] = df['onice_for'] + ';' + df['onice_against']
161
+
162
+ skaters = query['skaters']
163
+ #Four aggregations to be completed:
164
+ #Team with both players on the ice
165
+ #Team with each player on the ice without the other
166
+ #Team with neither player on the ice
167
+ both = df.loc[(df['onice'].str.contains(skaters[0]))&(df['onice'].str.contains(skaters[1]))]
168
+ p1 = df.loc[(df['onice'].str.contains(skaters[0]))&(~(df['onice'].str.contains(skaters[1])))]
169
+ p2 = df.loc[(~(df['onice'].str.contains(skaters[0])))&(df['onice'].str.contains(skaters[1]))]
170
+ neither = df.loc[~((df['onice'].str.contains(skaters[0]))&(df['onice'].str.contains(skaters[1])))]
171
+
172
+ dfs = []
173
+ if 'Other' in query['strength_state']:
174
+ strength_state = query['strength_state'] + df.loc[~(df['strength_state'].isin(['5v5','5v4','4v5'])),'strength_state'].drop_duplicates().to_list()
175
+ else:
176
+ strength_state = query['strength_state']
177
+
178
+ skater_names = {}
179
+ #Find player names
180
+ for i in range(2):
181
+ skater = skaters[i]
182
+ data = rs.get(f'https://api-web.nhle.com/v1/player/{skater}/landing').json()
183
+
184
+ name = data['firstName']['default'].upper() + ' ' + data['lastName']['default'].upper()
185
+
186
+ skater_names.update({f'skater{i+1}':name})
187
+
188
+ team = query['team'][0]
189
+
190
+ #Calculate stats for each df
191
+ skater1 = skater_names['skater1']
192
+ skater2 = skater_names['skater2']
193
+
194
+ for df, data in zip([both, p1, p2, neither],['With Both',f'With {skater1}, Without {skater2}',f'With {skater2}, Without {skater1}','With Neither']):
195
+ stats = calc.calculate_stats(df,team,strength_state).replace({team: f'{team} {data}'})
196
+ dfs.append(stats)
197
+
198
+ total = pd.concat(dfs)[['Team',
199
+ 'TOI',
200
+ 'GF/60','GA/60',
201
+ 'SF/60','SA/60',
202
+ 'FF/60','FA/60',
203
+ 'xGF/60','xGA/60',
204
+ 'GF%','SF%',
205
+ 'FF%','xGF%',
206
+ 'GSAx']].round(2)
207
+
208
+ return render.DataTable(total)
209
+
210
+ app = App(app_ui, server)
@@ -0,0 +1,163 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ def calc_team(pbp,game_strength):
5
+ teams = []
6
+ fenwick_events = ['missed-shot','shot-on-goal','goal']
7
+
8
+ for team in [('away','home'),('home','away')]:
9
+ #Flip strength state (when necessary) and filter by game strength if not "all"
10
+ if game_strength != "all":
11
+ if game_strength not in ['3v3','4v4','5v5']:
12
+ for strength in game_strength:
13
+ pbp['strength_state'] = np.where(np.logical_and(pbp['event_team_venue']==team[1],pbp['strength_state']==strength[::-1]),strength,pbp['strength_state'])
14
+
15
+ pbp = pbp.loc[pbp['strength_state'].isin(game_strength)]
16
+
17
+ pbp['xGF'] = np.where(pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'], pbp['xG'], 0)
18
+ pbp['xGA'] = np.where(pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'], pbp['xG'], 0)
19
+ pbp['GF'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
20
+ pbp['GA'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
21
+ pbp['SF'] = np.where((pbp['event_type'].isin(['shot-on-goal','goal'])) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
22
+ pbp['SA'] = np.where((pbp['event_type'].isin(['shot-on-goal','goal'])) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
23
+ pbp['FF'] = np.where((pbp['event_type'].isin(fenwick_events)) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
24
+ pbp['FA'] = np.where((pbp['event_type'].isin(fenwick_events)) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
25
+ pbp['CF'] = np.where((pbp['event_type'].isin(fenwick_events+['blocked-shot'])) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
26
+ pbp['CA'] = np.where((pbp['event_type'].isin(fenwick_events+['blocked-shot'])) & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
27
+ pbp['HF'] = np.where((pbp['event_type']=='hit') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
28
+ pbp['HA'] = np.where((pbp['event_type']=='hit') & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
29
+ pbp['Penl'] = np.where((pbp['event_type']=='penalty') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
30
+ pbp['Penl2'] = np.where((pbp['event_type']=='penalty') & (pbp['penalty_duration']==2) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
31
+ pbp['Penl5'] = np.where((pbp['event_type']=='penalty') & (pbp['penalty_duration']==5) & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
32
+ pbp['PIM'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), pbp['penalty_duration'], 0)
33
+ pbp['Draw'] = np.where((pbp['event_type']=='penalty') & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr']), 1, 0)
34
+ pbp['Give'] = np.where((pbp['event_type']=='giveaway') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
35
+ pbp['Take'] = np.where((pbp['event_type']=='takeaway') & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr']), 1, 0)
36
+ pbp['Block'] = pbp['CA'] - pbp['FA']
37
+ pbp['RushF'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), 1, 0)
38
+ pbp['RushA'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), 1, 0)
39
+ pbp['RushFxG'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), pbp['xG'], 0)
40
+ pbp['RushAxG'] = np.where((pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), pbp['xG'], 0)
41
+ pbp['RushFG'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[0]}_team_abbr'])&(pbp['rush']>0), 1, 0)
42
+ pbp['RushAG'] = np.where((pbp['event_type'] == "goal") & (pbp['event_team_abbr'] == pbp[f'{team[1]}_team_abbr'])&(pbp['rush']>0), 1, 0)
43
+
44
+ stats = pbp.groupby([f'{team[0]}_team_abbr','season']).agg(
45
+ GP=('game_id','nunique'),
46
+ TOI=('event_length','sum'),
47
+ FF=('FF', 'sum'),
48
+ FA=('FA', 'sum'),
49
+ GF=('GF', 'sum'),
50
+ GA=('GA', 'sum'),
51
+ SF=('SF','sum'),
52
+ SA=('SA','sum'),
53
+ xGF=('xGF', 'sum'),
54
+ xGA=('xGA', 'sum'),
55
+ CF=('CF','sum'),
56
+ CA=('CA','sum'),
57
+ HF=('HF','sum'),
58
+ HA=('HA','sum'),
59
+ Penl=('Penl','sum'),
60
+ Penl2=('Penl2','sum'),
61
+ Penl5=('Penl5','sum'),
62
+ PIM=('PIM','sum'),
63
+ Draw=('Draw','sum'),
64
+ Give=('Give','sum'),
65
+ Take=('Take','sum'),
66
+ Block=('Block','sum'),
67
+ RushF=('RushF','sum'),
68
+ RushA=('RushA','sum'),
69
+ RushFxG=('RushFxG','sum'),
70
+ RushAxG=('RushAxG','sum'),
71
+ RushFG=('RushFG','sum'),
72
+ RushAG=('RushAG','sum'),
73
+ ).reset_index().rename(columns={f'{team[0]}_team_abbr':"Team",'season':"Season",'game_id':'Game'})
74
+ teams.append(stats)
75
+
76
+ onice_stats = pd.concat(teams).groupby(['Team','Season']).agg(
77
+ GP=('GP','sum'),
78
+ TOI=('TOI','sum'),
79
+ FF=('FF', 'sum'),
80
+ FA=('FA', 'sum'),
81
+ GF=('GF', 'sum'),
82
+ GA=('GA', 'sum'),
83
+ SF=('SF','sum'),
84
+ SA=('SA','sum'),
85
+ xGF=('xGF', 'sum'),
86
+ xGA=('xGA', 'sum'),
87
+ CF=('CF','sum'),
88
+ CA=('CA','sum'),
89
+ HF=('HF','sum'),
90
+ HA=('HA','sum'),
91
+ Penl=('Penl','sum'),
92
+ Penl2=('Penl2','sum'),
93
+ Penl5=('Penl5','sum'),
94
+ PIM=('PIM','sum'),
95
+ Draw=('Draw','sum'),
96
+ Give=('Give','sum'),
97
+ Take=('Take','sum'),
98
+ Block=('Block','sum'),
99
+ RushF=('RushF','sum'),
100
+ RushA=('RushA','sum'),
101
+ RushFxG=('RushFxG','sum'),
102
+ RushAxG=('RushAxG','sum'),
103
+ RushFG=('RushFG','sum'),
104
+ RushAG=('RushAG','sum'),
105
+ ).reset_index()
106
+
107
+ for col in onice_stats.columns.to_list()[2:30]:
108
+ onice_stats[col] = onice_stats[col].astype(float)
109
+
110
+ onice_stats['ShF%'] = onice_stats['GF']/onice_stats['SF']
111
+ onice_stats['xGF/FF'] = onice_stats['xGF']/onice_stats['FF']
112
+ onice_stats['GF/xGF'] = onice_stats['GF']/onice_stats['xGF']
113
+ onice_stats['FshF%'] = onice_stats['GF']/onice_stats['FF']
114
+ onice_stats['ShA%'] = onice_stats['GA']/onice_stats['SA']
115
+ onice_stats['xGA/FA'] = onice_stats['xGA']/onice_stats['FA']
116
+ onice_stats['GA/xGA'] = onice_stats['GA']/onice_stats['xGA']
117
+ onice_stats['FshA%'] = onice_stats['GA']/onice_stats['FA']
118
+ onice_stats['PM%'] = onice_stats['Take']/(onice_stats['Give']+onice_stats['Take'])
119
+ onice_stats['HF%'] = onice_stats['HF']/(onice_stats['HF']+onice_stats['HA'])
120
+ onice_stats['PENL%'] = onice_stats['Draw']/(onice_stats['Draw']+onice_stats['Penl'])
121
+ onice_stats['GSAx'] = onice_stats['xGA']-onice_stats['GA']
122
+
123
+ return onice_stats
124
+
125
+
126
+ def calculate_stats(pbp,team,game_strength):
127
+ 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']
128
+
129
+ complete = calc_team(pbp,game_strength)
130
+
131
+ #WSBA
132
+ complete['WSBA'] = complete['Team']+complete['Season'].astype(str)
133
+
134
+ #Set TOI to minute
135
+ complete['TOI'] = complete['TOI']/60
136
+
137
+ #Add per 60 stats
138
+ for stat in per_sixty[11:len(per_sixty)]:
139
+ complete[f'{stat}/60'] = (complete[stat]/complete['TOI'])*60
140
+
141
+ complete['GF%'] = complete['GF']/(complete['GF']+complete['GA'])
142
+ complete['SF%'] = complete['SF']/(complete['SF']+complete['SA'])
143
+ complete['xGF%'] = complete['xGF']/(complete['xGF']+complete['xGA'])
144
+ complete['FF%'] = complete['FF']/(complete['FF']+complete['FA'])
145
+ complete['CF%'] = complete['CF']/(complete['CF']+complete['CA'])
146
+
147
+ head = ['Team','Game'] if 'Game' in complete.columns else ['Team']
148
+ complete = complete[head+[
149
+ 'Season','WSBA',
150
+ 'GP','TOI',
151
+ "GF","SF","FF","xGF","xGF/FF","GF/xGF","ShF%","FshF%",
152
+ "GA","SA","FA","xGA","xGA/FA","GA/xGA","ShA%","FshA%",
153
+ 'CF','CA',
154
+ 'GF%','SF%','FF%','xGF%','CF%',
155
+ 'HF','HA','HF%',
156
+ 'Penl','Penl2','Penl5','PIM','Draw','PENL%',
157
+ 'Give','Take','PM%',
158
+ 'Block',
159
+ 'RushF','RushA','RushFxG','RushAxG','RushFG','RushAG',
160
+ 'GSAx'
161
+ ]+[f'{stat}/60' for stat in per_sixty[11:len(per_sixty)]]]
162
+
163
+ return complete.loc[complete['Team']==team]