patrickramos commited on
Commit
43049df
1 Parent(s): 7763ddf

Update app

Browse files
Files changed (3) hide show
  1. data.py +6 -10
  2. demo.py +12 -11
  3. gradio_function.py +141 -111
data.py CHANGED
@@ -69,14 +69,10 @@ df['swing'] = ~df['description'].isin(['B', 'BB', 'LS', 'inv_K', 'bunt_K', 'HBP'
69
  df['csw'] = df['description'].isin(['SS', 'K', 'LS', 'inv_K'])
70
  df['normal_pitch'] = ~df['description'].isin(['obstruction', 'illegal_pitch', 'defensive_interference']) # guess
71
 
72
- whiff_rate = df.groupby(['name', 'pitch_name'])
73
- whiff_rate = (whiff_rate['whiff'].sum() / whiff_rate['swing'].sum() * 100).round(1).rename('Whiff%').reset_index()
 
 
74
 
75
- csw_rate = df.groupby(['name', 'pitch_name'])
76
- csw_rate = (csw_rate['csw'].sum() / csw_rate['normal_pitch'].sum() * 100).round(1).rename('CSW%').reset_index()
77
-
78
- pitch_stats = pd.merge(
79
- whiff_rate,
80
- csw_rate,
81
- on=['name', 'pitch_name']
82
- ).set_index(['name', 'pitch_name'])
 
69
  df['csw'] = df['description'].isin(['SS', 'K', 'LS', 'inv_K'])
70
  df['normal_pitch'] = ~df['description'].isin(['obstruction', 'illegal_pitch', 'defensive_interference']) # guess
71
 
72
+ df_by_player_pitch = df.groupby(['name', 'pitch_name'])
73
+ whiff_rate = (df_by_player_pitch['whiff'].sum() / df_by_player_pitch['swing'].sum() * 100).round(1).rename('Whiff%')
74
+ csw_rate = (df_by_player_pitch['csw'].sum() / df_by_player_pitch['normal_pitch'].sum() * 100).round(1).rename('CSW%')
75
+ velo = df_by_player_pitch['release_speed'].apply(lambda x: round(x.mean(), 1)).rename('Velocity')
76
 
77
+ pitch_stats = pd.concat([whiff_rate, csw_rate, velo], axis=1)
78
+ league_pitch_stats = pd.DataFrame(df.groupby('pitch_name')['release_speed'].apply(lambda x: round(x.mean(), 1)).rename('Velocity'))
 
 
 
 
 
 
demo.py CHANGED
@@ -33,16 +33,14 @@ with gr.Blocks(css=css) as demo:
33
  # NPB data visualization demo
34
  [Data from SportsNavi](https://sports.yahoo.co.jp/)
35
  ''')
36
- player = gr.Dropdown(choices=sorted(player_df['name'].dropna().tolist()), label='Player')
37
  player_info = gr.Markdown()
38
  download_file = gr.DownloadButton(label='Download player data')
39
- with gr.Row():
40
- with gr.Column():
41
- gr.Markdown('## Pitch Distribution')
42
- usage = gr.Plot(label='Pitch Distribution', elem_classes='pitch-usage')
43
- with gr.Column():
44
- gr.Markdown('## Pitch Velocity')
45
- pitch_velo_summary = gr.Plot(show_label=False, elem_classes='pitch-velo-summary')
46
 
47
 
48
  max_pitch_maps = len(jp_pitch_to_en_pitch)
@@ -70,18 +68,21 @@ with gr.Blocks(css=css) as demo:
70
  pitch_names.append(gr.Markdown(f'### Pitch {col+1}', visible=visible))
71
  pitch_infos.append(gr.DataFrame(pd.DataFrame([{'Whiff%': None, 'CSW%': None}]), interactive=False, visible=visible))
72
  pitch_velos.append(gr.Plot(show_label=False, elem_classes='pitch-velo', visible=visible))
73
- pitch_maps.append(gr.Plot(label='Pitch location', elem_classes='pitch-loc', visible=visible))
 
 
 
74
 
75
  gr.Markdown('## Bugs and other notes')
76
  with gr.Accordion('Click to open', open=False):
77
  gr.Markdown('''
78
- - No padding in pie charts leads to hovertext getting cut off near the bottom for some players
79
  - Y axis ticks messy when no velocity distribution is plotted
80
  - Topmost distribution in summary velo distribution plot is clipped
 
81
  '''
82
  )
83
 
84
- player.input(get_data, inputs=player, outputs=[player_info, download_file, usage, *pitch_groups, *pitch_names, *pitch_infos, *pitch_velos, *pitch_maps, pitch_velo_summary])
85
 
86
  demo.launch(
87
  share=True,
 
33
  # NPB data visualization demo
34
  [Data from SportsNavi](https://sports.yahoo.co.jp/)
35
  ''')
36
+ player = gr.Dropdown(value=None, choices=sorted(player_df['name'].dropna().tolist()), label='Player')
37
  player_info = gr.Markdown()
38
  download_file = gr.DownloadButton(label='Download player data')
39
+ with gr.Group():
40
+ with gr.Row():
41
+ usage = gr.Plot(label='Pitch Distribution')#, elem_classes='pitch-usage')
42
+ pitch_velo_summary = gr.Plot(label='Velocity Summary')#, elem_classes='pitch-velo-summary')
43
+ pitch_loc_summary = gr.Plot(label='Overall Location')
 
 
44
 
45
 
46
  max_pitch_maps = len(jp_pitch_to_en_pitch)
 
68
  pitch_names.append(gr.Markdown(f'### Pitch {col+1}', visible=visible))
69
  pitch_infos.append(gr.DataFrame(pd.DataFrame([{'Whiff%': None, 'CSW%': None}]), interactive=False, visible=visible))
70
  pitch_velos.append(gr.Plot(show_label=False, elem_classes='pitch-velo', visible=visible))
71
+ pitch_maps.append(gr.Plot(label='Pitch Location', elem_classes='pitch-loc', visible=visible))
72
+
73
+ gr.Markdown('## Pitch Velocity')
74
+ velo_stats = gr.DataFrame(pd.DataFrame([{'Avg. Velo': None, 'League Avg. Velo': None}]), interactive=False, label='Pitch Velocity')
75
 
76
  gr.Markdown('## Bugs and other notes')
77
  with gr.Accordion('Click to open', open=False):
78
  gr.Markdown('''
 
79
  - Y axis ticks messy when no velocity distribution is plotted
80
  - Topmost distribution in summary velo distribution plot is clipped
81
+ - DataFrame precision inconsistent
82
  '''
83
  )
84
 
85
+ player.input(get_data, inputs=player, outputs=[player_info, download_file, usage, pitch_velo_summary, pitch_loc_summary, *pitch_groups, *pitch_names, *pitch_infos, *pitch_velos, *pitch_maps, velo_stats])
86
 
87
  demo.launch(
88
  share=True,
gradio_function.py CHANGED
@@ -8,7 +8,7 @@ import pandas as pd
8
  import gradio as gr
9
 
10
  from translate import max_pitch_types
11
- from data import df, pitch_stats
12
 
13
  # GRADIO FUNCTIONS
14
 
@@ -48,42 +48,54 @@ colorscale = [
48
  ]
49
 
50
 
51
- def plot_pitch_map(player=None, loc=None, pitch_type=None, pitch_name=None):
52
  assert not ((loc is None and player is None) or (loc is not None and player is not None)), 'exactly one of `player` or `loc` must be specified'
53
 
54
  if loc is None and player is not None:
55
- assert not ((pitch_type is None and pitch_name is None) or (pitch_type is not None and pitch_name is not None)), 'exactly one of `pitch_type` or `pitch_name` must be specified'
56
- pitch_val = pitch_type or pitch_name
57
- pitch_col = 'pitch_type' if pitch_type else 'pitch_name'
58
- loc = df.set_index(['name', pitch_col]).loc[(player, pitch_val), ['plate_x', 'plate_z']]
59
- Z = fit_pred_kde(loc.to_numpy().T, X, Y)
 
 
 
60
 
61
  fig = go.Figure()
62
- fig.add_shape(
63
- type="rect",
64
- **coordinatify(sz_h, sz_w),
65
- line_color='gray',
66
- # fillcolor='rgba(220, 220, 220, 0.75)', #gainsboro
67
- )
68
- fig.add_shape(
69
- type="rect",
70
- **coordinatify(h_h, h_w),
71
- line_color='dimgray',
72
- )
73
- fig.add_trace(go.Contour(
74
- z=Z,
75
- x=kde_range,
76
- y=kde_range,
77
- colorscale=colorscale,
78
- zmin=1e-5,
79
- zmax=Z.max(),
80
- contours={
81
- 'start': 1e-5,
82
- 'end': Z.max(),
83
- 'size': Z.max() / 6
84
- },
85
- showscale=False
86
- ))
 
 
 
 
 
 
 
 
 
87
  fig.update_layout(
88
  xaxis=dict(range=[-plot_s/2, plot_s/2+1], showticklabels=False),
89
  yaxis=dict(range=[-plot_s/2, plot_s/2+1], scaleanchor='x', scaleratio=1, showticklabels=False),
@@ -93,24 +105,24 @@ def plot_pitch_map(player=None, loc=None, pitch_type=None, pitch_name=None):
93
  return fig
94
 
95
 
96
- def plot_empty_pitch_map():
97
- fig = go.Figure()
98
- fig.add_annotation(
99
- x=0,
100
- y=0,
101
- text='No visualization<br>as less than 10 pitches thrown',
102
- showarrow=False
103
- )
104
- fig.update_layout(
105
- xaxis=dict(range=[-plot_s/2, plot_s/2+1], showticklabels=False),
106
- yaxis=dict(range=[-plot_s/2, plot_s/2+1], scaleanchor='x', scaleratio=1, showticklabels=False),
107
- # width=384,
108
- # height=384
109
- )
110
- return fig
111
 
112
  # velo distribution
113
- def plot_pitch_velo(player=None, velos=None, pitch_type=None, pitch_name=None):
114
  assert not ((velos is None and player is None) or (velos is not None and player is not None)), 'exactly one of `player` or `loc` must be specified'
115
 
116
  if velos is None and player is not None:
@@ -118,13 +130,27 @@ def plot_pitch_velo(player=None, velos=None, pitch_type=None, pitch_name=None):
118
  pitch_val = pitch_type or pitch_name
119
  pitch_col = 'pitch_type' if pitch_type else 'pitch_name'
120
  velos = df.set_index(['name', pitch_col]).loc[(player, pitch_val), 'release_speed']
 
 
 
121
 
122
- fig = go.Figure(data=go.Violin(x=velos, side='positive', hoveron='points', points=False, meanline_visible=True, name='Velocity Distribution'))
123
- median = velos.median()
 
 
 
 
 
 
 
 
 
 
 
124
  fig.update_layout(
125
  xaxis=dict(
126
  title='Velocity',
127
- range=[median-25, median+25],
128
  scaleratio=2
129
  ),
130
  yaxis=dict(
@@ -143,39 +169,39 @@ def plot_pitch_velo(player=None, velos=None, pitch_type=None, pitch_name=None):
143
  return fig
144
 
145
 
146
- def plot_empty_pitch_velo():
147
- fig = go.Figure()
148
- fig.add_annotation(
149
- x=(170+125)/2,
150
- y=0.3/2,
151
- text='No visualization<br>as less than 10 pitches thrown',
152
- showarrow=False,
153
- )
154
- fig.update_layout(
155
- xaxis=dict(
156
- title='Velocity',
157
- range=[125, 170],
158
- scaleratio=2
159
- ),
160
- yaxis=dict(
161
- title='Frequency',
162
- range=[0, 0.3],
163
- scaleanchor='x',
164
- scaleratio=1,
165
- # tickvals=np.linspace(0, 0.3, 3),
166
- # ticktext=np.linspace(0, 0.3, 3),
167
- tickvals=[0.15],
168
- ticktext=[0.15]
169
- ),
170
- autosize=True,
171
- # width=512,
172
- # height=256,
173
- modebar_remove=['zoom', 'autoScale', 'resetScale'],
174
- )
175
- return fig
176
-
177
-
178
- def plot_all_pitch_velo(player=None, player_df=None, pitch_counts=None, min_pitches=10):
179
  # assert not ((player is None and player_df is None) or (player is not None and player_df is not None)), 'exactly one of `player` or `player_df` must be specified'
180
 
181
  if player_df is None and player is not None:
@@ -221,7 +247,7 @@ def plot_all_pitch_velo(player=None, player_df=None, pitch_counts=None, min_pitc
221
  fig.add_trace(go.Scatter(
222
  x=[velo_center],
223
  y=[pitch_name],
224
- text=['No visualization as less than 10 pitches thrown'],
225
  textposition='top center',
226
  hovertext=False,
227
  mode="lines+text",
@@ -230,16 +256,6 @@ def plot_all_pitch_velo(player=None, player_df=None, pitch_counts=None, min_pitc
230
  name=pitch_name,
231
  ))
232
 
233
- fig.add_trace(go.Violin(
234
- x=player_df['release_speed'],
235
- y=[player]*len(player_df),
236
- side='positive',
237
- orientation='h',
238
- meanline_visible=True,
239
- points=False,
240
- legendrank=0,
241
- name=player
242
- ))
243
  fig.add_trace(go.Violin(
244
  x=league_df['release_speed'],
245
  y=[player]*len(league_df),
@@ -253,9 +269,20 @@ def plot_all_pitch_velo(player=None, player_df=None, pitch_counts=None, min_pitc
253
  # visible='legendonly',
254
  name='NPB',
255
  ))
 
 
 
 
 
 
 
 
 
 
256
 
257
- fig.update_xaxes(title='Velocity')
258
- fig.update_layout(violingap=0, violingroupgap=0)
 
259
 
260
  return fig
261
 
@@ -263,14 +290,18 @@ def plot_all_pitch_velo(player=None, player_df=None, pitch_counts=None, min_pitc
263
  def get_data(player):
264
  player_name = f'# {player}'
265
 
266
- _df = df.set_index('name').loc[player]
267
  _df.to_csv(f'files/npb.csv', index=False)
268
- _df_by_pitch_name = _df.set_index('pitch_name')
269
 
270
  usage_fig = px.pie(_df['pitch_name'], names='pitch_name')
271
  usage_fig.update_traces(texttemplate='%{percent:.1%}', hovertemplate=f'<b>{player}</b><br>' + 'threw a <b>%{label}</b><br><b>%{percent:.1%}</b> of the time (<b>%{value}</b> pitches)')
272
 
273
  pitch_counts = _df['pitch_name'].value_counts()
 
 
 
 
274
  pitch_groups = []
275
  pitch_names = []
276
  pitch_infos = []
@@ -288,16 +319,15 @@ def get_data(player):
288
  visible=True
289
  ))
290
 
291
- if count > 10:
292
- pitch_velos.append(gr.update(
293
- value=plot_pitch_velo(velos=_df_by_pitch_name.loc[pitch_name, 'release_speed']),
294
- visible=True
295
- ))
296
- pitch_maps.append(gr.update(value=plot_pitch_map(player, pitch_name=pitch_name), label='Pitch location', visible=True))
297
-
298
- else:
299
- pitch_velos.append(gr.update(value=plot_empty_pitch_velo(),visible=True ))
300
- pitch_maps.append(gr.update(value=plot_empty_pitch_map(), label=pitch_name, visible=True))
301
 
302
  for _ in range(max_pitch_types - len(pitch_names)):
303
  pitch_groups.append(gr.update(visible=False))
@@ -307,6 +337,6 @@ def get_data(player):
307
  pitch_velos.append(gr.update(value=None, visible=False))
308
  pitch_maps.append(gr.update(value=None, visible=False))
309
 
310
- pitch_velo_summary = plot_all_pitch_velo(player=player, player_df=_df_by_pitch_name, pitch_counts=pitch_counts.sort_values(ascending=True))
311
 
312
- return player_name, 'files/npb.csv', usage_fig, *pitch_groups, *pitch_names, *pitch_infos, *pitch_velos, *pitch_maps, pitch_velo_summary
 
8
  import gradio as gr
9
 
10
  from translate import max_pitch_types
11
+ from data import df, pitch_stats, league_pitch_stats
12
 
13
  # GRADIO FUNCTIONS
14
 
 
48
  ]
49
 
50
 
51
+ def plot_pitch_map(player=None, loc=None, pitch_type=None, pitch_name=None, all_pitches=False, min_pitches=2):
52
  assert not ((loc is None and player is None) or (loc is not None and player is not None)), 'exactly one of `player` or `loc` must be specified'
53
 
54
  if loc is None and player is not None:
55
+ if all_pitches:
56
+ assert not (pitch_type is not None or pitch_name is not None), 'cannot have `pitch_type` or `pitch_name` when `all_pitches` is `True`'
57
+ loc = df.sort_values('name').set_index('name').loc[player, ['plate_x', 'plate_z']]
58
+ else:
59
+ assert not ((pitch_type is None and pitch_name is None) or (pitch_type is not None and pitch_name is not None)), 'exactly one of `pitch_type` or `pitch_name` must be specified'
60
+ pitch_val = pitch_type or pitch_name
61
+ pitch_col = 'pitch_type' if pitch_type else 'pitch_name'
62
+ loc = df.sort_values('name').set_index(['name', pitch_col]).loc[(player, pitch_val), ['plate_x', 'plate_z']]
63
 
64
  fig = go.Figure()
65
+ if len(loc) >= min_pitches:
66
+ Z = fit_pred_kde(loc.to_numpy().T, X, Y)
67
+ fig.add_shape(
68
+ type="rect",
69
+ **coordinatify(sz_h, sz_w),
70
+ line_color='gray',
71
+ # fillcolor='rgba(220, 220, 220, 0.75)', #gainsboro
72
+ )
73
+ fig.add_shape(
74
+ type="rect",
75
+ **coordinatify(h_h, h_w),
76
+ line_color='dimgray',
77
+ )
78
+ fig.add_trace(go.Contour(
79
+ z=Z,
80
+ x=kde_range,
81
+ y=kde_range,
82
+ colorscale=colorscale,
83
+ zmin=1e-5,
84
+ zmax=Z.max(),
85
+ contours={
86
+ 'start': 1e-5,
87
+ 'end': Z.max(),
88
+ 'size': Z.max() / 6
89
+ },
90
+ showscale=False
91
+ ))
92
+ else:
93
+ fig.add_annotation(
94
+ x=0,
95
+ y=0,
96
+ text=f'No visualization<br>as less than {min_pitches} pitches thrown',
97
+ showarrow=False
98
+ )
99
  fig.update_layout(
100
  xaxis=dict(range=[-plot_s/2, plot_s/2+1], showticklabels=False),
101
  yaxis=dict(range=[-plot_s/2, plot_s/2+1], scaleanchor='x', scaleratio=1, showticklabels=False),
 
105
  return fig
106
 
107
 
108
+ # def plot_empty_pitch_map():
109
+ # fig = go.Figure()
110
+ # fig.add_annotation(
111
+ # x=0,
112
+ # y=0,
113
+ # text='No visualization<br>as less than 10 pitches thrown',
114
+ # showarrow=False
115
+ # )
116
+ # fig.update_layout(
117
+ # xaxis=dict(range=[-plot_s/2, plot_s/2+1], showticklabels=False),
118
+ # yaxis=dict(range=[-plot_s/2, plot_s/2+1], scaleanchor='x', scaleratio=1, showticklabels=False),
119
+ # # width=384,
120
+ # # height=384
121
+ # )
122
+ # return fig
123
 
124
  # velo distribution
125
+ def plot_pitch_velo(player=None, velos=None, pitch_type=None, pitch_name=None, min_pitches=2):
126
  assert not ((velos is None and player is None) or (velos is not None and player is not None)), 'exactly one of `player` or `loc` must be specified'
127
 
128
  if velos is None and player is not None:
 
130
  pitch_val = pitch_type or pitch_name
131
  pitch_col = 'pitch_type' if pitch_type else 'pitch_name'
132
  velos = df.set_index(['name', pitch_col]).loc[(player, pitch_val), 'release_speed']
133
+
134
+ if isinstance(velos, int):
135
+ velos = [velos]
136
 
137
+ fig = go.Figure()
138
+ if len(velos) >= min_pitches:
139
+ fig = fig.add_trace(go.Violin(x=velos, side='positive', hoveron='points', points=False, meanline_visible=True, name='Velocity Distribution'))
140
+ median = velos.median()
141
+ x_range = [median-25, median+25]
142
+ else:
143
+ fig.add_annotation(
144
+ x=(170+125)/2,
145
+ y=0.3/2,
146
+ text=f'No visualization<br>as less than {min_pitches} pitches thrown',
147
+ showarrow=False,
148
+ )
149
+ x_range = [125, 170]
150
  fig.update_layout(
151
  xaxis=dict(
152
  title='Velocity',
153
+ range=x_range,
154
  scaleratio=2
155
  ),
156
  yaxis=dict(
 
169
  return fig
170
 
171
 
172
+ # def plot_empty_pitch_velo():
173
+ # fig = go.Figure()
174
+ # fig.add_annotation(
175
+ # x=(170+125)/2,
176
+ # y=0.3/2,
177
+ # text='No visualization<br>as less than 10 pitches thrown',
178
+ # showarrow=False,
179
+ # )
180
+ # fig.update_layout(
181
+ # xaxis=dict(
182
+ # title='Velocity',
183
+ # range=[125, 170],
184
+ # scaleratio=2
185
+ # ),
186
+ # yaxis=dict(
187
+ # title='Frequency',
188
+ # range=[0, 0.3],
189
+ # scaleanchor='x',
190
+ # scaleratio=1,
191
+ # # tickvals=np.linspace(0, 0.3, 3),
192
+ # # ticktext=np.linspace(0, 0.3, 3),
193
+ # tickvals=[0.15],
194
+ # ticktext=[0.15]
195
+ # ),
196
+ # autosize=True,
197
+ # # width=512,
198
+ # # height=256,
199
+ # modebar_remove=['zoom', 'autoScale', 'resetScale'],
200
+ # )
201
+ # return fig
202
+
203
+
204
+ def plot_all_pitch_velo(player=None, player_df=None, pitch_counts=None, min_pitches=2):
205
  # assert not ((player is None and player_df is None) or (player is not None and player_df is not None)), 'exactly one of `player` or `player_df` must be specified'
206
 
207
  if player_df is None and player is not None:
 
247
  fig.add_trace(go.Scatter(
248
  x=[velo_center],
249
  y=[pitch_name],
250
+ text=[f'No visualization as less than {min_pitches} pitches thrown'],
251
  textposition='top center',
252
  hovertext=False,
253
  mode="lines+text",
 
256
  name=pitch_name,
257
  ))
258
 
 
 
 
 
 
 
 
 
 
 
259
  fig.add_trace(go.Violin(
260
  x=league_df['release_speed'],
261
  y=[player]*len(league_df),
 
269
  # visible='legendonly',
270
  name='NPB',
271
  ))
272
+ fig.add_trace(go.Violin(
273
+ x=player_df['release_speed'],
274
+ y=[player]*len(player_df),
275
+ side='positive',
276
+ orientation='h',
277
+ meanline_visible=True,
278
+ points=False,
279
+ legendrank=0,
280
+ name=player
281
+ ))
282
 
283
+ fig.update_xaxes(title='Velocity', range=[player_df['release_speed'].dropna().min() - 2, player_df['release_speed'].dropna().max() + 2])
284
+ fig.update_yaxes(range=[0, len(pitch_counts)+1-0.25], visible=False)
285
+ fig.update_layout(violingap=0, violingroupgap=0, legend=dict(orientation='h', y=-0.15, yanchor='top'))
286
 
287
  return fig
288
 
 
290
  def get_data(player):
291
  player_name = f'# {player}'
292
 
293
+ _df = df.sort_values('name').set_index('name').loc[player]
294
  _df.to_csv(f'files/npb.csv', index=False)
295
+ _df_by_pitch_name = _df.set_index('pitch_name').sort_values('pitch_name')
296
 
297
  usage_fig = px.pie(_df['pitch_name'], names='pitch_name')
298
  usage_fig.update_traces(texttemplate='%{percent:.1%}', hovertemplate=f'<b>{player}</b><br>' + 'threw a <b>%{label}</b><br><b>%{percent:.1%}</b> of the time (<b>%{value}</b> pitches)')
299
 
300
  pitch_counts = _df['pitch_name'].value_counts()
301
+
302
+ pitch_velo_summary = plot_all_pitch_velo(player=player, player_df=_df_by_pitch_name, pitch_counts=pitch_counts.sort_values(ascending=True))
303
+ pitch_loc_summary = plot_pitch_map(player, all_pitches=True)
304
+
305
  pitch_groups = []
306
  pitch_names = []
307
  pitch_infos = []
 
319
  visible=True
320
  ))
321
 
322
+ pitch_velos.append(gr.update(
323
+ value=plot_pitch_velo(velos=_df_by_pitch_name.loc[pitch_name, 'release_speed']),
324
+ visible=True
325
+ ))
326
+ pitch_maps.append(gr.update(
327
+ value=plot_pitch_map(player, pitch_name=pitch_name),
328
+ label='Pitch location',
329
+ visible=True
330
+ ))
 
331
 
332
  for _ in range(max_pitch_types - len(pitch_names)):
333
  pitch_groups.append(gr.update(visible=False))
 
337
  pitch_velos.append(gr.update(value=None, visible=False))
338
  pitch_maps.append(gr.update(value=None, visible=False))
339
 
340
+ velo_stats = pd.concat([pitch_stats.loc[player, 'Velocity'].rename('Avg. Velo'), league_pitch_stats['Velocity'].rename('League Avg. Velo')], join='inner', axis=1).rename_axis(['Pitch']).reset_index()
341
 
342
+ return player_name, 'files/npb.csv', usage_fig, pitch_velo_summary, pitch_loc_summary, *pitch_groups, *pitch_names, *pitch_infos, *pitch_velos, *pitch_maps, velo_stats