nesticot commited on
Commit
e7e6a62
·
verified ·
1 Parent(s): b1c6329

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +399 -399
app.py CHANGED
@@ -1,400 +1,400 @@
1
- import polars as pl
2
- import numpy as np
3
- import pandas as pd
4
- import api_scraper
5
- scrape = api_scraper.MLB_Scrape()
6
- from functions import df_update
7
- from functions import pitch_summary_functions
8
- update = df_update.df_update()
9
- from stuff_model import feature_engineering as fe
10
- from stuff_model import stuff_apply
11
- import requests
12
- import joblib
13
- from matplotlib.gridspec import GridSpec
14
- from shiny import App, reactive, ui, render
15
- from shiny.ui import h2, tags
16
- import matplotlib.pyplot as plt
17
- import matplotlib.gridspec as gridspec
18
- import seaborn as sns
19
- from functions.pitch_summary_functions import *
20
- from shiny import App, reactive, ui, render
21
- from shiny.ui import h2, tags
22
-
23
- colour_palette = ['#FFB000','#648FFF','#785EF0',
24
- '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
25
-
26
-
27
- year_list = [2017,2018,2019,2020,2021,2022,2023,2024]
28
-
29
-
30
-
31
- level_dict = {'1':'MLB',
32
- '11':'AAA',
33
- # '12':'AA',
34
- #'13':'A+',
35
- '14':'A',
36
- '17':'AFL',
37
- '22':'College',
38
- '21':'Prospects',
39
- '51':'International' }
40
-
41
- function_dict={
42
- 'velocity_kdes':'Velocity Distributions',
43
- 'break_plot':'Pitch Movement',
44
- 'tj_stuff_roling':'Rolling tjStuff+ by Pitch',
45
- 'tj_stuff_roling_game':'Rolling tjStuff+ by Game',
46
- 'location_plot_lhb':'Locations vs LHB',
47
- 'location_plot_rhb':'Locations vs RHB',
48
- }
49
-
50
-
51
- split_dict = {'all':'All',
52
- 'left':'LHH',
53
- 'right':'RHH'}
54
-
55
- split_dict_hand = {'all':['L','R'],
56
- 'left':['L'],
57
- 'right':['R']}
58
-
59
-
60
- type_dict = {'R':'Regular Season',
61
- 'S':'Spring',
62
- 'P':'Playoffs' }
63
-
64
-
65
-
66
- # List of MLB teams and their corresponding ESPN logo URLs
67
- mlb_teams = [
68
- {"team": "AZ", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/ari.png&h=500&w=500"},
69
- {"team": "ATH", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/oak.png&h=500&w=500"},
70
- {"team": "ATL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/atl.png&h=500&w=500"},
71
- {"team": "BAL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/bal.png&h=500&w=500"},
72
- {"team": "BOS", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/bos.png&h=500&w=500"},
73
- {"team": "CHC", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/chc.png&h=500&w=500"},
74
- {"team": "CWS", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/chw.png&h=500&w=500"},
75
- {"team": "CIN", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/cin.png&h=500&w=500"},
76
- {"team": "CLE", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/cle.png&h=500&w=500"},
77
- {"team": "COL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/col.png&h=500&w=500"},
78
- {"team": "DET", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/det.png&h=500&w=500"},
79
- {"team": "HOU", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/hou.png&h=500&w=500"},
80
- {"team": "KC", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/kc.png&h=500&w=500"},
81
- {"team": "LAA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/laa.png&h=500&w=500"},
82
- {"team": "LAD", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/lad.png&h=500&w=500"},
83
- {"team": "MIA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/mia.png&h=500&w=500"},
84
- {"team": "MIL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/mil.png&h=500&w=500"},
85
- {"team": "MIN", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/min.png&h=500&w=500"},
86
- {"team": "NYM", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/nym.png&h=500&w=500"},
87
- {"team": "NYY", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/nyy.png&h=500&w=500"},
88
- {"team": "PHI", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/phi.png&h=500&w=500"},
89
- {"team": "PIT", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/pit.png&h=500&w=500"},
90
- {"team": "SD", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sd.png&h=500&w=500"},
91
- {"team": "SF", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sf.png&h=500&w=500"},
92
- {"team": "SEA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sea.png&h=500&w=500"},
93
- {"team": "STL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/stl.png&h=500&w=500"},
94
- {"team": "TB", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tb.png&h=500&w=500"},
95
- {"team": "TEX", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tex.png&h=500&w=500"},
96
- {"team": "TOR", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tor.png&h=500&w=500"},
97
- {"team": "WSH", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/wsh.png&h=500&w=500"},
98
- {"team": "ZZZ", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/mlb.png&w=500&h=500"}
99
- ]
100
-
101
-
102
- df_image = pd.DataFrame(mlb_teams)
103
- image_dict = df_image.set_index('team')['logo_url'].to_dict()
104
- image_dict_flip = df_image.set_index('logo_url')['team'].to_dict()
105
-
106
-
107
-
108
- # # Define the features to be used for training
109
- # features_table = ['start_speed',
110
- # 'spin_rate',
111
- # 'extension',
112
- # 'ivb',
113
- # 'hb',
114
- # 'x0',
115
- # 'z0',
116
- # 'tj_stuff_plus']
117
-
118
- from shiny import App, reactive, ui, render
119
- from shiny.ui import h2, tags
120
-
121
- # Define the UI layout for the app
122
- app_ui = ui.page_sidebar(
123
- ui.sidebar(
124
- # Row for selecting season and level
125
- ui.row(
126
- ui.column(4, ui.input_select('year_input', 'Select Season', year_list, selected=2024)),
127
- ui.column(4, ui.input_select('level_input', 'Select Level', level_dict)),
128
- ui.column(4, ui.input_select('type_input', 'Select Type', type_dict,selected='R'))
129
- ),
130
- # Row for the action button to get player list
131
- ui.row(ui.input_action_button("player_button", "Get Player List", class_="btn-primary")),
132
- # Row for selecting the player
133
- ui.row(ui.column(12, ui.output_ui('player_select_ui', 'Select Player'))),
134
- # Row for selecting the date range
135
- ui.row(ui.column(12, ui.output_ui('date_id', 'Select Date'))),
136
-
137
- # Rows for selecting plots and split options
138
- ui.row(
139
- ui.column(4, ui.input_select('plot_id_1', 'Plot Left', function_dict, multiple=False, selected='velocity_kdes')),
140
- ui.column(4, ui.input_select('plot_id_2', 'Plot Middle', function_dict, multiple=False, selected='tj_stuff_roling')),
141
- ui.column(4, ui.input_select('plot_id_3', 'Plot Right', function_dict, multiple=False, selected='break_plot'))
142
- ),
143
- ui.row(
144
- ui.column(6, ui.input_select('split_id', 'Select Split', split_dict, multiple=False)),
145
- ui.column(6, ui.input_numeric('rolling_window', 'Rolling Window (for tjStuff+ Plot)', min=1, value=50))
146
- ),
147
- ui.row(
148
- ui.column(6, ui.input_switch("switch", "Custom Team?", False)),
149
- ui.column(6, ui.input_select('logo_select', 'Select Custom Logo', image_dict_flip, multiple=False))
150
- ),
151
-
152
- # Row for the action button to generate plot
153
- ui.row(ui.input_action_button("generate_plot", "Generate Plot", class_="btn-primary")),
154
- width="400px" # Added this parameter to control sidebar width
155
- ),
156
-
157
- # Main content area with tabs (placed directly in page_sidebar)
158
- ui.navset_tab(
159
- ui.nav_panel("Pitching Summary",
160
- ui.output_text("status"),
161
- ui.output_plot('plot', width='2100px', height='2100px')
162
- ),
163
- ui.nav_panel("Summary Table",
164
- ui.output_data_frame("grid"))
165
- )
166
- )
167
-
168
-
169
- def server(input, output, session):
170
-
171
- @reactive.calc
172
- @reactive.event(input.pitcher_id, input.date_id,input.split_id)
173
- def cached_data():
174
-
175
- year_input = int(input.year_input())
176
- sport_id = int(input.level_input())
177
- player_input = int(input.pitcher_id())
178
- start_date = str(input.date_id()[0])
179
- end_date = str(input.date_id()[1])
180
- # Simulate an expensive data operation
181
- game_list = scrape.get_player_games_list(sport_id = sport_id,
182
- season = year_input,
183
- player_id = player_input,
184
- start_date = start_date,
185
- end_date = end_date,
186
- game_type = [input.type_input()])
187
-
188
- data_list = scrape.get_data(game_list_input = game_list[:])
189
- df = (stuff_apply.stuff_apply(fe.feature_engineering(update.update(scrape.get_data_df(data_list = data_list).filter(
190
- (pl.col("pitcher_id") == player_input)&
191
- (pl.col("is_pitch") == True)&
192
- (pl.col("start_speed") >= 50)&
193
- (pl.col('batter_hand').is_in(split_dict_hand[input.split_id()]))
194
-
195
- )))).with_columns(
196
- pl.col('pitch_type').count().over('pitch_type').alias('pitch_count')
197
- ))
198
- return df
199
-
200
- @render.ui
201
- @reactive.event(input.player_button, input.year_input, input.level_input, input.type_input,ignore_none=False)
202
- def player_select_ui():
203
- # Get the list of pitchers for the selected level and season
204
- df_pitcher_info = scrape.get_players(sport_id=int(input.level_input()), season=int(input.year_input()), game_type = [input.type_input()]).filter(
205
- pl.col("position").is_in(['P','TWP'])).sort("name")
206
-
207
- # Create a dictionary of pitcher IDs and names
208
- pitcher_dict = dict(zip(df_pitcher_info['player_id'], df_pitcher_info['name']))
209
-
210
- # Return a select input for choosing a pitcher
211
- return ui.input_select("pitcher_id", "Select Pitcher", pitcher_dict, selectize=True)
212
-
213
- @render.ui
214
- @reactive.event(input.player_button, input.year_input, input.level_input, input.type_input,ignore_none=False)
215
- def date_id():
216
- # Create a date range input for selecting the date range within the selected year
217
- return ui.input_date_range("date_id", "Select Date Range",
218
- start=f"{int(input.year_input())}-01-01",
219
- end=f"{int(input.year_input())}-12-31",
220
- min=f"{int(input.year_input())}-01-01",
221
- max=f"{int(input.year_input())}-12-31")
222
- @output
223
- @render.text
224
- def status():
225
- # Only show status when generating
226
- if input.generate == 0:
227
- return ""
228
- return ""
229
-
230
- @output
231
- @render.plot
232
- @reactive.event(input.generate_plot, ignore_none=False)
233
- def plot():
234
- # Show progress/loading notification
235
- with ui.Progress(min=0, max=1) as p:
236
- p.set(message="Generating plot", detail="This may take a while...")
237
-
238
-
239
- p.set(0.3, "Gathering data...")
240
- year_input = int(input.year_input())
241
- sport_id = int(input.level_input())
242
- player_input = int(input.pitcher_id())
243
- start_date = str(input.date_id()[0])
244
- end_date = str(input.date_id()[1])
245
-
246
- print(year_input, sport_id, player_input, start_date, end_date)
247
-
248
- df = cached_data()
249
- df = df.clone()
250
-
251
- p.set(0.6, "Creating plot...")
252
-
253
-
254
- #plt.rcParams["figure.figsize"] = [10,10]
255
- fig = plt.figure(figsize=(26,26))
256
- plt.rcParams.update({'figure.autolayout': True})
257
- fig.set_facecolor('white')
258
- sns.set_theme(style="whitegrid", palette=colour_palette)
259
- print('this is the one plot')
260
-
261
- gs = gridspec.GridSpec(6, 8,
262
- height_ratios=[5,20,12,36,36,7],
263
- width_ratios=[4,18,18,18,18,18,18,4])
264
-
265
-
266
- gs.update(hspace=0.2, wspace=0.5)
267
-
268
- # Define the positions of each subplot in the grid
269
- ax_headshot = fig.add_subplot(gs[1,1:3])
270
- ax_bio = fig.add_subplot(gs[1,3:5])
271
- ax_logo = fig.add_subplot(gs[1,5:7])
272
-
273
- ax_season_table = fig.add_subplot(gs[2,1:7])
274
-
275
- ax_plot_1 = fig.add_subplot(gs[3,1:3])
276
- ax_plot_2 = fig.add_subplot(gs[3,3:5])
277
- ax_plot_3 = fig.add_subplot(gs[3,5:7])
278
-
279
- ax_table = fig.add_subplot(gs[4,1:7])
280
-
281
- ax_footer = fig.add_subplot(gs[-1,1:7])
282
- ax_header = fig.add_subplot(gs[0,1:7])
283
- ax_left = fig.add_subplot(gs[:,0])
284
- ax_right = fig.add_subplot(gs[:,-1])
285
-
286
- # Hide axes for footer, header, left, and right
287
- ax_footer.axis('off')
288
- ax_header.axis('off')
289
- ax_left.axis('off')
290
- ax_right.axis('off')
291
-
292
- sns.set_theme(style="whitegrid", palette=colour_palette)
293
- fig.set_facecolor('white')
294
-
295
- df_teams = scrape.get_teams()
296
-
297
- player_headshot(player_input=player_input, ax=ax_headshot,sport_id=sport_id,season=year_input)
298
- player_bio(pitcher_id=player_input, ax=ax_bio,sport_id=sport_id,year_input=year_input)
299
-
300
- if input.switch():
301
-
302
- # Get the logo URL from the image dictionary using the team abbreviation
303
- logo_url = input.logo_select()
304
-
305
- # Send a GET request to the logo URL
306
- response = requests.get(logo_url)
307
-
308
- # Open the image from the response content
309
- img = Image.open(BytesIO(response.content))
310
-
311
- # Display the image on the axis
312
- ax_logo.set_xlim(0, 1.3)
313
- ax_logo.set_ylim(0, 1)
314
- ax_logo.imshow(img, extent=[0.3, 1.3, 0, 1], origin='upper')
315
-
316
- # Turn off the axis
317
- ax_logo.axis('off')
318
-
319
- else:
320
- plot_logo(pitcher_id=player_input, ax=ax_logo, df_team=df_teams,df_players=scrape.get_players(sport_id,year_input))
321
-
322
- stat_summary_table(df=df,
323
- ax=ax_season_table,
324
- player_input=player_input,
325
- split=input.split_id(),
326
- sport_id=sport_id,
327
- game_type=[input.type_input()])
328
-
329
- # break_plot(df=df_plot,ax=ax2)
330
- for x,y,z in zip([input.plot_id_1(),input.plot_id_2(),input.plot_id_3()],[ax_plot_1,ax_plot_2,ax_plot_3],[1,3,5]):
331
- if x == 'velocity_kdes':
332
- velocity_kdes(df,
333
- ax=y,
334
- gs=gs,
335
- gs_x=[3,4],
336
- gs_y=[z,z+2],
337
- fig=fig)
338
- if x == 'tj_stuff_roling':
339
- tj_stuff_roling(df=df,
340
- window=int(input.rolling_window()),
341
- ax=y)
342
-
343
- if x == 'tj_stuff_roling_game':
344
- tj_stuff_roling_game(df=df,
345
- window=int(input.rolling_window()),
346
- ax=y)
347
-
348
- if x == 'break_plot':
349
- break_plot(df = df,ax=y)
350
-
351
- if x == 'location_plot_lhb':
352
- location_plot(df = df,ax=y,hand='L')
353
-
354
- if x == 'location_plot_rhb':
355
- location_plot(df = df,ax=y,hand='R')
356
-
357
- summary_table(df=df,
358
- ax=ax_table)
359
-
360
- plot_footer(ax_footer)
361
-
362
- fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01)
363
-
364
-
365
- @output
366
- @render.data_frame
367
- @reactive.event(input.generate_plot, ignore_none=False)
368
- def grid():
369
-
370
- df = cached_data()
371
- df = df.clone()
372
- features_table = ['start_speed',
373
- 'spin_rate',
374
- 'extension',
375
- 'ivb',
376
- 'hb',
377
- 'x0',
378
- 'z0']
379
-
380
-
381
-
382
- selection = ['game_id','pitcher_id','pitcher_name','batter_id','batter_name','pitcher_hand',
383
- 'batter_hand','balls','strikes','play_code','event_type','pitch_type','vaa','haa']+features_table+['tj_stuff_plus']
384
-
385
-
386
-
387
- return render.DataGrid(
388
- df.select(selection).to_pandas().round(1),
389
- row_selection_mode='multiple',
390
- height='700px',
391
- width='fit-content',
392
- filters=True,
393
- )
394
-
395
-
396
- app = App(app_ui, server)
397
-
398
-
399
-
400
  app = App(app_ui, server)
 
1
+ import polars as pl
2
+ import numpy as np
3
+ import pandas as pd
4
+ import api_scraper
5
+ scrape = api_scraper.MLB_Scrape()
6
+ from functions import df_update
7
+ from functions import pitch_summary_functions
8
+ update = df_update.df_update()
9
+ from stuff_model import feature_engineering as fe
10
+ from stuff_model import stuff_apply
11
+ import requests
12
+ import joblib
13
+ from matplotlib.gridspec import GridSpec
14
+ from shiny import App, reactive, ui, render
15
+ from shiny.ui import h2, tags
16
+ import matplotlib.pyplot as plt
17
+ import matplotlib.gridspec as gridspec
18
+ import seaborn as sns
19
+ from functions.pitch_summary_functions import *
20
+ from shiny import App, reactive, ui, render
21
+ from shiny.ui import h2, tags
22
+
23
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
24
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
25
+
26
+
27
+ year_list = [2017,2018,2019,2020,2021,2022,2023,2024]
28
+
29
+
30
+
31
+ level_dict = {'1':'MLB',
32
+ '11':'AAA',
33
+ # '12':'AA',
34
+ #'13':'A+',
35
+ '14':'A',
36
+ '17':'AFL',
37
+ '22':'College',
38
+ '21':'Prospects',
39
+ '51':'International' }
40
+
41
+ function_dict={
42
+ 'velocity_kdes':'Velocity Distributions',
43
+ 'break_plot':'Pitch Movement',
44
+ 'tj_stuff_roling':'Rolling tjStuff+ by Pitch',
45
+ 'tj_stuff_roling_game':'Rolling tjStuff+ by Game',
46
+ 'location_plot_lhb':'Locations vs LHB',
47
+ 'location_plot_rhb':'Locations vs RHB',
48
+ }
49
+
50
+
51
+ split_dict = {'all':'All',
52
+ 'left':'LHH',
53
+ 'right':'RHH'}
54
+
55
+ split_dict_hand = {'all':['L','R'],
56
+ 'left':['L'],
57
+ 'right':['R']}
58
+
59
+
60
+ type_dict = {'R':'Regular Season',
61
+ 'S':'Spring',
62
+ 'P':'Playoffs' }
63
+
64
+
65
+
66
+ # List of MLB teams and their corresponding ESPN logo URLs
67
+ mlb_teams = [
68
+ {"team": "AZ", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/ari.png&h=500&w=500"},
69
+ {"team": "ATH", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/oak.png&h=500&w=500"},
70
+ {"team": "ATL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/atl.png&h=500&w=500"},
71
+ {"team": "BAL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/bal.png&h=500&w=500"},
72
+ {"team": "BOS", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/bos.png&h=500&w=500"},
73
+ {"team": "CHC", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/chc.png&h=500&w=500"},
74
+ {"team": "CWS", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/chw.png&h=500&w=500"},
75
+ {"team": "CIN", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/cin.png&h=500&w=500"},
76
+ {"team": "CLE", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/cle.png&h=500&w=500"},
77
+ {"team": "COL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/col.png&h=500&w=500"},
78
+ {"team": "DET", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/det.png&h=500&w=500"},
79
+ {"team": "HOU", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/hou.png&h=500&w=500"},
80
+ {"team": "KC", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/kc.png&h=500&w=500"},
81
+ {"team": "LAA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/laa.png&h=500&w=500"},
82
+ {"team": "LAD", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/lad.png&h=500&w=500"},
83
+ {"team": "MIA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/mia.png&h=500&w=500"},
84
+ {"team": "MIL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/mil.png&h=500&w=500"},
85
+ {"team": "MIN", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/min.png&h=500&w=500"},
86
+ {"team": "NYM", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/nym.png&h=500&w=500"},
87
+ {"team": "NYY", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/nyy.png&h=500&w=500"},
88
+ {"team": "PHI", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/phi.png&h=500&w=500"},
89
+ {"team": "PIT", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/pit.png&h=500&w=500"},
90
+ {"team": "SD", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sd.png&h=500&w=500"},
91
+ {"team": "SF", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sf.png&h=500&w=500"},
92
+ {"team": "SEA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sea.png&h=500&w=500"},
93
+ {"team": "STL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/stl.png&h=500&w=500"},
94
+ {"team": "TB", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tb.png&h=500&w=500"},
95
+ {"team": "TEX", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tex.png&h=500&w=500"},
96
+ {"team": "TOR", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tor.png&h=500&w=500"},
97
+ {"team": "WSH", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/wsh.png&h=500&w=500"},
98
+ {"team": "ZZZ", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/mlb.png&w=500&h=500"}
99
+ ]
100
+
101
+
102
+ df_image = pd.DataFrame(mlb_teams)
103
+ image_dict = df_image.set_index('team')['logo_url'].to_dict()
104
+ image_dict_flip = df_image.set_index('logo_url')['team'].to_dict()
105
+
106
+
107
+
108
+ # # Define the features to be used for training
109
+ # features_table = ['start_speed',
110
+ # 'spin_rate',
111
+ # 'extension',
112
+ # 'ivb',
113
+ # 'hb',
114
+ # 'x0',
115
+ # 'z0',
116
+ # 'tj_stuff_plus']
117
+
118
+ from shiny import App, reactive, ui, render
119
+ from shiny.ui import h2, tags
120
+
121
+ # Define the UI layout for the app
122
+ app_ui = ui.page_sidebar(
123
+ ui.sidebar(
124
+ # Row for selecting season and level
125
+ ui.row(
126
+ ui.column(4, ui.input_select('year_input', 'Select Season', year_list, selected=2024)),
127
+ ui.column(4, ui.input_select('level_input', 'Select Level', level_dict)),
128
+ ui.column(4, ui.input_select('type_input', 'Select Type', type_dict,selected='R'))
129
+ ),
130
+ # Row for the action button to get player list
131
+ ui.row(ui.input_action_button("player_button", "Get Player List", class_="btn-primary")),
132
+ # Row for selecting the player
133
+ ui.row(ui.column(12, ui.output_ui('player_select_ui', 'Select Player'))),
134
+ # Row for selecting the date range
135
+ ui.row(ui.column(12, ui.output_ui('date_id', 'Select Date'))),
136
+
137
+ # Rows for selecting plots and split options
138
+ ui.row(
139
+ ui.column(4, ui.input_select('plot_id_1', 'Plot Left', function_dict, multiple=False, selected='velocity_kdes')),
140
+ ui.column(4, ui.input_select('plot_id_2', 'Plot Middle', function_dict, multiple=False, selected='tj_stuff_roling')),
141
+ ui.column(4, ui.input_select('plot_id_3', 'Plot Right', function_dict, multiple=False, selected='break_plot'))
142
+ ),
143
+ ui.row(
144
+ ui.column(6, ui.input_select('split_id', 'Select Split', split_dict, multiple=False)),
145
+ ui.column(6, ui.input_numeric('rolling_window', 'Rolling Window (for tjStuff+ Plot)', min=1, value=50))
146
+ ),
147
+ ui.row(
148
+ ui.column(6, ui.input_switch("switch", "Custom Team?", False)),
149
+ ui.column(6, ui.input_select('logo_select', 'Select Custom Logo', image_dict_flip, multiple=False))
150
+ ),
151
+
152
+ # Row for the action button to generate plot
153
+ ui.row(ui.input_action_button("generate_plot", "Generate Plot", class_="btn-primary")),
154
+ width="400px" # Added this parameter to control sidebar width
155
+ ),
156
+
157
+ # Main content area with tabs (placed directly in page_sidebar)
158
+ ui.navset_tab(
159
+ ui.nav_panel("Pitching Summary",
160
+ ui.output_text("status"),
161
+ ui.output_plot('plot', width='2100px', height='2100px')
162
+ ),
163
+ ui.nav_panel("Summary Table",
164
+ ui.output_data_frame("grid"))
165
+ )
166
+ )
167
+
168
+
169
+ def server(input, output, session):
170
+
171
+ @reactive.calc
172
+ @reactive.event(input.pitcher_id, input.date_id,input.split_id)
173
+ def cached_data():
174
+
175
+ year_input = int(input.year_input())
176
+ sport_id = int(input.level_input())
177
+ player_input = int(input.pitcher_id())
178
+ start_date = str(input.date_id()[0])
179
+ end_date = str(input.date_id()[1])
180
+ # Simulate an expensive data operation
181
+ game_list = scrape.get_player_games_list(sport_id = sport_id,
182
+ season = year_input,
183
+ player_id = player_input,
184
+ start_date = start_date,
185
+ end_date = end_date,
186
+ game_type = [input.type_input()])
187
+
188
+ data_list = scrape.get_data(game_list_input = game_list[:])
189
+ df = (stuff_apply.stuff_apply(fe.feature_engineering(update.update(scrape.get_data_df(data_list = data_list).filter(
190
+ (pl.col("pitcher_id") == player_input)&
191
+ (pl.col("is_pitch") == True)&
192
+ (pl.col("start_speed") >= 50)&
193
+ (pl.col('batter_hand').is_in(split_dict_hand[input.split_id()]))
194
+
195
+ )))).with_columns(
196
+ pl.col('pitch_type').count().over('pitch_type').alias('pitch_count')
197
+ ))
198
+ return df
199
+
200
+ @render.ui
201
+ @reactive.event(input.player_button, input.year_input, input.level_input, input.type_input,ignore_none=False)
202
+ def player_select_ui():
203
+ # Get the list of pitchers for the selected level and season
204
+ df_pitcher_info = scrape.get_players(sport_id=int(input.level_input()), season=int(input.year_input()), game_type = [input.type_input()]).filter(
205
+ pl.col("position").is_in(['P','TWP'])).sort("name")
206
+
207
+ # Create a dictionary of pitcher IDs and names
208
+ pitcher_dict = dict(zip(df_pitcher_info['player_id'], df_pitcher_info['name']))
209
+
210
+ # Return a select input for choosing a pitcher
211
+ return ui.input_select("pitcher_id", "Select Pitcher", pitcher_dict, selectize=True)
212
+
213
+ @render.ui
214
+ @reactive.event(input.player_button, input.year_input, input.level_input, input.type_input,ignore_none=False)
215
+ def date_id():
216
+ # Create a date range input for selecting the date range within the selected year
217
+ return ui.input_date_range("date_id", "Select Date Range",
218
+ start=f"{int(input.year_input())}-01-01",
219
+ end=f"{int(input.year_input())}-12-31",
220
+ min=f"{int(input.year_input())}-01-01",
221
+ max=f"{int(input.year_input())}-12-31")
222
+ @output
223
+ @render.text
224
+ def status():
225
+ # Only show status when generating
226
+ if input.generate == 0:
227
+ return ""
228
+ return ""
229
+
230
+ @output
231
+ @render.plot
232
+ @reactive.event(input.generate_plot, ignore_none=False)
233
+ def plot():
234
+ # Show progress/loading notification
235
+ with ui.Progress(min=0, max=1) as p:
236
+ p.set(message="Generating plot", detail="This may take a while...")
237
+
238
+
239
+ p.set(0.3, "Gathering data...")
240
+ year_input = int(input.year_input())
241
+ sport_id = int(input.level_input())
242
+ player_input = int(input.pitcher_id())
243
+ start_date = str(input.date_id()[0])
244
+ end_date = str(input.date_id()[1])
245
+
246
+ print(year_input, sport_id, player_input, start_date, end_date)
247
+
248
+ df = cached_data()
249
+ df = df.clone()
250
+
251
+ p.set(0.6, "Creating plot...")
252
+
253
+
254
+ #plt.rcParams["figure.figsize"] = [10,10]
255
+ fig = plt.figure(figsize=(26,26))
256
+ plt.rcParams.update({'figure.autolayout': True})
257
+ fig.set_facecolor('white')
258
+ sns.set_theme(style="whitegrid", palette=colour_palette)
259
+ print('this is the one plot')
260
+
261
+ gs = gridspec.GridSpec(6, 8,
262
+ height_ratios=[5,20,12,36,36,7],
263
+ width_ratios=[4,18,18,18,18,18,18,4])
264
+
265
+
266
+ gs.update(hspace=0.2, wspace=0.5)
267
+
268
+ # Define the positions of each subplot in the grid
269
+ ax_headshot = fig.add_subplot(gs[1,1:3])
270
+ ax_bio = fig.add_subplot(gs[1,3:5])
271
+ ax_logo = fig.add_subplot(gs[1,5:7])
272
+
273
+ ax_season_table = fig.add_subplot(gs[2,1:7])
274
+
275
+ ax_plot_1 = fig.add_subplot(gs[3,1:3])
276
+ ax_plot_2 = fig.add_subplot(gs[3,3:5])
277
+ ax_plot_3 = fig.add_subplot(gs[3,5:7])
278
+
279
+ ax_table = fig.add_subplot(gs[4,1:7])
280
+
281
+ ax_footer = fig.add_subplot(gs[-1,1:7])
282
+ ax_header = fig.add_subplot(gs[0,1:7])
283
+ ax_left = fig.add_subplot(gs[:,0])
284
+ ax_right = fig.add_subplot(gs[:,-1])
285
+
286
+ # Hide axes for footer, header, left, and right
287
+ ax_footer.axis('off')
288
+ ax_header.axis('off')
289
+ ax_left.axis('off')
290
+ ax_right.axis('off')
291
+
292
+ sns.set_theme(style="whitegrid", palette=colour_palette)
293
+ fig.set_facecolor('white')
294
+
295
+ df_teams = scrape.get_teams()
296
+
297
+ player_headshot(player_input=player_input, ax=ax_headshot,sport_id=sport_id,season=year_input)
298
+ player_bio(pitcher_id=player_input, ax=ax_bio,sport_id=sport_id,year_input=year_input)
299
+
300
+ if input.switch():
301
+
302
+ # Get the logo URL from the image dictionary using the team abbreviation
303
+ logo_url = input.logo_select()
304
+
305
+ # Send a GET request to the logo URL
306
+ response = requests.get(logo_url)
307
+
308
+ # Open the image from the response content
309
+ img = Image.open(BytesIO(response.content))
310
+
311
+ # Display the image on the axis
312
+ ax_logo.set_xlim(0, 1.3)
313
+ ax_logo.set_ylim(0, 1)
314
+ ax_logo.imshow(img, extent=[0.3, 1.3, 0, 1], origin='upper')
315
+
316
+ # Turn off the axis
317
+ ax_logo.axis('off')
318
+
319
+ else:
320
+ plot_logo(pitcher_id=player_input, ax=ax_logo, df_team=df_teams,df_players=scrape.get_players(sport_id,year_input))
321
+
322
+ stat_summary_table(df=df,
323
+ ax=ax_season_table,
324
+ player_input=player_input,
325
+ split=input.split_id(),
326
+ sport_id=sport_id,
327
+ game_type=[input.type_input()])
328
+
329
+ # break_plot(df=df_plot,ax=ax2)
330
+ for x,y,z in zip([input.plot_id_1(),input.plot_id_2(),input.plot_id_3()],[ax_plot_1,ax_plot_2,ax_plot_3],[1,3,5]):
331
+ if x == 'velocity_kdes':
332
+ velocity_kdes(df,
333
+ ax=y,
334
+ gs=gs,
335
+ gs_x=[3,4],
336
+ gs_y=[z,z+2],
337
+ fig=fig)
338
+ if x == 'tj_stuff_roling':
339
+ tj_stuff_roling(df=df,
340
+ window=int(input.rolling_window()),
341
+ ax=y)
342
+
343
+ if x == 'tj_stuff_roling_game':
344
+ tj_stuff_roling_game(df=df,
345
+ window=int(input.rolling_window()),
346
+ ax=y)
347
+
348
+ if x == 'break_plot':
349
+ break_plot(df = df,ax=y)
350
+
351
+ if x == 'location_plot_lhb':
352
+ location_plot(df = df,ax=y,hand='L')
353
+
354
+ if x == 'location_plot_rhb':
355
+ location_plot(df = df,ax=y,hand='R')
356
+
357
+ summary_table(df=df,
358
+ ax=ax_table)
359
+
360
+ plot_footer(ax_footer)
361
+
362
+ fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01)
363
+
364
+
365
+ @output
366
+ @render.data_frame
367
+ @reactive.event(input.generate_plot, ignore_none=False)
368
+ def grid():
369
+
370
+ df = cached_data()
371
+ df = df.clone()
372
+ features_table = ['start_speed',
373
+ 'spin_rate',
374
+ 'extension',
375
+ 'ivb',
376
+ 'hb',
377
+ 'x0',
378
+ 'z0']
379
+
380
+
381
+
382
+ selection = ['play_id','game_id','pitcher_id','pitcher_name','batter_id','batter_name','pitcher_hand',
383
+ 'batter_hand','balls','strikes','play_code','event_type','pitch_type','vaa','haa']+features_table+['tj_stuff_plus']
384
+
385
+
386
+
387
+ return render.DataGrid(
388
+ df.select(selection).to_pandas().round(1),
389
+ row_selection_mode='multiple',
390
+ height='700px',
391
+ width='fit-content',
392
+ filters=True,
393
+ )
394
+
395
+
396
+ app = App(app_ui, server)
397
+
398
+
399
+
400
  app = App(app_ui, server)