wsba-hockey 1.1.0__py3-none-any.whl → 1.1.2__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/api/api/index.py +129 -0
- wsba_hockey/data_pipelines.py +71 -8
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/game_stats/app.py +6 -5
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/app.py +101 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/plot.py +71 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/goalie/rink_plot.py +245 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/app.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/plot.py +2 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/heatmaps/rink_plot.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/app.py +3 -3
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/plot.py +2 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/matchups/rink_plot.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/app.py +44 -28
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/plot.py +12 -3
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/pbp/rink_plot.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/app.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/plot.py +5 -4
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/skater/rink_plot.py +1 -1
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/app.py +103 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/plot.py +95 -0
- wsba_hockey/evidence/weakside-breakout/wsba_nhl_apps/wsba_nhl_apps/team_heatmaps/rink_plot.py +245 -0
- wsba_hockey/flask/app.py +77 -0
- wsba_hockey/tools/plotting.py +1 -0
- wsba_hockey/tools/scraping.py +6 -2
- wsba_hockey/tools/xg_model.py +1 -1
- wsba_hockey/workspace.py +28 -12
- wsba_hockey/wsba_main.py +10 -17
- {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.2.dist-info}/METADATA +1 -1
- {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.2.dist-info}/RECORD +32 -24
- {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.2.dist-info}/WHEEL +0 -0
- {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {wsba_hockey-1.1.0.dist-info → wsba_hockey-1.1.2.dist-info}/top_level.txt +0 -0
@@ -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://
|
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://
|
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://
|
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://
|
58
|
-
df_2 = pd.read_parquet(f'https://
|
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://
|
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(
|
@@ -109,7 +109,7 @@ def server(input, output, session):
|
|
109
109
|
query = reactive.Value(None)
|
110
110
|
|
111
111
|
def schedule():
|
112
|
-
schedule = pd.read_csv('https://
|
112
|
+
schedule = pd.read_csv('https://weakside-breakout.s3.us-east-2.amazonaws.com/info/schedule.csv')
|
113
113
|
|
114
114
|
return schedule.loc[schedule['gameState'].isin(['OFF','FINAL'])]
|
115
115
|
|
@@ -126,7 +126,8 @@ def server(input, output, session):
|
|
126
126
|
'event_type':['missed-shot,shot-on-goal,goal'],
|
127
127
|
'strength_state':['all'],
|
128
128
|
'filters':['false'],
|
129
|
-
'table':['false']
|
129
|
+
'table':['false'],
|
130
|
+
'title':['true']
|
130
131
|
}
|
131
132
|
|
132
133
|
for key in defaults.keys():
|
@@ -223,7 +224,7 @@ def server(input, output, session):
|
|
223
224
|
front_year = int(query['game_id'][0][0:4])
|
224
225
|
season = f'{front_year}{front_year+1}'
|
225
226
|
#Load appropriate dataframe
|
226
|
-
df = pd.read_parquet(f'https://
|
227
|
+
df = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/pbp/{season}.parquet')
|
227
228
|
|
228
229
|
#Prepare dataframe for plotting based on URL parameters
|
229
230
|
game_data = df.loc[df['game_id'].astype(str).isin(query['game_id'])].replace({np.nan: None})
|
@@ -255,7 +256,7 @@ def server(input, output, session):
|
|
255
256
|
color='Team',
|
256
257
|
color_discrete_map=colors,
|
257
258
|
hover_name='Description',
|
258
|
-
hover_data=['Event Num.', 'Period', 'Time (in
|
259
|
+
hover_data=['Event Num.', 'Period', 'Time (in Period)',
|
259
260
|
'Strength',
|
260
261
|
'Away Score', 'Home Score', 'x', 'y',
|
261
262
|
'Event Distance from Attacking Net',
|
@@ -265,28 +266,44 @@ def server(input, output, session):
|
|
265
266
|
for trace in plot.data:
|
266
267
|
rink.add_trace(trace)
|
267
268
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
orientation='h',
|
279
|
-
x=0.49,
|
280
|
-
y=-0.04,
|
281
|
-
xanchor='center',
|
282
|
-
yanchor='bottom',
|
283
|
-
font=dict(color='white')
|
284
|
-
),
|
269
|
+
if active_params()['title'][0]=='false':
|
270
|
+
return rink.update_layout(
|
271
|
+
legend=dict(
|
272
|
+
orientation='h',
|
273
|
+
x=0.49,
|
274
|
+
y=-0.04,
|
275
|
+
xanchor='center',
|
276
|
+
yanchor='bottom',
|
277
|
+
font=dict(color='white')
|
278
|
+
),
|
285
279
|
|
286
|
-
|
287
|
-
|
280
|
+
hoverlabel=dict(
|
281
|
+
font_size=10
|
282
|
+
)
|
283
|
+
)
|
284
|
+
else:
|
285
|
+
return rink.update_layout(
|
286
|
+
title=dict(
|
287
|
+
text=game_title,
|
288
|
+
x=0.5, y=0.94,
|
289
|
+
xanchor='center',
|
290
|
+
yanchor='top',
|
291
|
+
font=dict(color='white')
|
292
|
+
),
|
293
|
+
|
294
|
+
legend=dict(
|
295
|
+
orientation='h',
|
296
|
+
x=0.49,
|
297
|
+
y=-0.04,
|
298
|
+
xanchor='center',
|
299
|
+
yanchor='bottom',
|
300
|
+
font=dict(color='white')
|
301
|
+
),
|
302
|
+
|
303
|
+
hoverlabel=dict(
|
304
|
+
font_size=10
|
305
|
+
)
|
288
306
|
)
|
289
|
-
)
|
290
307
|
|
291
308
|
@output
|
292
309
|
@render.ui
|
@@ -322,10 +339,9 @@ def server(input, output, session):
|
|
322
339
|
if input.metric_select()=='Timelines':
|
323
340
|
return None
|
324
341
|
else:
|
325
|
-
df = game_df.get().copy()[['event_num','period','
|
342
|
+
df = game_df.get().copy()[['event_num','period','Time (in Period)','strength_state','event_type','Description','event_team_abbr','event_player_1_name','shot_type','zone_code','x','y','away_score','home_score','xG']].rename(columns={
|
326
343
|
'event_num':'#',
|
327
344
|
'period':'Period',
|
328
|
-
'seconds_elapsed':'Seconds',
|
329
345
|
'strength_state':'Strength State',
|
330
346
|
'event_type':'Event',
|
331
347
|
'event_team_abbr':'Team',
|
@@ -352,7 +368,7 @@ def server(input, output, session):
|
|
352
368
|
data = wsba_plt.timelines(game_df.get().copy())
|
353
369
|
colors = wsba_plt.colors(data)
|
354
370
|
timelines = px.line(data,
|
355
|
-
x='Time (in
|
371
|
+
x='Time (in Period)',
|
356
372
|
y=input.timeline_select(),
|
357
373
|
color='Team',
|
358
374
|
color_discrete_map=colors,
|
@@ -368,7 +384,7 @@ def server(input, output, session):
|
|
368
384
|
paper_bgcolor="rgba(0,0,0,0)",
|
369
385
|
plot_bgcolor="rgba(0,0,0,0)",
|
370
386
|
font_color='white',
|
371
|
-
xaxis=dict(title=dict(text='Time (in
|
387
|
+
xaxis=dict(title=dict(text='Time (in Period)'),showgrid=False),
|
372
388
|
yaxis=dict(title=dict(text=input.timeline_select()),showgrid=False),
|
373
389
|
|
374
390
|
legend=dict(
|
@@ -23,7 +23,7 @@ def colors(df):
|
|
23
23
|
away_abbr = list(df['away_team_abbr'])[0]
|
24
24
|
home_abbr = list(df['home_team_abbr'])[0]
|
25
25
|
season = list(df['season'])[0]
|
26
|
-
team_data = pd.read_csv('https://
|
26
|
+
team_data = pd.read_csv('https://weakside-breakout.s3.us-east-2.amazonaws.com/info/nhl_teaminfo.csv')
|
27
27
|
|
28
28
|
team_info ={
|
29
29
|
away_abbr: list(team_data.loc[team_data['WSBA']==f'{away_abbr}{season}','Primary Color'])[0],
|
@@ -32,13 +32,21 @@ def colors(df):
|
|
32
32
|
|
33
33
|
return team_info
|
34
34
|
|
35
|
+
def convert_time(seconds,period):
|
36
|
+
period_seconds = seconds - ((period-1)*1200)
|
37
|
+
minutes = int(period_seconds/60)
|
38
|
+
seconds = int(((period_seconds/60)-minutes)*60)
|
39
|
+
|
40
|
+
return f'{minutes}:{seconds:02}'
|
41
|
+
|
35
42
|
def prep(df,events,strengths):
|
36
43
|
df = df.loc[(df['event_type'].isin(events))]
|
37
|
-
|
44
|
+
|
45
|
+
df['strength_state'] = np.where(df['strength_state'].isin(['5v5','5v4','4v5']),df['strength_state'],'Other')
|
38
46
|
if 'all' not in strengths:
|
39
47
|
df = df.loc[((df['strength_state'].isin(strengths)))]
|
40
48
|
|
41
|
-
df
|
49
|
+
df = df.fillna(0)
|
42
50
|
df['size'] = np.where(df['xG']<=0,40,df['xG']*400)
|
43
51
|
|
44
52
|
df['marker'] = df['event_type'].replace(event_markers)
|
@@ -48,6 +56,7 @@ def prep(df,events,strengths):
|
|
48
56
|
df['Event Num.'] = df['event_num']
|
49
57
|
df['Period'] = df['period']
|
50
58
|
df['Time (in seconds)'] = df['seconds_elapsed']
|
59
|
+
df['Time (in Period)'] = df.apply(lambda x: convert_time(x['Time (in seconds)'],x['Period']), axis=1)
|
51
60
|
df['Strength'] = df['strength_state']
|
52
61
|
df['Away Score'] = df['away_score']
|
53
62
|
df['Home Score'] = df['home_score']
|
@@ -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://
|
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(
|
@@ -46,7 +46,7 @@ def server(input, output, session):
|
|
46
46
|
#Determine which season to load based on the input
|
47
47
|
season = query['season'][0]
|
48
48
|
#Load appropriate dataframe
|
49
|
-
df = pd.read_parquet(f'https://
|
49
|
+
df = pd.read_parquet(f'https://weakside-breakout.s3.us-east-2.amazonaws.com/pbp/{season}.parquet')
|
50
50
|
|
51
51
|
#Prepare dataframe for plotting based on URL parameters
|
52
52
|
df = df.loc[(df['event_player_1_id'].astype(str).str.replace('.0','').isin(query['skater']))&(df['season'].astype(str).isin(query['season']))&(df['event_team_abbr'].astype(str).isin(query['team']))&(df['season_type'].astype(str).isin(query['season_type']))].replace({np.nan: None})
|
@@ -20,9 +20,9 @@ def wsba_rink(setting='full', vertical=False):
|
|
20
20
|
return rink_plot.rink(setting=setting, vertical=vertical)
|
21
21
|
|
22
22
|
def colors(df):
|
23
|
-
team = list(df['
|
23
|
+
team = list(df['event_team_abbr'])[0]
|
24
24
|
season = list(df['season'])[0]
|
25
|
-
team_data = pd.read_csv('https://
|
25
|
+
team_data = pd.read_csv('https://weakside-breakout.s3.us-east-2.amazonaws.com/info/nhl_teaminfo.csv')
|
26
26
|
|
27
27
|
team_info ={
|
28
28
|
team: list(team_data.loc[team_data['WSBA']==f'{team}{season}','Primary Color'])[0],
|
@@ -37,7 +37,7 @@ def prep(df,events,strengths):
|
|
37
37
|
if strengths != 'all':
|
38
38
|
df = df.loc[((df['strength_state'].isin(strengths)))]
|
39
39
|
|
40
|
-
df
|
40
|
+
df = df.fillna(0)
|
41
41
|
df['size'] = np.where(df['xG']<=0,40,df['xG']*400)
|
42
42
|
|
43
43
|
df['marker'] = df['event_type'].replace(event_markers)
|
@@ -50,9 +50,10 @@ def prep(df,events,strengths):
|
|
50
50
|
df['Strength'] = df['strength_state']
|
51
51
|
df['Away Score'] = df['away_score']
|
52
52
|
df['Home Score'] = df['home_score']
|
53
|
-
df['x'] = np.where(df['x_adj']<0
|
53
|
+
df['x'] = np.where(df['x_adj']<0,df['y_adj'],-df['y_adj'])
|
54
54
|
df['y'] = abs(df['x_adj'])
|
55
55
|
df['Event Distance from Attacking Net'] = df['event_distance']
|
56
56
|
df['Event Angle to Attacking Net'] = df['event_angle']
|
57
57
|
df['xG'] = df['xG']*100
|
58
|
+
|
58
59
|
return df
|
@@ -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://
|
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(
|