patrickramos commited on
Commit
bb5e4ba
·
1 Parent(s): 2967b2a

Add features to pitch leaderboard

Browse files
Files changed (3) hide show
  1. app.py +1 -0
  2. pitch_leaderboard.py +50 -28
  3. 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
- def gr_create_pitch_leaderboard(start_date, end_date, min_pitches, include_pitches):
 
 
 
 
 
 
 
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
- both, left, right = [
20
- (
21
- compute_pitch_stats(df, player_type='pitcher', min_pitches=min_pitches, pitch_class_type='specific')
22
- .filter(pl.col('qualified') & (pl.col('ballKind').is_in(include_pitches)))
23
- .drop('qualified')
24
- .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})
25
- .with_columns(
26
- pl.col(stat).mul(100).round(1)
27
- for stat in PCT_STATS + [f'{stat} (Pctl)' for stat in STATS_WITH_PCTLS]
28
- )
29
- [['pitId', 'ballKind_code', 'Pitcher', 'Pitch', 'Pitch (General)', 'Count', 'Usage'] + STATS_WITH_PCTLS]
30
- )
31
- for df
32
- in [data, data.filter(pl.col('batLR') == 'l'), data.filter(pl.col('batLR') == 'r')]
33
- ]
 
 
 
 
 
 
 
 
 
34
  pitch_stats = (
35
- both
36
- .join(left, on=['pitId', 'ballKind_code'], suffix=' (LHH)', how='full')
37
- .join(right, on=['pitId', 'ballKind_code'], suffix=' (RHH)', how='full')
38
- .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')])))
 
 
 
 
 
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=2)
56
- # with gr.Column(scale=1):
57
- # all_pitches = gr.Checkbox(label='Select all pitches')
 
 
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
- # include_pitches.input(lambda _pitch_types: (_pitch_types == pitch_types), inputs=include_pitches, outputs=all_pitches)
71
- # all_pitches.input(lambda _all_pitches : pitch_types, inputs=all_outputs=all_pitches)
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 overview')
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')