patrickramos commited on
Commit
7e9d407
·
1 Parent(s): 8fe9801

Add colors to pitch leaderboard

Browse files
Files changed (4) hide show
  1. convert.py +26 -4
  2. pitch_leaderboard.py +48 -9
  3. pitcher_overview.py +6 -4
  4. stats.py +1 -1
convert.py CHANGED
@@ -311,7 +311,7 @@ game_kind = {
311
  }
312
 
313
  ball_kind_code_to_color = {
314
- '-': '',
315
  '4S': 'crimson',
316
  'SL': 'gold',
317
  'VS': 'khaki',
@@ -326,6 +326,7 @@ ball_kind_code_to_color = {
326
  'SI': 'orange',
327
  'SB': 'lightgreen',
328
  'PB': 'yellowgreen',
 
329
  'SH': 'tomato',
330
  '2S': 'orangered',
331
  '1S': 'lightsalmon',
@@ -336,9 +337,30 @@ ball_kind_code_to_color = {
336
  }
337
 
338
 
339
- ball_kind_code_to_color = {k: v if v else 'C0' for k, v in ball_kind_code_to_color.items()}
340
-
341
  def get_text_color_from_color(color):
342
- if color in ['gold', 'paleturquoise']:
343
  return 'black'
344
  return 'white'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  }
312
 
313
  ball_kind_code_to_color = {
314
+ '-': 'C0',
315
  '4S': 'crimson',
316
  'SL': 'gold',
317
  'VS': 'khaki',
 
326
  'SI': 'orange',
327
  'SB': 'lightgreen',
328
  'PB': 'yellowgreen',
329
+ 'KN': 'mediumblue',
330
  'SH': 'tomato',
331
  '2S': 'orangered',
332
  '1S': 'lightsalmon',
 
337
  }
338
 
339
 
 
 
340
  def get_text_color_from_color(color):
341
+ if color in ['gold', 'paleturquoise', 'turquoise']:
342
  return 'black'
343
  return 'white'
344
+
345
+ ball_kind_to_color = {ball_kind: ball_kind_code_to_color[ball_kind_code[code]] for code, ball_kind in ball_kind.items()}
346
+
347
+ team_to_color = {
348
+ 'G': '#f69727',
349
+ 'S': '#abcd05',
350
+ 'DB': '#004a8f',
351
+ 'D': '#08b1e8',
352
+ 'T': '#ffe100',
353
+ 'C': '#f92b20',
354
+ 'F': '#016299',
355
+ 'E': '#7d001a',
356
+ 'L': '#00214b',
357
+ 'M': '#efefef',
358
+ 'B': '#baa834',
359
+ 'H': '#fcc700'
360
+ }
361
+
362
+ def get_text_color_from_team(team):
363
+ if team in ['DB', 'F', 'L', 'E']:
364
+ return 'white'
365
+ else:
366
+ return 'black'
pitch_leaderboard.py CHANGED
@@ -1,16 +1,19 @@
1
  import gradio as gr
2
  import polars as pl
 
3
 
4
  from datetime import datetime
5
  # from itertools import chain
6
 
7
  from data import data_df
8
  from stats import compute_pitch_stats, filter_data_by_date_and_game_kind
9
- from convert import ball_kind
 
10
 
11
  STATS = ['Count', 'Usage', 'Swing%', 'Z-Swing%', 'Chase%', 'Contact%', 'Z-Contact%', 'O-Contact%', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%', 'Zone%', 'Arm%', 'Glove%', 'High%', 'Low%', 'MM%']
12
  PCT_STATS = ['Usage', 'Swing%', 'Z-Swing%', 'Chase%', 'Contact%', 'Z-Contact%', 'O-Contact%', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%', 'Zone%', 'Arm%', 'Glove%', 'High%', 'Low%', 'MM%']
13
- STATS_WITH_PCTLS = ['Swing%', 'Z-Swing%', 'Chase%', 'Contact%', 'Z-Contact%', 'O-Contact%', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%', 'Zone%']
 
14
 
15
  PITCH_TYPES = [pitch_type for pitch_type in ball_kind.values() if pitch_type != '-']
16
  TEAMS = ['G', 'S', 'DB', 'D', 'T', 'C', 'F', 'E', 'L', 'M', 'B', 'H']
@@ -59,14 +62,50 @@ def gr_create_pitch_leaderboard(start_date, end_date, min_pitches, pitcher_lr='B
59
  compute_pitch_stats(data, player_type='pitcher', min_pitches=min_pitches, pitch_class_type='specific')
60
  .filter(pl.col('qualified') & (pl.col('ballKind').is_in(include_pitches)))
61
  .drop('pitId', 'ballKind_code', 'qualified')
62
- .rename({'pitcher_name': 'Pitcher', 'pitcher_team': 'Team', 'count': 'Count', 'usage': 'Usage', 'ballKind': 'Pitch', 'general_ballKind': 'Pitch (General)'} | {f'{stat}_pctl': f'{stat} (Pctl)' for stat in STATS_WITH_PCTLS})
63
- .with_columns(
64
- pl.col(stat).mul(100).round(1)
65
- for stat in PCT_STATS + [f'{stat} (Pctl)' for stat in STATS_WITH_PCTLS]
66
- )
67
- [['Pitcher', 'Team', 'Pitch', 'Pitch (General)'] + STATS]
68
  )
69
- return pitch_stats
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
 
72
  def create_pitch_leaderboard():
 
1
  import gradio as gr
2
  import polars as pl
3
+ import numpy as np
4
 
5
  from datetime import datetime
6
  # from itertools import chain
7
 
8
  from data import data_df
9
  from stats import compute_pitch_stats, filter_data_by_date_and_game_kind
10
+ from convert import ball_kind, ball_kind_to_color, get_text_color_from_color, team_to_color, get_text_color_from_team
11
+ from plotting import stat_cmap
12
 
13
  STATS = ['Count', 'Usage', 'Swing%', 'Z-Swing%', 'Chase%', 'Contact%', 'Z-Contact%', 'O-Contact%', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%', 'Zone%', 'Arm%', 'Glove%', 'High%', 'Low%', 'MM%']
14
  PCT_STATS = ['Usage', 'Swing%', 'Z-Swing%', 'Chase%', 'Contact%', 'Z-Contact%', 'O-Contact%', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%', 'Zone%', 'Arm%', 'Glove%', 'High%', 'Low%', 'MM%']
15
+ STATS_WITH_PCTLS = ['Swing%', 'Z-Swing%', 'Chase%', 'Contact%', 'Z-Contact%', 'O-Contact%', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%']
16
+ COLUMNS = ['Pitcher', 'Team', 'Pitch', 'Pitch (General)'] + STATS
17
 
18
  PITCH_TYPES = [pitch_type for pitch_type in ball_kind.values() if pitch_type != '-']
19
  TEAMS = ['G', 'S', 'DB', 'D', 'T', 'C', 'F', 'E', 'L', 'M', 'B', 'H']
 
62
  compute_pitch_stats(data, player_type='pitcher', min_pitches=min_pitches, pitch_class_type='specific')
63
  .filter(pl.col('qualified') & (pl.col('ballKind').is_in(include_pitches)))
64
  .drop('pitId', 'ballKind_code', 'qualified')
65
+ .rename({'pitcher_name': 'Pitcher', 'pitcher_team': 'Team', 'count': 'Count', 'usage': 'Usage', 'ballKind': 'Pitch', 'general_ballKind': 'Pitch (General)'})
66
+ # .with_columns(
67
+ # pl.col(stat).mul(100).round(1)
68
+ # for stat in PCT_STATS + [f'{stat}_pctl' for stat in STATS_WITH_PCTLS]
69
+ # )
70
+ # [['Pitcher', 'Team', 'Pitch', 'Pitch (General)'] + STATS + [f'{stat}_pctl' for stat in STATS_WITH_PCTLS]]
71
  )
72
+
73
+ styling = []
74
+ for i, row in enumerate(pitch_stats[COLUMNS].iter_rows()):
75
+ styling_row = []
76
+ for col, item in zip(pitch_stats[COLUMNS].columns, row):
77
+ if f'{col}_pctl' in pitch_stats:
78
+ r, g, b = (stat_cmap([pitch_stats[f'{col}_pctl'][i]])[0, :3]*255).astype(np.uint8)
79
+ styling_row.append(f'background-color: rgba({r}, {g}, {b})')
80
+ elif col == 'Team':
81
+ styling_row.append(f'color: {get_text_color_from_team(item)}; background-color: {team_to_color[item]}')
82
+ elif col in ['Pitch', 'Pitch (General)']:
83
+ color = ball_kind_to_color[item]
84
+ styling_row.append(f'color: {get_text_color_from_color(color)}; background-color: {color}')
85
+ else:
86
+ styling_row.append('')
87
+ styling.append(styling_row)
88
+
89
+ display_value = []
90
+ for row in pitch_stats[COLUMNS].iter_rows():
91
+ display_value_row = []
92
+ for item in row:
93
+ if isinstance(item, float):
94
+ display_value_row.append(f'{item:.1%}')
95
+ else:
96
+ display_value_row.append(item)
97
+ display_value.append(display_value_row)
98
+
99
+ value = {
100
+ 'data': pitch_stats[COLUMNS].rows(),
101
+ 'headers': COLUMNS,
102
+ 'metadata': {
103
+ 'styling': styling,
104
+ 'display_value': display_value,
105
+ }
106
+ }
107
+
108
+ return value
109
 
110
 
111
  def create_pitch_leaderboard():
pitcher_overview.py CHANGED
@@ -10,11 +10,12 @@ 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):
@@ -22,9 +23,10 @@ def dummy(*inputs):
22
 
23
  def gr_create_pitcher_overview_card(name, season):
24
  # pit_id = name.split(' | ')[-1]
25
- pit_id = data_df.filter(pl.col('pitcher_name') == name)['pitId'].unique()
 
26
  if len(pit_id) == 0:
27
- raise gr.Error(f"No data found for {name}. If the name looks strangely spelled or formatted there's a possibility that's what causing the error.")
28
  elif len(pit_id) > 1:
29
  raise gr.Error(f'Multiple IDs for {name}')
30
  else:
@@ -49,7 +51,7 @@ def create_pitcher_overview(data_df):
49
  # names = [f'{pit_name} | {pit_id}' for pit_name, pit_id in data_df[['pitcher_name', 'pitId']].unique().sort('pitId').iter_rows()]
50
  names = data_df['pitcher_name'].unique().sort().to_list()
51
  name = gr.Dropdown(names, label='Name')
52
- season = gr.Dropdown(SEASONS, label='Season')
53
  # season_start = gr.Dropdown(SEASONS, label='Season start')
54
  # season_end = gr.Dropdown(SEASONS, label='Season end')
55
  # game_type = gr.Dropdown(['Spring Training', 'Regular Season', 'Postseason'], label='Game Type'])
 
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 players
14
  - Add teams insignias
15
  - Measure percentiles per pitcher handedness
16
  - Allow for arbitrary date ranges
17
  - Improve readability of pitch velocities
18
+ - Add post-season
19
  '''
20
 
21
  def dummy(*inputs):
 
23
 
24
  def gr_create_pitcher_overview_card(name, season):
25
  # pit_id = name.split(' | ')[-1]
26
+ # hard-coded to only do regular season
27
+ pit_id = data_df.filter((pl.col('pitcher_name') == name) & (pl.col('date').dt.year() == season) & (pl.col('coarse_game_kind') == 'Regular Season'))['pitId'].unique()
28
  if len(pit_id) == 0:
29
+ raise gr.Error(f"No data found for {name} in {season}. If the name looks strangely spelled or formatted there's a possibility that's what causing the error.")
30
  elif len(pit_id) > 1:
31
  raise gr.Error(f'Multiple IDs for {name}')
32
  else:
 
51
  # names = [f'{pit_name} | {pit_id}' for pit_name, pit_id in data_df[['pitcher_name', 'pitId']].unique().sort('pitId').iter_rows()]
52
  names = data_df['pitcher_name'].unique().sort().to_list()
53
  name = gr.Dropdown(names, label='Name')
54
+ season = gr.Dropdown(SEASONS, value=max(SEASONS), label='Season')
55
  # season_start = gr.Dropdown(SEASONS, label='Season start')
56
  # season_end = gr.Dropdown(SEASONS, label='Season end')
57
  # game_type = gr.Dropdown(['Spring Training', 'Regular Season', 'Postseason'], label='Game Type'])
stats.py CHANGED
@@ -99,7 +99,7 @@ def compute_pitch_stats(data, player_type, pitch_class_type, min_pitches=1):
99
  .drop('G', 'F', 'B', 'P', 'L', 'null')
100
  .with_columns(
101
  (pl.when(pl.col('qualified')).then(pl.col(stat)).rank(descending=((stat in ['FB%', 'LD%'] or 'Contact%' in stat)))/pl.when(pl.col('qualified')).then(pl.col(stat)).count()).alias(f'{stat}_pctl')
102
- for stat in ['Zone%', 'Swing%', 'Z-Swing%', 'Chase%', 'Contact%', 'Z-Contact%', 'O-Contact%', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%']
103
  )
104
  .rename({pitch_col: 'ballKind_code', pitch_name_col: 'ballKind'} if pitch_class_type == 'general' else {})
105
  .sort(id_col, 'count', descending=[False, True])
 
99
  .drop('G', 'F', 'B', 'P', 'L', 'null')
100
  .with_columns(
101
  (pl.when(pl.col('qualified')).then(pl.col(stat)).rank(descending=((stat in ['FB%', 'LD%'] or 'Contact%' in stat)))/pl.when(pl.col('qualified')).then(pl.col(stat)).count()).alias(f'{stat}_pctl')
102
+ for stat in ['Swing%', 'Z-Swing%', 'Chase%', 'Contact%', 'Z-Contact%', 'O-Contact%', 'SwStr%', 'Whiff%', 'CSW%', 'GB%', 'FB%', 'LD%']
103
  )
104
  .rename({pitch_col: 'ballKind_code', pitch_name_col: 'ballKind'} if pitch_class_type == 'general' else {})
105
  .sort(id_col, 'count', descending=[False, True])