nesticot commited on
Commit
1baffca
·
verified ·
1 Parent(s): 8b0615d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +296 -502
app.py CHANGED
@@ -1,494 +1,350 @@
1
- from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
2
- import datasets
3
- from datasets import load_dataset
4
  import pandas as pd
5
  import numpy as np
6
  import matplotlib.pyplot as plt
7
  import seaborn as sns
8
- import numpy as np
9
- from scipy.stats import gaussian_kde
10
  import matplotlib
11
- from matplotlib.ticker import MaxNLocator
12
- from matplotlib.gridspec import GridSpec
13
- from scipy.stats import zscore
14
  import math
15
- import matplotlib
16
- from adjustText import adjust_text
17
- import matplotlib.ticker as mtick
18
- from shinywidgets import output_widget, render_widget
19
- import pandas as pd
20
- from configure import base_url
21
- import shinyswatch
 
 
 
 
 
 
 
 
 
22
 
23
  ### Import Datasets
24
- dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2024.csv' ])
25
  dataset_train = dataset['train']
26
- df_2023 = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
27
- print(df_2023)
28
- ### Normalize Hit Locations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
- df_2023['season'] = df_2023['game_date'].str[0:4].astype(int)
31
- # df_2023['hit_x'] = df_2023['hit_x'] - df_2023['hit_x'].median()
32
- # df_2023['hit_y'] = -df_2023['hit_y']+df_2023['hit_y'].quantile(0.9999)
33
 
34
- df_2023['hit_x'] = df_2023['hit_x'] - 126#df_2023['hit_x'].median()
35
- df_2023['hit_y'] = -df_2023['hit_y']+204.5#df_2023['hit_y'].quantile(0.9999)
36
 
37
- df_2023['hit_x_og'] = df_2023['hit_x']
38
- df_2023.loc[df_2023['batter_hand'] == 'R','hit_x'] = -1*df_2023.loc[df_2023['batter_hand'] == 'R','hit_x']
39
- df_2023['h_la'] = np.arctan(df_2023['hit_x'] / df_2023['hit_y'])*180/np.pi
40
- conditions_ss = [
41
- (df_2023['h_la']<-15),
42
- (df_2023['h_la']<15)&(df_2023['h_la']>=-15),
43
- (df_2023['h_la']>=15)
44
- ]
45
 
46
- choices_ss = ['Oppo','Straight','Pull']
47
- df_2023['traj'] = np.select(conditions_ss, choices_ss, default=np.nan)
48
- df_2023['bip'] = [1 if x > 0 else np.nan for x in df_2023['launch_speed']]
49
 
50
- conditions_woba = [
51
- (df_2023['event_type']=='walk'),
52
- (df_2023['event_type']=='hit_by_pitch'),
53
- (df_2023['event_type']=='single'),
54
- (df_2023['event_type']=='double'),
55
- (df_2023['event_type']=='triple'),
56
- (df_2023['event_type']=='home_run'),
57
- ]
58
 
 
59
 
60
- choices_woba = [1,
61
- 1,
62
- 1,
63
- 2,
64
- 3,
65
- 4]
 
 
 
 
 
 
66
 
 
67
 
68
- # choices_woba = [0.698,
69
- # 0.728,
70
- # 0.887,
71
- # 1.253,
72
- # 1.583,
73
- # 2.027]
74
 
75
- df_2023['woba'] = np.select(conditions_woba, choices_woba, default=0)
76
 
77
- choices_woba_train = [1,
78
- 1,
79
- 1,
80
- 2,
81
- 3,
82
- 4]
83
 
84
- df_2023['woba_train'] = np.select(conditions_woba, choices_woba_train, default=0)
 
 
85
 
86
 
87
- df_2023_bip = df_2023[~df_2023['bip'].isnull()].dropna(subset=['h_la','launch_angle'])
88
- df_2023_bip['h_la'] = df_2023_bip['h_la'].round(0)
 
89
 
 
 
 
90
 
91
- df_2023_bip['season'] = df_2023_bip['game_date'].str[0:4].astype(int)
 
92
 
93
- df_2023_bip = df_2023[~df_2023['bip'].isnull()].dropna(subset=['launch_angle','bip'])
94
- df_2023_bip_train = df_2023_bip[df_2023_bip['season'] == 2024]
95
 
96
- batter_dict = df_2023_bip.sort_values('batter_name').set_index('batter_id')['batter_name'].to_dict()
97
 
98
- features = ['launch_angle','launch_speed','h_la']
99
- target = ['woba_train']
100
 
101
- df_2023_bip_train = df_2023_bip_train.dropna(subset=features)
102
 
103
- import joblib
104
- # # Dump the model to a file named 'model.joblib'
105
- model = joblib.load('xtb_model.joblib')
106
 
 
107
 
108
- df_2023_bip_train['y_pred'] = [sum(x) for x in model.predict_proba(df_2023_bip_train[features]) * ([0,1,2,3,4])]
109
- # df_2023_bip_train['y_pred_noh'] = [sum(x) for x in model_noh.predict_proba(df_2023_bip_train[['launch_angle','launch_speed']]) * ([0,0.887,1.253,1.583,2.027])]
110
 
111
- df_2023_output = df_2023_bip_train.groupby(['batter_id','batter_name']).agg(
112
- bip = ('y_pred','count'),
113
- y_pred = ('y_pred','sum'),
114
- slgcon = ('woba','mean'),
115
- xslgcon = ('y_pred','mean'),
116
- launch_speed = ('launch_speed','mean'),
117
- launch_angle_std = ('launch_angle','median'),
118
- h_la_std = ('h_la','mean'))
119
 
120
- df_2023_output_copy = df_2023_output.copy()
121
- # df_2023_output = df_2023_output[df_2023_output['bip'] > 100]
122
- # df_2023_output[df_2023_output['bip'] > 100].sort_values(by='h_la_std',ascending=True).head(20)
123
 
124
- import pandas as pd
125
- import numpy as np
126
 
 
 
 
 
127
 
128
- # Create grid coordinates
129
- x = np.arange(30, 121,1 )
130
- y = np.arange(-30, 61,1 )
131
- z = np.arange(-45, 46,1 )
132
 
133
- # Create a meshgrid
134
- X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
135
- # Flatten the meshgrid to get x and y coordinates
136
- x_flat = X.flatten()
137
- y_flat = Y.flatten()
138
- z_flat = Z.flatten()
 
139
 
