Spaces:
Running
Running
Commit
·
7e9d407
1
Parent(s):
8fe9801
Add colors to pitch leaderboard
Browse files- convert.py +26 -4
- pitch_leaderboard.py +48 -9
- pitcher_overview.py +6 -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%'
|
|
|
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)'}
|
63 |
-
.with_columns(
|
64 |
-
pl.col(stat).mul(100).round(1)
|
65 |
-
for stat in PCT_STATS + [f'{stat}
|
66 |
-
)
|
67 |
-
[['Pitcher', 'Team', 'Pitch', 'Pitch (General)'] + STATS]
|
68 |
)
|
69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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 |
-
|
|
|
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 ['
|
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])
|