patrickramos commited on
Commit
3d0c0d1
·
1 Parent(s): e5c3583

Separate apps into tabs

Browse files
Files changed (6) hide show
  1. README.md +1 -1
  2. app.py +25 -0
  3. css.py +12 -0
  4. data.py +2 -3
  5. pitch_leaderboard.py +40 -0
  6. pitcher_dashboard.py +118 -0
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: npb
3
- app_file: demo.py
4
  sdk: gradio
5
  sdk_version: 4.44.1
6
  ---
 
1
  ---
2
  title: npb
3
+ app_file: app.py
4
  sdk: gradio
5
  sdk_version: 4.44.1
6
  ---
app.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ from pitcher_dashboard import create_pitcher_dashboard
4
+ from pitch_leaderboard import create_pitch_leaderboard
5
+ from css import css
6
+
7
+
8
+ with gr.Blocks(
9
+ css=css
10
+ ) as demo:
11
+ gr.Markdown('''
12
+ # NPB data visualization demo
13
+ [Data from SportsNavi](https://sports.yahoo.co.jp/)
14
+ ''')
15
+
16
+ with gr.Tab('Pitcher Dashboard'):
17
+ pitcher_dashboard = create_pitcher_dashboard()
18
+
19
+ with gr.Tab('Pitch Leaderboard'):
20
+ pitch_leaderboard_app = create_pitch_leaderboard()
21
+
22
+ demo.launch(
23
+ share=True,
24
+ debug=True
25
+ )
css.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ css = '''
2
+ .pitch-usage {height: 256px}
3
+ .pitch-usage .js-plotly-plot {height: 100%}
4
+
5
+ .pitch-velo {height: 100px}
6
+ .pitch-velo .js-plotly-plot {height: 100%}
7
+
8
+ .pitch-loc {height: 320px}
9
+ .pitch-loc .js-plotly-plot {height: 100%}
10
+
11
+ .pitch-velo-summary div.plotly-notifier {visibility: hidden}
12
+ '''
data.py CHANGED
@@ -100,8 +100,8 @@ pitch_df = (
100
  pl.col('pitch_name').alias('jp_pitch_name')
101
  )