140
- # Create a DataFrame
141
- df = pd.DataFrame({'launch_speed': x_flat, 'launch_angle': y_flat,'h_la':z_flat})
142
 
143
- df['y_pred'] = [sum(x) for x in model.predict_proba(df[features]) * ([0,1,2,3,4])]
144
 
145
 
146
- import matplotlib
 
147
 
148
- colour_palette = ['#FFB000','#648FFF','#785EF0',
149
- '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
150
 
151
- cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],'#ffffff',colour_palette[0]])
152
- cmap_hue2 = matplotlib.colors.LinearSegmentedColormap.from_list("",['#ffffff',colour_palette[0]])
153
 
 
154
 
155
- from matplotlib.pyplot import text
156
- import inflect
157
- from scipy.stats import percentileofscore
158
- p = inflect.engine()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
 
161
 
 
 
 
162
 
163
- def server(input,output,session):
164
 
 
 
 
165
 
166
- @output
167
- @render.plot(alt="hex_plot")
168
- @reactive.event(input.go, ignore_none=False)
169
- def hex_plot():
170
-
171
- if input.batter_id() is "":
172
- fig = plt.figure(figsize=(12, 12))
173
- fig.text(s='Please Select a Batter',x=0.5,y=0.5)
174
- return
175
-
176
- batter_select_id = int(input.batter_id())
177
- # batter_select_name = 'Edouard Julien'
178
- quant = int(input.quant())/100
179
- df_batter_og = df_2023_bip_train[df_2023_bip_train['batter_id']==batter_select_id]
180
- # df_batter_og = df_2023_bip_train[df_2023_bip_train['batter_name']==batter_select_name]
181
- df_batter = df_batter_og[df_batter_og['launch_speed'] >= df_batter_og['launch_speed'].quantile(quant)]
182
- # df_batter_best_speed = df_batter['launch_speed'].mean().round()
183
-
184
- # df_bip_league = df_2023_bip_train[df_2023_bip_train['launch_speed'] >= df_2023_bip_train['launch_speed'].quantile(quant)]
185
-
186
- import pandas as pd
187
- import numpy as np
188
-
189
-
190
- # Create grid coordinates
191
- #x = np.arange(30, 121,1 )
192
- y_b = np.arange(df_batter['launch_angle'].median()-df_batter['launch_angle'].std(),
193
- df_batter['launch_angle'].median()+df_batter['launch_angle'].std(),1 )
194
-
195
- z_b = np.arange(df_batter['h_la'].median()-df_batter['h_la'].std(),
196
- df_batter['h_la'].median()+df_batter['h_la'].std(),1 )
197
-
198
- # Create a meshgrid
199
- Y_b, Z_b = np.meshgrid( y_b,z_b, indexing='ij')
200
- # Flatten the meshgrid to get x and y coordinates
201
-
202
- y_flat_b = Y_b.flatten()
203
- z_flat_b = Z_b.flatten()
204
-
205
- # Create a DataFrame
206
- df_batter_base = pd.DataFrame({'launch_angle': y_flat_b,'h_la':z_flat_b,'c':[0]*len(y_flat_b)})
207
-
208
- # df_batter_base['y_pred'] = [sum(x) for x in model.predict_proba(df_batter_base[features]) * ([0,1,2,3,4])]
209
-
210
- from matplotlib.gridspec import GridSpec
211
- # fig,ax = plt.subplots(figsize=(12, 12),dpi=150)
212
- fig = plt.figure(figsize=(12,12))
213
- gs = GridSpec(4, 3, height_ratios=[0.5,10,1.5,0.2], width_ratios=[0.05,0.9,0.05])
214
-
215
- axheader = fig.add_subplot(gs[0, :])
216
- ax10 = fig.add_subplot(gs[1, 0])
217
- ax = fig.add_subplot(gs[1, 1]) # Subplot at the top-right position
218
- ax12 = fig.add_subplot(gs[1, 2])
219
- ax2_ = fig.add_subplot(gs[2, :])
220
- axfooter1 = fig.add_subplot(gs[-1, :])
221
-
222
- axheader.axis('off')
223
- ax10.axis('off')
224
- ax12.axis('off')
225
- ax2_.axis('off')
226
- axfooter1.axis('off')
227
-
228
-
229
-
230
- extents = [-45,45,-30,60]
231
-
232
- def hexLines(a=None,i=None,off=[0,0]):
233
- '''regular hexagon segment lines as `(xy1,xy2)` in clockwise
234
- order with points in line sorted top to bottom
235
- for irregular hexagon pass both `a` (vertical) and `i` (horizontal)'''
236
- if a is None: a = 2 / np.sqrt(3) * i;
237
- if i is None: i = np.sqrt(3) / 2 * a;
238
- h = a / 2
239
- xy = np.array([ [ [ 0, a], [ i, h] ],
240
- [ [ i, h], [ i,-h] ],
241
- [ [ i,-h], [ 0,-a] ],
242
- [ [-i,-h], [ 0,-a] ], #flipped
243
- [ [-i, h], [-i,-h] ], #flipped
244
- [ [ 0, a], [-i, h] ] #flipped
245
- ])
246
- return xy+off;
247
-
248
-
249
- h = ax.hexbin(x=df_batter_base['h_la'],
250
- y=df_batter_base['launch_angle'],
251
- gridsize=25,
252
- edgecolors='k',
253
- extent=extents,mincnt=1,lw=2,zorder=-3,)
254
-
255
- # cfg = {**cfg,'vmin':h.get_clim()[0], 'vmax':h.get_clim()[1]}
256
- # plt.hexbin( ec="black" ,lw=6,zorder=4,mincnt=2,**cfg,alpha=0.1)
257
- # plt.hexbin( ec="#ffffff",lw=1,zorder=5,mincnt=2,**cfg,alpha=0.1)
258
-
259
-
260
- ax.hexbin(x=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['h_la'],
261
- y=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['launch_angle'],
262
- C=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['y_pred'],
263
- gridsize=25,
264
- vmin=0,
265
- vmax=4,
266
- cmap=cmap_hue2,
267
- extent=extents,zorder=-3)
268
-
269
-
270
- # Get the counts and centers of the hexagons
271
- counts = ax.hexbin(x=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['h_la'],
272
- y=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['launch_angle'],
273
- C=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['y_pred'],
274
- gridsize=25,
275
- vmin=0,
276
- vmax=4,
277
- cmap=cmap_hue2,
278
- extent=extents).get_array()
279
-
280
- bin_centers = ax.hexbin(x=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['h_la'],
281
- y=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['launch_angle'],
282
- C=df[(df['launch_angle']>=-30)&(df['launch_angle']<=60)&(df['launch_speed']>=df_batter['launch_speed'].median())&(df['launch_speed']<=df_batter['launch_speed'].max())]['y_pred'],
283
- gridsize=25,
284
- vmin=0,
285
- vmax=4,
286
- cmap=cmap_hue2,
287
- extent=extents).get_offsets()
288
-
289
- # Add text with the values of "C" to each hexagon
290
- for count, (x, y) in zip(counts, bin_centers):
291
- if count >= 1:
292
- ax.text(x, y, f'{count:.1f}', color='black', ha='center', va='center',fontsize=7)
293
-
294
-
295
-
296
- #get hexagon centers that should be highlighted
297
- verts = h.get_offsets()
298
- cnts = h.get_array()
299
- highl = verts[cnts > .5*cnts.max()]
300
-
301
- #create hexagon lines
302
- a = ((verts[0,1]-verts[1,1])/3).round(6)
303
- i = ((verts[1:,0]-verts[:-1,0])/2).round(6)
304
- i = i[i>0][0]
305
- lines = np.concatenate([hexLines(a,i,off) for off in highl])
306
-
307
- #select contour lines and draw
308
- uls,c = np.unique(lines.round(4),axis=0,return_counts=True)
309
- for l in uls[c==1]: ax.plot(*l.transpose(),'w-',lw=2,scalex=False,scaley=False,color=colour_palette[1],zorder=100)
310
-
311
-
312
- # Plot filled hexagons
313
- for hc in highl:
314
- hx = hc[0] + np.array([0, i, i, 0, -i, -i])
315
- hy = hc[1] + np.array([a, a/2, -a/2, -a, -a/2, a/2])
316
- ax.fill(hx, hy, color=colour_palette[1], alpha=0.15, edgecolor=None) # Adjust color and alpha as needed
317
-
318
- # # Create grid coordinates
319
- # #x = np.arange(30, 121,1 )
320
- # y_b = np.arange(df_bip_league['launch_angle'].median()-df_bip_league['launch_angle'].std(),
321
- # df_bip_league['launch_angle'].median()+df_bip_league['launch_angle'].std(),1 )
322
-
323
- # z_b = np.arange(df_bip_league['h_la'].median()-df_bip_league['h_la'].std(),
324
- # df_bip_league['h_la'].median()+df_bip_league['h_la'].std(),1 )
325
-
326
- # # Create a meshgrid
327
- # Y_b, Z_b = np.meshgrid( y_b,z_b, indexing='ij')
328
- # # Flatten the meshgrid to get x and y coordinates
329
-
330
- # y_flat_b = Y_b.flatten()
331
- # z_flat_b = Z_b.flatten()
332
-
333
- # # Create a DataFrame
334
- # df_league_base = pd.DataFrame({'launch_angle': y_flat_b,'h_la':z_flat_b,'c':[0]*len(y_flat_b)})
335
-
336
- # h_league = ax.hexbin(x=df_league_base['h_la'],
337
- # y=df_league_base['launch_angle'],
338
- # gridsize=25,
339
- # edgecolors=colour_palette[1],
340
- # extent=extents,mincnt=1,lw=2,zorder=-3,)
341
-
342
- # #get hexagon centers that should be highlighted
343
- # verts = h_league.get_offsets()
344
- # cnts = h_league.get_array()
345
- # highl = verts[cnts > .5*cnts.max()]
346
 
