wsba-hockey 1.1.0__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.
Files changed (44) hide show
  1. wsba_hockey/api/api/index.py +129 -0
  2. wsba_hockey/api/api/main.py +4 -0
  3. wsba_hockey/api/api/tools/__init__.py +0 -0
  4. wsba_hockey/api/api/tools/agg.py +374 -0
  5. wsba_hockey/api/api/tools/archive/old_scraping.py +1104 -0
  6. wsba_hockey/api/api/tools/plotting.py +144 -0
  7. wsba_hockey/api/api/tools/scraping.py +1000 -0
  8. wsba_hockey/api/api/tools/utils/__init__.py +1 -0
  9. wsba_hockey/api/api/tools/utils/config.py +14 -0
  10. wsba_hockey/api/api/tools/utils/save_pages.py +133 -0
  11. wsba_hockey/api/api/tools/utils/shared.py +450 -0
  12. wsba_hockey/api/api/tools/xg_model.py +455 -0
  13. wsba_hockey/api/api/wsba_main.py +1213 -0
  14. wsba_hockey/data_pipelines.py +71 -8
  15. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/app.py +6 -5
  16. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/app.py +101 -0
  17. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/plot.py +71 -0
  18. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/rink_plot.py +245 -0
  19. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/app.py +1 -1
  20. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/plot.py +2 -0
  21. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/rink_plot.py +1 -1
  22. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/app.py +3 -3
  23. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/plot.py +2 -0
  24. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/rink_plot.py +1 -1
  25. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/app.py +44 -28
  26. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/plot.py +12 -3
  27. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/rink_plot.py +1 -1
  28. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/app.py +1 -1
  29. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/plot.py +5 -4
  30. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/rink_plot.py +1 -1
  31. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/app.py +103 -0
  32. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/plot.py +95 -0
  33. wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/rink_plot.py +245 -0
  34. wsba_hockey/flask/app.py +77 -0
  35. wsba_hockey/tools/plotting.py +2 -1
  36. wsba_hockey/tools/scraping.py +7 -3
  37. wsba_hockey/tools/xg_model.py +3 -3
  38. wsba_hockey/workspace.py +28 -12
  39. wsba_hockey/wsba_main.py +10 -17
  40. {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.1.dist-info}/METADATA +1 -1
  41. {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.1.dist-info}/RECORD +44 -24
  42. {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.1.dist-info}/WHEEL +0 -0
  43. {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.1.dist-info}/licenses/LICENSE +0 -0
  44. {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.1.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import numpy as np
2
3
  import pandas as pd
3
4
  import wsba_main as wsba
@@ -23,15 +24,16 @@ def pbp_db(seasons):
23
24
  for season in seasons:
24
25
  pbp = pd.read_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet')
25
26
  pbp.loc[pbp['event_type'].isin(wsba.events+['penalty'])].to_csv('temp.csv',index=False)
26
- pd.read_csv('temp.csv').to_parquet(f'backblaze_pbp/{season}.parquet',index=False)
27
+ pd.read_csv('temp.csv').to_parquet(f'aws_pbp/{season}.parquet',index=False)
28
+ os.remove('temp.csv')
27
29
 
28
30
  def load_pbp(seasons):
29
31
  return pd.concat([pd.read_parquet(f'pbp/parquet/nhl_pbp_{season}.parquet') for season in seasons])
30
32
 
31
33
  def load_pbp_db(seasons):
32
- return pd.concat([pd.read_parquet(f'backblaze_pbp/{season}.parquet') for season in seasons])
34
+ return pd.concat([pd.read_parquet(f'aws_pbp/{season}.parquet') for season in seasons])
33
35
 
34
- def stats(arg,seasons):
36
+ def build_stats(arg,seasons):
35
37
  #Stats building
36
38
  for group in arg:
37
39
  for season in seasons:
@@ -73,9 +75,70 @@ def game_log(arg,seasons):
73
75
  data['Span'] = season_type[0]
74
76
  dfs.append(data)
75
77
  stat = pd.concat(dfs)
76
- stat.to_csv(f'stats/game_log/wsba_nhl_{season}_game_log.csv',index=False)
77
- stats = pd.read_csv(f'stats/game_log/wsba_nhl_{season}_game_log.csv')
78
- stats.to_parquet(f'stats/game_log/wsba_nhl_{season}_game_log.parquet',index=False)
78
+ path = 'stats/game_log' if group == 'skater' else 'stats/game_log/goalie'
79
+ stat.to_csv(f'{path}/temp.csv',index=False)
80
+ stats = pd.read_csv(f'{path}/temp.csv')
81
+ os.remove('temp.csv')
82
+ stats.to_parquet(f'{path}/wsba_nhl_{season}_game_log{'_goalie' if group == 'goalie' else ''}.parquet',index=False)
83
+
84
+ def fix_names(arg,seasons):
85
+ #Stats building
86
+ for group in arg:
87
+ for season in seasons:
88
+ print(f'Fixing names for {group} stats in {season}...')
89
+
90
+ group_name = 'Player' if 'skater' in group else 'Goalie'
91
+ if 'game_log' in group:
92
+ if 'skater' in group:
93
+ path = f'stats/{group[-8:]}/wsba_nhl_{season}_game_log.parquet'
94
+ else:
95
+ path = f'stats/{group[-8:]}/goalie/wsba_nhl_{season}_game_log_goalie.parquet'
96
+ else:
97
+ path = f'stats/{group}/wsba_nhl_{season}_{group}.csv'
98
+
99
+ if 'game_log' in group:
100
+ stats = pd.read_parquet(path)
101
+ else:
102
+ stats = pd.read_csv(path)
103
+
104
+ missing = stats.loc[stats[group_name].astype(str)=='0','ID'].drop_duplicates()
105
+
106
+ if not missing.to_list():
107
+ ''
108
+ else:
109
+ info = wsba.nhl_scrape_player_data(missing)
110
+ columns={'playerId':'ID',
111
+ 'fullName':group_name,
112
+ 'position':'Position',
113
+ 'headshot':'Headshot',
114
+ 'shootsCatches':'Handedness',
115
+ 'heightInInches':'Height (in)',
116
+ 'weightInPounds':'Weight (lbs)',
117
+ 'birthDate':'Birthday' }
118
+
119
+ info = info[list(columns.keys())]
120
+ complete = pd.merge(stats,info,how='left',left_on=['ID'],right_on=['playerId']).replace({'0':np.nan})
121
+
122
+ for key, value in zip(columns.keys(), columns.values()):
123
+ complete[value] = complete[value].combine_first(complete[key])
124
+ complete = complete.drop(columns=[key])
125
+
126
+ complete.to_csv('wtf.csv')
127
+ #Add player age
128
+ complete['Birthday'] = pd.to_datetime(complete['Birthday'],format='mixed')
129
+ complete['season_year'] = complete['Season'].astype(str).str[4:8].astype(int)
130
+ complete['Age'] = complete['season_year'] - complete['Birthday'].dt.year
131
+
132
+ complete['WSBA'] = complete[group_name]+complete['Team']+complete['Season'].astype(str)
133
+ complete = complete.sort_values(by=['Player','Season','Team','ID'])
134
+
135
+ if 'game_log' in group:
136
+ complete.to_csv('temp.csv',index=False)
137
+ pd.read_csv('temp.csv').to_parquet(path,index=False)
138
+ os.remove('temp.csv')
139
+
140
+ else:
141
+ complete.to_csv(path)
79
142
 
80
143
  def push_to_sheet(seasons, types = ['skaters','team','goalie','info'], msg = 'Data Update'):
81
144
  spread = Spread('WSBA - NHL 5v5 Shooting Metrics Public v1.0')
@@ -100,8 +163,8 @@ def push_to_sheet(seasons, types = ['skaters','team','goalie','info'], msg = 'Da
100
163
  spread.df_to_sheet(goalie,index=False,sheet='Goalie DB')
101
164
 
102
165
  if 'info' in types:
103
- team_info = pd.read_csv('teaminfo/nhl_teaminfo.csv')
104
- country = pd.read_csv('teaminfo/nhl_countryinfo.csv')
166
+ team_info = pd.read_csv('tools/teaminfo/nhl_teaminfo.csv')
167
+ country = pd.read_csv('tools/teaminfo/nhl_countryinfo.csv')
105
168
 
106
169
  spread.df_to_sheet(team_info,index=False,sheet='Team Info')
107
170
  spread.df_to_sheet(country,index=False,sheet='Country Info')
@@ -125,7 +125,7 @@ def server(input, output, session):
125
125
  game_info = reactive.Value(None)
126
126
 
127
127
  def get_schedule():
128
- games = pd.read_csv('https://f005.backblazeb2.com/file/weakside-breakout/info/schedule.csv')
128
+ games = pd.read_csv('https://weakside-breakout.s3.us-east-2.amazonaws.com/info/schedule.csv')
129
129
 
130
130
  return games.loc[games['gameState'].isin(['OFF','FINAL'])]
131
131
 
@@ -139,6 +139,7 @@ def server(input, output, session):
139
139
 
140
140
  defaults = {
141
141
  'game_id':['2024020001'],
142
+ 'title':['true']
142
143
  }
143
144
 
144
145
  for key in defaults.keys():
@@ -180,9 +181,9 @@ def server(input, output, session):
180
181
  info = game_info.get()
181
182
  season = info['season']
182
183
  #Load appropriate dataframe
183
- df = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/game_log/wsba_nhl_{season}_game_log.parquet')
184
- goalie_df = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/game_log/goalie/wsba_nhl_{season}_game_log_goalie.parquet')
185
- pbp = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/pbp/{season}.parquet')
184
+ df = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/game_log/wsba_nhl_{season}_game_log.parquet')
185
+ goalie_df = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/game_log/goalie/wsba_nhl_{season}_game_log_goalie.parquet')
186
+ pbp = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/pbp/{season}.parquet')
186
187
 
187
188
  game_df.set([df.loc[(df['Game']==info['game_id'])], pbp.loc[(pbp['game_id']==info['game_id'])&(pbp['event_type']=='goal')], goalie_df.loc[(goalie_df['Game']==info['game_id'])]])
188
189
 
@@ -247,7 +248,7 @@ def server(input, output, session):
247
248
  @output
248
249
  @render.text
249
250
  def game_header():
250
- return game_info.get()['title']
251
+ return game_info.get()['title'] if active_params()['title'][0] == 'true' else None
251
252
 
252
253
  @output
253
254
  @render.text
@@ -0,0 +1,101 @@
1
+ import pandas as pd
2
+ import plot as wsba_plt
3
+ import numpy as np
4
+ from urllib.parse import *
5
+ from shiny import *
6
+ from shinywidgets import output_widget, render_widget
7
+
8
+ app_ui = ui.page_fluid(
9
+ ui.tags.style(
10
+ "body {background:#09090b"
11
+ "}"
12
+ ),
13
+ output_widget("plot_goalie"),
14
+ )
15
+
16
+ def server(input, output, session):
17
+ @output()
18
+ @render_widget
19
+ def plot_goalie():
20
+ #Retreive query parameters
21
+ search = session.input[".clientdata_url_search"]()
22
+ query = parse_qs(urlparse(search).query)
23
+
24
+ print(query)
25
+ #If no input data is provided automatically provide a select goalie and plot all 5v5 fenwick shots
26
+ defaults = {
27
+ 'goalie':['8471679'],
28
+ 'season':['20142015'],
29
+ 'team':['MTL'],
30
+ 'strength_state':['5v5'],
31
+ 'season_type':['2']
32
+ }
33
+
34
+ for key in defaults.keys():
35
+ if key not in query.keys():
36
+ query.update({key:defaults[key]})
37
+
38
+ #Iterate through query and parse params with multiple selections
39
+ for param in query.keys():
40
+ q_string = query[param][0]
41
+ query[param] = q_string.split(',')
42
+
43
+ print(query)
44
+ #Determine which season to load based on the input
45
+ season = query['season'][0]
46
+ #Load appropriate dataframe
47
+ df = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/pbp/{season}.parquet')
48
+
49
+ #Prepare dataframe for plotting based on URL parameters
50
+ df = df.loc[(df['season'].astype(str).isin(query['season']))&(df['season_type'].astype(str).isin(query['season_type']))].replace({np.nan: None})
51
+ #Return empty rink if no data exists else continue
52
+ if df.empty:
53
+ return wsba_plt.wsba_rink(setting='offense',vertical=True)
54
+ else:
55
+ rink = wsba_plt.wsba_rink(setting='offense',vertical=True)
56
+
57
+ try:
58
+ plot = wsba_plt.heatmap(df,goalie=query['goalie'][0],team=query['team'][0],events=['missed-shot','shot-on-goal','goal'],strengths=query['strength_state'])
59
+
60
+ for trace in plot.data:
61
+ rink.add_trace(trace)
62
+
63
+ player = query['goalie'][0]
64
+ season = int(season[0:4])
65
+ team = query['team'][0]
66
+ strengths = 'All Situations' if len(query['strength_state']) == 4 else query['strength_state']
67
+ span = 'Regular Season' if query['season_type'][0]=='2' else 'Playoffs'
68
+
69
+ return rink.update_layout(
70
+ title=dict(
71
+ text=f'{player} GSAx at {strengths}; {season}-{season+1}, {span}, {team}',
72
+ x=0.5, y=0.96,
73
+ xanchor='center',
74
+ yanchor='top',
75
+ font=dict(color='white')
76
+ ),
77
+ ).add_annotation(
78
+ text='Lower GSAx',
79
+ xref="paper",
80
+ yref="paper",
81
+ xanchor='right',
82
+ yanchor='top',
83
+ font=dict(color='white'),
84
+ x=0.3,
85
+ y=0.04,
86
+ showarrow=False
87
+ ).add_annotation(
88
+ text='Higher GSAx',
89
+ xref="paper",
90
+ yref="paper",
91
+ xanchor='right',
92
+ yanchor='top',
93
+ font=dict(color='white'),
94
+ x=0.76,
95
+ y=0.04,
96
+ showarrow=False
97
+ )
98
+ except:
99
+ return wsba_plt.wsba_rink(setting='offense',vertical=True)
100
+
101
+ app = App(app_ui, server)
@@ -0,0 +1,71 @@
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 heatmap(df,goalie,team,events,strengths):
13
+ df['event_team_abbr_2'] = np.where(df['home_team_abbr']==df['event_team_abbr'],df['away_team_abbr'],df['home_team_abbr'])
14
+ df['strength_state_2'] = df['strength_state'].str[::-1]
15
+
16
+ df = df.loc[(df['event_type'].isin(events))&(df['x_adj'].notna())&(df['y_adj'].notna())]
17
+ df['x'] = np.where(df['x_adj']<0,-df['y_adj'],df['y_adj'])
18
+ df['y'] = abs(df['x_adj'])
19
+ df['event_distance'] = abs(df['event_distance'].fillna(0))
20
+ df = df.loc[(df['event_distance']<=89)&(df['y']<=89)&(df['empty_net']==0)]
21
+
22
+ y_min = 0
23
+ y_max = 100
24
+
25
+ df['G'] = (df['event_type']=='goal').astype(int)
26
+ df['strength_state_2'] = np.where(df['strength_state_2'].isin(['5v5','5v4','4v5']),df['strength_state_2'],'Other')
27
+
28
+ if strengths != 'all':
29
+ df = df.loc[((df['strength_state_2'].isin(strengths)))]
30
+
31
+ [x,y] = np.round(np.meshgrid(np.linspace(-42.5,42.5,85),np.linspace(y_min,y_max,(y_max-y_min))))
32
+ xgoals = griddata((df['x'],df['y']),df['xG']-df['G'],(x,y),method='cubic',fill_value=0)
33
+ xgoals_smooth = gaussian_filter(xgoals,sigma=3)
34
+
35
+ player_shots = df.loc[(df['event_goalie_id'].astype(str).str.contains(goalie))&(df['event_team_abbr_2']==team)]
36
+ [x,y] = np.round(np.meshgrid(np.linspace(-42.5,42.5,85),np.linspace(y_min,y_max,(y_max-y_min))))
37
+ xgoals_player = griddata((player_shots['x'],player_shots['y']),player_shots['xG']-player_shots['G'],(x,y),method='cubic',fill_value=0)
38
+
39
+ difference = (gaussian_filter(xgoals_player,sigma = 3)) - xgoals_smooth
40
+ data_min= difference.min()
41
+ data_max= difference.max()
42
+
43
+ if abs(data_min) > data_max:
44
+ data_max = data_min * -1
45
+ elif data_max > abs(data_min):
46
+ data_min = data_max * -1
47
+
48
+ fig = go.Figure(
49
+ data = go.Contour( x=np.linspace(-42.5,42,5,85),
50
+ y=np.linspace(y_min,y_max,(y_max-y_min)),
51
+ z=xgoals_smooth,
52
+ colorscale=[[0.0,'red'],[0.5,'#09090b'],[1.0,'blue']],
53
+ connectgaps=True,
54
+ contours=dict(
55
+ type='levels',
56
+ start = data_min,
57
+ end = data_max,
58
+ size=(data_max-data_min)/11
59
+ ),
60
+ colorbar=dict(
61
+ len = 0.7,
62
+ orientation='h',
63
+ showticklabels=False,
64
+ thickness=15,
65
+ yref='paper',
66
+ yanchor='top',
67
+ y=0
68
+ ))
69
+ )
70
+
71
+ return fig
@@ -0,0 +1,245 @@
1
+
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+ import io
5
+ import base64
6
+ import requests as rs
7
+ from PIL import Image
8
+
9
+ def rink(setting = "full", vertical = False):
10
+ '''
11
+ Function to plot rink in Plotly. Takes 2 arguments :
12
+
13
+ setting : full (default) for full ice, offense positive half of the ice, ozone positive quarter of ice, defense for negative half of the ice, dzone for negative quarter of the ice, and neutral for the neutral zone
14
+ vertical : True if you want a vertical rink, False (default) is for an horizontal rink
15
+
16
+ '''
17
+
18
+ def faceoff_circle(x, y, outer=True):
19
+ segments = []
20
+ theta = np.linspace(0, 2*np.pi, 300)
21
+ if outer:
22
+ # Outer circle
23
+ x_outer = x + 15*np.cos(theta)
24
+ y_outer = y + 15*np.sin(theta)
25
+ outer_circle = go.Scatter(x=x_outer, y=y_outer, mode='lines', line=dict(width=2, color='red'), showlegend=False, hoverinfo='skip')
26
+
27
+ segments.append(outer_circle)
28
+
29
+ # Inner circle
30
+ x_inner = x + np.cos(theta)
31
+ y_inner = y + np.sin(theta)
32
+ inner_circle = go.Scatter(x=x_inner, y=y_inner, mode='lines', fill='toself', fillcolor='rgba(255, 0, 0, 0.43)', line=dict(color='rgba(255, 0, 0, 1)', width=2), showlegend=False, hoverinfo='skip')
33
+
34
+ segments.append(inner_circle)
35
+
36
+ return segments #segments
37
+
38
+ fig = go.Figure()
39
+
40
+ if vertical :
41
+ setting_dict = {
42
+ "full" : [-101, 101],
43
+ "offense" : [0, 101],
44
+ "ozone" : [25, 101],
45
+ "defense" : [-101, 0],
46
+ "dzone" : [-101, -25],
47
+ "neutral" : [-25,25]
48
+ }
49
+ fig.update_layout(xaxis=dict(range=[-42.6, 42.6], showgrid=False, zeroline=False, showticklabels=False, constrain="domain"), yaxis=dict(range=setting_dict[setting], showgrid=False, zeroline=False, showticklabels=False, constrain="domain"),
50
+ showlegend=False, autosize=True, template="plotly_white")
51
+ fig.update_yaxes(
52
+ scaleanchor="x",
53
+ scaleratio=1,
54
+ )
55
+ def goal_crease(flip=1):
56
+ x_seq = np.linspace(-4, 4, 100)
57
+ x_goal = np.concatenate(([-4], x_seq, [4]))
58
+ y_goal = flip * np.concatenate(([89], 83 + x_seq**2/4**2*1.5, [89]))
59
+ goal_crease = go.Scatter(x=x_goal, y=y_goal, fill='toself', fillcolor='rgba(173, 216, 230, 0.3)', line=dict(color='red'))
60
+ return goal_crease
61
+
62
+ # Outer circle
63
+ theta = np.linspace(0, 2*np.pi, 300)
64
+ x_outer = 15 * np.cos(theta)
65
+ y_outer = 15 * np.sin(theta)
66
+ fig.add_trace(go.Scatter(x=x_outer, y=y_outer, mode='lines', line=dict(color='royalblue', width=2), showlegend=False, hoverinfo='skip'))
67
+ # Inner circle
68
+ theta2 = np.linspace(np.pi/2, 3*np.pi/2, 300)
69
+ x_inner = 42.5 + 10 * np.cos(theta2)
70
+ y_inner = 10 * np.sin(theta2)
71
+ fig.add_trace(go.Scatter(x=x_inner, y=y_inner, mode='lines', line=dict(color='red', width=2), showlegend=False, hoverinfo='skip'))
72
+ # Rink boundaries
73
+ fig.add_shape(type='rect', xref='x', yref='y', x0=-42.5, y0=25, x1=42.5, y1=26, line=dict(color='royalblue', width=1), fillcolor='royalblue', opacity=1)
74
+ fig.add_shape(type='rect', xref='x', yref='y', x0=-42.5, y0=-25, x1=42.5, y1=-26, line=dict(color='royalblue', width=1), fillcolor='royalblue', opacity=1)
75
+ fig.add_shape(type='rect', xref='x', yref='y', x0=-42.5, y0=-0.5, x1=42.5, y1=0.5, line=dict(color='red', width=2), fillcolor='red')
76
+
77
+ # Goal crease
78
+ fig.add_trace(goal_crease())
79
+ fig.add_trace(goal_crease(-1))
80
+ # Goal lines
81
+ goal_line_extreme = 42.5 - 28 + np.sqrt(28**2 - (28-11)**2)
82
+ fig.add_shape(type='line', xref='x', yref='y', x0=-goal_line_extreme, y0=89, x1=goal_line_extreme, y1=89, line=dict(color='red', width=2))
83
+ fig.add_shape(type='line', xref='x', yref='y', x0=-goal_line_extreme, y0=-89, x1=goal_line_extreme, y1=-89, line=dict(color='red', width=2))
84
+
85
+ # Faceoff circles
86
+ fig.add_traces(faceoff_circle(-22, 69))
87
+ fig.add_traces(faceoff_circle(22, 69))
88
+ fig.add_traces(faceoff_circle(-22, -69))
89
+ fig.add_traces(faceoff_circle(22, -69))
90
+ fig.add_traces(faceoff_circle(-22, -20, False))
91
+ fig.add_traces(faceoff_circle(22, -20, False))
92
+ fig.add_traces(faceoff_circle(-22, 20, False))
93
+ fig.add_traces(faceoff_circle(22, 20, False))
94
+
95
+ # Sidelines
96
+ theta_lines = np.linspace(0, np.pi/2, 20)
97
+ x_lines1 = np.concatenate(([-42.5], -42.5 + 28 - 28*np.cos(theta_lines), 42.5 - 28 + 28*np.cos(np.flip(theta_lines))))
98
+ y_lines1 = np.concatenate(([15], 72 + 28*np.sin(theta_lines), 72 + 28*np.sin(np.flip(theta_lines))))
99
+ x_lines2 = np.concatenate(([-42.5], -42.5 + 28 - 28*np.cos(theta_lines), 42.5 - 28 + 28*np.cos(np.flip(theta_lines))))
100
+ y_lines2 = np.concatenate(([15], -72 - 28*np.sin(theta_lines), -72 - 28*np.sin(np.flip(theta_lines))))
101
+ fig.add_trace(go.Scatter(x=x_lines1, y=y_lines1, mode='lines', line=dict(color='white', width=2), showlegend=False, hoverinfo='skip'))
102
+ fig.add_trace(go.Scatter(x=x_lines2, y=y_lines2, mode='lines', line=dict(color='white', width=2), showlegend=False, hoverinfo='skip'))
103
+ fig.add_shape(type='line', xref='x', yref='y', x0=42.5, y0=-72.5, x1=42.5, y1=72.5, line=dict(color='white', width=2))
104
+ fig.add_shape(type='line', xref='x', yref='y', x0=-42.5, y0=-72.5, x1=-42.5, y1=72.5, line=dict(color='white', width=2))
105
+
106
+ # Add goals
107
+ goal_width = 6 # feet
108
+ goal_depth = 4 # feet
109
+
110
+ # Top goal
111
+ fig.add_shape(
112
+ type="rect",
113
+ xref="x",
114
+ yref="y",
115
+ x0=-goal_width / 2,
116
+ y0=89,
117
+ x1=goal_width / 2,
118
+ y1=89 + goal_depth,
119
+ line=dict(color="red", width=2),
120
+ )
121
+ # Bottom goal
122
+ fig.add_shape(
123
+ type="rect",
124
+ xref="x",
125
+ yref="y",
126
+ x0=-goal_width / 2,
127
+ y0=-89 - goal_depth,
128
+ x1=goal_width / 2,
129
+ y1=-89,
130
+ line=dict(color="red", width=2),
131
+ )
132
+
133
+ else :
134
+ setting_dict = {
135
+ "full" : [-101, 101],
136
+ "offense" : [0, 101],
137
+ "ozone" : [25, 101],
138
+ "defense" : [-101, 0],
139
+ "dzone" : [-101, -25]
140
+ }
141
+ fig.update_layout(xaxis=dict(range=setting_dict[setting], showgrid=False, zeroline=False, showticklabels=False), yaxis=dict(range=[-42.6, 42.6], showgrid=False, zeroline=False, showticklabels=False, constrain="domain"),
142
+ showlegend=True, autosize =True, template="plotly_white")
143
+ fig.update_yaxes(
144
+ scaleanchor="x",
145
+ scaleratio=1,
146
+ )
147
+ def goal_crease(flip=1):
148
+ y_seq = np.linspace(-4, 4, 100)
149
+ y_goal = np.concatenate(([-4], y_seq, [4]))
150
+ x_goal = flip * np.concatenate(([89], 83 + y_seq**2/4**2*1.5, [89]))
151
+ goal_crease = go.Scatter(x=x_goal, y=y_goal, fill='toself', fillcolor='rgba(173, 216, 230, 0.3)', line=dict(color='red'), showlegend=False, hoverinfo='skip')
152
+ return goal_crease
153
+
154
+ # Outer circle
155
+ theta = np.linspace(0, 2 * np.pi, 300)
156
+ x_outer = 15 * np.sin(theta)
157
+ y_outer = 15 * np.cos(theta)
158
+ fig.add_trace(go.Scatter(x=x_outer, y=y_outer, mode='lines', line=dict(color='royalblue', width=2), showlegend=False, hoverinfo='skip'))
159
+ # Inner circle
160
+ theta2 = np.linspace(3 * np.pi / 2, np.pi / 2, 300) # Update theta2 to rotate the plot by 180 degrees
161
+ x_inner = 10 * np.sin(theta2) # Update x_inner to rotate the plot by 180 degrees
162
+ y_inner = -42.5 - 10 * np.cos(theta2) # Update y_inner to rotate the plot by 180 degrees
163
+ fig.add_trace(go.Scatter(x=x_inner, y=y_inner, mode='lines', line=dict(color='red', width=2), showlegend=False, hoverinfo='skip'))
164
+
165
+ # Rink boundaries
166
+ fig.add_shape(type='rect', xref='x', yref='y', x0=25, y0=-42.5, x1=26, y1=42.5, line=dict(color='royalblue', width=1), fillcolor='royalblue', opacity=1)
167
+ fig.add_shape(type='rect', xref='x', yref='y', x0=-25, y0=-42.5, x1=-26, y1=42.5, line=dict(color='royalblue', width=1), fillcolor='royalblue', opacity=1)
168
+ fig.add_shape(type='rect', xref='x', yref='y', x0=-0.5, y0=-42.5, x1=0.5, y1=42.5, line=dict(color='red', width=2), fillcolor='red')
169
+ # Goal crease
170
+ fig.add_trace(goal_crease())
171
+ fig.add_trace(goal_crease(-1))
172
+ # Goal lines
173
+ goal_line_extreme = 42.5 - 28 + np.sqrt(28 ** 2 - (28 - 11) ** 2)
174
+ fig.add_shape(type='line', xref='x', yref='y', x0=89, y0=-goal_line_extreme, x1=89, y1=goal_line_extreme, line=dict(color='red', width=2))
175
+ fig.add_shape(type='line', xref='x', yref='y', x0=-89, y0=-goal_line_extreme, x1=-89, y1=goal_line_extreme, line=dict(color='red', width=2))
176
+ # Faceoff circles
177
+ fig.add_traces(faceoff_circle(-69, -22))
178
+ fig.add_traces(faceoff_circle(-69, 22))
179
+ fig.add_traces(faceoff_circle(69, -22))
180
+ fig.add_traces(faceoff_circle(69, 22))
181
+ fig.add_traces(faceoff_circle(-20, -22, False))
182
+ fig.add_traces(faceoff_circle(-20, 22, False))
183
+ fig.add_traces(faceoff_circle(20, -22, False))
184
+ fig.add_traces(faceoff_circle(20, 22, False))
185
+
186
+ # Sidelines
187
+ theta_lines = np.linspace(0, np.pi / 2, 20)
188
+ x_lines1 = np.concatenate(([15], 72 + 28 * np.sin(theta_lines), 72 + 28 * np.sin(np.flip(theta_lines))))
189
+ y_lines1 = np.concatenate(([-42.5], -42.5 + 28 - 28 * np.cos(theta_lines), 42.5 - 28 + 28 * np.cos(np.flip(theta_lines))))
190
+ x_lines2 = np.concatenate(([15], -72 - 28 * np.sin(theta_lines), -72 - 28 * np.sin(np.flip(theta_lines))))
191
+ y_lines2 = np.concatenate(([-42.5], -42.5 + 28 - 28 * np.cos(theta_lines), 42.5 - 28 + 28 * np.cos(np.flip(theta_lines))))
192
+ fig.add_trace(go.Scatter(x=x_lines1, y=y_lines1, mode='lines', line=dict(color='white', width=2), showlegend=False, hoverinfo='skip'))
193
+ fig.add_trace(go.Scatter(x=x_lines2, y=y_lines2, mode='lines', line=dict(color='white', width=2), showlegend=False, hoverinfo='skip'))
194
+ fig.add_shape(type='line', xref='x', yref='y', x0=-72.5, y0=-42.5, x1=72.5, y1=-42.5, line=dict(color='white', width=2))
195
+ fig.add_shape(type='line', xref='x', yref='y', x0=-72.5, y0=42.5, x1=72.5, y1=42.5, line=dict(color='white', width=2))
196
+
197
+ # Add goals
198
+ goal_width = 6 # feet
199
+ goal_depth = 4 # feet
200
+
201
+ # Right goal
202
+ fig.add_shape(
203
+ type="rect",
204
+ xref="x",
205
+ yref="y",
206
+ x0=89,
207
+ y0=-goal_width / 2,
208
+ x1=89 + goal_depth,
209
+ y1=goal_width / 2,
210
+ line=dict(color="red", width=2),
211
+ )
212
+ # Left goal
213
+ fig.add_shape(
214
+ type="rect",
215
+ xref="x",
216
+ yref="y",
217
+ x0=-89 - goal_depth,
218
+ y0=-goal_width / 2,
219
+ x1=-89,
220
+ y1=goal_width / 2,
221
+ line=dict(color="red", width=2),
222
+ )
223
+
224
+ # Add logo
225
+ logo = Image.open(rs.get('https://f005.backblazeb2.com/file/weakside-breakout/utils/wsba.png',stream=True).raw)
226
+
227
+ fig.add_layout_image(
228
+ dict(
229
+ source=logo,
230
+ xref="x",
231
+ yref="y",
232
+ x=-12,
233
+ y=12,
234
+ sizex=24,
235
+ sizey=24,
236
+ sizing="stretch",
237
+ opacity=1)
238
+ )
239
+
240
+ #Set background to transparent
241
+ fig.update_layout(
242
+ paper_bgcolor="rgba(0,0,0,0)",
243
+ plot_bgcolor="rgba(0,0,0,0)"
244
+ )
245
+ return fig
@@ -44,7 +44,7 @@ def server(input, output, session):
44
44
  #Determine which season to load based on the input
45
45
  season = query['season'][0]
46
46
  #Load appropriate dataframe
47
- df = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/pbp/{season}.parquet')
47
+ df = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/pbp/{season}.parquet')
48
48
 
49
49
  #Prepare dataframe for plotting based on URL parameters
50
50
  df = df.loc[(df['season'].astype(str).isin(query['season']))&(df['season_type'].astype(str).isin(query['season_type']))].replace({np.nan: None})
@@ -13,6 +13,7 @@ def heatmap(df,skater,team,events,strengths,onice):
13
13
  df['event_team_abbr_2'] = np.where(df['home_team_abbr']==df['event_team_abbr'],df['away_team_abbr'],df['home_team_abbr'])
14
14
  df['strength_state_2'] = df['strength_state'].str[::-1]
15
15
 
16
+ df = df.fillna(0)
16
17
  df = df.loc[(df['event_type'].isin(events))&(df['x_adj'].notna())&(df['y_adj'].notna())]
17
18
  if onice == 'for':
18
19
  df['x'] = abs(df['x_adj'])
@@ -38,6 +39,7 @@ def heatmap(df,skater,team,events,strengths,onice):
38
39
  df['onice_against'] = np.where(df['away_team_abbr']==df['event_team_abbr'],df['home_on_ice'],df['away_on_ice'])
39
40
 
40
41
  df['strength_state'] = np.where(df['strength_state'].isin(['5v5','5v4','4v5']),df['strength_state'],'Other')
42
+ df['strength_state_2'] = np.where(df['strength_state_2'].isin(['5v5','5v4','4v5']),df['strength_state_2'],'Other')
41
43
 
42
44
  if strengths != 'all':
43
45
  if onice == 'against':
@@ -222,7 +222,7 @@ def rink(setting = "full", vertical = False):
222
222
  )
223
223
 
224
224
  # Add logo
225
- logo = Image.open(rs.get('https://f005.backblazeb2.com/file/weakside-breakout/utils/wsba.png',stream=True).raw)
225
+ logo = Image.open(rs.get('https://weakside-breakout.s3.us-east-2.amazonaws.com/utils/wsba.png',stream=True).raw)
226
226
 
227
227
  fig.add_layout_image(
228
228
  dict(
@@ -50,12 +50,12 @@ def server(input, output, session):
50
50
  season_2 = query['season_2'][0]
51
51
  #Load appropriate dataframes
52
52
  if season_1 == season_2:
53
- df_1 = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/pbp/{season_1}.parquet')
53
+ df_1 = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/pbp/{season_1}.parquet')
54
54
  df_2 = df_1
55
55
 
56
56
  else:
57
- df_1 = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/pbp/{season_1}.parquet')
58
- df_2 = pd.read_parquet(f'https://f005.backblazeb2.com/file/weakside-breakout/pbp/{season_2}.parquet')
57
+ df_1 = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/pbp/{season_1}.parquet')
58
+ df_2 = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/pbp/{season_2}.parquet')
59
59
 
60
60
  events = ['missed-shot','shot-on-goal','goal']
61
61
  team_xg = {}
@@ -12,6 +12,7 @@ def wsba_rink(setting='full', vertical=False):
12
12
  def heatmap_prep(df,team,events,strengths,onice,flip=False):
13
13
  df['event_team_abbr_2'] = np.where(df['home_team_abbr']==df['event_team_abbr'],df['away_team_abbr'],df['home_team_abbr'])
14
14
 
15
+ df = df.fillna(0)
15
16
  df = df.loc[(df['event_type'].isin(events))&(df['x_adj'].notna())&(df['y_adj'].notna())]
16
17
  if flip:
17
18
  if onice == 'for':
@@ -55,6 +56,7 @@ def heatmap_prep(df,team,events,strengths,onice,flip=False):
55
56
  df['onice_against'] = np.where(df['away_team_abbr']==df['event_team_abbr'],df['home_on_ice'],df['away_on_ice'])
56
57
 
57
58
  df['strength_state'] = np.where(df['strength_state'].isin(['5v5','5v4','4v5']),df['strength_state'],'Other')
59
+ df['strength_state_2'] = np.where(df['strength_state_2'].isin(['5v5','5v4','4v5']),df['strength_state_2'],'Other')
58
60
 
59
61
  if strengths != 'all':
60
62
  df = df.loc[((df['strength_state'].isin(strengths)))]
@@ -222,7 +222,7 @@ def rink(setting = "full", vertical = False):
222
222
  )
223
223
 
224
224
  # Add logo
225
- logo = Image.open(rs.get('https://f005.backblazeb2.com/file/weakside-breakout/utils/wsba.png',stream=True).raw)
225
+ logo = Image.open(rs.get('https://weakside-breakout.s3.us-east-2.amazonaws.com/utils/wsba.png',stream=True).raw)
226
226
 
227
227
  fig.add_layout_image(
228
228
  dict(