102
  .with_columns(
103
- pl.col('jp_pitch_name').map_elements(lambda pitch_name: jp_pitch_to_en_pitch[pitch_name], return_dtype=str).alias('pitch_name'),
104
- pl.col('jp_pitch_name').map_elements(lambda pitch_name: jp_pitch_to_pitch_code[pitch_name], return_dtype=str).alias('pitch_type'),
105
  pl.col('description').str.split(' ').list.first().map_elements(translate_pitch_outcome, return_dtype=str),
106
  pl.when(
107
  pl.col('release_speed') != '-'
@@ -168,7 +168,6 @@ pitch_stats, rhb_pitch_stats, lhb_pitch_stats = [
168
  pl.len().alias('Count')
169
  )
170
  .sort(['name', 'Count'], descending=[False, True])
171
- .rename({'name': 'Player', 'pitch_name': 'Pitch'})
172
  )
173
  for _df
174
  in (
 
100
  pl.col('pitch_name').alias('jp_pitch_name')
101
  )
102
  .with_columns(
103
+ pl.col('jp_pitch_name').replace_strict(jp_pitch_to_en_pitch).alias('pitch_name'),
104
+ pl.col('jp_pitch_name').replace_strict(jp_pitch_to_pitch_code).alias('pitch_type'),
105
  pl.col('description').str.split(' ').list.first().map_elements(translate_pitch_outcome, return_dtype=str),
106
  pl.when(
107
  pl.col('release_speed') != '-'
 
168
  pl.len().alias('Count')
169
  )
170
  .sort(['name', 'Count'], descending=[False, True])
 
171
  )
172
  for _df
173
  in (
pitch_leaderboard.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import polars as pl
3
+
4
+ from math import ceil
5
+ import os
6
+
7
+ from data import pitch_stats
8
+ from gradio_function import *
9
+ from css import css
10
+
11
+ pitch_stats = pitch_stats.rename({'name': 'Player', 'pitch_name': 'Pitch'})
12
+
13
+ def filter_pitch_leaderboard(min_pitches):
14
+ return pitch_stats.filter(pl.col('Count') >= min_pitches).sort('CSW%', descending=True)
15
+
16
+ def create_pitch_leaderboard():
17
+ with gr.Blocks(
18
+ css=css
19
+ ) as pitch_leaderboard_app:
20
+ init_min_pitches = 100
21
+ pitch_stats.write_csv('pitch_leaderboard.csv')
22
+ pitch_leaderboard_df = gr.State(filter_pitch_leaderboard(init_min_pitches))
23
+
24
+ min_pitches = gr.Number(init_min_pitches, precision=0, label='Min. Pitches')
25
+ pitch_leaderboard_download_file = gr.DownloadButton(value='pitch_leaderboard.csv', label='Download leaderboard')
26
+ pitch_leaderboard = gr.Dataframe(value=pitch_leaderboard_df.value)
27
+
28
+ min_pitches.change(filter_pitch_leaderboard, inputs=min_pitches, outputs=pitch_leaderboard_df)
29
+ (
30
+ pitch_leaderboard_df
31
+ .change(create_set_download_file_fn('files/pitch_leaderboard.csv'), inputs=pitch_leaderboard_df, outputs=pitch_leaderboard_download_file)
32
+ .then(lambda df: df, inputs=pitch_leaderboard_df, outputs=pitch_leaderboard)
33
+ )
34
+ return pitch_leaderboard_app
35
+
36
+ if __name__ == '__main__':
37
+ create_pitch_leaderboard().launch(
38
+ share=True,
39
+ debug=True
40
+ )
pitcher_dashboard.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
14
+ os.makedirs('files', exist_ok=True)
15
+
16
+ def create_pitcher_dashboard():
17
+ with gr.Blocks(
18
+ css=css
19
+ ) as pitcher_dashboard_app:
20
+ gr.Markdown('''
21
+ # NPB data visualization demo
22
+ [Data from SportsNavi](https://sports.yahoo.co.jp/)
23
+ ''')
24
+
25
+ source_df = gr.State(df)
26
+ app_df = gr.State(df)
27
+ app_league_df = gr.State(df)
28
+ app_pitch_stats = gr.State(pitch_stats)
29
+ app_league_pitch_stats = gr.State(league_pitch_stats)
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')
37
+
38
+ with gr.Group():
39
+ with gr.Row():
40
+ usage = gr.Plot(label='Pitch usage')
41
+ velo_summary = gr.Plot(label='Velocity summary', elem_classes='pitch-velo-summary')
42
+ loc_summary = gr.Plot(label='Overall location')
43
+
44
+ max_locs = len(jp_pitch_to_en_pitch)
45
+ locs_per_row = 4
46
+ max_rows = ceil(max_locs/locs_per_row)
47
+
48
+ gr.Markdown('''
49
+ ## Pitch Locations
50
+ Pitcher's persective
51
+ <br>
52
+ `NPB` refers to the top 10% of pitches thrown across the league with the current search constraints e.g. handedness
53
+ <br>
54
+ Note: To speed up the KDE, we restrict the league-wide pitches to 5,000 pitches
55
+ ''')
56
+ pitch_rows = []
57
+ pitch_groups = []
58
+ pitch_names = []
59
+ pitch_infos = []
60
+ pitch_velos = []
61
+ pitch_locs = []
62
+ for row in range(max_rows):
63
+ visible = row==0
64
+ pitch_row = gr.Row(visible=visible)
65
+ pitch_rows.append(pitch_row)
66
+ with pitch_row:
67
+ _locs_per_row = locs_per_row if row < max_rows-1 else max_locs - locs_per_row * (max_rows - 1)
68
+ for col in range(_locs_per_row):
69
+ with gr.Column(min_width=256):
70
+ pitch_group = gr.Group(visible=visible)
71
+ pitch_groups.append(pitch_group)
72
+ with pitch_group:
73
+ pitch_names.append(gr.Markdown(f'### Pitch {col+1}', visible=visible))
74
+ pitch_infos.append(gr.DataFrame(pl.DataFrame([{'Whiff%': None, 'CSW%': None}]), interactive=False, visible=visible))
75
+ pitch_velos.append(gr.Plot(show_label=False, elem_classes='pitch-velo', visible=visible))
76
+ pitch_locs.append(gr.Plot(label='Pitch Location', elem_classes='pitch-loc', visible=visible))
77
+
78
+ gr.Markdown('## Pitch Velocity')
79
+ velo_stats = gr.DataFrame(pl.DataFrame([{'Avg. Velo': None, 'League Avg. Velo': None}]), interactive=False, label='Pitch Velocity')
80
+
81
+ (
82
+ player
83
+ .input(update_dfs, inputs=[player, handedness, source_df], outputs=[app_df, app_league_df, app_pitch_stats, app_league_pitch_stats])
84
+ .then(lambda : gr.update(value='Both', interactive=True), outputs=handedness)
85
+ )
86
+ handedness.input(update_dfs, inputs=[player, handedness, source_df], outputs=[app_df, app_league_df, app_pitch_stats, app_league_pitch_stats])
87
+
88
+ # app_df.change(preview_df, inputs=app_df, outputs=preview)
89
+ # app_df.change(set_download_file, inputs=app_df, outputs=download_file)
90
+ # app_df.change(plot_usage, inputs=[app_df, player], outputs=usage)
91
+ # app_df.change(plot_velo_summary, inputs=[app_df, app_league_df, player], outputs=velo_summary)
92
+ # app_df.change(lambda df: plot_loc(df), inputs=app_df, outputs=loc_summary)
93
+ # app_df.change(plot_pitch_cards, inputs=[app_df, app_pitch_stats], outputs=pitch_rows+pitch_groups+pitch_names+pitch_infos+pitch_velos+pitch_locs)
94
+ app_pitch_stats.change(update_velo_stats, inputs=[app_pitch_stats, app_league_pitch_stats], outputs=velo_stats)
95
+
96
+ (
97
+ app_df
98
+ .change(create_set_download_file_fn('files/player.csv'), inputs=app_df, outputs=download_file)
99
+ .then(plot_usage, inputs=[app_df, player], outputs=usage)
100
+ .then(plot_velo_summary, inputs=[app_df, app_league_df, player], outputs=velo_summary)
101
+ .then(lambda df: plot_loc(df), inputs=app_df, outputs=loc_summary)
102
+ .then(plot_pitch_cards, inputs=[app_df, app_league_df, app_pitch_stats], outputs=pitch_rows+pitch_groups+pitch_names+pitch_infos+pitch_velos+pitch_locs)
103
+ )
104
+
105
+ gr.Markdown('## Bugs and other notes')
106
+ with gr.Accordion('Click to open', open=False):
107
+ gr.Markdown('''
108
+ - Y axis ticks messy when no velocity distribution is plotted
109
+ - DataFrame precision inconsistent
110
+ '''
111
+ )
112
+ return pitcher_dashboard_app
113
+
114
+ if __name__ == '__main__':
115
+ create_pitcher_dashboard().launch(
116
+ share=True,
117
+ debug=True
118
+ )