347
- # #create hexagon lines
348
- # a = ((verts[0,1]-verts[1,1])/3).round(6)
349
- # i = ((verts[1:,0]-verts[:-1,0])/2).round(6)
350
- # i = i[i>0][0]
351
- # lines = np.concatenate([hexLines(a,i,off) for off in highl])
352
-
353
- # #select contour lines and draw
354
- # uls,c = np.unique(lines.round(4),axis=0,return_counts=True)
355
- # for l in uls[c==1]: ax.plot(*l.transpose(),'w-',lw=2,scalex=False,scaley=False,color=colour_palette[3],zorder=99)
356
-
357
-
358
- axheader.text(s=f"{df_batter['batter_name'].values[0]} - {int(quant*100)}th% EV and Greater Batted Ball Tendencies",x=0.5,y=0.2,fontsize=20,ha='center',va='bottom')
359
- axheader.text(s=f"2024 Season",x=0.5,y=-0.1,fontsize=14,ha='center',va='top')
360
-
361
- ax.set_xlabel(f"Horizontal Spray Angle (°)",fontsize=12)
362
- ax.set_ylabel(f"Vertical Launch Angle (°)",fontsize=12)
363
-
364
- ax2_.text(x=0.5,
365
- y=0.0,
366
 
367
- s="Notes:\n" \
368
- f"- {int(quant*100)}th% EV and Greater BBE is defined as a batter's top {100 - int(quant*100)}% hardest hit BBE\n" \
369
- f"- Colour Scale and Number Labels Represents the Expected Total Bases for a batter's range of Best Speeds\n" \
370
- f"- Shaded Area Represents the 2-D Region bounded by ±1σ Launch Angle and Horizontal Spray Angle on batter's Best Speed BBE\n"\
371
- f"- {df_batter['batter_name'].values[0]} {int(quant*100)}th% EV and Greater BBE Range from {df_batter['launch_speed'].min():.0f} to {df_batter['launch_speed'].max():.0f} mph ({len(df_batter)} BBE)\n"\
372
- f"- Positive Horizontal Spray Angle Represents a BBE hit in same direction as batter handedness (i.e. Pulled)" ,
373
-
374
- fontsize=11,
375
- fontstyle='oblique',
376
- va='bottom',
377
- ha='center',
378
- bbox=dict(facecolor='white', edgecolor='black'),ma='left')
379
 
380
- axfooter1.text(0.05, 0.5, "By: Thomas Nestico\n @TJStats",ha='left', va='bottom',fontsize=12)
381
- axfooter1.text(0.95, 0.5, "Data: MLB",ha='right', va='bottom',fontsize=12)
382
 
383
- if df_batter['batter_hand'].values[0] == 'R':
384
- ax.invert_xaxis()
385
- ax.grid(False)
386
- ax.axis('equal')
387
- # Adjusting subplot to center it within the figure
388
- fig.subplots_adjust(left=0.01, right=0.99, top=0.975, bottom=0.025)
389
 
