nesticot commited on
Commit
12a5a63
·
verified ·
1 Parent(s): ecc19a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -803
app.py CHANGED
@@ -1,803 +0,0 @@
1
- import pandas as pd
2
- import numpy as np
3
- import matplotlib.pyplot as plt
4
- import seaborn as sns
5
- import pitch_summary_functions as psf
6
- import requests
7
- import matplotlib
8
- from api_scraper import MLB_Scrape
9
- from shinywidgets import output_widget, render_widget
10
- import shinyswatch
11
-
12
-
13
-
14
- colour_palette = ['#FFB000','#648FFF','#785EF0',
15
- '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
16
-
17
- import datasets
18
- from datasets import load_dataset
19
- ### Import Datasets
20
- dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2024.csv' ])
21
- dataset_train = dataset['train']
22
- df_2024 = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True).drop_duplicates(subset=['play_id'],keep='last')
23
-
24
- # ### Import Datasets
25
- # import datasets
26
- # from datasets import load_dataset
27
- # dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2020.csv' ])
28
- # dataset_train = dataset['train']
29
- # df_2024 = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
30
-
31
- ### PITCH COLOURS ###
32
- pitch_colours = {
33
- 'Four-Seam Fastball':'#FF007D',#BC136F
34
- 'Sinker':'#98165D',#DC267F
35
- 'Cutter':'#BE5FA0',
36
-
37
- 'Changeup':'#F79E70',#F75233
38
- 'Splitter':'#FE6100',#F75233
39
- 'Screwball':'#F08223',
40
- 'Forkball':'#FFB000',
41
-
42
- 'Slider':'#67E18D',#1BB999#785EF0
43
- 'Sweeper':'#1BB999',#37CD85#904039
44
- 'Slurve':'#376748',#785EF0#549C07#BEABD8
45
-
46
- 'Knuckle Curve':'#311D8B',
47
- 'Curveball':'#3025CE',
48
- 'Slow Curve':'#274BFC',
49
- 'Eephus':'#648FFF',
50
-
51
- 'Knuckleball':'#867A08',
52
-
53
- 'Pitch Out':'#472C30',
54
- 'Other':'#9C8975',
55
- }
56
-
57
- spring_teams = df_2024.groupby(['pitcher_id']).tail(1)[['pitcher_id','pitcher_team']].set_index(['pitcher_id'])['pitcher_team'].to_dict()
58
-
59
- season_start = '2024-03-20'
60
- season_end = '2024-09-29'
61
- season_fg=2024
62
- chad_fg = requests.get(f'https://www.fangraphs.com/api/leaders/major-league/data?age=&pos=all&stats=pit&lg=all&qual=0&season={season_fg}&season={season_fg}&month=1000&season1={season_fg}&ind=0&pageitems=2000000000&pagenum=1&ind=0&rost=0&players=&type=36&postseason=&sortdir=default&sortstat=sp_pitching').json()
63
-
64
-
65
- chadwick_df_small = pd.DataFrame(data={
66
- 'key_mlbam':[x['xMLBAMID'] for x in chad_fg['data']],
67
- 'key_fangraphs':[x['playerid'] for x in chad_fg['data']],
68
- 'Name':[x['PlayerName'] for x in chad_fg['data']],
69
- })
70
-
71
- pitcher_dicts = chadwick_df_small.set_index('key_mlbam')['Name'].sort_values().to_dict()
72
- mlb_fg_dicts = chadwick_df_small.set_index('key_mlbam')['key_fangraphs'].sort_values().to_dict()
73
-
74
-
75
- statcast_pitch_summary = pd.read_csv('statcast_pitch_summary.csv')
76
- cmap_sum = matplotlib.colors.LinearSegmentedColormap.from_list("", ['#648FFF','#FFFFFF','#FFB000',])
77
-
78
-
79
- df_2024_codes = psf.df_update_code(df_2024)
80
-
81
- df_2024_update = psf.df_clean(df_2024_codes)
82
- import joblib
83
- model = joblib.load('joblib_model/tjstuff_model_20240318.joblib')
84
- y_pred_mean = 0.0011434511
85
- y_pred_std = 0.006554768
86
-
87
- features = ['start_speed','spin_rate','extension','ivb','hb','x0','z0','fb_max_velo_diff','fb_max_ivb_diff','fb_max_hb_diff']
88
-
89
- targets = ['delta_run_exp_mean']
90
-
91
-
92
- df_2024_update['y_pred'] = model.predict(df_2024_update[features])
93
-
94
- df_2024_update['tj_stuff_plus'] = 100 + 10*((-df_2024_update.y_pred +y_pred_mean) / y_pred_std)
95
-
96
- team_logos = pd.read_csv('team_logos.csv')
97
-
98
- mlb_stats = MLB_Scrape()
99
- teams_df = mlb_stats.get_teams()
100
- team_logo_dict = teams_df.set_index(['team_id'])['parent_org_id'].to_dict()
101
-
102
- font_properties = {'family': 'calibi', 'size': 12}
103
- font_properties_titles = {'family': 'calibi', 'size': 20}
104
- font_properties_axes = {'family': 'calibi', 'size': 16}
105
- df_plot = []
106
- ax2_loc = []
107
- gs = []
108
- fig = []
109
-
110
- function_dict={
111
- 'velocity_kde':'Velocity Distributions',
112
- 'break_plot':'Pitch Movement',
113
- 'rolling_tj_stuff':'Rolling tjStuff+',
114
- 'location_lhb':'Locations vs LHB',
115
- 'location_rhb':'Locations vs RHB',
116
- }
117
-
118
- split_dict = {'all':'All',
119
- 'left':'LHB',
120
- 'right':'RHB'}
121
-
122
- split_dict_hand = {'all':['L','R'],
123
- 'left':['L'],
124
- 'right':['R']}
125
-
126
- ball_dict = {'0':'0',
127
- '1':'1',
128
- '2':'2',
129
- '3':'3'}
130
-
131
- strike_dict = {'0':'0',
132
- '1':'1',
133
- '2':'2'}
134
-
135
- # count_dict = {'0_0':'Through 0-0',
136
- # '0_1':'Through 0-1',
137
- # '0_2':'Through 0-2',
138
- # '1_0':'Through 1-0',
139
- # '1_1':'Through 1-1',
140
- # '1_2':'Through 1-2',
141
- # '2_1':'Through 2-1',
142
- # '2_0':'Through 2-0',
143
- # '3_0':'Through 3-0',
144
- # '3_1':'Through 3-1',
145
- # '2_2':'Through 2-2',
146
- # '3_2':'Through 3-2'}
147
-
148
- # count_dict_fg = {'0_0':'',
149
- # '0_1':'61',
150
- # '0_2':'62',
151
- # '1_0':'63',
152
- # '1_1':'64',
153
- # '1_2':'65',
154
- # '2_1':'66',
155
- # '2_0':'67',
156
- # '3_0':'68',
157
- # '3_1':'69',
158
- # '2_2':'70',
159
- # '3_2':'71'}
160
-
161
- from urllib.request import Request, urlopen
162
- from shiny import App, reactive, ui, render
163
- from shiny.ui import h2, tags
164
- # importing OpenCV(cv2) module
165
- app_ui = ui.page_fluid(
166
- ui.tags.div(
167
- {"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
168
- ui.tags.style(
169
- """
170
- h4 {
171
- margin-top: 1em;font-size:35px;
172
- }
173
- h2{
174
- font-size:25px;
175
- }
176
- """
177
- ),
178
- shinyswatch.theme.simplex(),
179
- ui.tags.h4("TJStats"),
180
- ui.tags.i("Baseball Analytics and Visualizations"),
181
- ui.row(
182
-
183
-
184
- ui.layout_sidebar(
185
-
186
- ui.panel_sidebar(
187
- ui.row(
188
- ui.column(6,
189
- ui.input_select('player_id','Select Player',pitcher_dicts,selectize=True,multiple=False)),
190
- ui.column(6, ui.output_ui('test','Select Game'))),
191
-
192
- ui.row(
193
- ui.column(4,
194
- ui.input_select('plot_id_1','Plot Left',function_dict,multiple=False,selected='velocity_kde')),
195
- ui.column(4,
196
- ui.input_select('plot_id_2','Plot Middle',function_dict,multiple=False,selected='rolling_tj_stuff')),
197
- ui.column(4,
198
- ui.input_select('plot_id_3','Plot Right',function_dict,multiple=False,selected='break_plot'))),
199
-
200
- # ui.input_select('count_id','Count',count_dict,multiple=True,selectize=True,selected='0_0'),
201
-
202
- ui.row(
203
- ui.column(6,
204
- ui.input_select('ball_id','Balls',ball_dict,multiple=False,selected='0'),
205
- ui.input_radio_buttons(
206
- "count_id_balls",
207
- "Count Filter Balls",
208
- {
209
- "exact": "Exact Balls",
210
- "greater": ">= Balls",
211
- "lesser": "<= Balls",
212
- },selected='greater')),
213
- ui.column(6,
214
- ui.input_select('strike_id','Strikes',strike_dict,multiple=False,selected='0'),
215
- ui.input_radio_buttons(
216
- "count_id_strikes",
217
- "Count Filter Strikes",
218
- {
219
- "exact": "Exact Strikes",
220
- "greater": ">= Strikes",
221
- "lesser": "<= Strikes",
222
- },selected='greater'))),
223
- ui.row(
224
- ui.column(6,
225
- ui.input_select('split_id','Select Split',split_dict,multiple=False)),
226
- ui.column(6,
227
- ui.input_numeric('rolling_window','Rolling Window (for tjStuff+ Plot)',min=1,value=10))),
228
-
229
-
230
-
231
- ui.input_action_button("go", "Generate",class_="btn-primary"),
232
-
233
-
234
- width=4)
235
- ,
236
- ui.panel_main(
237
- ui.navset_tab(
238
- # ui.nav("Raw Data",
239
- # ui.output_data_frame("raw_table")),
240
- ui.nav("Season Summary",
241
- ui.output_plot('plot',
242
- width='2000px',
243
- height='2000px')),
244
- ui.nav("Game Summary",
245
- ui.output_plot('plot_game',
246
- width='2000px',
247
- height='2000px'))
248
- ,id="my_tabs"))))))
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
- #print(app_ui)
260
- def server(input, output, session):
261
-
262
- @render.ui
263
- def test():
264
-
265
- # @reactive.Effect
266
- if input.my_tabs() == 'Season Summary':
267
-
268
- return ui.input_date_range("date_range_id", "Date range input",start = df_2024.game_date.min(),
269
- end = df_2024.game_date.max(),width=2,min=df_2024.game_date.min(),
270
- max=df_2024.game_date.max()),
271
- # @reactive.Effect
272
- if input.my_tabs() == 'Game Summary':
273
- pitcher_id_select = int(input.player_id())
274
- df_plot = df_2024_update[(df_2024_update['pitcher_id']==pitcher_id_select)]
275
-
276
-
277
- # ax0.text(x=0.5,y=0.30,s=f'2024 Spring Training',fontname='Calibri',ha='center',fontsize=30,va='top')
278
- df_plot['game_opp'] = df_plot['game_date'].astype(str) + ' vs ' + df_plot['batter_team'].astype(str)
279
- #print(df_plot['game_opp'])
280
-
281
- date_dict = pd.concat([df_plot.drop_duplicates(subset=['pitcher_id','game_id','game_opp'])[['game_id','game_opp']]]).set_index('game_id').to_dict()
282
- return ui.input_select("game_id", "Select Game",date_dict,selectize=True)
283
-
284
- @output
285
- @render.plot
286
- @reactive.event(input.go, ignore_none=False)
287
- def plot():
288
- #fig, ax = plt.subplots(3, 2, figsize=(9, 9))
289
-
290
- font_properties = {'family': 'calibi', 'size': 12}
291
- font_properties_titles = {'family': 'calibi', 'size': 20}
292
- font_properties_axes = {'family': 'calibi', 'size': 16}
293
-
294
- if len((input.player_id()))<1:
295
- fig, ax = plt.subplots(1, 1, figsize=(9, 9))
296
- ax.text(x=0.5,y=0.5,s='Please Select\nA Player',fontsize=150,ha='center')
297
- ax.grid('off')
298
- return
299
-
300
- pitcher_id_select = int(input.player_id())
301
-
302
-
303
- df_plot = df_2024_update[(df_2024_update['pitcher_id']==pitcher_id_select)]
304
-
305
- df_plot = df_plot[df_plot['batter_hand'].isin(split_dict_hand[input.split_id()])]
306
-
307
- if input.count_id_balls()=='greater' and input.count_id_strikes()=='greater' and int(input.ball_id())==0 and int(input.strike_id())==0:
308
- ball_title = ''
309
- strike_title = ''
310
- else:
311
- if input.count_id_balls()=='exact':
312
- df_plot = df_plot[df_plot['balls']==int(input.ball_id())]
313
- ball_title = str(f'{(input.ball_id())} Ball Count; ')
314
- elif input.count_id_balls()=='greater':
315
- df_plot = df_plot[df_plot['balls']>=int(input.ball_id())]
316
- ball_title = str(f'At Least {(input.ball_id())} Ball Count; ')
317
- elif input.count_id_balls()=='lesser':
318
- df_plot = df_plot[df_plot['balls']<=int(input.ball_id())]
319
- ball_title = str(f'At Most {(input.ball_id())} Ball Count; ')
320
-
321
- if input.count_id_strikes()=='exact':
322
- df_plot = df_plot[df_plot['strikes']==int(input.strike_id())]
323
- strike_title = str(f'{(input.strike_id())} Strike Count; ')
324
- elif input.count_id_strikes()=='greater':
325
- df_plot = df_plot[df_plot['strikes']>=int(input.strike_id())]
326
- strike_title = str(f'At Least {(input.strike_id())} Strike Count; ')
327
- elif input.count_id_strikes()=='lesser':
328
- df_plot = df_plot[df_plot['strikes']<=int(input.strike_id())]
329
- strike_title = str(f'At Most {(input.strike_id())} Strike Count; ')
330
-
331
-
332
-
333
- if input.split_id() == 'all':
334
- split_title = ''
335
-
336
- elif input.split_id() == 'left':
337
- split_title = 'vs. LHH'
338
-
339
- elif input.split_id() == 'right':
340
- split_title = 'vs. RHH'
341
-
342
-
343
- if len(df_plot)<1:
344
- fig, ax = plt.subplots(1, 1, figsize=(9, 9))
345
- ax.text(x=0.5,y=0.5,s='Please Select\nOther Parameters',fontsize=150,ha='center')
346
- ax.grid('off')
347
- return
348
-
349
- df_plot['pitch_type_count'] = df_plot.groupby(['pitcher_id'])['pitch_type'].cumcount()+1
350
- df_plot['pitch_type_count_each'] = df_plot.groupby(['pitch_type'])['pitch_type'].cumcount()+1
351
- #df_plot = df_plot.merge(df_2024_update[['tj_stuff_plus','play_id']],left_on=['play_id'],right_on=['play_id'],how='left')
352
- df_plot = df_plot.sort_values(by=['pitch_description'])
353
-
354
- grouped_ivb = psf.group_ivb_update(df=df_plot,agg_list=['pitcher_id','pitcher_name','pitcher_hand','pitch_type','pitch_description'])
355
- grouped_ivb_all = psf.group_ivb_update(df=df_plot,agg_list=['pitcher_id','pitcher_name','pitcher_hand'])
356
-
357
-
358
-
359
- from matplotlib.gridspec import GridSpec
360
- plt.rcParams['font.family'] = 'Calibri'
361
- df_plot['prop'] = df_plot.groupby("pitch_type")["is_pitch"].transform("sum")
362
- label_labels = df_plot.sort_values(by=['prop','pitch_type'],ascending=[False,True]).pitch_description.unique()
363
-
364
- #plt.rcParams["figure.figsize"] = [10,10]
365
- fig = plt.figure(figsize=(20, 20))
366
- plt.rcParams.update({'figure.autolayout': True})
367
- fig.set_facecolor('white')
368
- sns.set_theme(style="whitegrid", palette=colour_palette)
369
- print('this is the one plot')
370
- # gs = GridSpec(7, 2, width_ratios=[1,1], height_ratios=[1.5,1,1,1,1,1,2.5])
371
- gs = GridSpec(5, 5, height_ratios=[150,75,225,325,50],width_ratios=[1,100,100,100,1])
372
- #### NO FG
373
- ####gs = GridSpec(5, 5, height_ratios=[225,0,225,325,50],width_ratios=[1,100,100,100,1])
374
- #gs = GridSpec(4, 1, width_ratios=[1], height_ratios=[1,0.75,7-len(label_labels)/4,1+len(label_labels)/4])
375
-
376
- gs.update(hspace=0.2, wspace=0.3)
377
-
378
- # Add subplots to the grid
379
- ax0 = fig.add_subplot(gs[0, :])
380
- ax1_table = fig.add_subplot(gs[1, :])
381
- ax2_left = fig.add_subplot(gs[2, 1])
382
- ax2_middle = fig.add_subplot(gs[2, 2])
383
- ax2_right = fig.add_subplot(gs[2, 3])
384
- ax3 = fig.add_subplot(gs[-2, :])
385
- #axfooter = fig.add_subplot(gs[-1, :])
386
-
387
- ax1_table.axis('off')
388
-
389
- sns.set_theme(style="whitegrid", palette=colour_palette)
390
- fig.set_facecolor('white')
391
-
392
- font_properties = {'family': 'calibi', 'size': 12}
393
- font_properties_titles = {'family': 'calibi', 'size': 20}
394
- font_properties_axes = {'family': 'calibi', 'size': 16}
395
-
396
- ## FANGRAPHS TABLE ###
397
- data_pull = psf.fangraphs_scrape(pitcher_id=pitcher_id_select,
398
- split=input.split_id(),
399
- start_date=input.date_range_id()[0],
400
- end_date=input.date_range_id()[1])
401
-
402
-
403
- psf.fangraphs_table(data=data_pull,
404
- stats=['IP','WHIP','ERA','FIP','TBF','K%','BB%','K-BB%'],
405
- ax=ax1_table)
406
-
407
- for x,y,z in zip([input.plot_id_1(),input.plot_id_2(),input.plot_id_3()],[ax2_left,ax2_middle,ax2_right],[1,2,3]):
408
- if x == 'velocity_kde':
409
- psf.velocity_kdes(df=df_plot,ax=y,gs=gs,gs_list=z,fig=fig)
410
- if x == 'rolling_tj_stuff':
411
- psf.tj_stuff_roling(df = df_plot,window = int(input.rolling_window()),ax=y)
412
- if x == 'break_plot':
413
- psf.break_plot(df=df_plot,ax=y)
414
- if x == 'location_lhb':
415
- psf.location_plot(df=df_plot,ax=y,hand='L')
416
- if x == 'location_rhb':
417
- psf.location_plot(df=df_plot,ax=y,hand='R')
418
-
419
- pitches_list = df_plot['pitch_description'].unique()
420
- colour_pitches = [pitch_colours[x] for x in pitches_list]
421
-
422
- # handles, labels = ax2_right.get_legend_handles_labels()
423
-
424
- # # Manually create handles and labels for each pitch-color pair
425
- handles = [plt.scatter([], [], color=color, marker='o', s=100) for color in colour_pitches]
426
- labels = pitches_list
427
-
428
-
429
-
430
-
431
- ### FANGRAPHS TABLE ###
432
- psf.table_summary(df=df_plot.copy(),
433
- pitcher_id=pitcher_id_select,
434
- ax=ax3,
435
- df_group=grouped_ivb.copy(),
436
- df_group_all=grouped_ivb_all.copy(),
437
- statcast_pitch_summary=statcast_pitch_summary.copy())
438
-
439
-
440
- # ############ FOOTER ################
441
- # #fig.text(x=0.5,y=0.05,s='Note: Colour Coding Compares to League Average By Pitch',ha='center',fontname='Calibri',fontsize=10)
442
- # axfooter.text(x=0.05,y=1,s='By: Thomas Nestico\n @TJStats',fontname='Calibri',ha='left',fontsize=24,va='top')
443
- # axfooter.text(x=1-0.05,y=1,s='Data: MLB, Fangraphs',ha='right',fontname='Calibri',fontsize=24,va='top')
444
-
445
-
446
- # axfooter.text(x=0.5,y=0.8,s='Colour Coding Compares to League Average By Pitch\ntjStuff+ calculates the Expected Run Value (xRV) of a pitch regardless of type\ntjStuff+ is normally distributed, where 100 is the mean and Standard Deviation is 10',
447
- # ha='center',va='center',fontname='Calibri',fontsize=16)
448
- # axfooter.axis('off')
449
- # #fig.tight_layout()
450
-
451
-
452
-
453
- # Get value counts of the column and sort in descending order
454
- sorted_value_counts = df_plot['pitch_description'].value_counts().sort_values(ascending=False)
455
-
456
- # Get the list of items ordered from most to least frequent
457
- items_in_order = sorted_value_counts.index.tolist()
458
- # Create a dictionary to map names to colors
459
- name_to_color = dict(zip(labels, handles))
460
-
461
- # Order the colors based on the correct order of names
462
- ordered_colors = [name_to_color[name] for name in items_in_order]
463
-
464
-
465
- ax3.legend(ordered_colors, items_in_order, bbox_to_anchor=(0.1, 0.81, 0.8, 0.2), ncol=5,
466
- fancybox=True,loc='lower center',fontsize=20,framealpha=1.0, markerscale=2,prop={'family': 'calibi', 'size': 20})
467
-
468
-
469
- ################## Title ##########
470
- title_spot = f'{df_plot.pitcher_name.values[0]}'
471
-
472
-
473
- ax0.text(x=0.5,y=0.8,s=title_spot,fontname='Calibri',ha='center',fontsize=56,va='top')
474
- ax0.text(x=0.5,y=0.5,s='Season Pitching Summary',fontname='Calibri',ha='center',fontsize=40,va='top',fontstyle='italic')
475
-
476
-
477
- #ax0.text(x=0.5,y=0.25,s=f'2024 Spring Training',fontname='Calibri',ha='center',fontsize=30,va='top')
478
- # ax0.text(x=0.5,y=0.25,s=f'{season_fg} MLB Season',fontname='Calibri',ha='center',fontsize=30,va='top')
479
- # ax0.axis('off')
480
-
481
-
482
- ax0.text(x=0.5,y=0.25,s=f'{input.date_range_id()[0]} to {input.date_range_id()[1]}',fontname='Calibri',ha='center',fontsize=30,va='top',fontstyle='italic')
483
-
484
- ax0.text(x=0.5,y=0.05,s=f'{ball_title}{strike_title}{split_title}',fontname='Calibri',ha='center',fontsize=20,va='top')
485
- ax0.axis('off')
486
-
487
- from matplotlib.offsetbox import (OffsetImage, AnnotationBbox)
488
- import urllib
489
- import urllib.request
490
- import urllib.error
491
- from urllib.error import HTTPError
492
-
493
- try:
494
- url = f'https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png/w_213,q_auto:best/v1/people/{pitcher_id_select}/headshot/67/current.png'
495
- test_mage = plt.imread(url)
496
- except urllib.error.HTTPError as err:
497
- url = f'https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png/w_213,q_auto:best/v1/people/1/headshot/67/current.png'
498
- imagebox = OffsetImage(test_mage, zoom = 0.5)
499
- ab = AnnotationBbox(imagebox, (0.125, 0.4), frameon = False)
500
- ax0.add_artist(ab)
501
-
502
- player_bio = requests.get(url=f"https://statsapi.mlb.com/api/v1/people?personIds={pitcher_id_select}&hydrate=currentTeam").json()
503
-
504
-
505
-
506
-
507
- if 'currentTeam' in player_bio['people'][0]:
508
- try:
509
- url = team_logos[team_logos['id'] == team_logo_dict[player_bio['people'][0]['currentTeam']['id']]]['imageLink'].values[0]
510
-
511
- im = plt.imread(url)
512
- # response = requests.get(url)
513
- # im = Image.open(BytesIO(response.content))
514
- # im = plt.imread(team_logos[team_logos['id'] == player_bio['people'][0]['currentTeam']['parentOrgId']]['imageLink'].values[0])
515
- # ax = fig.add_axes([0,0,1,0.85], anchor='C', zorder=1)
516
- imagebox = OffsetImage(im, zoom = 0.4)
517
- ab = AnnotationBbox(imagebox, (0.875, 0.40), frameon = False)
518
- ax0.add_artist(ab)
519
- except IndexError:
520
- print()
521
-
522
-
523
- ############ FOOTER ################
524
- #fig.text(x=0.5,y=0.05,s='Note: Colour Coding Compares to League Average By Pitch',ha='center',fontname='Calibri',fontsize=10)
525
- axfooter = fig.add_subplot(gs[-1, :])
526
- axfooter.text(x=0.05,y=1,s='By: Thomas Nestico\n @TJStats',fontname='Calibri',ha='left',fontsize=24,va='top')
527
- axfooter.text(x=1-0.05,y=1,s='Data: MLB, Fangraphs',ha='right',fontname='Calibri',fontsize=24,va='top')
528
-
529
-
530
- axfooter.text(x=0.5,y=0.8,s='Colour Coding Compares to League Average By Pitch\ntjStuff+ calculates the Expected Run Value (xRV) of a pitch regardless of type\ntjStuff+ is normally distributed, where 100 is the mean and Standard Deviation is 10',
531
- ha='center',va='center',fontname='Calibri',fontsize=16)
532
- axfooter.axis('off')
533
- #fig.tight_layout()
534
-
535
- fig.subplots_adjust(left=0.03, right=0.97, top=0.97, bottom=0.03)
536
-
537
-
538
- @output
539
- @render.plot
540
- @reactive.event(input.go, ignore_none=False)
541
- def plot_game():
542
- #fig, ax = plt.subplots(3, 2, figsize=(9, 9))
543
-
544
- font_properties = {'family': 'calibi', 'size': 12}
545
- font_properties_titles = {'family': 'calibi', 'size': 20}
546
- font_properties_axes = {'family': 'calibi', 'size': 16}
547
-
548
- if len((input.player_id()))<1:
549
- fig, ax = plt.subplots(1, 1, figsize=(9, 9))
550
- ax.text(x=0.5,y=0.5,s='Please Select\nA Player',fontsize=150,ha='center')
551
- ax.grid('off')
552
- return
553
-
554
- pitcher_id_select = int(input.player_id())
555
-
556
-
557
-
558
-
559
- df_plot = df_2024_update[(df_2024_update['pitcher_id']==pitcher_id_select)&(df_2024_update['game_id']==int(input.game_id()))]
560
- df_plot = df_plot[df_plot['batter_hand'].isin(split_dict_hand[input.split_id()])]
561
-
562
- if input.count_id_balls()=='greater' and input.count_id_strikes()=='greater' and int(input.ball_id())==0 and int(input.strike_id())==0:
563
- ball_title = ''
564
- strike_title = ''
565
- else:
566
- if input.count_id_balls()=='exact':
567
- df_plot = df_plot[df_plot['balls']==int(input.ball_id())]
568
- ball_title = str(f'{(input.ball_id())} Ball Count; ')
569
- elif input.count_id_balls()=='greater':
570
- df_plot = df_plot[df_plot['balls']>=int(input.ball_id())]
571
- ball_title = str(f'At Least {(input.ball_id())} Ball Count; ')
572
- elif input.count_id_balls()=='lesser':
573
- df_plot = df_plot[df_plot['balls']<=int(input.ball_id())]
574
- ball_title = str(f'At Most {(input.ball_id())} Ball Count; ')
575
-
576
- if input.count_id_strikes()=='exact':
577
- df_plot = df_plot[df_plot['strikes']==int(input.strike_id())]
578
- strike_title = str(f'{(input.strike_id())} Strike Count; ')
579
- elif input.count_id_strikes()=='greater':
580
- df_plot = df_plot[df_plot['strikes']>=int(input.strike_id())]
581
- strike_title = str(f'At Least {(input.strike_id())} Strike Count; ')
582
- elif input.count_id_strikes()=='lesser':
583
- df_plot = df_plot[df_plot['strikes']<=int(input.strike_id())]
584
- strike_title = str(f'At Most {(input.strike_id())} Strike Count; ')
585
-
586
-
587
-
588
-
589
-
590
- if input.split_id() == 'all':
591
- split_title = ''
592
-
593
- elif input.split_id() == 'left':
594
- split_title = 'vs. LHH'
595
-
596
- elif input.split_id() == 'right':
597
- split_title = 'vs. RHH'
598
-
599
- if len(df_plot)<1:
600
- fig, ax = plt.subplots(1, 1, figsize=(9, 9))
601
- ax.text(x=0.5,y=0.5,s='Please Select\nOther Parameters',fontsize=150,ha='center')
602
- ax.grid('off')
603
- return
604
-
605
-
606
- df_plot['pitch_type_count'] = df_plot.groupby(['pitcher_id'])['pitch_type'].cumcount()+1
607
- df_plot['pitch_type_count_each'] = df_plot.groupby(['pitch_type'])['pitch_type'].cumcount()+1
608
- #df_plot = df_plot.merge(df_2024_update[['tj_stuff_plus','play_id']],left_on=['play_id'],right_on=['play_id'],how='left')
609
- df_plot = df_plot.sort_values(by=['pitch_description'])
610
-
611
- # ax0.text(x=0.5,y=0.30,s=f'2024 Spring Training',fontname='Calibri',ha='center',fontsize=30,va='top')
612
- df_plot['game_opp'] = df_plot['game_date'].astype(str) + ' vs ' + df_plot['batter_team'].astype(str)
613
- #print(df_plot['game_opp'])
614
-
615
- #date_dict = pd.concat([df_plot.drop_duplicates(subset=['pitcher_id','game_id','game_opp'])[['game_id','game_opp']]]).set_index('game_id').to_dict()
616
-
617
- grouped_ivb = psf.group_ivb_update(df=df_plot,agg_list=['pitcher_id','pitcher_name','pitcher_hand','pitch_type','pitch_description'])
618
- grouped_ivb_all = psf.group_ivb_update(df=df_plot,agg_list=['pitcher_id','pitcher_name','pitcher_hand'])
619
-
620
-
621
-
622
- from matplotlib.gridspec import GridSpec
623
- plt.rcParams['font.family'] = 'Calibri'
624
- df_plot['prop'] = df_plot.groupby("pitch_type")["is_pitch"].transform("sum")
625
- label_labels = df_plot.sort_values(by=['prop','pitch_type'],ascending=[False,True]).pitch_description.unique()
626
-
627
- #plt.rcParams["figure.figsize"] = [10,10]
628
- fig = plt.figure(figsize=(20, 20))
629
- plt.rcParams.update({'figure.autolayout': True})
630
- fig.set_facecolor('white')
631
- sns.set_theme(style="whitegrid", palette=colour_palette)
632
- print('this is the one plot')
633
- # gs = GridSpec(7, 2, width_ratios=[1,1], height_ratios=[1.5,1,1,1,1,1,2.5])
634
- gs = GridSpec(5, 5, height_ratios=[150,75,225,325,50],width_ratios=[1,100,100,100,1])
635
- #### NO FG
636
- ####gs = GridSpec(5, 5, height_ratios=[225,0,225,325,50],width_ratios=[1,100,100,100,1])
637
- #gs = GridSpec(4, 1, width_ratios=[1], height_ratios=[1,0.75,7-len(label_labels)/4,1+len(label_labels)/4])
638
-
639
- gs.update(hspace=0.2, wspace=0.3)
640
-
641
- # Add subplots to the grid
642
- ax0 = fig.add_subplot(gs[0, :])
643
- ax1_table = fig.add_subplot(gs[1, :])
644
- ax2_left = fig.add_subplot(gs[2, 1])
645
- ax2_middle = fig.add_subplot(gs[2, 2])
646
- ax2_right = fig.add_subplot(gs[2, 3])
647
- ax3 = fig.add_subplot(gs[-2, :])
648
- # axfooter = fig.add_subplot(gs[-1, :])
649
-
650
- ax1_table.axis('off')
651
-
652
- sns.set_theme(style="whitegrid", palette=colour_palette)
653
- fig.set_facecolor('white')
654
-
655
- font_properties = {'family': 'calibi', 'size': 12}
656
- font_properties_titles = {'family': 'calibi', 'size': 20}
657
- font_properties_axes = {'family': 'calibi', 'size': 16}
658
-
659
- print(df_2024_update['game_date'].values[0])
660
- ## FANGRAPHS TABLE ###
661
- data_pull = psf.fangraphs_scrape(pitcher_id=pitcher_id_select,
662
- split=input.split_id(),
663
- start_date=df_plot['game_date'].values[0],
664
- end_date=df_plot['game_date'].values[0])
665
-
666
-
667
- psf.fangraphs_table(data=data_pull,
668
- stats=['IP','WHIP','ERA','FIP','TBF','K%','BB%','K-BB%'],
669
- ax=ax1_table)
670
-
671
- # psf.velocity_kdes(df=df_plot,
672
- # ax=ax2_loc,
673
- # gs=gs,
674
- # fig=fig)
675
-
676
- # # psf.tj_stuff_roling(df = df_plot,
677
- # # window = 5,
678
- # # ax=ax2_velo)
679
- # psf.location_plot(df=df_plot,ax=ax2_velo,hand='L')
680
-
681
- # psf.location_plot(df=df_plot,ax=ax2_loc,hand='R')
682
- # # # ## Break Plot
683
- # psf.break_plot(df=df_plot,ax=ax2)
684
- for x,y,z in zip([input.plot_id_1(),input.plot_id_2(),input.plot_id_3()],[ax2_left,ax2_middle,ax2_right],[1,2,3]):
685
- if x == 'velocity_kde':
686
- psf.velocity_kdes(df=df_plot,ax=y,gs=gs,gs_list=z,fig=fig)
687
- if x == 'rolling_tj_stuff':
688
- psf.tj_stuff_roling(df = df_plot,window = int(input.rolling_window()),ax=y)
689
- if x == 'break_plot':
690
- psf.break_plot(df=df_plot,ax=y)
691
- if x == 'location_lhb':
692
- psf.location_plot(df=df_plot,ax=y,hand='L')
693
- if x == 'location_rhb':
694
- psf.location_plot(df=df_plot,ax=y,hand='R')
695
-
696
- pitches_list = df_plot['pitch_description'].unique()
697
- colour_pitches = [pitch_colours[x] for x in pitches_list]
698
-
699
- # handles, labels = ax2_right.get_legend_handles_labels()
700
-
701
- # # Manually create handles and labels for each pitch-color pair
702
- handles = [plt.scatter([], [], color=color, marker='o', s=100) for color in colour_pitches]
703
- labels = pitches_list
704
-
705
-
706
- ### FANGRAPHS TABLE ###
707
- psf.table_summary(df=df_plot.copy(),
708
- pitcher_id=pitcher_id_select,
709
- ax=ax3,
710
- df_group=grouped_ivb.copy(),
711
- df_group_all=grouped_ivb_all.copy(),
712
- statcast_pitch_summary=statcast_pitch_summary.copy())
713
-
714
-
715
-
716
-
717
-
718
- # Get value counts of the column and sort in descending order
719
- sorted_value_counts = df_plot['pitch_description'].value_counts().sort_values(ascending=False)
720
-
721
- # Get the list of items ordered from most to least frequent
722
- items_in_order = sorted_value_counts.index.tolist()
723
- # Create a dictionary to map names to colors
724
- name_to_color = dict(zip(labels, handles))
725
-
726
- # Order the colors based on the correct order of names
727
- ordered_colors = [name_to_color[name] for name in items_in_order]
728
-
729
-
730
- ax3.legend(ordered_colors, items_in_order, bbox_to_anchor=(0.1, 0.81, 0.8, 0.2), ncol=5,
731
- fancybox=True,loc='lower center',fontsize=20,framealpha=1.0, markerscale=2,prop={'family': 'calibi', 'size': 20})
732
-
733
-
734
- ################## Title ##########
735
- title_spot = f'{df_plot.pitcher_name.values[0]}'
736
-
737
-
738
- ax0.text(x=0.5,y=0.8,s=title_spot,fontname='Calibri',ha='center',fontsize=56,va='top')
739
- ax0.text(x=0.5,y=0.5,s='Game Pitching Summary',fontname='Calibri',ha='center',fontsize=40,va='top',fontstyle='italic')
740
-
741
-
742
- #ax0.text(x=0.5,y=0.25,s=f'2024 Spring Training',fontname='Calibri',ha='center',fontsize=30,va='top')
743
- #ax0.text(x=0.5,y=0.25,s=f'{season_fg} MLB Season',fontname='Calibri',ha='center',fontsize=30,va='top')
744
- #ax0.text(x=0.5,y=0.25,s=f'2024 Spring Training',fontname='Calibri',ha='center',fontsize=30,va='top')
745
- # ax0.text(x=0.5,y=0.25,s=f'{season_fg} MLB Season',fontname='Calibri',ha='center',fontsize=30,va='top')
746
-
747
- ax0.text(x=0.5,y=0.25,s= df_plot['game_opp'].values[0],fontname='Calibri',ha='center',fontstyle='italic',fontsize=30,va='top')
748
-
749
- ax0.text(x=0.5,y=0.05,s=f'{ball_title}{strike_title}{split_title}',fontname='Calibri',ha='center',fontsize=20,va='top')
750
- ax0.axis('off')
751
-
752
-
753
- from matplotlib.offsetbox import (OffsetImage, AnnotationBbox)
754
- import urllib
755
- import urllib.request
756
- import urllib.error
757
- from urllib.error import HTTPError
758
-
759
- try:
760
- url = f'https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png/w_213,q_auto:best/v1/people/{pitcher_id_select}/headshot/67/current.png'
761
- test_mage = plt.imread(url)
762
- except urllib.error.HTTPError as err:
763
- url = f'https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png/w_213,q_auto:best/v1/people/1/headshot/67/current.png'
764
- imagebox = OffsetImage(test_mage, zoom = 0.5)
765
- ab = AnnotationBbox(imagebox, (0.125, 0.4), frameon = False)
766
- ax0.add_artist(ab)
767
-
768
- player_bio = requests.get(url=f"https://statsapi.mlb.com/api/v1/people?personIds={pitcher_id_select}&hydrate=currentTeam").json()
769
-
770
-
771
-
772
-
773
- if 'currentTeam' in player_bio['people'][0]:
774
- try:
775
- url = team_logos[team_logos['id'] == team_logo_dict[player_bio['people'][0]['currentTeam']['id']]]['imageLink'].values[0]
776
-
777
- im = plt.imread(url)
778
- # response = requests.get(url)
779
- # im = Image.open(BytesIO(response.content))
780
- # im = plt.imread(team_logos[team_logos['id'] == player_bio['people'][0]['currentTeam']['parentOrgId']]['imageLink'].values[0])
781
- # ax = fig.add_axes([0,0,1,0.85], anchor='C', zorder=1)
782
- imagebox = OffsetImage(im, zoom = 0.4)
783
- ab = AnnotationBbox(imagebox, (0.875, 0.40), frameon = False)
784
- ax0.add_artist(ab)
785
- except IndexError:
786
- print()
787
-
788
- ############ FOOTER ################
789
- #fig.text(x=0.5,y=0.05,s='Note: Colour Coding Compares to League Average By Pitch',ha='center',fontname='Calibri',fontsize=10)
790
- axfooter = fig.add_subplot(gs[-1, :])
791
- axfooter.text(x=0.05,y=1,s='By: Thomas Nestico\n @TJStats',fontname='Calibri',ha='left',fontsize=24,va='top')
792
- axfooter.text(x=1-0.05,y=1,s='Data: MLB, Fangraphs',ha='right',fontname='Calibri',fontsize=24,va='top')
793
-
794
-
795
- axfooter.text(x=0.5,y=0.8,s='Colour Coding Compares to League Average By Pitch\ntjStuff+ calculates the Expected Run Value (xRV) of a pitch regardless of type\ntjStuff+ is normally distributed, where 100 is the mean and Standard Deviation is 10',
796
- ha='center',va='center',fontname='Calibri',fontsize=16)
797
- axfooter.axis('off')
798
- #fig.tight_layout()
799
-
800
-
801
- fig.subplots_adjust(left=0.03, right=0.97, top=0.97, bottom=0.03)
802
-
803
- app = App(app_ui, server)