Spaces:
Building
Building
Commit
·
bb5e4ba
1
Parent(s):
2967b2a
Add features to pitch leaderboard
Browse files- app.py +1 -0
- pitch_leaderboard.py +50 -28
- pitcher_overview.py +13 -16
app.py
CHANGED
@@ -11,4 +11,5 @@ if __name__ == '__main__':
|
|
11 |
with gr.Tab('Pitch Leaderboard'):
|
12 |
create_pitch_leaderboard()
|
13 |
|
|
|
14 |
app.launch()
|
|
|
11 |
with gr.Tab('Pitch Leaderboard'):
|
12 |
create_pitch_leaderboard()
|
13 |
|
14 |
+
gr.Markdown('Last updated: 2025-07-19')
|
15 |
app.launch()
|
pitch_leaderboard.py
CHANGED
@@ -12,30 +12,51 @@ STATS = ['Count', 'Usage', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%']
|
|
12 |
PCT_STATS = ['Usage', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%']
|
13 |
STATS_WITH_PCTLS = ['SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%']
|
14 |
|
15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
data = data_df.filter(pl.col('ballKind_code') != '-')
|
17 |
|
18 |
data = filter_data_by_date_and_game_kind(data, start_date=start_date, end_date=end_date, game_kind='Regular Season')
|
19 |
-
|
20 |
-
(
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
.
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
pitch_stats = (
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
39 |
)
|
40 |
return pitch_stats
|
41 |
|
@@ -50,14 +71,16 @@ def create_pitch_leaderboard():
|
|
50 |
with gr.Row():
|
51 |
start_date = gr.DateTime(start_datetime_init, include_time=False, type='datetime', label='Start')
|
52 |
end_date = gr.DateTime(end_datetime_init, include_time=False, type='datetime', label='End')
|
53 |
-
min_pitches = gr.Number(100, label='Min. Pitches', precision=0, minimum=0)
|
54 |
with gr.Row():
|
55 |
-
include_pitches = gr.CheckboxGroup(pitch_types, value=pitch_types, label='Pitches', scale=
|
56 |
-
|
57 |
-
|
|
|
|
|
58 |
|
59 |
search = gr.Button('Search')
|
60 |
leaderboard = gr.DataFrame(
|
|
|
61 |
# gr_create_pitch_leaderboard(start_date=start_date_init, end_date=end_date_init, min_pitches=100),
|
62 |
column_widths=[200]*3 + [100]*(3*len(STATS)),
|
63 |
show_copy_button=True,
|
@@ -66,10 +89,9 @@ def create_pitch_leaderboard():
|
|
66 |
)
|
67 |
|
68 |
|
69 |
-
search.click(gr_create_pitch_leaderboard, inputs=[start_date, end_date, min_pitches, include_pitches], outputs=leaderboard)
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
return app
|
74 |
|
75 |
if __name__ == '__main__':
|
|
|
12 |
PCT_STATS = ['Usage', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%']
|
13 |
STATS_WITH_PCTLS = ['SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%']
|
14 |
|
15 |
+
todo = '''
|
16 |
+
**To-do**
|
17 |
+
- Color cells according to percentiles
|
18 |
+
'''
|
19 |
+
|
20 |
+
def gr_create_pitch_leaderboard(start_date, end_date, min_pitches, pitcher_lr, include_pitches):
|
21 |
+
assert pitcher_lr in ['Both', 'Left', 'Right']
|
22 |
+
|
23 |
data = data_df.filter(pl.col('ballKind_code') != '-')
|
24 |
|
25 |
data = filter_data_by_date_and_game_kind(data, start_date=start_date, end_date=end_date, game_kind='Regular Season')
|
26 |
+
if pitcher_lr != 'Both':
|
27 |
+
data = data.filter(pl.col('batLR') == pitcher_lr[0].lower())
|
28 |
+
|
29 |
+
# both, left, right = [
|
30 |
+
# (
|
31 |
+
# compute_pitch_stats(df, player_type='pitcher', min_pitches=min_pitches, pitch_class_type='specific')
|
32 |
+
# .filter(pl.col('qualified') & (pl.col('ballKind').is_in(include_pitches)))
|
33 |
+
# .drop('qualified')
|
34 |
+
# .rename({'pitcher_name': 'Pitcher', 'count': 'Count', 'usage': 'Usage', 'ballKind': 'Pitch', 'general_ballKind': 'Pitch (General)'} | {f'{stat}_pctl': f'{stat} (Pctl)' for stat in STATS_WITH_PCTLS})
|
35 |
+
# .with_columns(
|
36 |
+
# pl.col(stat).mul(100).round(1)
|
37 |
+
# for stat in PCT_STATS + [f'{stat} (Pctl)' for stat in STATS_WITH_PCTLS]
|
38 |
+
# )
|
39 |
+
# [['pitId', 'ballKind_code', 'Pitcher', 'Pitch', 'Pitch (General)', 'Count', 'Usage'] + STATS_WITH_PCTLS]
|
40 |
+
# )
|
41 |
+
# for df
|
42 |
+
# in [data, data.filter(pl.col('batLR') == 'l'), data.filter(pl.col('batLR') == 'r')]
|
43 |
+
# ]
|
44 |
+
# pitch_stats = (
|
45 |
+
# both
|
46 |
+
# .join(left, on=['pitId', 'ballKind_code'], suffix=' (LHH)', how='full')
|
47 |
+
# .join(right, on=['pitId', 'ballKind_code'], suffix=' (RHH)', how='full')
|
48 |
+
# .drop('pitId', 'ballKind_code', *list(chain.from_iterable([[f'{col} ({handedness}HH)' for col in ['pitId', 'ballKind_code', 'Pitcher', 'Pitch', 'Pitch (General)']] for handedness in ('L', 'R')])))
|
49 |
+
# )
|
50 |
pitch_stats = (
|
51 |
+
compute_pitch_stats(data, player_type='pitcher', min_pitches=min_pitches, pitch_class_type='specific')
|
52 |
+
.filter(pl.col('qualified') & (pl.col('ballKind').is_in(include_pitches)))
|
53 |
+
.drop('pitId', 'ballKind_code', 'qualified')
|
54 |
+
.rename({'pitcher_name': 'Pitcher', 'count': 'Count', 'usage': 'Usage', 'ballKind': 'Pitch', 'general_ballKind': 'Pitch (General)'} | {f'{stat}_pctl': f'{stat} (Pctl)' for stat in STATS_WITH_PCTLS})
|
55 |
+
.with_columns(
|
56 |
+
pl.col(stat).mul(100).round(1)
|
57 |
+
for stat in PCT_STATS + [f'{stat} (Pctl)' for stat in STATS_WITH_PCTLS]
|
58 |
+
)
|
59 |
+
[['Pitcher', 'Pitch', 'Pitch (General)', 'Count', 'Usage'] + STATS_WITH_PCTLS]
|
60 |
)
|
61 |
return pitch_stats
|
62 |
|
|
|
71 |
with gr.Row():
|
72 |
start_date = gr.DateTime(start_datetime_init, include_time=False, type='datetime', label='Start')
|
73 |
end_date = gr.DateTime(end_datetime_init, include_time=False, type='datetime', label='End')
|
|
|
74 |
with gr.Row():
|
75 |
+
include_pitches = gr.CheckboxGroup(pitch_types, value=pitch_types, label='Pitches', scale=3)
|
76 |
+
with gr.Column(scale=1):
|
77 |
+
all_pitches = gr.Button('Select/Deselect all pitches', scale=1)
|
78 |
+
min_pitches = gr.Number(100, label='Min. Pitches', precision=0, minimum=0)
|
79 |
+
pitcher_lr = gr.Radio(['Both', 'Left', 'Right'], value='Both', label='Batter handedness')
|
80 |
|
81 |
search = gr.Button('Search')
|
82 |
leaderboard = gr.DataFrame(
|
83 |
+
pl.DataFrame({'Pitcher': [], 'Pitch': []}),
|
84 |
# gr_create_pitch_leaderboard(start_date=start_date_init, end_date=end_date_init, min_pitches=100),
|
85 |
column_widths=[200]*3 + [100]*(3*len(STATS)),
|
86 |
show_copy_button=True,
|
|
|
89 |
)
|
90 |
|
91 |
|
92 |
+
search.click(gr_create_pitch_leaderboard, inputs=[start_date, end_date, min_pitches, pitcher_lr, include_pitches], outputs=leaderboard)
|
93 |
+
all_pitches.click(lambda _pitch_types : [] if _pitch_types == pitch_types else pitch_types, inputs=include_pitches, outputs=include_pitches)
|
94 |
+
gr.Markdown(todo)
|
|
|
95 |
return app
|
96 |
|
97 |
if __name__ == '__main__':
|
pitcher_overview.py
CHANGED
@@ -6,6 +6,17 @@ from data import SEASONS, data_df
|
|
6 |
|
7 |
from plotting import create_pitcher_overview_card
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
def dummy(*inputs):
|
10 |
return inputs
|
11 |
|
@@ -31,7 +42,7 @@ def gr_create_pitcher_overview_card(name, season):
|
|
31 |
|
32 |
def create_pitcher_overview(data_df):
|
33 |
with gr.Blocks() as app:
|
34 |
-
gr.Markdown('# Pitcher
|
35 |
|
36 |
with gr.Row():
|
37 |
with gr.Column():
|
@@ -43,21 +54,7 @@ def create_pitcher_overview(data_df):
|
|
43 |
# season_end = gr.Dropdown(SEASONS, label='Season end')
|
44 |
# game_type = gr.Dropdown(['Spring Training', 'Regular Season', 'Postseason'], label='Game Type'])
|
45 |
view = gr.Button('View')
|
46 |
-
gr.Markdown(
|
47 |
-
'''
|
48 |
-
**Limitations**
|
49 |
-
- Foreign players names are in Hebpurn romanization. Contact me if you need a card for a foreign player.
|
50 |
-
|
51 |
-
**To-do**
|
52 |
-
- Fix names of foreign playeres
|
53 |
-
- Add teams insignias
|
54 |
-
- Measure percentiles per pitcher handedness
|
55 |
-
- Allow for arbitrary date ranges
|
56 |
-
- Improve readability of pitch velocities
|
57 |
-
|
58 |
-
Last updated: 2025-07-19
|
59 |
-
'''
|
60 |
-
)
|
61 |
|
62 |
with gr.Column():
|
63 |
overview_card = gr.Image(label='Overview')
|
|
|
6 |
|
7 |
from plotting import create_pitcher_overview_card
|
8 |
|
9 |
+
notes = '''**Limitations**
|
10 |
+
- Foreign players names are in Hebpurn romanization. Contact me if you need a card for a foreign player.
|
11 |
+
|
12 |
+
**To-do**
|
13 |
+
- Fix names of foreign playeres
|
14 |
+
- Add teams insignias
|
15 |
+
- Measure percentiles per pitcher handedness
|
16 |
+
- Allow for arbitrary date ranges
|
17 |
+
- Improve readability of pitch velocities
|
18 |
+
'''
|
19 |
+
|
20 |
def dummy(*inputs):
|
21 |
return inputs
|
22 |
|
|
|
42 |
|
43 |
def create_pitcher_overview(data_df):
|
44 |
with gr.Blocks() as app:
|
45 |
+
gr.Markdown('# Pitcher Overview')
|
46 |
|
47 |
with gr.Row():
|
48 |
with gr.Column():
|
|
|
54 |
# season_end = gr.Dropdown(SEASONS, label='Season end')
|
55 |
# game_type = gr.Dropdown(['Spring Training', 'Regular Season', 'Postseason'], label='Game Type'])
|
56 |
view = gr.Button('View')
|
57 |
+
gr.Markdown(notes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
with gr.Column():
|
60 |
overview_card = gr.Image(label='Overview')
|