390
- #ax.text(f"Vertical Spray Angle (°)")
391
 
392
 
393
- @output
394
- @render.plot(alt="roll_plot")
395
- @reactive.event(input.go, ignore_none=False)
396
- def roll_plot():
397
- # player_select = 'Nolan Gorman'
398
- # player_select_full =player_select
399
-
400
- if input.batter_id() is "":
401
- fig = plt.figure(figsize=(12, 12))
402
- fig.text(s='Please Select a Batter',x=0.5,y=0.5)
403
- return
404
-
405
- # df_will = df_model_2023[df_model_2023.batter_name == player_select].sort_values(by=['game_date','start_time'])
406
- # df_will = df_will[df_will['is_swing'] != 1]
407
- batter_select_id = int(input.batter_id())
408
- # batter_select_name = 'Edouard Julien'
409
- df_batter_og = df_2023_bip_train[df_2023_bip_train['batter_id']==batter_select_id]
410
- batter_select_name = df_batter_og['batter_name'].values[0]
411
- win = min(int(input.rolling_window()),len(df_batter_og))
412
- df_2023_output = df_2023_output_copy[df_2023_output_copy['bip'] >= win]
413
- sns.set_theme(style="whitegrid", palette="pastel")
414
- #fig, ax = plt.subplots(1, 1, figsize=(10, 10),dpi=300)
415
 
416
- from matplotlib.gridspec import GridSpec
417
- # fig,ax = plt.subplots(figsize=(12, 12),dpi=150)
418
- fig = plt.figure(figsize=(12,12))
419
- gs = GridSpec(3, 3, height_ratios=[0.3,10,0.2], width_ratios=[0.01,2,0.01])
420
 
421
- axheader = fig.add_subplot(gs[0, :])
422
- ax10 = fig.add_subplot(gs[1, 0])
423
- ax = fig.add_subplot(gs[1, 1]) # Subplot at the top-right position
424
- ax12 = fig.add_subplot(gs[1, 2])
425
- axfooter1 = fig.add_subplot(gs[-1, :])
426
 
427
- axheader.axis('off')
428
- ax10.axis('off')
429
- ax12.axis('off')
430
- axfooter1.axis('off')
 
431
 
 
 
432
 
433
- sns.lineplot( x= range(win,len(df_batter_og.y_pred.rolling(window=win).mean())+1),
434
- y= df_batter_og.y_pred.rolling(window=win).mean().dropna(),
435
- color=colour_palette[0],linewidth=2,ax=ax)
436
 
437
- ax.hlines(y=df_batter_og.y_pred.mean(),xmin=win,xmax=len(df_batter_og),color=colour_palette[0],linestyle='--',
438
- label=f'{batter_select_name} Average: {df_batter_og.y_pred.mean():.3f} xSLGCON ({p.ordinal(int(np.around(percentileofscore(df_2023_output["xslgcon"],df_batter_og.y_pred.mean(), kind="strict"))))} Percentile)')
 
 
 
 
 
 
 
439
 
440
- # ax.hlines(y=df_model_2023.y_pred_no_swing.std()*100,xmin=win,xmax=len(df_will))
 
441
 
442
- # sns.scatterplot( x= [976],
443
- # y= df_will.y_pred.rolling(window=win).mean().min()*100,
444
- # color=colour_palette[0],linewidth=2,ax=ax,zorder=100,s=100,edgecolor=colour_palette[7])
445
 
446
 
447
- ax.hlines(y=df_2023_bip_train['y_pred'].mean(),xmin=win,xmax=len(df_batter_og),color=colour_palette[1],linestyle='-.',alpha=1,
448
- label = f'MLB Average: {df_2023_bip_train["y_pred"].mean():.3f} xSLGCON')
449
 
450
- ax.legend()
451
 
452
- hard_hit_dates = [df_2023_output['xslgcon'].quantile(0.9),
453
- df_2023_output['xslgcon'].quantile(0.75),
454
- df_2023_output['xslgcon'].quantile(0.25),
455
- df_2023_output['xslgcon'].quantile(0.1)]
456
 
457
 
458
 
459
- ax.hlines(y=df_2023_output['xslgcon'].quantile(0.9),xmin=win,xmax=len(df_batter_og),color=colour_palette[2],linestyle='dotted',alpha=0.5,zorder=1)
460
- ax.hlines(y=df_2023_output['xslgcon'].quantile(0.75),xmin=win,xmax=len(df_batter_og),color=colour_palette[3],linestyle='dotted',alpha=0.5,zorder=1)
461
- ax.hlines(y=df_2023_output['xslgcon'].quantile(0.25),xmin=win,xmax=len(df_batter_og),color=colour_palette[4],linestyle='dotted',alpha=0.5,zorder=1)
462
- ax.hlines(y=df_2023_output['xslgcon'].quantile(0.1),xmin=win,xmax=len(df_batter_og),color=colour_palette[5],linestyle='dotted',alpha=0.5,zorder=1)
463
 
464
- hard_hit_text = ['90th %','75th %','25th %','10th %']
465
- for i, x in enumerate(hard_hit_dates):
466
- ax.text(min(win+win/50,win+win+5), x ,hard_hit_text[i], rotation=0,va='center', ha='left',
467
- bbox=dict(facecolor='white',alpha=0.7, edgecolor=colour_palette[2+i], pad=2),zorder=11)
468
 
469
- # # Annotate with an arrow
470
- # ax.annotate('June 6, 2023\nSeason Worst Decision Value', xy=(976, df_will.y_pred.rolling(window=win).mean().min()*100-0.03),
471
- # xytext=(976 - 150, df_will.y_pred.rolling(window=win).mean().min()*100 - 0.2),
472
- # arrowprops=dict(facecolor=colour_palette[7], shrink=0.01),zorder=150,fontsize=10,
473
- # bbox=dict(facecolor='white', edgecolor='black'),va='top')
474
 
475
- ax.set_xlim(win,len(df_batter_og))
476
- # ax.set_ylim(0.2,max(1,))
 
 
477
 
478
- ax.set_yticks([0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1])
 
 
 
479
 
480
- ax.set_xlabel('Balls In Play')
481
- ax.set_ylabel('Expected Total Bases per Ball In Play (xSLGCON)')
482
 
483
- from matplotlib.ticker import FormatStrFormatter
 
 
484
 
