Spaces:
Build error
Build error
Commit
·
eaf2663
1
Parent(s):
c0335c8
Add date filtering for pitcher dashboard
Browse files- daily_weekly_leaderboard.py +1 -1
- data.py +8 -6
- gradio_function.py +29 -25
- pitch_leaderboard.py +4 -3
- pitcher_dashboard.py +17 -2
- seasons.py +5 -0
daily_weekly_leaderboard.py
CHANGED
@@ -10,7 +10,7 @@ import datetime
|
|
10 |
df = (
|
11 |
df
|
12 |
# .join(game_df, on='game_pk')
|
13 |
-
.with_columns(pl.col('game_date').str.to_datetime())
|
14 |
.rename({
|
15 |
'name': 'Name',
|
16 |
'release_speed': 'Velocity',
|
|
|
10 |
df = (
|
11 |
df
|
12 |
# .join(game_df, on='game_pk')
|
13 |
+
# .with_columns(pl.col('game_date').str.to_datetime())
|
14 |
.rename({
|
15 |
'name': 'Name',
|
16 |
'release_speed': 'Velocity',
|
data.py
CHANGED
@@ -8,6 +8,7 @@ from tqdm.auto import tqdm
|
|
8 |
import os
|
9 |
import re
|
10 |
|
|
|
11 |
from translate import (
|
12 |
translate_pa_outcome, translate_pitch_outcome,
|
13 |
jp_pitch_to_en_pitch, jp_pitch_to_pitch_code,
|
@@ -31,7 +32,7 @@ def identify_bb_type(hit_type):
|
|
31 |
|
32 |
|
33 |
DATA_DIR = 'data'
|
34 |
-
SEASONS =
|
35 |
|
36 |
game_df, pa_df, pitch_df, player_df, df = [], [], [], [], []
|
37 |
|
@@ -117,10 +118,10 @@ for season in SEASONS:
|
|
117 |
pl.col('pitch_name').alias('jp_pitch_name')
|
118 |
)
|
119 |
.with_columns(
|
120 |
-
pl.col('jp_pitch_name').map_elements(lambda pitch_name: jp_pitch_to_en_pitch[pitch_name], return_dtype=str).alias('pitch_name'),
|
121 |
-
|
122 |
-
pl.col('jp_pitch_name').map_elements(lambda pitch_name: jp_pitch_to_pitch_code[pitch_name], return_dtype=str).alias('pitch_type'),
|
123 |
-
|
124 |
pl.col('description').str.split(' ').list.first().map_elements(translate_pitch_outcome, return_dtype=str),
|
125 |
pl.when(
|
126 |
pl.col('release_speed') != '-'
|
@@ -167,7 +168,8 @@ for season in SEASONS:
|
|
167 |
pl.col('description').is_in(['SS', 'K']).alias('whiff'),
|
168 |
~pl.col('description').is_in(['B', 'BB', 'LS', 'inv_K', 'bunt_K', 'HBP', 'SH', 'SH E', 'SH FC', 'obstruction', 'illegal_pitch', 'defensive_interference']).alias('swing'),
|
169 |
pl.col('description').is_in(['SS', 'K', 'LS', 'inv_K']).alias('csw'),
|
170 |
-
~pl.col('description').is_in(['obstruction', 'illegal_pitch', 'defensive_interference']).alias('normal_pitch') # guess
|
|
|
171 |
)
|
172 |
).sort(['game_pk', 'pa_pk', 'pitch_id'])
|
173 |
|
|
|
8 |
import os
|
9 |
import re
|
10 |
|
11 |
+
from seasons import SEASONS
|
12 |
from translate import (
|
13 |
translate_pa_outcome, translate_pitch_outcome,
|
14 |
jp_pitch_to_en_pitch, jp_pitch_to_pitch_code,
|
|
|
32 |
|
33 |
|
34 |
DATA_DIR = 'data'
|
35 |
+
SEASONS = [str(season) for season in SEASONS]
|
36 |
|
37 |
game_df, pa_df, pitch_df, player_df, df = [], [], [], [], []
|
38 |
|
|
|
118 |
pl.col('pitch_name').alias('jp_pitch_name')
|
119 |
)
|
120 |
.with_columns(
|
121 |
+
# pl.col('jp_pitch_name').map_elements(lambda pitch_name: jp_pitch_to_en_pitch[pitch_name], return_dtype=str).alias('pitch_name'),
|
122 |
+
pl.col('jp_pitch_name').replace_strict(jp_pitch_to_en_pitch).alias('pitch_name'),
|
123 |
+
# pl.col('jp_pitch_name').map_elements(lambda pitch_name: jp_pitch_to_pitch_code[pitch_name], return_dtype=str).alias('pitch_type'),
|
124 |
+
pl.col('jp_pitch_name').replace_strict(jp_pitch_to_pitch_code).alias('pitch_type'),
|
125 |
pl.col('description').str.split(' ').list.first().map_elements(translate_pitch_outcome, return_dtype=str),
|
126 |
pl.when(
|
127 |
pl.col('release_speed') != '-'
|
|
|
168 |
pl.col('description').is_in(['SS', 'K']).alias('whiff'),
|
169 |
~pl.col('description').is_in(['B', 'BB', 'LS', 'inv_K', 'bunt_K', 'HBP', 'SH', 'SH E', 'SH FC', 'obstruction', 'illegal_pitch', 'defensive_interference']).alias('swing'),
|
170 |
pl.col('description').is_in(['SS', 'K', 'LS', 'inv_K']).alias('csw'),
|
171 |
+
~pl.col('description').is_in(['obstruction', 'illegal_pitch', 'defensive_interference']).alias('normal_pitch'), # guess
|
172 |
+
pl.col('game_date').str.to_datetime()
|
173 |
)
|
174 |
).sort(['game_pk', 'pa_pk', 'pitch_id'])
|
175 |
|
gradio_function.py
CHANGED
@@ -26,20 +26,20 @@ INSUFFICIENT_PITCHES_MSG_MULTI_LINE = 'No visualization:<br>Not enough pitches t
|
|
26 |
|
27 |
# GRADIO FUNCTIONS
|
28 |
|
29 |
-
def clone_if_dataframe(item):
|
30 |
-
if isinstance(item, pl.DataFrame):
|
31 |
-
# print(type(item))
|
32 |
-
return item.clone()
|
33 |
-
else:
|
34 |
-
return item
|
35 |
-
|
36 |
-
def clone_df(fn):
|
37 |
-
def _fn(*args, **kwargs):
|
38 |
-
args = [clone_if_dataframe(arg) for arg in args]
|
39 |
-
kwargs = {k: clone_if_dataframe(arg) for k, arg in kwargs.items()}
|
40 |
-
return fn(*args, **kwargs)
|
41 |
-
return _fn
|
42 |
-
|
43 |
def copy_dataframe(df, num_copy_to):
|
44 |
return [df.clone() for _ in range(num_copy_to)]
|
45 |
|
@@ -79,7 +79,7 @@ colorscale = [
|
|
79 |
]
|
80 |
|
81 |
|
82 |
-
@clone_df
|
83 |
def plot_loc(df, handedness, league_df=None, min_pitches=3, max_pitches=5000):
|
84 |
|
85 |
loc = df.select(['plate_x', 'plate_z'])
|
@@ -166,7 +166,7 @@ def plot_loc(df, handedness, league_df=None, min_pitches=3, max_pitches=5000):
|
|
166 |
|
167 |
|
168 |
# velo distribution
|
169 |
-
@clone_df
|
170 |
def plot_velo(df=None, player=None, velos=None, pitch_type=None, pitch_name=None, min_pitches=2):
|
171 |
assert not ((velos is None and player is None) or (velos is not None and player is not None)), 'exactly one of `player` or `velos` must be specified'
|
172 |
|
@@ -212,7 +212,7 @@ def plot_velo(df=None, player=None, velos=None, pitch_type=None, pitch_name=None
|
|
212 |
)
|
213 |
return fig
|
214 |
|
215 |
-
@clone_df
|
216 |
def plot_velo_summary(df, league_df, player):
|
217 |
|
218 |
min_pitches = 2
|
@@ -227,7 +227,9 @@ def plot_velo_summary(df, league_df, player):
|
|
227 |
|
228 |
fig = go.Figure()
|
229 |
|
230 |
-
|
|
|
|
|
231 |
# for i, (pitch_name, count) in enumerate(pitch_counts.items()):
|
232 |
for i, (pitch_name, count) in enumerate(pitch_counts.iter_rows()):
|
233 |
# velos = player_df.loc[pitch_name, 'release_speed']
|
@@ -299,7 +301,7 @@ def plot_velo_summary(df, league_df, player):
|
|
299 |
# ))
|
300 |
|
301 |
# fig.update_xaxes(title='Velocity', range=[player_df['release_speed'].dropna().min() - 2, player_df['release_speed'].dropna().max() + 2])
|
302 |
-
fig.update_xaxes(title='Velocity', range=[
|
303 |
# fig.update_yaxes(range=[0, len(pitch_counts)+1-0.25], visible=False)
|
304 |
fig.update_yaxes(range=[0, len(pitch_counts)-0.25], visible=False)
|
305 |
fig.update_layout(
|
@@ -313,7 +315,8 @@ def plot_velo_summary(df, league_df, player):
|
|
313 |
return fig
|
314 |
|
315 |
|
316 |
-
def update_dfs(player, handedness, df):
|
|
|
317 |
if handedness == 'Both':
|
318 |
handedness_filter = pl.col('stand').is_in(['R', 'L'])
|
319 |
# _pitch_stats = pitch_stats
|
@@ -327,9 +330,10 @@ def update_dfs(player, handedness, df):
|
|
327 |
# _pitch_stats = lhb_pitch_stats
|
328 |
# _league_pitch_stats = lhb_league_pitch_stats
|
329 |
player_filter = pl.col('name') == player
|
330 |
-
|
|
|
331 |
_df = df.filter(final_filter)
|
332 |
-
_league_df = df.filter(
|
333 |
|
334 |
return (
|
335 |
_df,
|
@@ -347,13 +351,13 @@ def create_set_download_file_fn(filepath):
|
|
347 |
def preview_df(df):
|
348 |
return df.head()
|
349 |
|
350 |
-
@clone_df
|
351 |
def plot_usage(df, player):
|
352 |
fig = px.pie(df.select('pitch_name'), names='pitch_name')
|
353 |
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)')
|
354 |
return fig
|
355 |
|
356 |
-
@clone_df
|
357 |
def plot_pitch_cards(df, league_df, pitch_stats, handedness):
|
358 |
pitch_counts = df['pitch_name'].value_counts().sort('count', descending=True)
|
359 |
|
@@ -400,7 +404,7 @@ def plot_pitch_cards(df, league_df, pitch_stats, handedness):
|
|
400 |
|
401 |
return pitch_rows + pitch_groups + pitch_names + pitch_infos + pitch_velos + pitch_locs
|
402 |
|
403 |
-
@clone_df
|
404 |
def update_velo_stats(pitch_stats, league_pitch_stats):
|
405 |
return (
|
406 |
pitch_stats
|
|
|
26 |
|
27 |
# GRADIO FUNCTIONS
|
28 |
|
29 |
+
# def clone_if_dataframe(item):
|
30 |
+
# if isinstance(item, pl.DataFrame):
|
31 |
+
# # print(type(item))
|
32 |
+
# return item.clone()
|
33 |
+
# else:
|
34 |
+
# return item
|
35 |
+
#
|
36 |
+
# def clone_df(fn):
|
37 |
+
# def _fn(*args, **kwargs):
|
38 |
+
# args = [clone_if_dataframe(arg) for arg in args]
|
39 |
+
# kwargs = {k: clone_if_dataframe(arg) for k, arg in kwargs.items()}
|
40 |
+
# return fn(*args, **kwargs)
|
41 |
+
# return _fn
|
42 |
+
#
|
43 |
def copy_dataframe(df, num_copy_to):
|
44 |
return [df.clone() for _ in range(num_copy_to)]
|
45 |
|
|
|
79 |
]
|
80 |
|
81 |
|
82 |
+
# @clone_df
|
83 |
def plot_loc(df, handedness, league_df=None, min_pitches=3, max_pitches=5000):
|
84 |
|
85 |
loc = df.select(['plate_x', 'plate_z'])
|
|
|
166 |
|
167 |
|
168 |
# velo distribution
|
169 |
+
# @clone_df
|
170 |
def plot_velo(df=None, player=None, velos=None, pitch_type=None, pitch_name=None, min_pitches=2):
|
171 |
assert not ((velos is None and player is None) or (velos is not None and player is not None)), 'exactly one of `player` or `velos` must be specified'
|
172 |
|
|
|
212 |
)
|
213 |
return fig
|
214 |
|
215 |
+
# @clone_df
|
216 |
def plot_velo_summary(df, league_df, player):
|
217 |
|
218 |
min_pitches = 2
|
|
|
227 |
|
228 |
fig = go.Figure()
|
229 |
|
230 |
+
min_velo = player_df['release_speed'].min() if len(player_df) else 130
|
231 |
+
max_velo = player_df['release_speed'].max() if len(player_df) else 160
|
232 |
+
velo_center = (min_velo + max_velo) / 2
|
233 |
# for i, (pitch_name, count) in enumerate(pitch_counts.items()):
|
234 |
for i, (pitch_name, count) in enumerate(pitch_counts.iter_rows()):
|
235 |
# velos = player_df.loc[pitch_name, 'release_speed']
|
|
|
301 |
# ))
|
302 |
|
303 |
# fig.update_xaxes(title='Velocity', range=[player_df['release_speed'].dropna().min() - 2, player_df['release_speed'].dropna().max() + 2])
|
304 |
+
fig.update_xaxes(title='Velocity', range=[min_velo - 2, max_velo + 2])
|
305 |
# fig.update_yaxes(range=[0, len(pitch_counts)+1-0.25], visible=False)
|
306 |
fig.update_yaxes(range=[0, len(pitch_counts)-0.25], visible=False)
|
307 |
fig.update_layout(
|
|
|
315 |
return fig
|
316 |
|
317 |
|
318 |
+
def update_dfs(player, handedness, start_date, end_date, df):
|
319 |
+
date_filter = (pl.col('game_date') >= start_date) & (pl.col('game_date') <= end_date)
|
320 |
if handedness == 'Both':
|
321 |
handedness_filter = pl.col('stand').is_in(['R', 'L'])
|
322 |
# _pitch_stats = pitch_stats
|
|
|
330 |
# _pitch_stats = lhb_pitch_stats
|
331 |
# _league_pitch_stats = lhb_league_pitch_stats
|
332 |
player_filter = pl.col('name') == player
|
333 |
+
non_player_filter = handedness_filter & date_filter
|
334 |
+
final_filter = player_filter & non_player_filter
|
335 |
_df = df.filter(final_filter)
|
336 |
+
_league_df = df.filter(non_player_filter)
|
337 |
|
338 |
return (
|
339 |
_df,
|
|
|
351 |
def preview_df(df):
|
352 |
return df.head()
|
353 |
|
354 |
+
# @clone_df
|
355 |
def plot_usage(df, player):
|
356 |
fig = px.pie(df.select('pitch_name'), names='pitch_name')
|
357 |
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)')
|
358 |
return fig
|
359 |
|
360 |
+
# @clone_df
|
361 |
def plot_pitch_cards(df, league_df, pitch_stats, handedness):
|
362 |
pitch_counts = df['pitch_name'].value_counts().sort('count', descending=True)
|
363 |
|
|
|
404 |
|
405 |
return pitch_rows + pitch_groups + pitch_names + pitch_infos + pitch_velos + pitch_locs
|
406 |
|
407 |
+
# @clone_df
|
408 |
def update_velo_stats(pitch_stats, league_pitch_stats):
|
409 |
return (
|
410 |
pitch_stats
|
pitch_leaderboard.py
CHANGED
@@ -2,11 +2,12 @@ import gradio as gr
|
|
2 |
import polars as pl
|
3 |
|
4 |
|
5 |
-
from data import df, game_df, compute_pitch_stats
|
|
|
6 |
from gradio_function import *
|
7 |
from css import css
|
8 |
|
9 |
-
SEASONS = [int(season) for season in SEASONS]
|
10 |
|
11 |
def filter_pitch_leaderboard(season, min_pitches):
|
12 |
return (
|
@@ -21,7 +22,7 @@ def create_pitch_leaderboard():
|
|
21 |
css=css
|
22 |
) as demo:
|
23 |
init_min_pitches = 100
|
24 |
-
init_season =
|
25 |
init_pitch_stats = filter_pitch_leaderboard(init_season, init_min_pitches)
|
26 |
init_pitch_stats.write_csv('pitch_leaderboard.csv')
|
27 |
pitch_leaderboard_df = gr.State(init_pitch_stats)
|
|
|
2 |
import polars as pl
|
3 |
|
4 |
|
5 |
+
from data import df, game_df, compute_pitch_stats
|
6 |
+
from seasons import SEASONS, LATEST_SEASON
|
7 |
from gradio_function import *
|
8 |
from css import css
|
9 |
|
10 |
+
# SEASONS = [int(season) for season in SEASONS]
|
11 |
|
12 |
def filter_pitch_leaderboard(season, min_pitches):
|
13 |
return (
|
|
|
22 |
css=css
|
23 |
) as demo:
|
24 |
init_min_pitches = 100
|
25 |
+
init_season = LATEST_SEASON
|
26 |
init_pitch_stats = filter_pitch_leaderboard(init_season, init_min_pitches)
|
27 |
init_pitch_stats.write_csv('pitch_leaderboard.csv')
|
28 |
pitch_leaderboard_df = gr.State(init_pitch_stats)
|
pitcher_dashboard.py
CHANGED
@@ -1,13 +1,16 @@
|
|
1 |
|
2 |
import gradio as gr
|
3 |
# import pandas as pd
|
|
|
4 |
import polars as pl
|
5 |
|
6 |
from math import ceil
|
|
|
7 |
import os
|
8 |
|
9 |
from data import df, pitch_stats, league_pitch_stats, player_df
|
10 |
from gradio_function import *
|
|
|
11 |
from translate import jp_pitch_to_en_pitch, max_pitch_types
|
12 |
from css import css
|
13 |
|
@@ -30,7 +33,10 @@ def create_pitcher_dashboard():
|
|
30 |
|
31 |
with gr.Row():
|
32 |
player = gr.Dropdown(value=None, choices=sorted(player_df.filter(pl.col('name').is_not_null())['name'].to_list()), label='Player')
|
|
|
|
|
33 |
handedness = gr.Radio(value='Both', choices=['Both', 'Left', 'Right'], type='value', interactive=False, label='Batter Handedness')
|
|
|
34 |
|
35 |
# preview = gr.DataFrame()
|
36 |
download_file = gr.DownloadButton(label='Download player data')
|
@@ -91,12 +97,21 @@ def create_pitcher_dashboard():
|
|
91 |
fn_configs[k]['df'] = gr.State(df)
|
92 |
fn_configs[k]['inputs'] = [fn_configs[k]['df']] + fn_configs[k]['inputs']
|
93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
(
|
95 |
player
|
96 |
-
.input(
|
97 |
.then(lambda : gr.update(value='Both', interactive=True), outputs=handedness)
|
|
|
98 |
)
|
99 |
-
|
|
|
|
|
100 |
|
101 |
# app_df.change(preview_df, inputs=app_df, outputs=preview)
|
102 |
# app_df.change(set_download_file, inputs=app_df, outputs=download_file)
|
|
|
1 |
|
2 |
import gradio as gr
|
3 |
# import pandas as pd
|
4 |
+
from gradio_calendar import Calendar
|
5 |
import polars as pl
|
6 |
|
7 |
from math import ceil
|
8 |
+
import datetime
|
9 |
import os
|
10 |
|
11 |
from data import df, pitch_stats, league_pitch_stats, player_df
|
12 |
from gradio_function import *
|
13 |
+
from seasons import LATEST_SEASON
|
14 |
from translate import jp_pitch_to_en_pitch, max_pitch_types
|
15 |
from css import css
|
16 |
|
|
|
33 |
|
34 |
with gr.Row():
|
35 |
player = gr.Dropdown(value=None, choices=sorted(player_df.filter(pl.col('name').is_not_null())['name'].to_list()), label='Player')
|
36 |
+
start_date = Calendar(value=f'{LATEST_SEASON}-03-01', type='datetime', label='Start Date')
|
37 |
+
end_date = Calendar(value=f'{LATEST_SEASON}-11-30', type='datetime', label='End Date')
|
38 |
handedness = gr.Radio(value='Both', choices=['Both', 'Left', 'Right'], type='value', interactive=False, label='Batter Handedness')
|
39 |
+
gr.Markdown('Note: We do not have spring training data, or 2024 postseason data')
|
40 |
|
41 |
# preview = gr.DataFrame()
|
42 |
download_file = gr.DownloadButton(label='Download player data')
|
|
|
97 |
fn_configs[k]['df'] = gr.State(df)
|
98 |
fn_configs[k]['inputs'] = [fn_configs[k]['df']] + fn_configs[k]['inputs']
|
99 |
|
100 |
+
update_dfs_kwargs = dict(
|
101 |
+
fn=update_dfs,
|
102 |
+
inputs=[player, handedness, start_date, end_date, source_df],
|
103 |
+
outputs=[app_df, app_league_df, app_pitch_stats, app_league_pitch_stats]
|
104 |
+
)
|
105 |
+
non_player_search_inputs = [handedness, start_date, end_date]
|
106 |
(
|
107 |
player
|
108 |
+
.input(**update_dfs_kwargs)
|
109 |
.then(lambda : gr.update(value='Both', interactive=True), outputs=handedness)
|
110 |
+
# .then(lambda: [gr.update(interactive=True) for _ in range(len(non_player_search_inputs))], outputs=non_player_search_inputs) # breaks Calendar for some reason
|
111 |
)
|
112 |
+
for component in non_player_search_inputs:
|
113 |
+
component.input(**update_dfs_kwargs)
|
114 |
+
# start_date.input(**update_dfs_kwargs)
|
115 |
|
116 |
# app_df.change(preview_df, inputs=app_df, outputs=preview)
|
117 |
# app_df.change(set_download_file, inputs=app_df, outputs=download_file)
|
seasons.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
DATA_DIR = 'data'
|
4 |
+
SEASONS = sorted([int(folder) for folder in os.listdir(DATA_DIR) if not folder.startswith('.')])
|
5 |
+
LATEST_SEASON = max(SEASONS)
|