485
- ax.yaxis.set_major_formatter(FormatStrFormatter('%.3f'))
 
 
 
 
 
 
486
 
487
- axheader.text(s=f'{batter_select_name} - MLB - {win} Rolling BIP Expected Slugging on Contact (xSLGCON)',x=0.5,y=-0.5,ha='center',va='bottom',fontsize=14)
488
- axfooter1.text(.05, 0.2, "By: Thomas Nestico",ha='left', va='bottom',fontsize=12)
489
- axfooter1.text(0.95, 0.2, "Data: MLB",ha='right', va='bottom',fontsize=12)
490
 
491
- fig.subplots_adjust(left=0.01, right=0.99, top=0.98, bottom=0.02)
492
 
493
  app = App(ui.page_fluid(
494
  # ui.tags.base(href=base_url),
@@ -507,99 +363,37 @@ app = App(ui.page_fluid(
507
  shinyswatch.theme.simplex(),
508
  ui.tags.h4("TJStats"),
509
  ui.tags.i("Baseball Analytics and Visualizations"),
510
- # ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
511
-
512
- # ui.navset_tab(
513
- # ui.nav_control(
514
- # ui.a(
515
- # "Home",
516
- # href="https://nesticot-tjstats-site.hf.space/home/"
517
- # ),
518
- # ),
519
- # ui.nav_menu(
520
- # "Batter Charts",
521
- # ui.nav_control(
522
- # ui.a(
523
- # "Batting Rolling",
524
- # href="https://nesticot-tjstats-site-rolling-batter.hf.space/"
525
- # ),
526
- # ui.a(
527
- # "Spray",
528
- # href="https://nesticot-tjstats-site-spray.hf.space/"
529
- # ),
530
- # ui.a(
531
- # "Decision Value",
532
- # href="https://nesticot-tjstats-site-decision-value.hf.space/"
533
- # ),
534
- # ui.a(
535
- # "Damage Model",
536
- # href="https://nesticot-tjstats-site-damage.hf.space/"
537
- # ),
538
- # ui.a(
539
- # "Batter Scatter",
540
- # href="https://nesticot-tjstats-site-batter-scatter.hf.space/"
541
- # ),
542
- # ui.a(
543
- # "EV vs LA Plot",
544
- # href="https://nesticot-tjstats-site-ev-angle.hf.space/"
545
- # ),
546
- # ui.a(
547
- # "Statcast Compare",
548
- # href="https://nesticot-tjstats-site-statcast-compare.hf.space/"
549
- # ),
550
- # ui.a(
551
- # "MLB/MiLB Cards",
552
- # href="https://nesticot-tjstats-site-mlb-cards.hf.space/"
553
- # )
554
- # ),
555
- # ),
556
- # ui.nav_menu(
557
- # "Pitcher Charts",
558
- # ui.nav_control(
559
- # ui.a(
560
- # "Pitcher Rolling",
561
- # href="https://nesticot-tjstats-site-rolling-pitcher.hf.space/"
562
- # ),
563
- # ui.a(
564
- # "Pitcher Summary",
565
- # href="https://nesticot-tjstats-site-pitching-summary-graphic-new.hf.space/"
566
- # ),
567
- # ui.a(
568
- # "Pitcher Scatter",
569
- # href="https://nesticot-tjstats-site-pitcher-scatter.hf.space"
570
- # )
571
- # ),
572
- # )),
573
  ui.row(
574
  ui.layout_sidebar(
575
-
576
- ui.panel_sidebar(
577
- ui.input_select("batter_id",
578
- "Select Batter",
579
- batter_dict,
580
- width=1,
581
- size=1,
582
- selectize=True),
583
- ui.input_numeric("quant",
584
- "Select Percentile",
585
- value=50,
586
- min=0,max=100),
587
- ui.input_numeric("rolling_window",
588
- "Select Rolling Window",
589
- value=50,
590
- min=1),
591
- ui.input_action_button("go", "Generate",class_="btn-primary")),
592
-
593
- ui.panel_main(
594
- ui.navset_tab(
595
-
596
- ui.nav("Damage Hex",
597
- ui.output_plot('hex_plot',
598
- width='1200px',
599
- height='1200px')),
600
- ui.nav("Damage Roll",
601
- ui.output_plot('roll_plot',
602
- width='1200px',
603
- height='1200px'))
604
- ))
605
- )),)),server)
 
1
+
 
 
2
  import pandas as pd
3
  import numpy as np
4
  import matplotlib.pyplot as plt
5
  import seaborn as sns
6
+ #import pitch_summary_functions as psf
7
+ import requests
8
  import matplotlib
9
+ from api_scraper import MLB_Scrape
 
 
10
  import math
11
+
12
+ season = 2024
13
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
14
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
15
+
16
+ import datasets
17
+ from datasets import load_dataset
18
+ # from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
19
+ from shiny import ui, render, App
20
+ # ### Import Datasets
21
+ # dataset = load_dataset('nesticot/mlb_data', data_files=[f'mlb_pitch_data_{season}.csv',
22
+ # f'mlb_pitch_data_{season-1}.csv',
23
+ # f'mlb_pitch_data_{season-2}.csv',
24
+ # f'mlb_pitch_data_{season-3}.csv',
25
+ # f'mlb_pitch_data_{season-4}.csv' ])
26
+
27
 
28
  ### Import Datasets
29
+ dataset = load_dataset('nesticot/mlb_data', data_files=[f'aaa_pitch_data_{season}.csv' ])
30
  dataset_train = dataset['train']
31
+ 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')
32
+
33
+ batter_dict_stat = { 'sweet_spot_percent':{'x_axis':'SweetSpot%','title':'SweetSpot%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
34
+ 'max_launch_speed':{'x_axis':'Max Exit Velocity','title':'Max Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
35
+ 'launch_speed_90':{'x_axis':'90th Percentile EV','title':'90th Percentile EV','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
36
+ 'launch_speed':{'x_axis':'Exit Velocity','title':'Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
37
+ 'launch_angle':{'x_axis':'Launch Angle','title':'Launch Angle','flip_p':False,'decimal_format':'string_0','percent_adjust':100},
38
+ 'avg':{'x_axis':'AVG','title':'AVG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
39
+ 'obp':{'x_axis':'OBP','title':'OBP','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
40
+ 'slg':{'x_axis':'SLG','title':'SLG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
41
+ 'ops':{'x_axis':'OPS','title':'OPS','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
42
+ 'k_percent':{'x_axis':'K%','title':'K%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
43
+ 'bb_percent':{'x_axis':'BB%','title':'BB%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
44
+ 'bb_over_k_percent':{'x_axis':'BB/K','title':'BB/K','flip_p':False,'decimal_format':'string_1','percent_adjust':100},
45
+ 'bb_minus_k_percent':{'x_axis':'BB%-K%','title':'BB%-K%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
46
+ 'csw_percent':{'x_axis':'CSW%','title':'CSW%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
47
+ 'woba_percent':{'x_axis':'wOBA','title':'wOBA','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
48
+ 'hard_hit_percent':{'x_axis':'HardHit%','title':'HardHit%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
49
+ 'barrel_percent':{'x_axis':'Barrel%','title':'Barrel%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
50
+ 'zone_contact_percent':{'x_axis':'Z-Contact%','title':'Z-Contact%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
51
+ 'zone_swing_percent':{'x_axis':'Z-Swing%','title':'Z-Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
52
+ 'zone_percent':{'x_axis':'Zone%','title':'Zone%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
53
+ 'chase_percent':{'x_axis':'O-Swing%','title':'O-Swing%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
54
+ 'chase_contact':{'x_axis':'O-Contact%','title':'O-Contact%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
55
+ 'swing_percent':{'x_axis':'Swing%','title':'Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
56
+ 'whiff_rate':{'x_axis':'Whiff%','title':'Whiff%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
57
+ 'swstr_rate':{'x_axis':'SwStr%','title':'SwStr%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
58
+ }
59
+
60
+ batter_dict_stat_small = { 'sweet_spot_percent':'SweetSpot%',
61
+ 'max_launch_speed':'Max Exit Velocity',
62
+ 'launch_speed_90':'90th Percentile EV',
63
+ 'launch_speed':'Exit Velocity',
64
+ 'launch_angle':'Launch Angle',
65
+ 'avg':'AVG',
66
+ 'obp':'OBP',
67
+ 'slg':'SLG',
68
+ 'ops':'OPS',
69
+ 'k_percent':'K%',
70
+ 'bb_percent':'BB%',
71
+ 'bb_over_k_percent':'BB/K',
72
+ 'bb_minus_k_percent':'BB%-K%',
73
+ 'csw_percent':'CSW%',
74
+ 'woba_percent':'wOBA',
75
+ 'hard_hit_percent':'HardHit%',
76
+ 'barrel_percent':'Barrel%',
77
+ 'zone_contact_percent':'Z-Contact%',
78
+ 'zone_swing_percent':'Z-Swing%',
79
+ 'zone_percent':'Zone%',
80
+ 'chase_percent':'O-Swing%',
81
+ 'chase_contact':'O-Contact%',
82
+ 'swing_percent':'Swing%',
83
+ 'whiff_rate':'Whiff%',
84
+ 'swstr_rate':'SwStr%',
85
+ }
86
+
87
+
88
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
89
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
90
+
91
+ level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A','ROK':'ROK'}
92
+
93
+ print('MLB TOP',df_2024.head(5))
94
+
95
+ import matplotlib.ticker as mtick
96
+ def decimal_format_assign(x):
97
+ if x['decimal_format'] == 'percent_1':
98
+ return mtick.PercentFormatter(1,decimals=1)
99
+ if x['decimal_format'] == 'string_3':
100
+ return mtick.FormatStrFormatter('%.3f')
101
+ if x['decimal_format'] == 'string_0':
102
+ return mtick.FormatStrFormatter('%.0f')
103
+ if x['decimal_format'] == 'string_1':
104
+ return mtick.FormatStrFormatter('%.1f')
105
+
106
+
107
+ from batting_update import df_update, df_update_summ, df_update_summ_avg,df_summ_changes
108
 
 
 
 
109
 
 
 
110
 
111
+ df_2024_update_copy = df_update(df_2024)
112
+ print('MLB TOP',df_2024_update_copy.head(5))
113
+ from adjustText import adjust_text
114
+ import seaborn as sns
 
 
 
 
115
 
 
 
 
116
 
117
+ def server(input,output,session):
 
 
 
 
 
 
 
118
 
119
+ #@reactive.event(input.go, ignore_none=False)
120
 
121
+ @output
122
+ @render.plot(alt="A histogram")
123
+ def plot():
124
+ print('we made it here2')
125
+
126
+ start_date_input = '2024-03-20'
127
+ end_date_input = '2024-12-31'
128
+ df_2024_update = df_2024_update_copy[(df_2024_update_copy['game_date']>=start_date_input)&
129
+ (df_2024_update_copy['game_date']<=end_date_input)]
130
+
131
+ df_2024_update_summ = df_update_summ(df_2024_update)
132
+ df_2024_update_summ_changes = df_summ_changes(df_2024_update_summ)
133
 
134
+ print('MLB TOP UPDATE ',df_2024_update_copy.head(5))
135
 
136
+ sns.set_theme(style="whitegrid", palette="pastel")
137
+
138
+ #print('we made it here')
139
+ #print(data_df)
140
+ #data_df = data_df.sort_values(by='level').reset_index(drop=True)
 
141
 
 
142
 
143
+ # x_flip = batter_dict_stat[x_stat]['flip_p']
144
+ # y_flip = batter_dict_stat[y_stat]['flip_p']
145
+ # cbr_flip = batter_dict_stat[z_stat]['flip_p']
 
 
 
146
 
147
+ x_stat = 'whiff_rate'
148
+ y_stat = 'barrel_percent'
149
+ z_stat = 'woba_percent'
150
 
151
 
152
+ x_flip = batter_dict_stat[x_stat]['flip_p']
153
+ y_flip = batter_dict_stat[y_stat]['flip_p']
154
+ cbr_flip = batter_dict_stat[z_stat]['flip_p']
155
 
156
+ level_id = 'AAA'
157
+ n_input = 200
158
+ n_age_input = 50
159
 
160
+ data_df = df_2024_update_summ.copy()
161
+ data_df = data_df[data_df['pa'] >= n_input].reset_index(drop=True)
162
 
163
+ data_df[x_stat+'_percent'] = data_df[x_stat].rank(pct=True,ascending=abs(x_flip-1))
 
164
 
165
+ data_df[y_stat+'_percent'] = data_df[y_stat].rank(pct=True,ascending=abs(y_flip-1))
166
 
167
+ data_df[z_stat+'_percent'] = data_df[z_stat].rank(pct=True,ascending=abs(cbr_flip-1))
 
168
 
 
169
 
 
 
 
170
 
171
+ fig, ax = plt.subplots(1, 1, figsize=(9, 9),dpi=300)
172
 
 
 
173
 
174
+ if cbr_flip:
175
+ cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[0],colour_palette[3],colour_palette[1]])
176
+ norm = plt.Normalize(data_df[z_stat].min(), data_df[z_stat].max())
 
 
 
 
 
177
 
178
+ else:
179
+ cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],colour_palette[3],colour_palette[0]])
180
+ norm = plt.Normalize(data_df[z_stat].min(), data_df[z_stat].max())
181
 
182
+ sm = plt.cm.ScalarMappable(cmap=cmap_hue, norm=norm)
183
+ print('we made it here')
184
 
185
+ scatter = sns.scatterplot(x = x_stat, y = y_stat, data=data_df, color = '#b3b3b3')
186
+ #ax.get_legend().remove()
187
+ scatter = sns.scatterplot(x = x_stat, y = y_stat, data=data_df, color = colour_palette[0],ax=ax,hue=z_stat,palette=cmap_hue)
188
+ sns.set_theme(style="whitegrid", palette="pastel")
189
 
190
+ fig.set_facecolor('#F0F0F0')
191
+ ax.set_facecolor('white')
 
 
192
 
193
+ print('we made it here')
194
+ # for i in range(0,len(pitch_group_unique)):
195
+ # data_df = elly_zone_df[elly_zone_df.pitch_group==pitch_group_unique[i]]
196
+ # len_df.append(len(data_df))
197
+ # sns.lineplot(x=range(1,len(data_df)+1),y=data_df.swings.rolling(window=rolling_window_input).sum()/data_df.pitches.rolling(window=rolling_window_input).sum(),color=colour_palette[i],linewidth=3,ax=ax,
198
+ # label=f'{pitch_group_unique[i]} (Season Average {float(data_df.swings.sum()/data_df.pitches.sum()):.1%})',zorder=i+10)
199
+ # ax.hlines(xmin=0,xmax=len(elly_zone_df),y=data_df.swings.sum()/data_df.pitches.sum(),color=colour_palette[i],linewidth=3,linestyle='-.',alpha=0.4,zorder=i)
200
 
 
 
201
 
 
202
 
203
 
204
+ x_min = 0.2
205
+ x_max = 0.5
206
 
207
+ y_min = 0
208
+ y_max = 115
209
 
210
+ z_min =0
211
+ z_max = 10000
212
 
213
+ names = True
214
 
215
+ ts=[]
216
+ print(len(data_df))
217
+ if names:
218
+ for i in range(len(data_df)):
219
+ if (data_df[x_stat].values[i] < x_min or data_df[x_stat].values[i] > x_max ) \
220
+ and (data_df[y_stat].values[i] < y_min or data_df[y_stat].values[i] > y_max):
221
+
222
+ #or (str(data_df.batter_id[i]) in (input.player_id())):
223
+ # print(data_df.batter[i])
224
+ # ax.annotate(data_df.batter[i], xy=((data_df[x_stat][i])+0.025/batter_dict_stat[x_stat]['percent_adjust'], data_df[y_stat][i]+0.01/batter_dict_stat[x_stat]['percent_adjust']), xytext=(-20,20),
225
+ # textcoords='offset points', ha='center', va='bottom',fontsize=7,
226
+ # bbox=dict(boxstyle='round,pad=0', fc=colour_palette[6], alpha=0.0),
227
+ # arrowprops=dict(arrowstyle='->', connectionstyle="angle,angleA=-90,angleB=-10,rad=2",
228
+ # color=colour_palette[8]))
229
+
230
+ #if data_df['batter'][i] != 'Jo Adell':
231
+ # ax.annotate(data_df.batter[i], (data_df[x_stat][i]-len(data_df.batter[i])*0.00025, data_df[y_stat][i]+0.001),fontsize=8)
232
+ ts.append(ax.text(data_df[x_stat][i], data_df[y_stat][i], data_df.batter_name[i],fontsize=8))
233
 
234
 
235
 
236
+ ax.hlines(xmin=(math.floor((data_df[x_stat].min()*batter_dict_stat[x_stat]['percent_adjust']-0.01)/5))*5/batter_dict_stat[x_stat]['percent_adjust'],
237
+ xmax= (math.ceil((data_df[x_stat].max()*batter_dict_stat[x_stat]['percent_adjust']+0.01)/5))*5/batter_dict_stat[x_stat]['percent_adjust'],
238
+ y=data_df[y_stat].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
239
 
240
+ print('we made it here')
241
 
242
+ ax.vlines(ymin=(math.floor((data_df[y_stat].min()*batter_dict_stat[y_stat]['percent_adjust']-0.01)/5))*5/batter_dict_stat[y_stat]['percent_adjust'],
243
+ ymax= (math.ceil((data_df[y_stat].max()*batter_dict_stat[y_stat]['percent_adjust']+0.01)/5))*5/batter_dict_stat[y_stat]['percent_adjust'],
244
+ x=data_df[x_stat].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
245
 
246
+ print(data_df[x_stat].min())
247
+ print(batter_dict_stat[x_stat]['percent_adjust'])
248
+ print((math.floor((data_df[x_stat].min()*batter_dict_stat[x_stat]['percent_adjust']-0.01)/5))*5/batter_dict_stat[x_stat]['percent_adjust'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
+ ax.set_xlim((math.floor((data_df[x_stat].min()*batter_dict_stat[x_stat]['percent_adjust'])/5))*5/batter_dict_stat[x_stat]['percent_adjust'],
252
+ (math.ceil((data_df[x_stat].max()*batter_dict_stat[x_stat]['percent_adjust'])/5))*5/batter_dict_stat[x_stat]['percent_adjust'])
 
 
 
 
 
 
 
 
 
 
253
 
 
 
254
 
255
+ ax.set_ylim((math.floor((data_df[y_stat].min()*batter_dict_stat[y_stat]['percent_adjust'])/5))*5/batter_dict_stat[y_stat]['percent_adjust'],
256
+ (math.ceil((data_df[y_stat].max()*batter_dict_stat[y_stat]['percent_adjust'])/5))*5/batter_dict_stat[y_stat]['percent_adjust'])
 
 
 
 
257
 
 
258
 
259
 
260
+ #title_level = str([x .strip("\'")for x in level_id]).strip('[').strip(']').replace("'",'')
261
+ title_level = level_id
262
+ if title_level == 'AAA, AA, A+, A':
263
+ title_level='MiLB'
264
+ # #title_level = level_id[0]
265
+ # if input.n_age() >= 50:
266
+ # title_spot = f'{title_level} Batter {batter_dict_stat[y_stat]["title"]} vs {batter_dict_stat[x_stat]["title"]} (min. {n_input} PA)'
267
+
268
+ else:
269
+ title_spot = f'{title_level} Batter - {season} - {batter_dict_stat[y_stat]["title"]} vs {batter_dict_stat[x_stat]["title"]} (min. {n_input} PA)'
270
+
271
+ ax.set_title(title_spot, fontsize=24/(len(title_spot)*0.03),fontname='Century Gothic')
272
+ # #vals = ax.get_yticks()
273
+ ax.set_xlabel(batter_dict_stat[x_stat]['x_axis'], fontsize=16,fontname='Century Gothic')
274
+ ax.set_ylabel(batter_dict_stat[y_stat]['x_axis'], fontsize=16,fontname='Century Gothic')
 
 
 
 
 
 
 
275
 
 
 
 
 
276
 
277
+ # if input.group_level():
278
+ # ax.get_legend().remove()
 
 
 
279
 
280
+ # if not input.group_level():
281
+ # if len(level_id) > 1:
282
+ # h,l = scatter.get_legend_handles_labels()
283
+ # l[-(len(level_id)+1)] = 'Level'
284
+ # ax.legend(h[-(len(level_id)+1):],l[-(len(level_id)+1):], borderaxespad=0.1,loc=0)
285
 
286
+ # else:
287
+ # ax.get_legend().remove()
288
 
289
+ #plt.show(g)
290
+ # ax.figure.colorbar(sm, ax=ax)
 
291
 
292
+ cbar = ax.figure.colorbar(sm, ax=ax,format=decimal_format_assign(x=batter_dict_stat[z_stat]),orientation='vertical',aspect=30)
293
+ cbar.set_label(batter_dict_stat[z_stat]['x_axis'])
294
+ #fig.axes[0].invert_yaxis()
295
+ print('we made it here5')
296
+ fig.subplots_adjust(wspace=.02, hspace=.02)
297
+ # ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
298
+ #ax.set_yticks([0,0.1,0.2,0.3,0.4,0.5])
299
+ # fig.colorbar(plot_dist, ax=ax)
300
+ # fig.colorbar(plot_dist)
301
 
302
+ if batter_dict_stat[x_stat]['flip_p']:
303
+ fig.axes[0].invert_xaxis()
304
 
305
+ if batter_dict_stat[y_stat]['flip_p']:
306
+ fig.axes[0].invert_yaxis()
 
307
 
308
 
309
+ # ax.xaxis.set_major_formatter(mtick.PercentFormatter(1,decimals=0))
310
+ # ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
311
 
 
312
 
 
 
 
 
313
 
314
 
315
 
316
+ print('we made it here6')
 
 
 
317
 
318
+ ax.xaxis.set_major_formatter(decimal_format_assign(x=batter_dict_stat[x_stat]))
319
+ ax.yaxis.set_major_formatter(decimal_format_assign(x=batter_dict_stat[y_stat]))
 
 
320
 
 
 
 
 
 
321
 
322
+ print('we made it here7')
323
+ # ax.text(0.5, 0.5, '/u/tomstoms', transform=ax.transAxes,
324
+ # fontsize=60, color='gray', alpha=0.075,
325
+ # ha='center', va='center', rotation=45)
326
 
327
+ print(ts)
328
+ if len(ts) > 0:
329
+ adjust_text(ts,
330
+ arrowprops=dict(arrowstyle="-", color=colour_palette[4], lw=1),ax=ax)
331
 
332
+ #ax.legend(fontsize='16')
333
+ ax.get_legend().remove()
334
 
335
+ fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
336
+ fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
337
+ fig.tight_layout()
338
 
339
+ import shinyswatch
340
+
341
+
342
+ # app_ui = ui.page_fluid(ui.output_plot("plot",height = "1000px",width="1000px"))
343
+
344
+ # app = App(ui.page_fluid(ui.output_plot("plot",height = "1000px",width="1000px")),server)
345
+ # app = App(app_ui, server)
346
 
 
 
 
347
 
 
348
 
349
  app = App(ui.page_fluid(
350
  # ui.tags.base(href=base_url),
 
363
  shinyswatch.theme.simplex(),
364
  ui.tags.h4("TJStats"),
365
  ui.tags.i("Baseball Analytics and Visualizations"),
366
+ # ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  ui.row(
368
  ui.layout_sidebar(
369
+
370
+ ui.panel_sidebar(
371
+ #ui.output_ui('test','Select Player'),
372
+ # #ui.input_select("id", "Select Pitcher",batter_dict,selected=675911,width=1,size=1,selectize=True),
373
+ # #ui.input_select("level_id", "Select Level",level_dict,width=1,size=1),
374
+ # #ui.input_select("stat_id", "Select Stat",plot_dict_small,width=1,size=1),
375
+ # ui.input_numeric("n", "Rolling Window Size", value=50),
376
+ # ui.input_action_button("go", "Generate",class_="btn-primary"),
377
+ # ui.output_table("result")
378
+ ),
379
+
380
+ ui.panel_main(
381
+ ui.navset_tab(
382
+ # ui.nav("Raw Data",
383
+ # ui.output_data_frame("raw_table")),
384
+ # ui.nav("Season Summary",
385
+ # ui.output_plot('plot',
386
+ # width='2000px',
387
+ # height='2000px')),
388
+ ui.nav("MLB",
389
+ ui.output_plot("plot",height = "1000px",width="1000px"))
390
+ # ui.nav("AAA",
391
+ # ui.output_plot("plot_aaa",height = "1000px",width="1000px")),
392
+ # ui.nav("AA",
393
+ # ui.output_plot("plot_aa",height = "1000px",width="1000px")) ,
394
+ # ui.nav("A+",
395
+ # ui.output_plot("plot_ha",height = "1000px",width="1000px")),
396
+ # ui.nav("A",
397
+ # ui.output_plot("plot_a",height = "1000px",width="1000px"))
398
+
399
+ ,id="my_tabs")))))),server)