nesticot commited on
Commit
efd6469
β€’
1 Parent(s): 7a06fbd

Upload 34 files

Browse files
app.py CHANGED
@@ -9,40 +9,36 @@ import shinyswatch
9
 
10
  #Import pages
11
  from home import home
12
- # from about import about
13
- # from gsax_timeline import gsax_timeline
14
- # from on_ice_xg_rates import on_ice_xg
15
- # from gsax_leaderboard import gsax_leaderboard
16
- # from on_ice_xgfp import on_ice_xgfp
17
- # from team_xg_rates import team_xg_rates
18
- # from gsax_comparison import gsax_comparison
19
- # from game import game
20
  from spray import spray
21
  from decision_value import decision_value
22
  from damage import damage
23
  from batter_scatter import batter_scatter
24
  from ev_angle import ev_angle
25
- # from articles import articles
26
- # from xg_model import xg_model
 
 
 
 
 
 
27
 
28
  # Create app
29
  routes = [
30
  Mount('/home', app=home),
31
- # Mount('/about', app=about),
32
- # Mount('/gsax-timeline', app=gsax_timeline),
33
- # Mount('/skater-xg-rates', app=on_ice_xg),
34
- # Mount('/gsax-leaderboard', app=gsax_leaderboard),
35
- # Mount('/skater-xg-percentages', app=on_ice_xgfp),
36
- # Mount('/team-xg-rates', app=team_xg_rates),
37
- # Mount('/gsax-comparison',app=gsax_comparison),
38
  Mount('/spray',app=spray),
39
  Mount('/decision_value',app=decision_value),
40
  Mount('/damage_model',app=damage),
41
  Mount('/batter_scatter',app=batter_scatter),
42
  Mount('/ev_angle',app=ev_angle),
43
- # Mount('/game/{game_id}',app=game),
44
- # Mount('/articles',app=articles),
45
- # Mount('/xg-model',app=xg_model)
 
 
 
46
  ]
47
 
48
  #Run App
 
9
 
10
  #Import pages
11
  from home import home
12
+
 
 
 
 
 
 
 
13
  from spray import spray
14
  from decision_value import decision_value
15
  from damage import damage
16
  from batter_scatter import batter_scatter
17
  from ev_angle import ev_angle
18
+ from rolling_batter import rolling_batter
19
+ from statcast_compare import statcast_compare
20
+
21
+ from rolling_pitcher import rolling_pitcher
22
+ from pitching_summary_graphic_new_fg_api import pitching_summary_graphic_new
23
+ from pitcher_scatter import pitcher_scatter
24
+
25
+
26
 
27
  # Create app
28
  routes = [
29
  Mount('/home', app=home),
30
+
 
 
 
 
 
 
31
  Mount('/spray',app=spray),
32
  Mount('/decision_value',app=decision_value),
33
  Mount('/damage_model',app=damage),
34
  Mount('/batter_scatter',app=batter_scatter),
35
  Mount('/ev_angle',app=ev_angle),
36
+ Mount('/rolling_batter',app=rolling_batter),
37
+ Mount('/statcast_compare',app=statcast_compare),
38
+
39
+ Mount('/rolling_pitcher',app=rolling_pitcher),
40
+ Mount('/pitching_summary_graphic_new',app=pitching_summary_graphic_new),
41
+ Mount('/pitcher_scatter',app=pitcher_scatter),
42
  ]
43
 
44
  #Run App
batter_scatter.py CHANGED
@@ -19,6 +19,8 @@ from shinywidgets import output_widget, render_widget
19
  import pandas as pd
20
  from configure import base_url
21
  import shinyswatch
 
 
22
 
23
 
24
  exit_velo_df_codes_summ_batter = pd.read_csv('summary_batter.csv',index_col=[0])
@@ -126,6 +128,7 @@ def server(input,output,session):
126
 
127
  @output
128
  @render.plot(alt="A histogram")
 
129
  def plot():
130
  sns.set_theme(style="whitegrid", palette="pastel")
131
  print(input.level_id())
@@ -387,6 +390,7 @@ batter_scatter = App(ui.page_fluid(
387
  shinyswatch.theme.simplex(),
388
  ui.tags.h4("TJStats"),
389
  ui.tags.i("Baseball Analytics and Visualizations"),
 
390
  ui.navset_tab(
391
  ui.nav_control(
392
  ui.a(
@@ -397,7 +401,11 @@ batter_scatter = App(ui.page_fluid(
397
  ui.nav_menu(
398
  "Batter Charts",
399
  ui.nav_control(
400
- ui.a(
 
 
 
 
401
  "Spray",
402
  href="spray/"
403
  ),
@@ -416,48 +424,29 @@ batter_scatter = App(ui.page_fluid(
416
  ui.a(
417
  "EV vs LA Plot",
418
  href="ev_angle/"
 
 
 
 
419
  )
420
  ),
421
  ),
422
  ui.nav_menu(
423
- "Goalie Charts",
424
  ui.nav_control(
425
  ui.a(
426
- "GSAx Timeline",
427
- href="gsax-timeline/"
428
  ),
429
  ui.a(
430
- "GSAx Leaderboard",
431
- href="gsax-leaderboard/"
432
  ),
433
  ui.a(
434
- "GSAx Comparison",
435
- href="gsax-comparison/"
436
  )
437
  ),
438
- ),ui.nav_menu(
439
- "Team Charts",
440
- ui.nav_control(
441
- ui.a(
442
- "Team xG Rates",
443
- href="team-xg-rates/"
444
- ),
445
- ),
446
- ),ui.nav_control(
447
- ui.a(
448
- "Games",
449
- href="games/"
450
- ),
451
- ),ui.nav_control(
452
- ui.a(
453
- "About",
454
- href="about/"
455
- ),
456
- ),ui.nav_control(
457
- ui.a(
458
- "Articles",
459
- href="articles/"
460
- ),
461
  )),ui.row(
462
  ui.layout_sidebar(
463
 
@@ -491,6 +480,7 @@ batter_scatter = App(ui.page_fluid(
491
  ui.row(
492
  ui.input_switch("names", "Toggle Names"),
493
  ui.input_switch("group_level", "Group Levels")),
 
494
  ),
495
 
496
  ui.panel_main(
 
19
  import pandas as pd
20
  from configure import base_url
21
  import shinyswatch
22
+ import inflect
23
+ from matplotlib.pyplot import text
24
 
25
 
26
  exit_velo_df_codes_summ_batter = pd.read_csv('summary_batter.csv',index_col=[0])
 
128
 
129
  @output
130
  @render.plot(alt="A histogram")
131
+ @reactive.event(input.go, ignore_none=False)
132
  def plot():
133
  sns.set_theme(style="whitegrid", palette="pastel")
134
  print(input.level_id())
 
390
  shinyswatch.theme.simplex(),
391
  ui.tags.h4("TJStats"),
392
  ui.tags.i("Baseball Analytics and Visualizations"),
393
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
394
  ui.navset_tab(
395
  ui.nav_control(
396
  ui.a(
 
401
  ui.nav_menu(
402
  "Batter Charts",
403
  ui.nav_control(
404
+ ui.a(
405
+ "Batting Rolling",
406
+ href="rolling_batter/"
407
+ ),
408
+ ui.a(
409
  "Spray",
410
  href="spray/"
411
  ),
 
424
  ui.a(
425
  "EV vs LA Plot",
426
  href="ev_angle/"
427
+ ),
428
+ ui.a(
429
+ "Statcast Compare",
430
+ href="statcast_compare/"
431
  )
432
  ),
433
  ),
434
  ui.nav_menu(
435
+ "Pitcher Charts",
436
  ui.nav_control(
437
  ui.a(
438
+ "Pitcher Rolling",
439
+ href="rolling_pitcher/"
440
  ),
441
  ui.a(
442
+ "Pitcher Summary",
443
+ href="pitching_summary_graphic_new/"
444
  ),
445
  ui.a(
446
+ "Pitcher Scatter",
447
+ href="pitcher_scatter/"
448
  )
449
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  )),ui.row(
451
  ui.layout_sidebar(
452
 
 
480
  ui.row(
481
  ui.input_switch("names", "Toggle Names"),
482
  ui.input_switch("group_level", "Group Levels")),
483
+ ui.input_action_button("go", "Generate",class_="btn-primary"),
484
  ),
485
 
486
  ui.panel_main(
batter_scatter_2.py ADDED
@@ -0,0 +1,502 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import inflect
23
+ from matplotlib.pyplot import text
24
+
25
+
26
+ exit_velo_df_codes_summ_batter = pd.read_csv('summary_batter.csv',index_col=[0])
27
+ #exit_velo_df_codes_summ = pd.read_csv('summary_pitcher.csv',index_col=[0])
28
+
29
+ exit_velo_df_codes_summ_non_level = pd.read_csv('summary_batter_level.csv',index_col=[0]).reset_index(drop=True)
30
+
31
+ exit_velo_df_codes_summ_non_level['levels'] = exit_velo_df_codes_summ_non_level.levels.str.split(', ')
32
+
33
+ exit_velo_df_codes_summ_non_level = exit_velo_df_codes_summ_non_level.rename(columns={'levels':'level'})
34
+
35
+
36
+
37
+ print(exit_velo_df_codes_summ_batter.bb_minus_k_percent)
38
+
39
+ batter_dict_stat = { 'sweet_spot_percent':{'x_axis':'SweetSpot%','title':'SweetSpot%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
40
+ 'max_launch_speed':{'x_axis':'Max Exit Velocity','title':'Max Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
41
+ 'launch_speed_90':{'x_axis':'90th Percentile EV','title':'90th Percentile EV','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
42
+ 'launch_speed':{'x_axis':'Exit Velocity','title':'Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
43
+ 'launch_angle':{'x_axis':'Launch Angle','title':'Launch Angle','flip_p':False,'decimal_format':'string_0','percent_adjust':100},
44
+ 'avg':{'x_axis':'AVG','title':'AVG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
45
+ 'obp':{'x_axis':'OBP','title':'OBP','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
46
+ 'slg':{'x_axis':'SLG','title':'SLG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
47
+ 'ops':{'x_axis':'OPS','title':'OPS','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
48
+ 'k_percent':{'x_axis':'K%','title':'K%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
49
+ 'bb_percent':{'x_axis':'BB%','title':'BB%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
50
+ 'bb_over_k_percent':{'x_axis':'BB/K','title':'BB/K','flip_p':False,'decimal_format':'string_1','percent_adjust':100},
51
+ 'bb_minus_k_percent':{'x_axis':'BB%-K%','title':'BB%-K%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
52
+ 'csw_percent':{'x_axis':'CSW%','title':'CSW%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
53
+ 'woba_percent':{'x_axis':'wOBA','title':'wOBA','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
54
+ 'hard_hit_percent':{'x_axis':'HardHit%','title':'HardHit%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
55
+ 'barrel_percent':{'x_axis':'Barrel%','title':'Barrel%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
56
+ 'zone_contact_percent':{'x_axis':'Z-Contact%','title':'Z-Contact%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
57
+ 'zone_swing_percent':{'x_axis':'Z-Swing%','title':'Z-Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
58
+ 'zone_percent':{'x_axis':'Zone%','title':'Zone%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
59
+ 'chase_percent':{'x_axis':'O-Swing%','title':'O-Swing%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
60
+ 'chase_contact':{'x_axis':'O-Contact%','title':'O-Contact%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
61
+ 'swing_percent':{'x_axis':'Swing%','title':'Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
62
+ 'whiff_rate':{'x_axis':'Whiff%','title':'Whiff%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
63
+ 'swstr_rate':{'x_axis':'SwStr%','title':'SwStr%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
64
+ }
65
+
66
+ batter_dict_stat_small = { 'sweet_spot_percent':'SweetSpot%',
67
+ 'max_launch_speed':'Max Exit Velocity',
68
+ 'launch_speed_90':'90th Percentile EV',
69
+ 'launch_speed':'Exit Velocity',
70
+ 'launch_angle':'Launch Angle',
71
+ 'avg':'AVG',
72
+ 'obp':'OBP',
73
+ 'slg':'SLG',
74
+ 'ops':'OPS',
75
+ 'k_percent':'K%',
76
+ 'bb_percent':'BB%',
77
+ 'bb_over_k_percent':'BB/K',
78
+ 'bb_minus_k_percent':'BB%-K%',
79
+ 'csw_percent':'CSW%',
80
+ 'woba_percent':'wOBA',
81
+ 'hard_hit_percent':'HardHit%',
82
+ 'barrel_percent':'Barrel%',
83
+ 'zone_contact_percent':'Z-Contact%',
84
+ 'zone_swing_percent':'Z-Swing%',
85
+ 'zone_percent':'Zone%',
86
+ 'chase_percent':'O-Swing%',
87
+ 'chase_contact':'O-Contact%',
88
+ 'swing_percent':'Swing%',
89
+ 'whiff_rate':'Whiff%',
90
+ 'swstr_rate':'SwStr%',
91
+ }
92
+
93
+
94
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
95
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
96
+
97
+ level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A','ROK':'ROK'}
98
+
99
+ batter_test_df = exit_velo_df_codes_summ_batter.sort_values(by='batter').drop_duplicates(subset='batter_id').reset_index(drop=True)[['batter_id','batter']]#['pitcher'].to_dict()
100
+ batter_test_df = batter_test_df.set_index('batter_id')
101
+
102
+
103
+ def decimal_format_assign(x):
104
+ if x['decimal_format'] == 'percent_1':
105
+ return mtick.PercentFormatter(1,decimals=1)
106
+ if x['decimal_format'] == 'string_3':
107
+ return mtick.FormatStrFormatter('%.3f')
108
+ if x['decimal_format'] == 'string_0':
109
+ return mtick.FormatStrFormatter('%.0f')
110
+ if x['decimal_format'] == 'string_1':
111
+ return mtick.FormatStrFormatter('%.1f')
112
+
113
+
114
+ #test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])
115
+
116
+ batter_dict = batter_test_df['batter'].to_dict()
117
+
118
+ exit_velo_df_codes_summ_batter.position = exit_velo_df_codes_summ_batter.position.replace(['LF','RF','CF','TWP'],['OF','OF','OF','DH'])
119
+ exit_velo_df_codes_summ_non_level.position = exit_velo_df_codes_summ_non_level.position.replace(['LF','RF','CF','TWP'],['OF','OF','OF','DH'])
120
+
121
+ position_list = ['All'] + list(exit_velo_df_codes_summ_batter.position.unique())
122
+ team_list = ['All'] + sorted(list(exit_velo_df_codes_summ_batter.parent_org_abb.unique()))
123
+
124
+
125
+
126
+ def server(input,output,session):
127
+
128
+
129
+ @output
130
+ @render.plot(alt="A histogram")
131
+ def plot():
132
+ sns.set_theme(style="whitegrid", palette="pastel")
133
+ print(input.level_id())
134
+ print(input.n())
135
+ print('we made it here',input.team_id(),input.position_id())
136
+ if input.group_level():
137
+ data_df = exit_velo_df_codes_summ_non_level.copy()
138
+
139
+ turth_list = []
140
+ #turth_list_2 = []
141
+ for x in range(0,len(data_df.level)):
142
+ turth_list_2 = []
143
+ for y in range(0,len(data_df.level[x])):
144
+ #print(level_list[x][y])
145
+ turth_list_2.append(data_df.level[x][y] in input.level_id())
146
+ turth_list.append(turth_list_2)
147
+
148
+ final_check_list = [True if True in x else False for x in turth_list]
149
+
150
+
151
+ data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(final_check_list)]
152
+
153
+
154
+ else:
155
+
156
+
157
+ data_df = exit_velo_df_codes_summ_batter.copy()
158
+ data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(data_df.level.isin(input.level_id()))]
159
+ print(data_df)
160
+ if 'All' in input.team_id():
161
+ print('nice')#data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)
162
+
163
+ else:
164
+ data_df = data_df[(data_df.parent_org_abb.isin(input.team_id()))].reset_index(drop=True)
165
+
166
+ if 'All' in input.position_id():
167
+ print('nice')#data_df = data_df[(data_df.level.isin(input.level_id()))&(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)
168
+
169
+ else:
170
+ data_df = data_df[(data_df.position.isin(input.position_id()))].reset_index(drop=True)
171
+
172
+
173
+ #print('we made it here')
174
+ print(data_df)
175
+ data_df = data_df.sort_values(by='level').reset_index(drop=True)
176
+ print(batter_dict_stat[input.stat_x()]['flip_p'])
177
+
178
+
179
+
180
+ x_flip = batter_dict_stat[input.stat_x()]['flip_p']
181
+ y_flip = batter_dict_stat[input.stat_y()]['flip_p']
182
+ cbr_flip = batter_dict_stat[input.stat_z()]['flip_p']
183
+
184
+
185
+
186
+ data_df[input.stat_x()+'_percent'] = data_df[input.stat_x()].rank(pct=True,ascending=abs(x_flip-1))
187
+
188
+ data_df[input.stat_y()+'_percent'] = data_df[input.stat_y()].rank(pct=True,ascending=abs(y_flip-1))
189
+
190
+ data_df[input.stat_z()+'_percent'] = data_df[input.stat_z()].rank(pct=True,ascending=abs(cbr_flip-1))
191
+
192
+
193
+
194
+ fig, ax = plt.subplots(1, 1, figsize=(9, 9))
195
+
196
+ #data_df['bb_over_obp'] = data_df['bb']/data_df['k']
197
+
198
+ #data_df[input.stat_z()]= data_df[input.stat_z()].fillna(-100000)
199
+
200
+
201
+ if cbr_flip:
202
+ cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[0],colour_palette[3],colour_palette[1]])
203
+ norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())
204
+
205
+ else:
206
+ cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],colour_palette[3],colour_palette[0]])
207
+ norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())
208
+
209
+ sm = plt.cm.ScalarMappable(cmap=cmap_hue, norm=norm)
210
+ print('we made it here')
211
+
212
+ # sns.regplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[6],ax=ax,scatter=False,
213
+ # line_kws=dict(alpha=0.3,linewidth=2,zorder=1))
214
+ # scatter_plot = sns.scatterplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[0],ax=ax,hue=stat_z,palette=cmap_hue)
215
+
216
+
217
+
218
+ # r, p = sp.stats.pearsonr(data_df[input.stat_x()], data_df[input.stat_y()])
219
+ # ax = plt.gca()
220
+ # # ax.text(.25, 0.3, 'r={:.2f}, p={:.2g}'.format(r, p),
221
+ # # transform=ax.transAxes, fontsize=12)
222
+
223
+ # ax.annotate('RΒ²={:.2f}'.format(r, p), ( math.ceil(data_df[input.stat_x()].max()*batter_dict_stat[input.stat_x()]['percent_adjust']/5)*5/batter_dict_stat[input.stat_x()]['percent_adjust']*(1-batter_dict_stat[input.stat_x()]['flip_p']),
224
+ # math.floor(data_df[input.stat_y()].min()*batter_dict_stat[input.stat_y()]['percent_adjust']/5)*5/batter_dict_stat[input.stat_y()]['percent_adjust']*(1-batter_dict_stat[input.stat_y()]['flip_p'])),
225
+ # fontsize=18,fontname='Century Gothic',ha='right')
226
+
227
+ if input.group_level():
228
+ scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3')
229
+ #ax.get_legend().remove()
230
+ scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue)
231
+ else:
232
+ scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3',style='level')
233
+ #ax.get_legend().remove()
234
+ scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue,style='level')
235
+ sns.set_theme(style="whitegrid", palette="pastel")
236
+
237
+ fig.set_facecolor('#F0F0F0')
238
+ ax.set_facecolor('white')
239
+
240
+ print('we made it here')
241
+ # for i in range(0,len(pitch_group_unique)):
242
+ # data_df = elly_zone_df[elly_zone_df.pitch_group==pitch_group_unique[i]]
243
+ # len_df.append(len(data_df))
244
+ # 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,
245
+ # label=f'{pitch_group_unique[i]} (Season Average {float(data_df.swings.sum()/data_df.pitches.sum()):.1%})',zorder=i+10)
246
+ # 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)
247
+
248
+ ts=[]
249
+ print(input.player_id())
250
+
251
+ print(len(data_df))
252
+ if input.names():
253
+ for i in range(len(data_df)):
254
+ if (data_df[input.stat_x()+'_percent'].values[i] < input.n_percent_bot_x() or data_df[input.stat_x()+'_percent'].values[i] > 1 - input.n_percent_top_x() ) \
255
+ or (data_df[input.stat_y()+'_percent'].values[i] < input.n_percent_bot_y() or data_df[input.stat_y()+'_percent'].values[i] > 1 -input.n_percent_top_y()) \
256
+ or (data_df[input.stat_z()+'_percent'].values[i] < input.n_percent_bot_z() or data_df[input.stat_z()+'_percent'].values[i] > 1 -input.n_percent_top_z() )\
257
+ or (str(data_df.batter_id[i]) in (input.player_id())):
258
+ # print(data_df.batter[i])
259
+ # ax.annotate(data_df.batter[i], xy=((data_df[input.stat_x()][i])+0.025/batter_dict_stat[input.stat_x()]['percent_adjust'], data_df[input.stat_y()][i]+0.01/batter_dict_stat[input.stat_x()]['percent_adjust']), xytext=(-20,20),
260
+ # textcoords='offset points', ha='center', va='bottom',fontsize=7,
261
+ # bbox=dict(boxstyle='round,pad=0', fc=colour_palette[6], alpha=0.0),
262
+ # arrowprops=dict(arrowstyle='->', connectionstyle="angle,angleA=-90,angleB=-10,rad=2",
263
+ # color=colour_palette[8]))
264
+
265
+ #if data_df['batter'][i] != 'Jo Adell':
266
+ # ax.annotate(data_df.batter[i], (data_df[input.stat_x()][i]-len(data_df.batter[i])*0.00025, data_df[input.stat_y()][i]+0.001),fontsize=8)
267
+ ts.append(ax.text(data_df[input.stat_x()][i], data_df[input.stat_y()][i], data_df.batter[i],fontsize=8))
268
+
269
+
270
+
271
+ ax.hlines(xmin=(math.floor((data_df[input.stat_x()].min()*batter_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'],
272
+ xmax= (math.ceil((data_df[input.stat_x()].max()*batter_dict_stat[input.stat_x()]['percent_adjust']+0.01)/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'],
273
+ y=data_df[input.stat_y()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
274
+
275
+ print('we made it here')
276
+
277
+ ax.vlines(ymin=(math.floor((data_df[input.stat_y()].min()*batter_dict_stat[input.stat_y()]['percent_adjust']-0.01)/5))*5/batter_dict_stat[input.stat_y()]['percent_adjust'],
278
+ ymax= (math.ceil((data_df[input.stat_y()].max()*batter_dict_stat[input.stat_y()]['percent_adjust']+0.01)/5))*5/batter_dict_stat[input.stat_y()]['percent_adjust'],
279
+ x=data_df[input.stat_x()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
280
+
281
+ print(data_df[input.stat_x()].min())
282
+ print(batter_dict_stat[input.stat_x()]['percent_adjust'])
283
+ print((math.floor((data_df[input.stat_x()].min()*batter_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'])
284
+
285
+
286
+ ax.set_xlim((math.floor((data_df[input.stat_x()].min()*batter_dict_stat[input.stat_x()]['percent_adjust'])/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'],
287
+ (math.ceil((data_df[input.stat_x()].max()*batter_dict_stat[input.stat_x()]['percent_adjust'])/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'])
288
+
289
+
290
+ ax.set_ylim((math.floor((data_df[input.stat_y()].min()*batter_dict_stat[input.stat_y()]['percent_adjust'])/5))*5/batter_dict_stat[input.stat_y()]['percent_adjust'],
291
+ (math.ceil((data_df[input.stat_y()].max()*batter_dict_stat[input.stat_y()]['percent_adjust'])/5))*5/batter_dict_stat[input.stat_y()]['percent_adjust'])
292
+
293
+
294
+
295
+ title_level = str([x .strip("\'")for x in input.level_id()]).strip('[').strip(']').replace("'",'')
296
+
297
+ if title_level == 'AAA, AA, A+, A':
298
+ title_level='MiLB'
299
+ #title_level = input.level_id()[0]
300
+ if input.n_age() >= 50:
301
+ title_spot = f'{title_level} Batter {batter_dict_stat[input.stat_y()]["title"]} vs {batter_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA)'
302
+
303
+ else:
304
+ title_spot = f'{title_level} Batter {batter_dict_stat[input.stat_y()]["title"]} vs {batter_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA, Max Age {input.n_age()})'
305
+
306
+ ax.set_title(title_spot, fontsize=24/(len(title_spot)*0.03),fontname='Century Gothic')
307
+ # #vals = ax.get_yticks()
308
+ ax.set_xlabel(batter_dict_stat[input.stat_x()]['x_axis'], fontsize=16,fontname='Century Gothic')
309
+ ax.set_ylabel(batter_dict_stat[input.stat_y()]['x_axis'], fontsize=16,fontname='Century Gothic')
310
+
311
+
312
+ if input.group_level():
313
+ ax.get_legend().remove()
314
+
315
+ if not input.group_level():
316
+ if len(input.level_id()) > 1:
317
+ h,l = scatter.get_legend_handles_labels()
318
+ l[-(len(input.level_id())+1)] = 'Level'
319
+ ax.legend(h[-(len(input.level_id())+1):],l[-(len(input.level_id())+1):], borderaxespad=0.1,loc=0)
320
+
321
+ else:
322
+ ax.get_legend().remove()
323
+
324
+ #plt.show(g)
325
+ # ax.figure.colorbar(sm, ax=ax)
326
+
327
+ cbar = ax.figure.colorbar(sm, ax=ax,format=decimal_format_assign(x=batter_dict_stat[input.stat_z()]),orientation='vertical',aspect=30)
328
+ cbar.set_label(batter_dict_stat[input.stat_z()]['x_axis'])
329
+ #fig.axes[0].invert_yaxis()
330
+ print('we made it here5')
331
+ fig.subplots_adjust(wspace=.02, hspace=.02)
332
+ # ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
333
+ #ax.set_yticks([0,0.1,0.2,0.3,0.4,0.5])
334
+ # fig.colorbar(plot_dist, ax=ax)
335
+ # fig.colorbar(plot_dist)
336
+
337
+ if batter_dict_stat[input.stat_x()]['flip_p']:
338
+ fig.axes[0].invert_xaxis()
339
+
340
+ if batter_dict_stat[input.stat_y()]['flip_p']:
341
+ fig.axes[0].invert_yaxis()
342
+
343
+
344
+ # ax.xaxis.set_major_formatter(mtick.PercentFormatter(1,decimals=0))
345
+ # ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
346
+
347
+
348
+
349
+
350
+
351
+ print('we made it here6')
352
+
353
+ ax.xaxis.set_major_formatter(decimal_format_assign(x=batter_dict_stat[input.stat_x()]))
354
+ ax.yaxis.set_major_formatter(decimal_format_assign(x=batter_dict_stat[input.stat_y()]))
355
+
356
+
357
+ print('we made it here7')
358
+ # ax.text(0.5, 0.5, '/u/tomstoms', transform=ax.transAxes,
359
+ # fontsize=60, color='gray', alpha=0.075,
360
+ # ha='center', va='center', rotation=45)
361
+
362
+ print(ts)
363
+ if len(ts) > 0:
364
+ adjust_text(ts,
365
+ arrowprops=dict(arrowstyle="-", color=colour_palette[4], lw=1),ax=ax)
366
+
367
+ #ax.legend(fontsize='16')
368
+ fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
369
+ fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
370
+ fig.tight_layout()
371
+ #matplotlib.rcParams["figure.dpi"] = 600
372
+ #plt.show()
373
+
374
+
375
+ rolling_pitcher = App(ui.page_fluid(
376
+ ui.tags.base(href=base_url),
377
+ ui.tags.div(
378
+ {"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
379
+ ui.tags.style(
380
+ """
381
+ h4 {
382
+ margin-top: 1em;font-size:35px;
383
+ }
384
+ h2{
385
+ font-size:25px;
386
+ }
387
+ """
388
+ ),
389
+ shinyswatch.theme.simplex(),
390
+ ui.tags.h4("TJStats"),
391
+ ui.tags.i("Baseball Analytics and Visualizations"),
392
+ ui.navset_tab(
393
+ ui.nav_control(
394
+ ui.a(
395
+ "Home",
396
+ href="home/"
397
+ ),
398
+ ),
399
+ ui.nav_menu(
400
+ "Batter Charts",
401
+ ui.nav_control(
402
+ ui.a(
403
+ "Spray",
404
+ href="spray/"
405
+ ),
406
+ ui.a(
407
+ "Decision Value",
408
+ href="decision_value/"
409
+ ),
410
+ ui.a(
411
+ "Damage Model",
412
+ href="damage_model/"
413
+ ),
414
+ ui.a(
415
+ "Batter Scatter",
416
+ href="batter_scatter/"
417
+ ),
418
+ ui.a(
419
+ "EV vs LA Plot",
420
+ href="ev_angle/"
421
+ )
422
+ ),
423
+ ),
424
+ ui.nav_menu(
425
+ "Goalie Charts",
426
+ ui.nav_control(
427
+ ui.a(
428
+ "GSAx Timeline",
429
+ href="gsax-timeline/"
430
+ ),
431
+ ui.a(
432
+ "GSAx Leaderboard",
433
+ href="gsax-leaderboard/"
434
+ ),
435
+ ui.a(
436
+ "GSAx Comparison",
437
+ href="gsax-comparison/"
438
+ )
439
+ ),
440
+ ),ui.nav_menu(
441
+ "Team Charts",
442
+ ui.nav_control(
443
+ ui.a(
444
+ "Team xG Rates",
445
+ href="team-xg-rates/"
446
+ ),
447
+ ),
448
+ ),ui.nav_control(
449
+ ui.a(
450
+ "Games",
451
+ href="games/"
452
+ ),
453
+ ),ui.nav_control(
454
+ ui.a(
455
+ "About",
456
+ href="about/"
457
+ ),
458
+ ),ui.nav_control(
459
+ ui.a(
460
+ "Articles",
461
+ href="articles/"
462
+ ),
463
+ )),ui.row(
464
+ ui.layout_sidebar(
465
+
466
+
467
+
468
+ ui.panel_sidebar(
469
+ #ui.input_select("id", "Select Batter",batter_dict,selected=675911,width=1,size=1),
470
+ ui.row(
471
+ ui.column(4,ui.input_select("level_id", "Select Level",level_dict,width=1,size=1,multiple=True,selected='MLB',selectize=True),),
472
+ ui.column(4,ui.input_select("team_id", "Select Team",team_list,width=1,size=1,multiple=True,selected='All',selectize=True),),
473
+ ui.column(4,ui.input_select("position_id", "Select Position",position_list,width=1,size=1,selected='All',multiple=True,selectize=True))),
474
+ ui.row(
475
+ ui.column(6,ui.input_numeric("n", "Minimum PA", value=100)),
476
+ ui.column(6,ui.input_numeric("n_age", "Maximum Age", value=50))),
477
+ ui.row(
478
+ ui.column(4,ui.input_select("stat_x", "X-Axis",batter_dict_stat_small,selected='k_percent',width=1,size=1)),
479
+ ui.column(4,ui.input_select("stat_y", "Y-Axis",batter_dict_stat_small,selected='bb_percent',width=1,size=1)),
480
+ ui.column(4,ui.input_select("stat_z", "Colour-Bar Axis",batter_dict_stat_small,selected='bb_over_k_percent',width=1,size=1))),
481
+
482
+ ui.row(
483
+ ui.column(6,ui.input_numeric("n_percent_top_x", "Top 'n' Percentile X-Labels", value=0.01)),
484
+ ui.column(6,ui.input_numeric("n_percent_bot_x", "Bottom 'n' Percentile X-Labels", value=0.01))),
485
+ ui.row(
486
+ ui.column(6,ui.input_numeric("n_percent_top_y", "Top 'n' Percentile Y-Labels", value=0.01)),
487
+ ui.column(6,ui.input_numeric("n_percent_bot_y", "Bottom 'n' Percentile Y-Labels", value=0.01))),
488
+ ui.row(
489
+ ui.column(6,ui.input_numeric("n_percent_top_z", "Top 'n' Percentile Z-Labels", value=0.01)),
490
+ ui.column(6,ui.input_numeric("n_percent_bot_z", "Bottom 'n' Percentile Z-Labels", value=0.01))),
491
+
492
+ ui.input_select("player_id", "Label Player",batter_dict,width=1,size=1,multiple=True,selectize=True),
493
+ ui.row(
494
+ ui.input_switch("names", "Toggle Names"),
495
+ ui.input_switch("group_level", "Group Levels")),
496
+ ),
497
+
498
+ ui.panel_main(
499
+ ui.output_plot("plot",height = "1000px",width="1000px")
500
+ ,
501
+ ),
502
+ )),)),server)
chadwick_df.csv ADDED
The diff for this file is too large to render. See raw diff
 
damage.py CHANGED
@@ -165,6 +165,7 @@ def server(input,output,session):
165
 
166
  @output
167
  @render.plot(alt="hex_plot")
 
168
  def hex_plot():
169
 
170
  if input.batter_id() is "":
@@ -391,6 +392,7 @@ def server(input,output,session):
391
 
392
  @output
393
  @render.plot(alt="roll_plot")
 
394
  def roll_plot():
395
  # player_select = 'Nolan Gorman'
396
  # player_select_full =player_select
@@ -505,6 +507,7 @@ damage = App(ui.page_fluid(
505
  shinyswatch.theme.simplex(),
506
  ui.tags.h4("TJStats"),
507
  ui.tags.i("Baseball Analytics and Visualizations"),
 
508
  ui.navset_tab(
509
  ui.nav_control(
510
  ui.a(
@@ -515,7 +518,11 @@ damage = App(ui.page_fluid(
515
  ui.nav_menu(
516
  "Batter Charts",
517
  ui.nav_control(
518
- ui.a(
 
 
 
 
519
  "Spray",
520
  href="spray/"
521
  ),
@@ -534,48 +541,29 @@ damage = App(ui.page_fluid(
534
  ui.a(
535
  "EV vs LA Plot",
536
  href="ev_angle/"
 
 
 
 
537
  )
538
  ),
539
  ),
540
  ui.nav_menu(
541
- "Goalie Charts",
542
  ui.nav_control(
543
  ui.a(
544
- "GSAx Timeline",
545
- href="gsax-timeline/"
546
  ),
547
  ui.a(
548
- "GSAx Leaderboard",
549
- href="gsax-leaderboard/"
550
  ),
551
  ui.a(
552
- "GSAx Comparison",
553
- href="gsax-comparison/"
554
  )
555
  ),
556
- ),ui.nav_menu(
557
- "Team Charts",
558
- ui.nav_control(
559
- ui.a(
560
- "Team xG Rates",
561
- href="team-xg-rates/"
562
- ),
563
- ),
564
- ),ui.nav_control(
565
- ui.a(
566
- "Games",
567
- href="games/"
568
- ),
569
- ),ui.nav_control(
570
- ui.a(
571
- "About",
572
- href="about/"
573
- ),
574
- ),ui.nav_control(
575
- ui.a(
576
- "Articles",
577
- href="articles/"
578
- ),
579
  )),ui.row(
580
  ui.layout_sidebar(
581
 
@@ -593,7 +581,8 @@ damage = App(ui.page_fluid(
593
  ui.input_numeric("rolling_window",
594
  "Select Rolling Window",
595
  value=50,
596
- min=1)),
 
597
 
598
  ui.panel_main(
599
  ui.navset_tab(
 
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 "":
 
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
 
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
  ui.navset_tab(
512
  ui.nav_control(
513
  ui.a(
 
518
  ui.nav_menu(
519
  "Batter Charts",
520
  ui.nav_control(
521
+ ui.a(
522
+ "Batting Rolling",
523
+ href="rolling_batter/"
524
+ ),
525
+ ui.a(
526
  "Spray",
527
  href="spray/"
528
  ),
 
541
  ui.a(
542
  "EV vs LA Plot",
543
  href="ev_angle/"
544
+ ),
545
+ ui.a(
546
+ "Statcast Compare",
547
+ href="statcast_compare/"
548
  )
549
  ),
550
  ),
551
  ui.nav_menu(
552
+ "Pitcher Charts",
553
  ui.nav_control(
554
  ui.a(
555
+ "Pitcher Rolling",
556
+ href="rolling_pitcher/"
557
  ),
558
  ui.a(
559
+ "Pitcher Summary",
560
+ href="pitching_summary_graphic_new/"
561
  ),
562
  ui.a(
563
+ "Pitcher Scatter",
564
+ href="pitcher_scatter/"
565
  )
566
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  )),ui.row(
568
  ui.layout_sidebar(
569
 
 
581
  ui.input_numeric("rolling_window",
582
  "Select Rolling Window",
583
  value=50,
584
+ min=1),
585
+ ui.input_action_button("go", "Generate",class_="btn-primary")),
586
 
587
  ui.panel_main(
588
  ui.navset_tab(
decision_value.py CHANGED
@@ -146,6 +146,7 @@ def server(input,output,session):
146
 
147
  @output
148
  @render.plot(alt="hex_plot")
 
149
  def scatter_plot():
150
 
151
  if input.batter_id() is "":
@@ -264,6 +265,7 @@ def server(input,output,session):
264
 
265
  @output
266
  @render.plot(alt="hex_plot")
 
267
  def dv_plot():
268
 
269
  if input.batter_id() is "":
@@ -357,6 +359,7 @@ def server(input,output,session):
357
 
358
  @output
359
  @render.plot(alt="hex_plot")
 
360
  def iz_plot():
361
 
362
  if input.batter_id() is "":
@@ -450,6 +453,7 @@ def server(input,output,session):
450
 
451
  @output
452
  @render.plot(alt="hex_plot")
 
453
  def oz_plot():
454
  if input.batter_id() is "":
455
  fig = plt.figure(figsize=(12, 12))
@@ -557,6 +561,7 @@ decision_value = App(ui.page_fluid(
557
  shinyswatch.theme.simplex(),
558
  ui.tags.h4("TJStats"),
559
  ui.tags.i("Baseball Analytics and Visualizations"),
 
560
  ui.navset_tab(
561
  ui.nav_control(
562
  ui.a(
@@ -567,7 +572,11 @@ decision_value = App(ui.page_fluid(
567
  ui.nav_menu(
568
  "Batter Charts",
569
  ui.nav_control(
570
- ui.a(
 
 
 
 
571
  "Spray",
572
  href="spray/"
573
  ),
@@ -586,48 +595,29 @@ decision_value = App(ui.page_fluid(
586
  ui.a(
587
  "EV vs LA Plot",
588
  href="ev_angle/"
 
 
 
 
589
  )
590
  ),
591
  ),
592
  ui.nav_menu(
593
- "Goalie Charts",
594
  ui.nav_control(
595
  ui.a(
596
- "GSAx Timeline",
597
- href="gsax-timeline/"
598
  ),
599
  ui.a(
600
- "GSAx Leaderboard",
601
- href="gsax-leaderboard/"
602
  ),
603
  ui.a(
604
- "GSAx Comparison",
605
- href="gsax-comparison/"
606
  )
607
  ),
608
- ),ui.nav_menu(
609
- "Team Charts",
610
- ui.nav_control(
611
- ui.a(
612
- "Team xG Rates",
613
- href="team-xg-rates/"
614
- ),
615
- ),
616
- ),ui.nav_control(
617
- ui.a(
618
- "Games",
619
- href="games/"
620
- ),
621
- ),ui.nav_control(
622
- ui.a(
623
- "About",
624
- href="about/"
625
- ),
626
- ),ui.nav_control(
627
- ui.a(
628
- "Articles",
629
- href="articles/"
630
- ),
631
  )),ui.row(
632
  ui.layout_sidebar(
633
 
@@ -658,7 +648,9 @@ decision_value = App(ui.page_fluid(
658
  ui.input_select("level_list",
659
  "Select Level",
660
  ['MLB','AAA'],
661
- selected='MLB')),
 
 
662
 
663
  ui.panel_main(
664
  ui.navset_tab(
 
146
 
147
  @output
148
  @render.plot(alt="hex_plot")
149
+ @reactive.event(input.go, ignore_none=False)
150
  def scatter_plot():
151
 
152
  if input.batter_id() is "":
 
265
 
266
  @output
267
  @render.plot(alt="hex_plot")
268
+ @reactive.event(input.go, ignore_none=False)
269
  def dv_plot():
270
 
271
  if input.batter_id() is "":
 
359
 
360
  @output
361
  @render.plot(alt="hex_plot")
362
+ @reactive.event(input.go, ignore_none=False)
363
  def iz_plot():
364
 
365
  if input.batter_id() is "":
 
453
 
454
  @output
455
  @render.plot(alt="hex_plot")
456
+ @reactive.event(input.go, ignore_none=False)
457
  def oz_plot():
458
  if input.batter_id() is "":
459
  fig = plt.figure(figsize=(12, 12))
 
561
  shinyswatch.theme.simplex(),
562
  ui.tags.h4("TJStats"),
563
  ui.tags.i("Baseball Analytics and Visualizations"),
564
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
565
  ui.navset_tab(
566
  ui.nav_control(
567
  ui.a(
 
572
  ui.nav_menu(
573
  "Batter Charts",
574
  ui.nav_control(
575
+ ui.a(
576
+ "Batting Rolling",
577
+ href="rolling_batter/"
578
+ ),
579
+ ui.a(
580
  "Spray",
581
  href="spray/"
582
  ),
 
595
  ui.a(
596
  "EV vs LA Plot",
597
  href="ev_angle/"
598
+ ),
599
+ ui.a(
600
+ "Statcast Compare",
601
+ href="statcast_compare/"
602
  )
603
  ),
604
  ),
605
  ui.nav_menu(
606
+ "Pitcher Charts",
607
  ui.nav_control(
608
  ui.a(
609
+ "Pitcher Rolling",
610
+ href="rolling_pitcher/"
611
  ),
612
  ui.a(
613
+ "Pitcher Summary",
614
+ href="pitching_summary_graphic_new/"
615
  ),
616
  ui.a(
617
+ "Pitcher Scatter",
618
+ href="pitcher_scatter/"
619
  )
620
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  )),ui.row(
622
  ui.layout_sidebar(
623
 
 
648
  ui.input_select("level_list",
649
  "Select Level",
650
  ['MLB','AAA'],
651
+ selected='MLB'),
652
+ ui.input_action_button("go", "Generate",class_="btn-primary"),
653
+ ),
654
 
655
  ui.panel_main(
656
  ui.navset_tab(
ev_angle.py CHANGED
@@ -60,6 +60,7 @@ angle_ranges = list(range(8,51))
60
  def server(input,output,session):
61
  @output
62
  @render.plot(alt="A histogram")
 
63
  def plot():
64
  data_df = exit_velo_df[exit_velo_df.batter_id==int(input.id())]
65
  #pitch_list = exit_velo_df_small.pitch_type.unique()
@@ -182,6 +183,7 @@ ev_angle = App(ui.page_fluid(
182
  shinyswatch.theme.simplex(),
183
  ui.tags.h4("TJStats"),
184
  ui.tags.i("Baseball Analytics and Visualizations"),
 
185
  ui.navset_tab(
186
  ui.nav_control(
187
  ui.a(
@@ -192,7 +194,11 @@ ev_angle = App(ui.page_fluid(
192
  ui.nav_menu(
193
  "Batter Charts",
194
  ui.nav_control(
195
- ui.a(
 
 
 
 
196
  "Spray",
197
  href="spray/"
198
  ),
@@ -211,48 +217,29 @@ ev_angle = App(ui.page_fluid(
211
  ui.a(
212
  "EV vs LA Plot",
213
  href="ev_angle/"
 
 
 
 
214
  )
215
  ),
216
  ),
217
  ui.nav_menu(
218
- "Goalie Charts",
219
  ui.nav_control(
220
  ui.a(
221
- "GSAx Timeline",
222
- href="gsax-timeline/"
223
  ),
224
  ui.a(
225
- "GSAx Leaderboard",
226
- href="gsax-leaderboard/"
227
  ),
228
  ui.a(
229
- "GSAx Comparison",
230
- href="gsax-comparison/"
231
  )
232
  ),
233
- ),ui.nav_menu(
234
- "Team Charts",
235
- ui.nav_control(
236
- ui.a(
237
- "Team xG Rates",
238
- href="team-xg-rates/"
239
- ),
240
- ),
241
- ),ui.nav_control(
242
- ui.a(
243
- "Games",
244
- href="games/"
245
- ),
246
- ),ui.nav_control(
247
- ui.a(
248
- "About",
249
- href="about/"
250
- ),
251
- ),ui.nav_control(
252
- ui.a(
253
- "Articles",
254
- href="articles/"
255
- ),
256
  )),ui.row(
257
  ui.layout_sidebar(
258
 
@@ -261,8 +248,11 @@ ev_angle = App(ui.page_fluid(
261
 
262
  ui.panel_sidebar(
263
  ui.input_select("id", "Select Batter",batter_dict,width=1),
264
- ui.input_select("plot_id", "Select Plot",{'scatter':'Scatter Plot','dist':'Distribution Plot'},width=1)
265
- ),
 
 
 
266
 
267
  ui.panel_main(
268
  ui.output_plot("plot",height = "1000px",width="1000px")
 
60
  def server(input,output,session):
61
  @output
62
  @render.plot(alt="A histogram")
63
+ @reactive.event(input.go, ignore_none=False)
64
  def plot():
65
  data_df = exit_velo_df[exit_velo_df.batter_id==int(input.id())]
66
  #pitch_list = exit_velo_df_small.pitch_type.unique()
 
183
  shinyswatch.theme.simplex(),
184
  ui.tags.h4("TJStats"),
185
  ui.tags.i("Baseball Analytics and Visualizations"),
186
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
187
  ui.navset_tab(
188
  ui.nav_control(
189
  ui.a(
 
194
  ui.nav_menu(
195
  "Batter Charts",
196
  ui.nav_control(
197
+ ui.a(
198
+ "Batting Rolling",
199
+ href="rolling_batter/"
200
+ ),
201
+ ui.a(
202
  "Spray",
203
  href="spray/"
204
  ),
 
217
  ui.a(
218
  "EV vs LA Plot",
219
  href="ev_angle/"
220
+ ),
221
+ ui.a(
222
+ "Statcast Compare",
223
+ href="statcast_compare/"
224
  )
225
  ),
226
  ),
227
  ui.nav_menu(
228
+ "Pitcher Charts",
229
  ui.nav_control(
230
  ui.a(
231
+ "Pitcher Rolling",
232
+ href="rolling_pitcher/"
233
  ),
234
  ui.a(
235
+ "Pitcher Summary",
236
+ href="pitching_summary_graphic_new/"
237
  ),
238
  ui.a(
239
+ "Pitcher Scatter",
240
+ href="pitcher_scatter/"
241
  )
242
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  )),ui.row(
244
  ui.layout_sidebar(
245
 
 
248
 
249
  ui.panel_sidebar(
250
  ui.input_select("id", "Select Batter",batter_dict,width=1),
251
+ ui.input_select("plot_id", "Select Plot",{'scatter':'Scatter Plot','dist':'Distribution Plot'},width=1),
252
+
253
+ ui.input_action_button("go", "Generate",class_="btn-primary",
254
+ )),
255
+
256
 
257
  ui.panel_main(
258
  ui.output_plot("plot",height = "1000px",width="1000px")
home.py CHANGED
@@ -28,6 +28,7 @@ home = App(ui.page_fluid(
28
  shinyswatch.theme.simplex(),
29
  ui.tags.h4("TJStats"),
30
  ui.tags.i("Baseball Analytics and Visualizations"),
 
31
  ui.navset_tab(
32
  ui.nav_control(
33
  ui.a(
@@ -38,7 +39,11 @@ home = App(ui.page_fluid(
38
  ui.nav_menu(
39
  "Batter Charts",
40
  ui.nav_control(
41
- ui.a(
 
 
 
 
42
  "Spray",
43
  href="spray/"
44
  ),
@@ -57,46 +62,77 @@ home = App(ui.page_fluid(
57
  ui.a(
58
  "EV vs LA Plot",
59
  href="ev_angle/"
 
 
 
 
60
  )
61
  ),
62
  ),
63
  ui.nav_menu(
64
- "Goalie Charts",
65
  ui.nav_control(
66
  ui.a(
67
- "GSAx Timeline",
68
- href="gsax-timeline/"
69
  ),
70
  ui.a(
71
- "GSAx Leaderboard",
72
- href="gsax-leaderboard/"
73
  ),
74
  ui.a(
75
- "GSAx Comparison",
76
- href="gsax-comparison/"
77
  )
78
  ),
79
- ),ui.nav_menu(
80
- "Team Charts",
81
- ui.nav_control(
82
- ui.a(
83
- "Team xG Rates",
84
- href="team-xg-rates/"
85
- ),
86
- ),
87
- ),ui.nav_control(
88
- ui.a(
89
- "Games",
90
- href="games/"
91
- ),
92
- ),ui.nav_control(
93
- ui.a(
94
- "About",
95
- href="about/"
96
- ),
97
- ),ui.nav_control(
98
- ui.a(
99
- "Articles",
100
- href="articles/"
101
- ),
102
- )),ui.tags.br(),ui.tags.h5("Welcome to TJStats!"),ui.tags.h6(""))), None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  shinyswatch.theme.simplex(),
29
  ui.tags.h4("TJStats"),
30
  ui.tags.i("Baseball Analytics and Visualizations"),
31
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
32
  ui.navset_tab(
33
  ui.nav_control(
34
  ui.a(
 
39
  ui.nav_menu(
40
  "Batter Charts",
41
  ui.nav_control(
42
+ ui.a(
43
+ "Batting Rolling",
44
+ href="rolling_batter/"
45
+ ),
46
+ ui.a(
47
  "Spray",
48
  href="spray/"
49
  ),
 
62
  ui.a(
63
  "EV vs LA Plot",
64
  href="ev_angle/"
65
+ ),
66
+ ui.a(
67
+ "Statcast Compare",
68
+ href="statcast_compare/"
69
  )
70
  ),
71
  ),
72
  ui.nav_menu(
73
+ "Pitcher Charts",
74
  ui.nav_control(
75
  ui.a(
76
+ "Pitcher Rolling",
77
+ href="rolling_pitcher/"
78
  ),
79
  ui.a(
80
+ "Pitcher Summary",
81
+ href="pitching_summary_graphic_new/"
82
  ),
83
  ui.a(
84
+ "Pitcher Scatter",
85
+ href="pitcher_scatter/"
86
  )
87
  ),
88
+ )),ui.tags.h6(""),
89
+ ui.markdown(
90
+ """
91
+ # Welcome to TJStats!
92
+ ---
93
+ ## Intro
94
+ Hello, my name is Thomas Nestico, and welcome to my Baseball Analytics and Visualizations Website, TJStats!
95
+ I am a Civil Engineering by trade, but I have a passion for data analytics and machine learning, specifically
96
+ with respect to sports. This site and all of my visualizations were created using Python and its vast array of packages.
97
+ The framework for this site and all of the apps is <a href='https://shiny.posit.co/py/'>Shiny Python</a><sup>1</sup>.
98
+ All sources of data are referenced on each visualization.
99
+
100
+ ## Apps
101
+ This site hosts my Baseball Data Viz Apps from the 2023 season. Here is a summary of what is accessible:
102
+
103
+ * Batter Charts
104
+ - **Batter Rolling**: Rolling Stats Charts for MLB and MiLB batters
105
+ - **Spray**: Batted Ball Trajectory Distributions of MLB batters vs rest of league
106
+ - **Decision Value**: Scatter plot and Rolling Stats Chart for my <a href='https://medium.com/@thomasjamesnestico/modelling-batter-decision-value-dac74c55e20a'>Decision Value Model</a><sup>1</sup>
107
+ - **Damage Model**: Hexbin and Rolling Stat Chart for my Damage Model (Article TBD)
108
+ - **Batter Scatter**: Scatter Plots for MLB and MiLB batters
109
+ - **EV vs LA Plot**: Scatter plot visualizing a MLB Batter's Exit Velocities and Launch Angles
110
+ - **Statcast Compare**: Table which compares MLB Batter's Statcast metrics
111
+ <br>
112
+ * Pitcher Charts
113
+ - **Pitcher Rolling**: Rolling Stats Charts for MLB and MiLB pitchers
114
+ - **Pitcher Summary**: Pitching Summary for MLB pitchers for any date range during 2023
115
+ - **Pitcher Scatter**: Scatter Plots for MLB and MiLB pitchers
116
+
117
+
118
+ ## Articles
119
+ I have published several articles on Medium covering different Baseball Analytics topics. In these articles I go through my methodology
120
+ when applying machine learning algorithms to create machine learning models to help better understand baseball concepts. Here is a list of
121
+ articles which I have published:
122
+ * <a href='https://medium.com/@thomasjamesnestico/modelling-tjstuff-d9a451765484'>Modelling tjStuff+</a><sup>1</sup>
123
+ * <a href='https://medium.com/@thomasjamesnestico/modelling-batter-decision-value-dac74c55e20a'>Modelling Batter Decision Value</a><sup>1</sup>
124
+ * <a href='https://medium.com/@thomasjamesnestico/classifying-mlb-pitch-zones-and-predicting-milb-zones-7e95cf308254'>Classifying MLB Pitch Zones and Predicting MiLB Zones</a><sup>1</sup>
125
+
126
+
127
+ ## 2024 Apps
128
+ Thank you for checking out the site. If you would like to further support my endeavours in the Baseball Analytics sphere,
129
+ here are few places where you can support me and get up to date baseball coverage and Apps for the 2024 season.
130
+
131
+ * Patreon: https://www.patreon.com/tj_stats
132
+ * Twitter: https://twitter.com/TJStats
133
+
134
+ ## About Me
135
+ Here are a few other sites to learn more about me:
136
+ * LinkedIn: https://www.linkedin.com/in/thomas-nestico-b66013173/
137
+ * GitHub: https://github.com/tnestico
138
+ """))), None)
pitcher_scatter.py ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import inflect
23
+ from matplotlib.pyplot import text
24
+
25
+
26
+ exit_velo_df_codes_summ = pd.read_csv('summary_pitcher.csv',index_col=[0]).reset_index(drop=True)
27
+ exit_velo_df_codes_summ_non_level = pd.read_csv('summary_pitcher_level.csv',index_col=[0]).reset_index(drop=True)
28
+
29
+ exit_velo_df_codes_summ_non_level['levels'] = exit_velo_df_codes_summ_non_level.levels.str.split(', ')
30
+
31
+ exit_velo_df_codes_summ_non_level = exit_velo_df_codes_summ_non_level.rename(columns={'levels':'level'})
32
+
33
+ print(exit_velo_df_codes_summ.bb_minus_k_percent)
34
+ exit_velo_df_codes_summ.bb_minus_k_percent = -1*exit_velo_df_codes_summ.bb_minus_k_percent
35
+ exit_velo_df_codes_summ.bb_over_k_percent = exit_velo_df_codes_summ.k_percent/exit_velo_df_codes_summ.bb_percent
36
+
37
+ exit_velo_df_codes_summ_non_level.bb_minus_k_percent = -1*exit_velo_df_codes_summ_non_level.bb_minus_k_percent
38
+ exit_velo_df_codes_summ_non_level.bb_over_k_percent = exit_velo_df_codes_summ_non_level.k_percent/exit_velo_df_codes_summ_non_level.bb_percent
39
+
40
+ pitcher_dict_stat = { 'sweet_spot_percent':{'x_axis':'SweetSpot%','title':'SweetSpot%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
41
+ 'max_launch_speed':{'x_axis':'Max Exit Velocity','title':'Max Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
42
+ 'launch_speed_90':{'x_axis':'90th Percentile EV','title':'90th Percentile EV','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
43
+ 'launch_speed':{'x_axis':'Exit Velocity','title':'Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
44
+ 'launch_angle':{'x_axis':'Launch Angle','title':'Launch Angle','flip_p':False,'decimal_format':'string_0','percent_adjust':100},
45
+ 'avg':{'x_axis':'AVG','title':'AVG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
46
+ 'obp':{'x_axis':'OBP','title':'OBP','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
47
+ 'slg':{'x_axis':'SLG','title':'SLG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
48
+ 'ops':{'x_axis':'OPS','title':'OPS','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
49
+ 'k_percent':{'x_axis':'K%','title':'K%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
50
+ 'bb_percent':{'x_axis':'BB%','title':'BB%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
51
+ 'bb_over_k_percent':{'x_axis':'K/BB','title':'K/BB','flip_p':True,'decimal_format':'string_1','percent_adjust':100},
52
+ 'bb_minus_k_percent':{'x_axis':'K%-BB%','title':'K%-BB%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
53
+ 'csw_percent':{'x_axis':'CSW%','title':'CSW%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
54
+ 'woba_percent':{'x_axis':'wOBA','title':'wOBA','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
55
+ 'hard_hit_percent':{'x_axis':'HardHit%','title':'HardHit%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
56
+ 'barrel_percent':{'x_axis':'Barrel%','title':'Barrel%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
57
+ 'zone_contact_percent':{'x_axis':'Z-Contact%','title':'Z-Contact%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
58
+ 'zone_swing_percent':{'x_axis':'Z-Swing%','title':'Z-Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
59
+ 'zone_percent':{'x_axis':'Zone%','title':'Zone%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
60
+ 'chase_percent':{'x_axis':'O-Swing%','title':'O-Swing%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
61
+ 'chase_contact':{'x_axis':'O-Contact%','title':'O-Contact%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
62
+ 'swing_percent':{'x_axis':'Swing%','title':'Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
63
+ 'whiff_rate':{'x_axis':'Whiff%','title':'Whiff%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
64
+ 'swstr_rate':{'x_axis':'SwStr%','title':'SwStr%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
65
+ }
66
+
67
+ pitcher_dict_stat_small = { 'sweet_spot_percent':'SweetSpot%',
68
+ 'max_launch_speed':'Max Exit Velocity',
69
+ 'launch_speed_90':'90th Percentile EV',
70
+ 'launch_speed':'Exit Velocity',
71
+ 'launch_angle':'Launch Angle',
72
+ 'avg':'AVG',
73
+ 'obp':'OBP',
74
+ 'slg':'SLG',
75
+ 'ops':'OPS',
76
+ 'k_percent':'K%',
77
+ 'bb_percent':'BB%',
78
+ 'bb_over_k_percent':'K/BB',
79
+ 'bb_minus_k_percent':'K%-BB%',
80
+ 'csw_percent':'CSW%',
81
+ 'woba_percent':'wOBA',
82
+ 'hard_hit_percent':'HardHit%',
83
+ 'barrel_percent':'Barrel%',
84
+ 'zone_contact_percent':'Z-Contact%',
85
+ 'zone_swing_percent':'Z-Swing%',
86
+ 'zone_percent':'Zone%',
87
+ 'chase_percent':'O-Swing%',
88
+ 'chase_contact':'O-Contact%',
89
+ 'swing_percent':'Swing%',
90
+ 'whiff_rate':'Whiff%',
91
+ 'swstr_rate':'SwStr%',
92
+ }
93
+
94
+
95
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
96
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
97
+
98
+ level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A','ROK':'ROK'}
99
+
100
+ batter_test_df = exit_velo_df_codes_summ.sort_values(by='pitcher').drop_duplicates(subset='pitcher_id').reset_index(drop=True)[['pitcher_id','pitcher']]#['pitcher'].to_dict()
101
+ batter_test_df = batter_test_df.set_index('pitcher_id')
102
+
103
+
104
+ def decimal_format_assign(x):
105
+ if x['decimal_format'] == 'percent_1':
106
+ return mtick.PercentFormatter(1,decimals=1)
107
+ if x['decimal_format'] == 'string_3':
108
+ return mtick.FormatStrFormatter('%.3f')
109
+ if x['decimal_format'] == 'string_0':
110
+ return mtick.FormatStrFormatter('%.0f')
111
+ if x['decimal_format'] == 'string_1':
112
+ return mtick.FormatStrFormatter('%.1f')
113
+
114
+
115
+ #test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])
116
+
117
+ pitcher_dict = batter_test_df['pitcher'].to_dict()
118
+
119
+ exit_velo_df_codes_summ.position = exit_velo_df_codes_summ.position.replace(['TWP'],['P'])
120
+
121
+ exit_velo_df_codes_summ_non_level.position = exit_velo_df_codes_summ_non_level.position.replace(['TWP'],['P'])
122
+
123
+
124
+ position_list = ['All'] + list(exit_velo_df_codes_summ.position.unique())
125
+ team_list = ['All'] + sorted(list(exit_velo_df_codes_summ.parent_org_abb.unique()))
126
+
127
+
128
+
129
+ def server(input,output,session):
130
+
131
+
132
+ @output
133
+ @render.plot(alt="A histogram")
134
+ @reactive.event(input.go, ignore_none=False)
135
+ def plot():
136
+ sns.set_theme(style="whitegrid", palette="pastel")
137
+
138
+
139
+ print(input.level_id())
140
+ print(input.n())
141
+ print('we made it here')
142
+
143
+ #data_df = exit_velo_df_codes_summ.copy()
144
+
145
+
146
+ if input.group_level():
147
+ data_df = exit_velo_df_codes_summ_non_level.copy()
148
+
149
+ turth_list = []
150
+ #turth_list_2 = []
151
+ for x in range(0,len(data_df.level)):
152
+ turth_list_2 = []
153
+ for y in range(0,len(data_df.level[x])):
154
+ #print(level_list[x][y])
155
+ turth_list_2.append(data_df.level[x][y] in input.level_id())
156
+ turth_list.append(turth_list_2)
157
+
158
+ final_check_list = [True if True in x else False for x in turth_list]
159
+
160
+
161
+ data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(final_check_list)]
162
+
163
+ else:
164
+ data_df = exit_velo_df_codes_summ.copy()
165
+
166
+ data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(data_df.level.isin(input.level_id()))]
167
+ print(data_df)
168
+ if 'All' in input.team_id():
169
+ print('nice')#data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)
170
+
171
+ else:
172
+ data_df = data_df[(data_df.parent_org_abb.isin(input.team_id()))].reset_index(drop=True)
173
+
174
+ if 'All' in input.position_id():
175
+ print('nice')#data_df = data_df[(data_df.level.isin(input.level_id()))&(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)
176
+
177
+ else:
178
+ data_df = data_df[(data_df.position.isin(input.position_id()))].reset_index(drop=True)
179
+
180
+
181
+
182
+
183
+
184
+
185
+ data_df = data_df.sort_values(by='level').reset_index(drop=True)
186
+ print(pitcher_dict_stat[input.stat_x()]['flip_p'])
187
+
188
+
189
+
190
+ x_flip = pitcher_dict_stat[input.stat_x()]['flip_p']
191
+ y_flip = pitcher_dict_stat[input.stat_y()]['flip_p']
192
+ cbr_flip = pitcher_dict_stat[input.stat_z()]['flip_p']
193
+
194
+
195
+
196
+ data_df[input.stat_x()+'_percent'] = data_df[input.stat_x()].rank(pct=True,ascending=abs(x_flip-1))
197
+
198
+ data_df[input.stat_y()+'_percent'] = data_df[input.stat_y()].rank(pct=True,ascending=abs(y_flip-1))
199
+
200
+ data_df[input.stat_z()+'_percent'] = data_df[input.stat_z()].rank(pct=True,ascending=abs(cbr_flip-1))
201
+
202
+
203
+
204
+ fig, ax = plt.subplots(1, 1, figsize=(9, 9))
205
+
206
+ #data_df['bb_over_obp'] = data_df['bb']/data_df['k']
207
+
208
+ #data_df[input.stat_z()]= data_df[input.stat_z()].fillna(-100000)
209
+
210
+
211
+ if not cbr_flip:
212
+ cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[0],colour_palette[3],colour_palette[1]])
213
+ norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())
214
+
215
+ else:
216
+ cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],colour_palette[3],colour_palette[0]])
217
+ norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())
218
+
219
+ sm = plt.cm.ScalarMappable(cmap=cmap_hue, norm=norm)
220
+ print('we made it here')
221
+
222
+ # sns.regplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[6],ax=ax,scatter=False,
223
+ # line_kws=dict(alpha=0.3,linewidth=2,zorder=1))
224
+ # scatter_plot = sns.scatterplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[0],ax=ax,hue=stat_z,palette=cmap_hue)
225
+
226
+
227
+
228
+ # r, p = sp.stats.pearsonr(data_df[input.stat_x()], data_df[input.stat_y()])
229
+ # ax = plt.gca()
230
+ # # ax.text(.25, 0.3, 'r={:.2f}, p={:.2g}'.format(r, p),
231
+ # # transform=ax.transAxes, fontsize=12)
232
+
233
+ # ax.annotate('RΒ²={:.2f}'.format(r, p), ( math.ceil(data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust']*(1-pitcher_dict_stat[input.stat_x()]['flip_p']),
234
+ # math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust']*(1-pitcher_dict_stat[input.stat_y()]['flip_p'])),
235
+ # fontsize=18,fontname='Century Gothic',ha='right')
236
+
237
+ if input.group_level():
238
+ scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3')
239
+ #ax.get_legend().remove()
240
+ scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue)
241
+ else:
242
+ scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3',style='level')
243
+ #ax.get_legend().remove()
244
+ scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue,style='level')
245
+
246
+
247
+ sns.set_theme(style="whitegrid", palette="pastel")
248
+
249
+ fig.set_facecolor('#F0F0F0')
250
+ ax.set_facecolor('white')
251
+
252
+ print('we made it here!')
253
+ # for i in range(0,len(pitch_group_unique)):
254
+ # data_df = elly_zone_df[elly_zone_df.pitch_group==pitch_group_unique[i]]
255
+ # len_df.append(len(data_df))
256
+ # 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,
257
+ # label=f'{pitch_group_unique[i]} (Season Average {float(data_df.swings.sum()/data_df.pitches.sum()):.1%})',zorder=i+10)
258
+ # 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)
259
+
260
+ ts=[]
261
+ xs=[]
262
+ ys=[]
263
+ labels=[]
264
+
265
+ print(input.player_id())
266
+ if input.names():
267
+ for i in range(len(data_df)):
268
+ if (data_df[input.stat_x()+'_percent'][i] < input.n_percent_bot_x() or data_df[input.stat_x()+'_percent'][i] > 1 - input.n_percent_top_x() ) \
269
+ or (data_df[input.stat_y()+'_percent'][i] < input.n_percent_bot_y() or data_df[input.stat_y()+'_percent'][i] > 1 -input.n_percent_top_y()) \
270
+ or (data_df[input.stat_z()+'_percent'][i] < input.n_percent_bot_z() or data_df[input.stat_z()+'_percent'][i] > 1 -input.n_percent_top_z() )\
271
+ or (str(data_df.pitcher_id[i]) in (input.player_id())):
272
+ ts.append(ax.text(data_df[input.stat_x()][i], data_df[input.stat_y()][i], data_df.pitcher[i],fontsize=8))
273
+
274
+
275
+ # ax.annotate(data_df.pitcher[i], xy=((data_df[input.stat_x()][i])+0.025/pitcher_dict_stat[input.stat_x()]['percent_adjust'], data_df[input.stat_y()][i]+0.01/pitcher_dict_stat[input.stat_x()]['percent_adjust']), xytext=(-20,20),
276
+ # textcoords='offset points', ha='center', va='bottom',fontsize=7,
277
+ # bbox=dict(boxstyle='round,pad=0', fc=colour_palette[6], alpha=0.0),
278
+ # arrowprops=dict(arrowstyle='->', connectionstyle="angle,angleA=-90,angleB=-10,rad=5",
279
+ # color=colour_palette[4]))
280
+
281
+ # print('CONDITION CHEK')
282
+ # print(data_df[input.stat_x()][i])
283
+ # #if data_df['pitcher'][i] != 'Jo Adell':
284
+ # #ax.annotate(data_df.pitcher[i], ((data_df[input.stat_x()][i])+0.025/pitcher_dict_stat[input.stat_x()]['percent_adjust'], data_df[input.stat_y()][i]+0.01/pitcher_dict_stat[input.stat_x()]['percent_adjust']),fontsize=8)
285
+ # #ts.append(data_df[input.stat_x()][i], data_df[input.stat_y()][i], data_df.pitcher[i],fontsize=8))
286
+ # xs.append(data_df[input.stat_x()][i])
287
+ # ys.append(data_df[input.stat_y()][i])
288
+ # labels.append(data_df.pitcher[i])
289
+
290
+ # # adjust_text(ts, force_points=0.2, force_text=0.2,
291
+ # # expand_points=(1, 1), expand_text=(1, 1),
292
+ # # arrowprops=dict(arrowstyle="-", color='black', lw=0))
293
+ # #print(xs)
294
+ # if len(xs)>0:
295
+ # repel_labels(ax=ax, x = xs,y = ys, labels = labels, k=0.0025)
296
+
297
+ ax.hlines(xmin=math.floor((data_df[input.stat_x()].min()*pitcher_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],
298
+ xmax= math.ceil((data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],
299
+ y=data_df[input.stat_y()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
300
+
301
+ print('we made it here')
302
+ ax.vlines(ymin=(math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],
303
+ ymax= (math.ceil(data_df[input.stat_y()].max()*pitcher_dict_stat[input.stat_y()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],
304
+ x=data_df[input.stat_x()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
305
+
306
+ ax.set_xlim((math.floor(data_df[input.stat_x()].min()*pitcher_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],
307
+ (math.ceil(data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'])
308
+
309
+ ax.set_ylim((math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],
310
+ (math.ceil(data_df[input.stat_y()].max()*pitcher_dict_stat[input.stat_y()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'])
311
+
312
+ title_level = str([x .strip("\'")for x in input.level_id()]).strip('[').strip(']').replace("'",'')
313
+
314
+ if title_level == 'AAA, AA, A+, A':
315
+ title_level='MiLB'
316
+
317
+
318
+ if input.n_age() >= 50:
319
+ title_spot = f'{title_level} Pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA)'
320
+
321
+ else:
322
+ title_spot = f'{title_level} Pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA, Max Age {input.n_age()})'
323
+
324
+ #title_level = input.level_id()[0]
325
+ #title_spot = f'{title_level} pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA)'
326
+
327
+ ax.set_title(title_spot, fontsize=24/(len(title_spot)*0.03),fontname='Century Gothic')
328
+ # #vals = ax.get_yticks()
329
+ ax.set_xlabel(pitcher_dict_stat[input.stat_x()]['x_axis'], fontsize=16,fontname='Century Gothic')
330
+ ax.set_ylabel(pitcher_dict_stat[input.stat_y()]['x_axis'], fontsize=16,fontname='Century Gothic')
331
+
332
+
333
+ if input.group_level():
334
+ ax.get_legend().remove()
335
+
336
+ if not input.group_level():
337
+ if len(input.level_id()) > 1:
338
+ h,l = scatter.get_legend_handles_labels()
339
+ l[-(len(input.level_id())+1)] = 'Level'
340
+ ax.legend(h[-(len(input.level_id())+1):],l[-(len(input.level_id())+1):], borderaxespad=0.1,loc=0)
341
+
342
+ else:
343
+ ax.get_legend().remove()
344
+
345
+
346
+
347
+ #plt.show(g)
348
+ # ax.figure.colorbar(sm, ax=ax)
349
+
350
+ cbar = ax.figure.colorbar(sm, ax=ax,format=decimal_format_assign(x=pitcher_dict_stat[input.stat_z()]),orientation='vertical',aspect=30)
351
+ cbar.set_label(pitcher_dict_stat[input.stat_z()]['x_axis'])
352
+ #fig.axes[0].invert_yaxis()
353
+ print('we made it here5')
354
+ fig.subplots_adjust(wspace=.02, hspace=.02)
355
+ # ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
356
+ #ax.set_yticks([0,0.1,0.2,0.3,0.4,0.5])
357
+ # fig.colorbar(plot_dist, ax=ax)
358
+ # fig.colorbar(plot_dist)
359
+
360
+ if not pitcher_dict_stat[input.stat_x()]['flip_p']:
361
+ fig.axes[0].invert_xaxis()
362
+
363
+ if not pitcher_dict_stat[input.stat_y()]['flip_p']:
364
+ fig.axes[0].invert_yaxis()
365
+
366
+
367
+ # ax.xaxis.set_major_formatter(mtick.PercentFormatter(1,decimals=0))
368
+ # ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
369
+
370
+
371
+
372
+
373
+
374
+ print('we made it here6')
375
+
376
+ ax.xaxis.set_major_formatter(decimal_format_assign(x=pitcher_dict_stat[input.stat_x()]))
377
+ ax.yaxis.set_major_formatter(decimal_format_assign(x=pitcher_dict_stat[input.stat_y()]))
378
+
379
+
380
+ print('we made it here7')
381
+ # ax.text(0.5, 0.5, '/u/tomstoms', transform=ax.transAxes,
382
+ # fontsize=60, color='gray', alpha=0.075,
383
+ # ha='center', va='center', rotation=45)
384
+ adjust_text(ts,
385
+ arrowprops=dict(arrowstyle="-", color=colour_palette[4], lw=1),ax=ax)
386
+
387
+ #ax.legend(fontsize='16')
388
+ fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
389
+ fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
390
+ fig.tight_layout()
391
+
392
+
393
+ pitcher_scatter = App(ui.page_fluid(
394
+ ui.tags.base(href=base_url),
395
+ ui.tags.div(
396
+ {"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
397
+ ui.tags.style(
398
+ """
399
+ h4 {
400
+ margin-top: 1em;font-size:35px;
401
+ }
402
+ h2{
403
+ font-size:25px;
404
+ }
405
+ """
406
+ ),
407
+ shinyswatch.theme.simplex(),
408
+ ui.tags.h4("TJStats"),
409
+ ui.tags.i("Baseball Analytics and Visualizations"),
410
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
411
+ ui.navset_tab(
412
+ ui.nav_control(
413
+ ui.a(
414
+ "Home",
415
+ href="home/"
416
+ ),
417
+ ),
418
+ ui.nav_menu(
419
+ "Batter Charts",
420
+ ui.nav_control(
421
+ ui.a(
422
+ "Batting Rolling",
423
+ href="rolling_batter/"
424
+ ),
425
+ ui.a(
426
+ "Spray",
427
+ href="spray/"
428
+ ),
429
+ ui.a(
430
+ "Decision Value",
431
+ href="decision_value/"
432
+ ),
433
+ ui.a(
434
+ "Damage Model",
435
+ href="damage_model/"
436
+ ),
437
+ ui.a(
438
+ "Batter Scatter",
439
+ href="batter_scatter/"
440
+ ),
441
+ ui.a(
442
+ "EV vs LA Plot",
443
+ href="ev_angle/"
444
+ ),
445
+ ui.a(
446
+ "Statcast Compare",
447
+ href="statcast_compare/"
448
+ )
449
+ ),
450
+ ),
451
+ ui.nav_menu(
452
+ "Pitcher Charts",
453
+ ui.nav_control(
454
+ ui.a(
455
+ "Pitcher Rolling",
456
+ href="rolling_pitcher/"
457
+ ),
458
+ ui.a(
459
+ "Pitcher Summary",
460
+ href="pitching_summary_graphic_new/"
461
+ ),
462
+ ui.a(
463
+ "Pitcher Scatter",
464
+ href="pitcher_scatter/"
465
+ )
466
+ ),
467
+ )),ui.row(
468
+ ui.layout_sidebar(
469
+
470
+
471
+
472
+ ui.panel_sidebar(
473
+ #ui.input_select("id", "Select Batter",batter_dict,selected=675911,width=1,size=1),
474
+ ui.row(
475
+ ui.column(4,ui.input_select("level_id", "Select Level",level_dict,width=1,size=1,multiple=True,selected='MLB',selectize=True),),
476
+ ui.column(4,ui.input_select("team_id", "Select Team",team_list,width=1,size=1,multiple=True,selected='All',selectize=True),),
477
+ ui.column(4,ui.input_select("position_id", "Select Position",position_list,width=1,size=1,selected='All',multiple=True,selectize=True))),
478
+ ui.row(
479
+ ui.column(6,ui.input_numeric("n", "Minimum PA", value=100)),
480
+ ui.column(6,ui.input_numeric("n_age", "Maximum Age", value=50))),
481
+ ui.row(
482
+ ui.column(4,ui.input_select("stat_x", "X-Axis",pitcher_dict_stat_small,selected='k_percent',width=1,size=1)),
483
+ ui.column(4,ui.input_select("stat_y", "Y-Axis",pitcher_dict_stat_small,selected='bb_percent',width=1,size=1)),
484
+ ui.column(4,ui.input_select("stat_z", "Colour-Bar Axis",pitcher_dict_stat_small,selected='bb_minus_k_percent',width=1,size=1))),
485
+
486
+ ui.row(
487
+ ui.column(6,ui.input_numeric("n_percent_top_x", "Top 'n' Percentile X-Labels", value=0.01)),
488
+ ui.column(6,ui.input_numeric("n_percent_bot_x", "Bottom 'n' Percentile X-Labels", value=0.01))),
489
+ ui.row(
490
+ ui.column(6,ui.input_numeric("n_percent_top_y", "Top 'n' Percentile Y-Labels", value=0.01)),
491
+ ui.column(6,ui.input_numeric("n_percent_bot_y", "Bottom 'n' Percentile Y-Labels", value=0.01))),
492
+ ui.row(
493
+ ui.column(6,ui.input_numeric("n_percent_top_z", "Top 'n' Percentile Z-Labels", value=0.01)),
494
+ ui.column(6,ui.input_numeric("n_percent_bot_z", "Bottom 'n' Percentile Z-Labels", value=0.01))),
495
+
496
+ ui.input_select("player_id", "Label Player",pitcher_dict,width=1,size=1,multiple=True,selectize=True),
497
+ ui.row(
498
+ ui.input_switch("names", "Toggle Names"),
499
+ ui.input_switch("group_level", "Group Levels")),
500
+ ui.input_action_button("go", "Generate",class_="btn-primary"),
501
+ ),
502
+
503
+
504
+ ui.panel_main(
505
+ ui.output_plot("plot",height = "1000px",width="1000px")
506
+ ,
507
+ ),
508
+ )),)),server)
pitcher_summary_graphic.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
pitching_summary_graphic.py ADDED
The diff for this file is too large to render. See raw diff
 
pitching_summary_graphic_new.py ADDED
The diff for this file is too large to render. See raw diff
 
pitching_summary_graphic_new_fg_api.py ADDED
The diff for this file is too large to render. See raw diff
 
rolling_batter.py ADDED
@@ -0,0 +1,732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import inflect
23
+ from matplotlib.pyplot import text
24
+
25
+ def percentile(n):
26
+ def percentile_(x):
27
+ return np.nanpercentile(x, n)
28
+ percentile_.__name__ = 'percentile_%s' % n
29
+ return percentile_
30
+
31
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
32
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
33
+
34
+
35
+
36
+ print('Starting Everything:')
37
+ # exit_velo_df = milb_a_ev_df.append([triple_a_ev_df,double_a_ev_df,a_high_a_ev_df,single_a_ev_df]).reset_index(drop=True)
38
+ # player_df_all = mlb_a_player_df.append([triple_a_player_df,double_a_player_df,a_high_a_player_df,single_a_player_df]).reset_index(drop=True)
39
+ # exit_velo_df = pd.read_csv('exit_velo_df_all.csv',index_col=[0])
40
+ # player_df_all = pd.read_csv('player_df_all.csv',index_col=[0])
41
+
42
+ # pa_df = pd.read_csv('pa_df_all.csv',index_col=[0])
43
+ # pa_df_full_na = pa_df.dropna()
44
+
45
+ ### Import Datasets
46
+ dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2023.csv',
47
+ ])
48
+ dataset_train = dataset['train']
49
+ exit_velo_df_mlb = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
50
+ #print(df_2023)
51
+ exit_velo_df_mlb['level'] = 'MLB'
52
+
53
+ # ### Import Datasets
54
+ # dataset = load_dataset('nesticot/mlb_data', data_files=['aaa_pitch_data_2023.csv',
55
+ # ])
56
+ # dataset_train = dataset['train']
57
+ # exit_velo_df_aaa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
58
+ # #print(df_2023)
59
+ # exit_velo_df_aaa['level'] = 'AAA'
60
+
61
+ # ### Import Datasets
62
+ # dataset = load_dataset('nesticot/mlb_data', data_files=['aa_pitch_data_2023.csv',
63
+ # ])
64
+ # dataset_train = dataset['train']
65
+ # exit_velo_df_aa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
66
+ # #print(df_2023)
67
+ # exit_velo_df_aa['level'] = 'AA'
68
+
69
+ # ### Import Datasets
70
+ # dataset = load_dataset('nesticot/mlb_data', data_files=['high_a_pitch_data_2023.csv',
71
+ # ])
72
+ # dataset_train = dataset['train']
73
+ # exit_velo_df_ha = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
74
+ # #print(df_2023)
75
+ # exit_velo_df_ha['level'] = 'A+'
76
+
77
+ # ### Import Datasets
78
+ # dataset = load_dataset('nesticot/mlb_data', data_files=['a_pitch_data_2023.csv',
79
+ # ])
80
+ # dataset_train = dataset['train']
81
+ # exit_velo_df_a = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
82
+ # #print(df_2023)
83
+ # exit_velo_df_a['level'] = 'A'
84
+
85
+ # exit_velo_df = pd.concat([exit_velo_df_mlb,exit_velo_df_aaa,exit_velo_df_aa,exit_velo_df_ha,exit_velo_df_a])
86
+ exit_velo_df = pd.concat([exit_velo_df_mlb])
87
+ # exit_velo_df_copy = exit_velo_df.copy()
88
+
89
+ # exit_velo_df = exit_velo_df_copy.copy()
90
+
91
+ end_codes = ['strikeout', 'field_out', 'single', 'walk', 'hit_by_pitch',
92
+ 'double', 'sac_fly', 'force_out', 'home_run',
93
+ 'grounded_into_double_play', 'fielders_choice', 'field_error',
94
+ 'triple', 'sac_bunt', 'double_play', 'intent_walk',
95
+ 'fielders_choice_out', 'strikeout_double_play',
96
+ 'sac_fly_double_play', 'catcher_interf', 'other_out']
97
+
98
+
99
+
100
+ exit_velo_df['pa'] = exit_velo_df.event_type.isin(end_codes)
101
+ #exit_velo_df['pa'] = 1
102
+ exit_velo_df['k'] = exit_velo_df.event_type.isin(list(filter(None, [x if 'strikeout' in x else '' for x in exit_velo_df.event_type.fillna('None').unique()])))
103
+ exit_velo_df['bb'] = exit_velo_df.event_type.isin(list(filter(None, [x if 'walk' in x else '' for x in exit_velo_df.event_type.fillna('None').unique()])))
104
+
105
+ #exit_velo_df['k_minus_bb'] = exit_velo_df['k'].astype(np.float32)-exit_velo_df['bb'].astype(np.float32)
106
+ exit_velo_df['bb_minus_k'] = exit_velo_df['bb'].astype(np.float32)-exit_velo_df['k'].astype(np.float32)
107
+
108
+
109
+
110
+ exit_velo_df = exit_velo_df.drop_duplicates(subset=['play_id'])
111
+
112
+
113
+
114
+ swing_codes = ['Swinging Strike', 'In play, no out',
115
+ 'Foul', 'In play, out(s)',
116
+ 'In play, run(s)', 'Swinging Strike (Blocked)',
117
+ 'Foul Bunt','Foul Tip', 'Missed Bunt','Foul Pitchout','Swinging Pitchout']
118
+
119
+ swings_in = ['Swinging Strike', 'In play, no out',
120
+ 'Foul', 'In play, out(s)',
121
+ 'In play, run(s)', 'Swinging Strike (Blocked)',
122
+ 'Foul Bunt','Foul Tip', 'Missed Bunt','Foul Pitchout','Swinging Pitchout']
123
+
124
+ swing_strike_codes = ['Swinging Strike',
125
+ 'Swinging Strike (Blocked)','Missed Bunt','Foul Tip','Swinging Pitchout']
126
+
127
+
128
+ contact_codes = ['In play, no out',
129
+ 'Foul', 'In play, out(s)',
130
+ 'In play, run(s)',
131
+ 'Foul Bunt']
132
+
133
+ codes_in = ['In play, out(s)',
134
+ 'Swinging Strike',
135
+ 'Ball',
136
+ 'Foul',
137
+ 'In play, no out',
138
+ 'Called Strike',
139
+ 'Foul Tip',
140
+ 'In play, run(s)',
141
+ 'Hit By Pitch',
142
+ 'Ball In Dirt',
143
+ 'Pitchout',
144
+ 'Swinging Strike (Blocked)',
145
+ 'Foul Bunt',
146
+ 'Missed Bunt',
147
+ 'Foul Pitchout',
148
+ 'Intent Ball',
149
+ 'Swinging Pitchout']
150
+
151
+ exit_velo_df['in_zone'] = exit_velo_df['zone'] < 10
152
+
153
+
154
+ exit_velo_df = exit_velo_df.drop_duplicates(subset=['play_id'])
155
+
156
+ exit_velo_df_codes = exit_velo_df[exit_velo_df.play_description.isin(codes_in)].dropna(subset=['in_zone'])
157
+
158
+ exit_velo_df_codes['bip'] = ~exit_velo_df_codes.launch_speed.isna()
159
+ conditions = [
160
+ (exit_velo_df_codes['launch_speed'].isna()),
161
+ (exit_velo_df_codes['launch_speed']*1.5 - exit_velo_df_codes['launch_angle'] >= 117 ) & (exit_velo_df_codes['launch_speed'] + exit_velo_df_codes['launch_angle'] >= 124) & (exit_velo_df_codes['launch_speed'] > 98) & (exit_velo_df_codes['launch_angle'] >= 8) & (exit_velo_df_codes['launch_angle'] <= 50)
162
+ ]
163
+
164
+ choices = [False,True]
165
+ exit_velo_df_codes['barrel'] = np.select(conditions, choices, default=np.nan)
166
+
167
+ conditions_ss = [
168
+ (exit_velo_df_codes['launch_angle'].isna()),
169
+ (exit_velo_df_codes['launch_angle'] >= 8 ) * (exit_velo_df_codes['launch_angle'] <= 32 )
170
+ ]
171
+
172
+ choices_ss = [False,True]
173
+ exit_velo_df_codes['sweet_spot'] = np.select(conditions_ss, choices_ss, default=np.nan)
174
+
175
+
176
+ conditions_hh = [
177
+ (exit_velo_df_codes['launch_speed'].isna()),
178
+ (exit_velo_df_codes['launch_speed'] >= 94.5 )
179
+ ]
180
+
181
+ choices_hh = [False,True]
182
+ exit_velo_df_codes['hard_hit'] = np.select(conditions_hh, choices_hh, default=np.nan)
183
+
184
+
185
+ conditions_tb = [
186
+ (exit_velo_df_codes['event_type']=='single'),
187
+ (exit_velo_df_codes['event_type']=='double'),
188
+ (exit_velo_df_codes['event_type']=='triple'),
189
+ (exit_velo_df_codes['event_type']=='home_run'),
190
+ ]
191
+
192
+ choices_tb = [1,2,3,4]
193
+
194
+ exit_velo_df_codes['tb'] = np.select(conditions_tb, choices_tb, default=np.nan)
195
+
196
+ conditions_woba = [
197
+ (exit_velo_df_codes['event_type']=='walk'),
198
+ (exit_velo_df_codes['event_type']=='hit_by_pitch'),
199
+ (exit_velo_df_codes['event_type']=='single'),
200
+ (exit_velo_df_codes['event_type']=='double'),
201
+ (exit_velo_df_codes['event_type']=='triple'),
202
+ (exit_velo_df_codes['event_type']=='home_run'),
203
+ ]
204
+
205
+ choices_woba = [0.705,
206
+ 0.688,
207
+ 0.897,
208
+ 1.233,
209
+ 1.612,
210
+ 2.013]
211
+
212
+ exit_velo_df_codes['woba'] = np.select(conditions_woba, choices_woba, default=np.nan)
213
+
214
+
215
+ woba_codes = ['strikeout', 'field_out', 'single', 'walk', 'hit_by_pitch',
216
+ 'double', 'sac_fly', 'force_out', 'home_run',
217
+ 'grounded_into_double_play', 'fielders_choice', 'field_error',
218
+ 'triple', 'sac_bunt', 'double_play',
219
+ 'fielders_choice_out', 'strikeout_double_play',
220
+ 'sac_fly_double_play', 'other_out']
221
+
222
+
223
+
224
+
225
+
226
+ conditions_woba_code = [
227
+ (exit_velo_df_codes['event_type'].isin(woba_codes))
228
+ ]
229
+
230
+ choices_woba_code = [1]
231
+
232
+ exit_velo_df_codes['woba_codes'] = np.select(conditions_woba_code, choices_woba_code, default=np.nan)
233
+
234
+
235
+ #exit_velo_df_codes['barrel'] = (exit_velo_df_codes.launch_speed >= 98) & (exit_velo_df_codes.launch_angle >= (26 - (-98 + exit_velo_df_codes.launch_speed))) & (exit_velo_df_codes.launch_angle <= 30 + (-98 + exit_velo_df_codes.launch_speed)) & (exit_velo_df_codes.launch_angle >= 8) & (exit_velo_df_codes.launch_angle <= 50)
236
+
237
+
238
+
239
+
240
+
241
+ #exit_velo_df_codes['barrel'] = (exit_velo_df_codes.launch_speed >= 98) & (exit_velo_df_codes.launch_angle >= (26 - (-98 + exit_velo_df_codes.launch_speed))) & (exit_velo_df_codes.launch_angle <= 30 + (-98 + exit_velo_df_codes.launch_speed)) & (exit_velo_df_codes.launch_angle >= 8) & (exit_velo_df_codes.launch_angle <= 50)
242
+ exit_velo_df_codes['pitches'] = 1
243
+ exit_velo_df_codes['whiffs'] = [1 if ((x == 'S')|(x == 'W')|(x =='T')) else 0 for x in exit_velo_df_codes.play_code]
244
+ exit_velo_df_codes['csw'] = [1 if ((x == 'S')|(x == 'W')|(x =='T')|(x == 'C')) else 0 for x in exit_velo_df_codes.play_code]
245
+ exit_velo_df_codes['swings'] = [1 if x in swings_in else 0 for x in exit_velo_df_codes.play_description]
246
+
247
+ exit_velo_df_codes['out_zone'] = exit_velo_df_codes.in_zone == False
248
+ exit_velo_df_codes['zone_swing'] = (exit_velo_df_codes.in_zone == True)&(exit_velo_df_codes.swings == 1)
249
+ exit_velo_df_codes['zone_contact'] = (exit_velo_df_codes.in_zone == True)&(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.whiffs == 0)
250
+ exit_velo_df_codes['ozone_swing'] = (exit_velo_df_codes.in_zone==False)&(exit_velo_df_codes.swings == 1)
251
+ exit_velo_df_codes['ozone_contact'] = (exit_velo_df_codes.in_zone==False)&(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.whiffs == 0)
252
+
253
+
254
+
255
+ exit_velo_df_codes_summ = exit_velo_df_codes.groupby(['batter_id','batter_name','level']).agg(
256
+ pa = ('pa','sum'),
257
+ k = ('k','sum'),
258
+ bb = ('bb','sum'),
259
+ bb_minus_k = ('bb_minus_k','sum'),
260
+ csw = ('csw','sum'),
261
+ bip = ('bip','sum'),
262
+ tb = ('tb','sum'),
263
+ woba = ('woba','sum'),
264
+ woba_codes = ('woba_codes','sum'),
265
+ hard_hit = ('hard_hit','sum'),
266
+ barrel = ('barrel','sum'),
267
+ sweet_spot = ('sweet_spot','sum'),
268
+ max_launch_speed = ('launch_speed','max'),
269
+ launch_speed_90 = ('launch_speed',percentile(90)),
270
+ launch_speed = ('launch_speed','mean'),
271
+ launch_angle = ('launch_angle','mean'),
272
+ pitches = ('pitches','sum'),
273
+ swings = ('swings','sum'),
274
+ in_zone = ('in_zone','sum'),
275
+ out_zone = ('out_zone','sum'),
276
+ whiffs = ('whiffs','sum'),
277
+ zone_swing = ('zone_swing','sum'),
278
+ zone_contact = ('zone_contact','sum'),
279
+ ozone_swing = ('ozone_swing','sum'),
280
+ ozone_contact = ('ozone_contact','sum'),
281
+ ).reset_index()
282
+
283
+ #exit_velo_df_codes_summ['out_zone'] = ~exit_velo_df_codes_summ.in_zone
284
+ #bip_min_input = int(input())
285
+ #bip_min = min(bip_min_input,50)
286
+ #exit_velo_df_codes_summ = exit_velo_df_codes_summ[exit_velo_df_codes_summ.balls_in_play>=bip_min]
287
+
288
+
289
+ exit_velo_df_codes_summ['k_percent'] = [exit_velo_df_codes_summ.k[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
290
+ exit_velo_df_codes_summ['bb_percent'] =[exit_velo_df_codes_summ.bb[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
291
+ exit_velo_df_codes_summ['bb_minus_k_percent'] =[exit_velo_df_codes_summ.bb_minus_k[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
292
+
293
+ exit_velo_df_codes_summ['csw_percent'] =[exit_velo_df_codes_summ.csw[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
294
+
295
+
296
+ exit_velo_df_codes_summ['sweet_spot_percent'] = [exit_velo_df_codes_summ.sweet_spot[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
297
+
298
+ exit_velo_df_codes_summ['woba_percent'] = [exit_velo_df_codes_summ.woba[x]/exit_velo_df_codes_summ.woba_codes[x] if exit_velo_df_codes_summ.woba_codes[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
299
+ #exit_velo_df_codes_summ['hard_hit_percent'] = [exit_velo_df_codes_summ.sweet_spot[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
300
+ exit_velo_df_codes_summ['hard_hit_percent'] = [exit_velo_df_codes_summ.hard_hit[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
301
+
302
+
303
+ exit_velo_df_codes_summ['barrel_percent'] = [exit_velo_df_codes_summ.barrel[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
304
+
305
+ exit_velo_df_codes_summ['zone_contact_percent'] = [exit_velo_df_codes_summ.zone_contact[x]/exit_velo_df_codes_summ.zone_swing[x] if exit_velo_df_codes_summ.zone_swing[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
306
+
307
+ exit_velo_df_codes_summ['zone_swing_percent'] = [exit_velo_df_codes_summ.zone_swing[x]/exit_velo_df_codes_summ.in_zone[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
308
+
309
+ exit_velo_df_codes_summ['zone_percent'] = [exit_velo_df_codes_summ.in_zone[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
310
+
311
+ exit_velo_df_codes_summ['chase_percent'] = [exit_velo_df_codes_summ.ozone_swing[x]/(exit_velo_df_codes_summ.pitches[x] - exit_velo_df_codes_summ.in_zone[x]) if (exit_velo_df_codes_summ.pitches[x]- exit_velo_df_codes_summ.in_zone[x]) != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
312
+
313
+ exit_velo_df_codes_summ['chase_contact'] = [exit_velo_df_codes_summ.ozone_contact[x]/exit_velo_df_codes_summ.ozone_swing[x] if exit_velo_df_codes_summ.ozone_swing[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
314
+
315
+ exit_velo_df_codes_summ['swing_percent'] = [exit_velo_df_codes_summ.swings[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
316
+
317
+ exit_velo_df_codes_summ['whiff_rate'] = [exit_velo_df_codes_summ.whiffs[x]/exit_velo_df_codes_summ.swings[x] if exit_velo_df_codes_summ.swings[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
318
+
319
+ exit_velo_df_codes_summ['swstr_rate'] = [exit_velo_df_codes_summ.whiffs[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
320
+
321
+ exit_velo_df_codes_summ = exit_velo_df_codes_summ.dropna(subset=['bip'])
322
+
323
+ woba_list = ['woba']
324
+ pa_list = ['k','bb','bb_minus_k']
325
+ balls_in_play_list = ['hard_hit','launch_speed','launch_speed_90','launch_angle','barrel','sweet_spot']
326
+ pitches_list = ['zone_percent','swing_percent','sw_str','csw']
327
+ swings_list = ['whiff_percent']
328
+ in_zone_pitches_list = ['zone_swing']
329
+ in_zone_swings_list = ['zone_contact']
330
+ out_zone_pitches_list = ['chase_percent']
331
+ out_zone_swings_list = ['chase_contact']
332
+
333
+ plot_dict = {
334
+ 'k':{'x_axis':'Plate Appearances','y_axis':'K%','title':'K%','x_value':'k','x_range':[0.0,0.1,0.2,0.3,0.4],'percent':True,'percentile_label':'k_percent','flip_p':True,'percentile':False,'avg_adjust':False},
335
+ 'bb':{'x_axis':'Plate Appearances','y_axis':'BB%','title':'BB%','x_value':'bb','x_range':[0.0,0.1,0.2,0.3],'percent':True,'percentile_label':'bb_percent','flip_p':False,'percentile':False,'avg_adjust':False},
336
+ 'bb_minus_k':{'x_axis':'Plate Appearances','y_axis':'BB-K%','title':'BB-K%','x_value':'bb_minus_k','x_range':[-0.3,-0.2,-0.1,0,0.1,0.2],'percent':True,'percentile_label':'bb_minus_k_percent','flip_p':False,'percentile':False,'avg_adjust':False},
337
+ 'csw':{'x_axis':'Pitches','y_axis':'CSW%','title':'CSW%','x_value':'csw','x_range':[.2,.25,.3,.35,.4],'percent':True,'percentile_label':'csw_percent','flip_p':True,'percentile':False,'avg_adjust':False},
338
+ 'woba':{'x_axis':'wOBA PA','y_axis':'wOBA','title':'wOBA','x_value':'woba','x_range':[.20,.30,.40,.50],'percent':False,'percentile_label':'woba_percent','flip_p':False,'percentile':False,'avg_adjust':True},
339
+ 'launch_speed':{'x_axis':'Balls In Play','y_axis':'Exit Velocity','title':'Exit Velocity','x_value':'launch_speed','x_range':[85,90,95,100],'percent':False,'percentile_label':'launch_speed','flip_p':False,'percentile':False,'avg_adjust':False},
340
+ 'launch_speed_90':{'x_axis':'Balls In Play','y_axis':'90th Percentile Exit Velocity','title':'90th Percentile Exit Velocity','x_value':'launch_speed','x_range':[95,100,105,110,115],'percent':False,'percentile_label':'launch_speed_90','flip_p':False,'percentile':True,'avg_adjust':False},
341
+ 'hard_hit':{'x_axis':'Balls In Play','y_axis':'HardHit%','title':'HardHit%','x_value':'hard_hit','x_range':[0.2,0.3,0.4,0.5,0.6,0.7],'percent':True,'percentile_label':'hard_hit_percent','flip_p':False,'percentile':False,'avg_adjust':False},
342
+ 'sweet_spot':{'x_axis':'Balls In Play','y_axis':'SweetSpot%','title':'SweetSpot%','x_value':'sweet_spot','x_range':[0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'sweet_spot_percent','flip_p':False,'percentile':False,'avg_adjust':False},
343
+ 'launch_angle':{'x_axis':'Balls In Play','y_axis':'Launch Angle','title':'Launch Angle','x_value':'launch_angle','x_range':[-20,-10,0,10,20],'percent':False,'percentile_label':'launch_angle','flip_p':False,'percentile':False,'avg_adjust':False},
344
+ 'barrel':{'x_axis':'Balls In Play','y_axis':'Barrel%','title':'Barrel%','x_value':'barrel','x_range':[0,0.05,0.10,.15,.20,.25,.30],'percent':True,'percentile_label':'barrel_percent','flip_p':False,'percentile':False,'avg_adjust':False},
345
+ 'zone_percent':{'x_axis':'Pitches','y_axis':'Zone%','title':'Zone%','x_value':'in_zone','x_range':[0.3,0.4,0.5,0.6,0.7],'percent':True,'percentile_label':'zone_percent','flip_p':False,'percentile':False,'avg_adjust':False},
346
+ 'swing_percent':{'x_axis':'Pitches','y_axis':'Swing%','title':'Swing%','x_value':'swings','x_range':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],'percent':True,'percentile_label':'swing_percent','flip_p':False,'percentile':False,'avg_adjust':False},
347
+ 'whiff_percent':{'x_axis':'Swings','y_axis':'Whiff%','title':'Whiff%','x_value':'whiffs','x_range':[0.0,0.1,0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'whiff_rate','flip_p':True,'percentile':False,'avg_adjust':False},
348
+ 'sw_str':{'x_axis':'Pitches','y_axis':'SwStr%','title':'SwStr%','x_value':'whiffs','x_range':[0.0,0.05,0.1,0.15,0.2,0.25],'percent':True,'percentile_label':'swstr_rate','flip_p':True,'percentile':False,'avg_adjust':False},
349
+ 'zone_swing':{'x_axis':'In-Zone Pitches','y_axis':'Z-Swing%','title':'Z-Swing%','x_value':'zone_swing','x_range':[0.3,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1],'percent':True,'percentile_label':'zone_swing_percent','flip_p':False,'percentile':False,'avg_adjust':False},
350
+ 'zone_contact':{'x_axis':'In-Zone Swings','y_axis':'Z-Contact%','title':'Z-Contact%','x_value':'zone_contact','x_range':[0.5,0.6,0.7,0.8,0.9,1],'percent':True,'percentile_label':'zone_contact_percent','flip_p':False,'percentile':False,'avg_adjust':False},
351
+ 'chase_percent':{'x_axis':'Out-of-Zone Pitches','y_axis':'O-Swing%','title':'O-Swing%','x_value':'ozone_swing','x_range':[0.0,0.1,0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'chase_percent','flip_p':True,'percentile':False,'avg_adjust':False},
352
+ 'chase_contact':{'x_axis':'Out-of-Zone Swings','y_axis':'O-Contact%','title':'O-Contact%','x_value':'ozone_contact','x_range':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],'percent':True,'percentile_label':'chase_contact','flip_p':False,'percentile':False,'avg_adjust':False},}
353
+
354
+
355
+
356
+
357
+ test_df = exit_velo_df.sort_values(by='batter_name').drop_duplicates(subset='batter_id').reset_index(drop=True)[['batter_id','batter_name']]#['pitcher'].to_dict()
358
+ test_df = test_df.dropna()
359
+ test_df['batter_id'] = test_df['batter_id'].astype(int)
360
+ test_df = test_df.set_index('batter_id')
361
+ #test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])
362
+
363
+ batter_dict = test_df['batter_name'].to_dict()
364
+
365
+ level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A'}
366
+
367
+ plot_dict_small = {
368
+ 'k':'K%',
369
+ 'bb':'BB%',
370
+ 'csw':'CSW%',
371
+ 'launch_speed':'Exit Velocity',
372
+ 'launch_speed_90':'90th Percentile Exit Velocity',
373
+ 'sweet_spot':'SweetSpot%',
374
+ 'launch_angle':'Launch Angle',
375
+ 'zone_percent':'Zone%',
376
+ 'barrel':'Barrel%',
377
+ 'swing_percent':'Swing%',
378
+ 'whiff_percent':'Whiff%',
379
+ 'sw_str':'SwStr%',
380
+ 'zone_swing':'Z-Swing%',
381
+ 'zone_contact':'Z-Contact%',
382
+ 'chase_percent':'O-Swing%',
383
+ 'chase_contact':'O-Contact%',}
384
+
385
+
386
+ def server(input,output,session):
387
+
388
+ @output
389
+ @render.plot(alt="A histogram")
390
+ @reactive.event(input.go, ignore_none=False)
391
+ def plot():
392
+ # np.random.seed(19680801)
393
+ # x = 100 + 15 * np.random.randn(437)
394
+ # fig, ax = plt.subplots()
395
+ # ax.hist(x, input.n(), density=True)
396
+ # return fig
397
+ sns.set_theme(style="whitegrid", palette="pastel")
398
+ if input.id() is "":
399
+ fig = plt.figure(figsize=(12, 12))
400
+ fig.text(s='Please Select a Pitcher',x=0.5,y=0.5)
401
+ return
402
+
403
+ swing_min = int(input.n())
404
+ fig, ax = plt.subplots(1, 1, figsize=(10, 10))
405
+
406
+ fig.set_facecolor('white')
407
+ #ax.set_facecolor('white')
408
+ #fig.patch.set_facecolor('lightblue')
409
+
410
+ print(input.stat_id())
411
+
412
+ if input.stat_id() in pa_list:
413
+ print('we hAVE MADE IT TO THIS PART OF THE CODE')
414
+
415
+
416
+ if input.stat_id() in pa_list:
417
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.pa==1)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
418
+ divisor_x = 'pa'
419
+ print('this is short')
420
+ print(elly_zone_df)
421
+
422
+
423
+ if input.stat_id() in balls_in_play_list:
424
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.bip)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
425
+ divisor_x = 'bip'
426
+ #print('this is short')
427
+
428
+ if input.stat_id() in balls_in_play_list:
429
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.bip)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
430
+ divisor_x = 'bip'
431
+ print('this is short')
432
+
433
+ if input.stat_id() in pitches_list:
434
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.pitches == 1)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
435
+ divisor_x = 'pitches'
436
+
437
+ if input.stat_id() in swings_list:
438
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
439
+ divisor_x = 'swings'
440
+
441
+
442
+ if input.stat_id() in in_zone_pitches_list:
443
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.in_zone)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
444
+ divisor_x = 'in_zone'
445
+
446
+
447
+ if input.stat_id() in in_zone_swings_list:
448
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.zone_swing)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
449
+ divisor_x = 'zone_swing'
450
+
451
+
452
+ if input.stat_id() in out_zone_pitches_list:
453
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.in_zone == False)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
454
+ divisor_x = 'out_zone'
455
+
456
+
457
+ if input.stat_id() in out_zone_swings_list:
458
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.ozone_swing)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
459
+ divisor_x = 'ozone_swing'
460
+
461
+ # penguins = sns.load_dataset("penguins")
462
+ # sns.histplot(data=penguins, x="flipper_length_mm")
463
+ # print('we made it here:')
464
+ # print(int(input.id()))
465
+ # print(input.stat_id())
466
+ # print(input.level_id())
467
+ # print(exit_velo_df_codes[(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())])
468
+ # print(exit_velo_df_codes.columns)
469
+ # print(elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum())
470
+
471
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ.copy()
472
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.set_index('batter_id','batter_name','level')
473
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new[divisor_x] >= int(input.n())]
474
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.level==input.level_id()]
475
+
476
+ exit_velo_df_codes_summ_rank = exit_velo_df_codes_summ_new.rank(method='max',ascending=False)
477
+ exit_velo_df_codes_summ_rank.columns = exit_velo_df_codes_summ_rank.columns+['_rank']
478
+
479
+ exit_velo_df_codes_summ_rank_percent = exit_velo_df_codes_summ_new.rank(pct=True)
480
+ exit_velo_df_codes_summ_rank_percent.columns = exit_velo_df_codes_summ_rank_percent.columns+['_percent']
481
+
482
+
483
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.reset_index()
484
+ exit_velo_df_codes_summ_rank = exit_velo_df_codes_summ_rank.reset_index()
485
+ exit_velo_df_codes_summ_rank_percent = exit_velo_df_codes_summ_rank_percent.reset_index()
486
+ print('Table columns:')
487
+
488
+ exit_velo_df_codes_summ_new.batter_id = exit_velo_df_codes_summ_new.batter_id.astype(int)
489
+ exit_velo_df_codes_summ_rank.batter_id = exit_velo_df_codes_summ_rank.batter_id.astype(int)
490
+ exit_velo_df_codes_summ_rank_percent.batter_id = exit_velo_df_codes_summ_rank_percent.batter_id.astype(int)
491
+
492
+ print('Table columns2:')
493
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.merge(exit_velo_df_codes_summ_rank,left_on=['batter_id'],right_on=['batter_id'],how='left',suffixes=['','_rank'])
494
+
495
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.merge(exit_velo_df_codes_summ_rank_percent,left_on=['batter_id'],right_on=['batter_id'],how='left',suffixes=['','_percent'])
496
+
497
+
498
+ print(exit_velo_df_codes_summ_new)
499
+ print(exit_velo_df_codes_summ_rank)
500
+ print(exit_velo_df_codes_summ_rank_percent)
501
+
502
+
503
+
504
+
505
+ #sns.scatterplot(x=data_df.launch_speed_90,y=data_df.zone_contact,color=colour_palette[0],s=75,label=int(input.id()))
506
+
507
+ exit_velo_df_codes_summ_new_select = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.batter_id == int(input.id())].reset_index(drop=True)
508
+ print('whiffing')
509
+ print(exit_velo_df_codes)
510
+ print('Player _df:')
511
+ print(exit_velo_df_codes_summ_new_select)
512
+
513
+ if len(exit_velo_df_codes_summ_new_select) < 1:
514
+ ax.text(x=0.5,y=0.5,s='Please Select Different Parameters to Produce a plot',fontsize=18,ha='center')
515
+ return
516
+
517
+ p = inflect.engine()
518
+
519
+ exit_velo_df_codes_summ_new_select = exit_velo_df_codes_summ_new_select.loc[:,~exit_velo_df_codes_summ_new_select.columns.duplicated(keep='last')].copy()
520
+ print('Table for the player:')
521
+ print(list(exit_velo_df_codes_summ_new_select.columns))
522
+ print(plot_dict[input.stat_id()]["percentile_label"])
523
+ print(plot_dict[input.stat_id()]["percentile_label"]+'_percent')
524
+ print(exit_velo_df_codes_summ_new_select)
525
+ print(1*plot_dict[input.stat_id()]["flip_p"])
526
+ print(round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))
527
+ print((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)
528
+
529
+ # print(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+'_percent'])
530
+
531
+ if plot_dict[input.stat_id()]['percent']:
532
+ label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum():.1%}'
533
+ label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
534
+ #label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%}'
535
+ ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
536
+
537
+ else:
538
+ label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum():.1f}'
539
+ label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1f} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
540
+ #label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1f}'
541
+ #ax.yaxis.set_major_formatter(mtick.int)
542
+
543
+
544
+ if plot_dict[input.stat_id()]['percentile']:
545
+ label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].quantile(0.9):.1f}'
546
+ label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].quantile(0.9):.1f} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
547
+ #label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%}'
548
+ #ax.yaxis.set_major_formatter(mtick.int)
549
+
550
+
551
+ print(plot_dict[input.stat_id()]["x_value"])
552
+ print(divisor_x)
553
+
554
+ # exit_velo_df_codes_summ_new = exit_velo_df_codes_summ.copy()
555
+ # exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.balls_in_play >= int(input.n())]
556
+ # exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.level==input.level_id()]
557
+
558
+
559
+ print('this is here:')
560
+ print(exit_velo_df_codes_summ_new.head())
561
+ print(exit_velo_df_codes_summ_new.columns)
562
+
563
+
564
+ if plot_dict[input.stat_id()]["flip_p"] == False:
565
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[2],linestyle='dotted',alpha=0.5)
566
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[3],linestyle='dotted',alpha=0.5)
567
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[4],linestyle='dotted',alpha=0.5)
568
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[5],linestyle='dotted',alpha=0.5)
569
+
570
+
571
+ hard_hit_dates = [(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),
572
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),
573
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),
574
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1)]
575
+ hard_hit_text = ['90th %','75th %','25th %','10th %']
576
+ for i, x in enumerate(hard_hit_dates):
577
+ text(min(input.n()+input.n()/100,+input.n()+1), x ,hard_hit_text[i], rotation=0, ha='left',
578
+ bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[2+i], pad=2))
579
+
580
+
581
+
582
+ if plot_dict[input.stat_id()]["flip_p"] == True:
583
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[2],linestyle='dotted',alpha=0.5)
584
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[3],linestyle='dotted',alpha=0.5)
585
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[4],linestyle='dotted',alpha=0.5)
586
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[5],linestyle='dotted',alpha=0.5)
587
+
588
+ hard_hit_dates = [(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),
589
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),
590
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),
591
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1)]
592
+ hard_hit_text = ['10th %','25th %','75th %','90th %']
593
+ for i, x in enumerate(hard_hit_dates):
594
+ text(min(input.n()+input.n()/100,input.n()+input.n()+3), x ,hard_hit_text[i], rotation=0, ha='left',
595
+ bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[2+i], pad=2))
596
+
597
+
598
+
599
+
600
+
601
+
602
+ if plot_dict[input.stat_id()]["percentile"] == False:
603
+ ax.hlines(y=exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum(),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[1],linestyle='-.',label=label_1)
604
+
605
+ ax.hlines(y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum(),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[0],linestyle='--',label=label_2)
606
+
607
+ sns.lineplot(x=range(1,len(elly_zone_df)+1),y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).rolling(window=swing_min).sum()/swing_min,color=colour_palette[0],linewidth=3,ax=ax)
608
+
609
+
610
+
611
+ if plot_dict[input.stat_id()]["percentile"] == True:
612
+
613
+ ax.hlines(y=exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[1],linestyle='-.',label=label_1)
614
+
615
+ ax.hlines(y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[0],linestyle='--',label=label_2)
616
+
617
+ sns.lineplot(x=range(1,len(elly_zone_df)+1),y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).rolling(window=swing_min).quantile(0.9),color=colour_palette[0],linewidth=3,ax=ax)
618
+
619
+
620
+ #ax.set_xlim(input.n(),exit_velo_df_small.pitch.max())
621
+ #plt.yticks([0,0.2,0.4,0.6,0.8,1])
622
+ #ax.set_ylim(math.floor((min(exit_velo_df_codes_summ.zone_contact)/5)*100)*5/100,1)
623
+ ax.set_xlim(math.floor(swing_min),len(elly_zone_df))
624
+ ax.set_title(f'{batter_dict[int(input.id())]} - {input.level_id()} - {swing_min} {plot_dict[input.stat_id()]["x_axis"]} Rolling {plot_dict[input.stat_id()]["title"]}', fontsize=16,fontname='Century Gothic',)
625
+ #vals = ax.get_yticks()
626
+ ax.set_xlabel(plot_dict[input.stat_id()]['x_axis'], fontsize=16,fontname='Century Gothic')
627
+ ax.set_ylabel(plot_dict[input.stat_id()]['y_axis'], fontsize=16,fontname='Century Gothic')
628
+
629
+ #fig.axes[0].invert_yaxis()
630
+
631
+ #fig.subplots_adjust(wspace=.02, hspace=.02)
632
+ #ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
633
+ ax.set_yticks(plot_dict[input.stat_id()]["x_range"])
634
+ #fig.colorbar(plot_dist, ax=ax)
635
+ #fig.colorbar(plot_dist)
636
+ #fig.axes[0].invert_yaxis()
637
+ ax.legend(fontsize='16')
638
+ fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
639
+ fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
640
+ fig.tight_layout()
641
+
642
+
643
+ rolling_batter = App(ui.page_fluid(
644
+ ui.tags.base(href=base_url),
645
+ ui.tags.div(
646
+ {"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
647
+ ui.tags.style(
648
+ """
649
+ h4 {
650
+ margin-top: 1em;font-size:35px;
651
+ }
652
+ h2{
653
+ font-size:25px;
654
+ }
655
+ """
656
+ ),
657
+ shinyswatch.theme.simplex(),
658
+ ui.tags.h4("TJStats"),
659
+ ui.tags.i("Baseball Analytics and Visualizations"),
660
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
661
+ ui.navset_tab(
662
+ ui.nav_control(
663
+ ui.a(
664
+ "Home",
665
+ href="home/"
666
+ ),
667
+ ),
668
+ ui.nav_menu(
669
+ "Batter Charts",
670
+ ui.nav_control(
671
+ ui.a(
672
+ "Batting Rolling",
673
+ href="rolling_batter/"
674
+ ),
675
+ ui.a(
676
+ "Spray",
677
+ href="spray/"
678
+ ),
679
+ ui.a(
680
+ "Decision Value",
681
+ href="decision_value/"
682
+ ),
683
+ ui.a(
684
+ "Damage Model",
685
+ href="damage_model/"
686
+ ),
687
+ ui.a(
688
+ "Batter Scatter",
689
+ href="batter_scatter/"
690
+ ),
691
+ ui.a(
692
+ "EV vs LA Plot",
693
+ href="ev_angle/"
694
+ ),
695
+ ui.a(
696
+ "Statcast Compare",
697
+ href="statcast_compare/"
698
+ )
699
+ ),
700
+ ),
701
+ ui.nav_menu(
702
+ "Pitcher Charts",
703
+ ui.nav_control(
704
+ ui.a(
705
+ "Pitcher Rolling",
706
+ href="rolling_pitcher/"
707
+ ),
708
+ ui.a(
709
+ "Pitcher Summary",
710
+ href="pitching_summary_graphic_new/"
711
+ ),
712
+ ui.a(
713
+ "Pitcher Scatter",
714
+ href="pitcher_scatter/"
715
+ )
716
+ ),
717
+ )),ui.row(
718
+ ui.layout_sidebar(
719
+
720
+ ui.panel_sidebar(
721
+ ui.input_select("id", "Select Pitcher",batter_dict,selected=675911,width=1,size=1,selectize=True),
722
+ ui.input_select("level_id", "Select Level",level_dict,width=1,size=1),
723
+ ui.input_select("stat_id", "Select Stat",plot_dict_small,width=1,size=1),
724
+ ui.input_numeric("n", "Rolling Window Size", value=50),
725
+ ui.input_action_button("go", "Generate",class_="btn-primary"),
726
+ ui.output_table("result")
727
+ ),
728
+
729
+ ui.panel_main(
730
+ ui.output_plot("plot",height = "1000px",width="1000px")
731
+ ),
732
+ )),)),server)
rolling_pitcher.py ADDED
@@ -0,0 +1,719 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import inflect
23
+ from matplotlib.pyplot import text
24
+
25
+ def percentile(n):
26
+ def percentile_(x):
27
+ return np.nanpercentile(x, n)
28
+ percentile_.__name__ = 'percentile_%s' % n
29
+ return percentile_
30
+
31
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
32
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
33
+
34
+
35
+
36
+ print('Starting Everything:')
37
+ # exit_velo_df = milb_a_ev_df.append([triple_a_ev_df,double_a_ev_df,a_high_a_ev_df,single_a_ev_df]).reset_index(drop=True)
38
+ # player_df_all = mlb_a_player_df.append([triple_a_player_df,double_a_player_df,a_high_a_player_df,single_a_player_df]).reset_index(drop=True)
39
+ # exit_velo_df = pd.read_csv('exit_velo_df_all.csv',index_col=[0])
40
+ # player_df_all = pd.read_csv('player_df_all.csv',index_col=[0])
41
+
42
+ # pa_df = pd.read_csv('pa_df_all.csv',index_col=[0])
43
+ # pa_df_full_na = pa_df.dropna()
44
+
45
+ ### Import Datasets
46
+ dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2023.csv',
47
+ ])
48
+ dataset_train = dataset['train']
49
+ exit_velo_df_mlb = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
50
+ #print(df_2023)
51
+ exit_velo_df_mlb['level'] = 'MLB'
52
+
53
+ ### Import Datasets
54
+ dataset = load_dataset('nesticot/mlb_data', data_files=['aaa_pitch_data_2023.csv',
55
+ ])
56
+ dataset_train = dataset['train']
57
+ exit_velo_df_aaa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
58
+ #print(df_2023)
59
+ exit_velo_df_aaa['level'] = 'AAA'
60
+
61
+ ### Import Datasets
62
+ dataset = load_dataset('nesticot/mlb_data', data_files=['aa_pitch_data_2023.csv',
63
+ ])
64
+ dataset_train = dataset['train']
65
+ exit_velo_df_aa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
66
+ #print(df_2023)
67
+ exit_velo_df_aa['level'] = 'AA'
68
+
69
+ ### Import Datasets
70
+ dataset = load_dataset('nesticot/mlb_data', data_files=['high_a_pitch_data_2023.csv',
71
+ ])
72
+ dataset_train = dataset['train']
73
+ exit_velo_df_ha = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
74
+ #print(df_2023)
75
+ exit_velo_df_ha['level'] = 'A+'
76
+
77
+ ### Import Datasets
78
+ dataset = load_dataset('nesticot/mlb_data', data_files=['a_pitch_data_2023.csv',
79
+ ])
80
+ dataset_train = dataset['train']
81
+ exit_velo_df_a = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
82
+ #print(df_2023)
83
+ exit_velo_df_a['level'] = 'A'
84
+
85
+ exit_velo_df = pd.concat([exit_velo_df_mlb,exit_velo_df_aaa,exit_velo_df_aa,exit_velo_df_ha,exit_velo_df_a])
86
+ # exit_velo_df_copy = exit_velo_df.copy()
87
+
88
+ # exit_velo_df = exit_velo_df_copy.copy()
89
+
90
+ end_codes = ['strikeout', 'field_out', 'single', 'walk', 'hit_by_pitch',
91
+ 'double', 'sac_fly', 'force_out', 'home_run',
92
+ 'grounded_into_double_play', 'fielders_choice', 'field_error',
93
+ 'triple', 'sac_bunt', 'double_play', 'intent_walk',
94
+ 'fielders_choice_out', 'strikeout_double_play',
95
+ 'sac_fly_double_play', 'catcher_interf', 'other_out']
96
+
97
+
98
+
99
+ exit_velo_df['pa'] = exit_velo_df.event_type.isin(end_codes)
100
+ #exit_velo_df['pa'] = 1
101
+ exit_velo_df['k'] = exit_velo_df.event_type.isin(list(filter(None, [x if 'strikeout' in x else '' for x in exit_velo_df.event_type.fillna('None').unique()])))
102
+ exit_velo_df['bb'] = exit_velo_df.event_type.isin(list(filter(None, [x if 'walk' in x else '' for x in exit_velo_df.event_type.fillna('None').unique()])))
103
+ exit_velo_df['k_minus_bb'] = exit_velo_df['k'].astype(np.float32)-exit_velo_df['bb'].astype(np.float32)
104
+
105
+ exit_velo_df = exit_velo_df.drop_duplicates(subset=['play_id'])
106
+
107
+
108
+
109
+ swing_codes = ['Swinging Strike', 'In play, no out',
110
+ 'Foul', 'In play, out(s)',
111
+ 'In play, run(s)', 'Swinging Strike (Blocked)',
112
+ 'Foul Bunt','Foul Tip', 'Missed Bunt','Foul Pitchout','Swinging Pitchout']
113
+
114
+ swings_in = ['Swinging Strike', 'In play, no out',
115
+ 'Foul', 'In play, out(s)',
116
+ 'In play, run(s)', 'Swinging Strike (Blocked)',
117
+ 'Foul Bunt','Foul Tip', 'Missed Bunt','Foul Pitchout','Swinging Pitchout']
118
+
119
+ swing_strike_codes = ['Swinging Strike',
120
+ 'Swinging Strike (Blocked)','Missed Bunt','Foul Tip','Swinging Pitchout']
121
+
122
+
123
+ contact_codes = ['In play, no out',
124
+ 'Foul', 'In play, out(s)',
125
+ 'In play, run(s)',
126
+ 'Foul Bunt']
127
+
128
+ codes_in = ['In play, out(s)',
129
+ 'Swinging Strike',
130
+ 'Ball',
131
+ 'Foul',
132
+ 'In play, no out',
133
+ 'Called Strike',
134
+ 'Foul Tip',
135
+ 'In play, run(s)',
136
+ 'Hit By Pitch',
137
+ 'Ball In Dirt',
138
+ 'Pitchout',
139
+ 'Swinging Strike (Blocked)',
140
+ 'Foul Bunt',
141
+ 'Missed Bunt',
142
+ 'Foul Pitchout',
143
+ 'Intent Ball',
144
+ 'Swinging Pitchout']
145
+
146
+ exit_velo_df['in_zone'] = exit_velo_df['zone'] < 10
147
+
148
+
149
+ exit_velo_df = exit_velo_df.drop_duplicates(subset=['play_id'])
150
+
151
+ exit_velo_df_codes = exit_velo_df[exit_velo_df.play_description.isin(codes_in)].dropna(subset=['in_zone'])
152
+
153
+ exit_velo_df_codes['bip'] = ~exit_velo_df_codes.launch_speed.isna()
154
+ conditions = [
155
+ (exit_velo_df_codes['launch_speed'].isna()),
156
+ (exit_velo_df_codes['launch_speed']*1.5 - exit_velo_df_codes['launch_angle'] >= 117 ) & (exit_velo_df_codes['launch_speed'] + exit_velo_df_codes['launch_angle'] >= 124) & (exit_velo_df_codes['launch_speed'] > 98) & (exit_velo_df_codes['launch_angle'] >= 8) & (exit_velo_df_codes['launch_angle'] <= 50)
157
+ ]
158
+
159
+ choices = [False,True]
160
+ exit_velo_df_codes['barrel'] = np.select(conditions, choices, default=np.nan)
161
+
162
+ conditions_ss = [
163
+ (exit_velo_df_codes['launch_angle'].isna()),
164
+ (exit_velo_df_codes['launch_angle'] >= 8 ) * (exit_velo_df_codes['launch_angle'] <= 32 )
165
+ ]
166
+
167
+ choices_ss = [False,True]
168
+ exit_velo_df_codes['sweet_spot'] = np.select(conditions_ss, choices_ss, default=np.nan)
169
+ conditions_hh = [
170
+ (exit_velo_df_codes['launch_speed'].isna()),
171
+ (exit_velo_df_codes['launch_speed'] >= 94.5 )
172
+ ]
173
+
174
+ choices_hh = [False,True]
175
+ exit_velo_df_codes['hard_hit'] = np.select(conditions_hh, choices_hh, default=np.nan)
176
+
177
+
178
+ conditions_tb = [
179
+ (exit_velo_df_codes['event_type']=='single'),
180
+ (exit_velo_df_codes['event_type']=='double'),
181
+ (exit_velo_df_codes['event_type']=='triple'),
182
+ (exit_velo_df_codes['event_type']=='home_run'),
183
+ ]
184
+
185
+ choices_tb = [1,2,3,4]
186
+
187
+ exit_velo_df_codes['tb'] = np.select(conditions_tb, choices_tb, default=np.nan)
188
+
189
+ conditions_woba = [
190
+ (exit_velo_df_codes['event_type']=='walk'),
191
+ (exit_velo_df_codes['event_type']=='hit_by_pitch'),
192
+ (exit_velo_df_codes['event_type']=='single'),
193
+ (exit_velo_df_codes['event_type']=='double'),
194
+ (exit_velo_df_codes['event_type']=='triple'),
195
+ (exit_velo_df_codes['event_type']=='home_run'),
196
+ ]
197
+
198
+ choices_woba = [0.705,
199
+ 0.688,
200
+ 0.897,
201
+ 1.233,
202
+ 1.612,
203
+ 2.013]
204
+
205
+ exit_velo_df_codes['woba'] = np.select(conditions_woba, choices_woba, default=np.nan)
206
+
207
+
208
+ woba_codes = ['strikeout', 'field_out', 'single', 'walk', 'hit_by_pitch',
209
+ 'double', 'sac_fly', 'force_out', 'home_run',
210
+ 'grounded_into_double_play', 'fielders_choice', 'field_error',
211
+ 'triple', 'sac_bunt', 'double_play',
212
+ 'fielders_choice_out', 'strikeout_double_play',
213
+ 'sac_fly_double_play', 'other_out']
214
+
215
+
216
+
217
+
218
+
219
+ conditions_woba_code = [
220
+ (exit_velo_df_codes['event_type'].isin(woba_codes))
221
+ ]
222
+
223
+ choices_woba_code = [1]
224
+
225
+ exit_velo_df_codes['woba_codes'] = np.select(conditions_woba_code, choices_woba_code, default=np.nan)
226
+
227
+
228
+ #exit_velo_df_codes['barrel'] = (exit_velo_df_codes.launch_speed >= 98) & (exit_velo_df_codes.launch_angle >= (26 - (-98 + exit_velo_df_codes.launch_speed))) & (exit_velo_df_codes.launch_angle <= 30 + (-98 + exit_velo_df_codes.launch_speed)) & (exit_velo_df_codes.launch_angle >= 8) & (exit_velo_df_codes.launch_angle <= 50)
229
+
230
+
231
+
232
+ #exit_velo_df_codes['barrel'] = (exit_velo_df_codes.launch_speed >= 98) & (exit_velo_df_codes.launch_angle >= (26 - (-98 + exit_velo_df_codes.launch_speed))) & (exit_velo_df_codes.launch_angle <= 30 + (-98 + exit_velo_df_codes.launch_speed)) & (exit_velo_df_codes.launch_angle >= 8) & (exit_velo_df_codes.launch_angle <= 50)
233
+ exit_velo_df_codes['pitches'] = 1
234
+ exit_velo_df_codes['whiffs'] = [1 if ((x == 'S')|(x == 'W')|(x =='T')) else 0 for x in exit_velo_df_codes.play_code]
235
+ exit_velo_df_codes['csw'] = [1 if ((x == 'S')|(x == 'W')|(x =='T')|(x == 'C')) else 0 for x in exit_velo_df_codes.play_code]
236
+ exit_velo_df_codes['swings'] = [1 if x in swings_in else 0 for x in exit_velo_df_codes.play_description]
237
+
238
+ exit_velo_df_codes['out_zone'] = exit_velo_df_codes.in_zone == False
239
+ exit_velo_df_codes['zone_swing'] = (exit_velo_df_codes.in_zone == True)&(exit_velo_df_codes.swings == 1)
240
+ exit_velo_df_codes['zone_contact'] = (exit_velo_df_codes.in_zone == True)&(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.whiffs == 0)
241
+ exit_velo_df_codes['ozone_swing'] = (exit_velo_df_codes.in_zone==False)&(exit_velo_df_codes.swings == 1)
242
+ exit_velo_df_codes['ozone_contact'] = (exit_velo_df_codes.in_zone==False)&(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.whiffs == 0)
243
+
244
+ exit_velo_df_codes_summ = exit_velo_df_codes.groupby(['pitcher_id','pitcher_name','level']).agg(
245
+ pa = ('pa','sum'),
246
+ k = ('k','sum'),
247
+ bb = ('bb','sum'),
248
+ k_minus_bb = ('k_minus_bb','sum'),
249
+ csw = ('csw','sum'),
250
+ bip = ('bip','sum'),
251
+ tb = ('tb','sum'),
252
+ woba = ('woba','sum'),
253
+ woba_codes = ('woba_codes','sum'),
254
+ hard_hit = ('hard_hit','sum'),
255
+ barrel = ('barrel','sum'),
256
+ sweet_spot = ('sweet_spot','sum'),
257
+ max_launch_speed = ('launch_speed','max'),
258
+ launch_speed_90 = ('launch_speed',percentile(90)),
259
+ launch_speed = ('launch_speed','mean'),
260
+ launch_angle = ('launch_angle','mean'),
261
+ pitches = ('pitches','sum'),
262
+ swings = ('swings','sum'),
263
+ in_zone = ('in_zone','sum'),
264
+ out_zone = ('out_zone','sum'),
265
+ whiffs = ('whiffs','sum'),
266
+ zone_swing = ('zone_swing','sum'),
267
+ zone_contact = ('zone_contact','sum'),
268
+ ozone_swing = ('ozone_swing','sum'),
269
+ ozone_contact = ('ozone_contact','sum'),
270
+ ).reset_index()
271
+
272
+
273
+
274
+ exit_velo_df_codes_summ['k_percent'] = [exit_velo_df_codes_summ.k[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
275
+ exit_velo_df_codes_summ['bb_percent'] =[exit_velo_df_codes_summ.bb[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
276
+ exit_velo_df_codes_summ['k_minus_bb_percent'] =[exit_velo_df_codes_summ.k_minus_bb[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
277
+
278
+
279
+ exit_velo_df_codes_summ['sweet_spot_percent'] = [exit_velo_df_codes_summ.sweet_spot[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
280
+
281
+ exit_velo_df_codes_summ['woba_percent'] = [exit_velo_df_codes_summ.woba[x]/exit_velo_df_codes_summ.woba_codes[x] if exit_velo_df_codes_summ.woba_codes[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
282
+
283
+
284
+ exit_velo_df_codes_summ['hard_hit_percent'] = [exit_velo_df_codes_summ.hard_hit[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
285
+
286
+
287
+
288
+ exit_velo_df_codes_summ['csw_percent'] = [exit_velo_df_codes_summ.csw[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
289
+
290
+
291
+ exit_velo_df_codes_summ['barrel_percent'] = exit_velo_df_codes_summ.barrel = [exit_velo_df_codes_summ.barrel[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
292
+
293
+ exit_velo_df_codes_summ['zone_contact_percent'] = [exit_velo_df_codes_summ.zone_contact[x]/exit_velo_df_codes_summ.zone_swing[x] if exit_velo_df_codes_summ.zone_swing[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
294
+
295
+ exit_velo_df_codes_summ['zone_swing_percent'] = [exit_velo_df_codes_summ.zone_swing[x]/exit_velo_df_codes_summ.in_zone[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
296
+
297
+ exit_velo_df_codes_summ['zone_percent'] = [exit_velo_df_codes_summ.in_zone[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
298
+
299
+ exit_velo_df_codes_summ['chase_percent'] = [exit_velo_df_codes_summ.ozone_swing[x]/(exit_velo_df_codes_summ.pitches[x] - exit_velo_df_codes_summ.in_zone[x]) if (exit_velo_df_codes_summ.pitches[x]- exit_velo_df_codes_summ.in_zone[x]) != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
300
+
301
+ exit_velo_df_codes_summ['chase_contact'] = [exit_velo_df_codes_summ.ozone_contact[x]/exit_velo_df_codes_summ.ozone_swing[x] if exit_velo_df_codes_summ.ozone_swing[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
302
+
303
+ exit_velo_df_codes_summ['swing_percent'] = [exit_velo_df_codes_summ.swings[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
304
+
305
+ exit_velo_df_codes_summ['whiff_rate'] = [exit_velo_df_codes_summ.whiffs[x]/exit_velo_df_codes_summ.swings[x] if exit_velo_df_codes_summ.swings[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
306
+
307
+ exit_velo_df_codes_summ['swstr_rate'] = [exit_velo_df_codes_summ.whiffs[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
308
+
309
+ exit_velo_df_codes_summ = exit_velo_df_codes_summ.dropna(subset=['bip'])
310
+
311
+ print('whiffing')
312
+ print(exit_velo_df_codes_summ['whiff_rate'])
313
+
314
+ exit_velo_df_codes_summ.head()
315
+
316
+ woba_list = ['woba']
317
+ pa_list = ['k','bb','k_minus_bb','bb_minus_k']
318
+ balls_in_play_list = ['hard_hit','launch_speed','launch_speed_90','launch_angle','barrel','sweet_spot']
319
+ pitches_list = ['zone_percent','swing_percent','sw_str','csw']
320
+ swings_list = ['whiff_percent']
321
+ in_zone_pitches_list = ['zone_swing']
322
+ in_zone_swings_list = ['zone_contact']
323
+ out_zone_pitches_list = ['chase_percent']
324
+ out_zone_swings_list = ['chase_contact']
325
+
326
+ plot_dict = {
327
+ 'k':{'x_axis':'Plate Appearances','y_axis':'K%','title':'K%','x_value':'k','x_range':[0.0,0.1,0.2,0.3,0.4],'percent':True,'percentile_label':'k_percent','flip_p':False,'percentile':False},
328
+ 'bb':{'x_axis':'Plate Appearances','y_axis':'BB%','title':'BB%','x_value':'bb','x_range':[0.0,0.1,0.2,0.3],'percent':True,'percentile_label':'bb_percent','flip_p':True,'percentile':False},
329
+ 'csw':{'x_axis':'Pitches','y_axis':'CSW%','title':'CSW%','x_value':'csw','x_range':[.2,.25,.3,.35,.4],'percent':True,'percentile_label':'csw_percent','flip_p':False,'percentile':False},
330
+ 'launch_speed':{'x_axis':'Balls In Play','y_axis':'Exit Velocity','title':'Exit Velocity','x_value':'launch_speed','x_range':[85,90,95,100,105],'percent':True,'percentile_label':'launch_speed','flip_p':True,'percentile':False},
331
+ 'launch_speed_90':{'x_axis':'Balls In Play','y_axis':'90th Percentile Exit Velocity','title':'90th Percentile Exit Velocity','x_value':'launch_speed','x_range':[90,95,100,105,110,115,120],'percent':False,'percentile_label':'launch_speed_90','flip_p':True,'percentile':True},
332
+ 'sweet_spot':{'x_axis':'Balls In Play','y_axis':'SweetSpot%','title':'SweetSpot%','x_value':'sweet_spot','x_range':[0.2,0.3,0.4,0.5,0.6],'percent':True,'percentile_label':'sweet_spot_percent','flip_p':True,'percentile':False},
333
+ 'launch_angle':{'x_axis':'Balls In Play','y_axis':'Launch Angle','title':'Launch Angle','x_value':'launch_angle','x_range':[-20,-10,0,10,20],'percent':False,'percentile_label':'launch_angle','flip_p':True,'percentile':False},
334
+ 'barrel':{'x_axis':'Balls In Play','y_axis':'Barrel%','title':'Barrel%','x_value':'barrel','x_range':[0,0.05,0.10,.15,.20,.25,.30],'percent':True,'percentile_label':'barrel_percent','flip_p':False,'percentile':False},
335
+ 'zone_percent':{'x_axis':'Pitches','y_axis':'Zone%','title':'Zone%','x_value':'in_zone','x_range':[0.3,0.4,0.5,0.6,0.7],'percent':True,'percentile_label':'zone_percent','flip_p':True,'percentile':False},
336
+ 'swing_percent':{'x_axis':'Pitches','y_axis':'Swing%','title':'Swing%','x_value':'swings','x_range':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],'percent':True,'percentile_label':'swing_percent','flip_p':False,'percentile':False},
337
+ 'whiff_percent':{'x_axis':'Swings','y_axis':'Whiff%','title':'Whiff%','x_value':'whiffs','x_range':[0.0,0.1,0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'whiff_rate','flip_p':False,'percentile':False},
338
+ 'sw_str':{'x_axis':'Pitches','y_axis':'SwStr%','title':'SwStr%','x_value':'whiffs','x_range':[0.0,0.05,0.1,0.15,0.2,0.25],'percent':True,'percentile_label':'swstr_rate','flip_p':False,'percentile':False},
339
+ 'zone_swing':{'x_axis':'In-Zone Pitches','y_axis':'Z-Swing%','title':'Z-Swing%','x_value':'zone_swing','x_range':[0.3,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1],'percent':True,'percentile_label':'zone_swing_percent','flip_p':True,'percentile':False},
340
+ 'zone_contact':{'x_axis':'In-Zone Swings','y_axis':'Z-Contact%','title':'Z-Contact%','x_value':'zone_contact','x_range':[0.5,0.6,0.7,0.8,0.9,1],'percent':True,'percentile_label':'zone_contact_percent','flip_p':True,'percentile':False},
341
+ 'chase_percent':{'x_axis':'Out-of-Zone Pitches','y_axis':'O-Swing%','title':'O-Swing%','x_value':'ozone_swing','x_range':[0.0,0.1,0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'chase_percent','flip_p':False,'percentile':False},
342
+ 'chase_contact':{'x_axis':'Out-of-Zone Swings','y_axis':'O-Contact%','title':'O-Contact%','x_value':'ozone_contact','x_range':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],'percent':True,'percentile_label':'chase_contact','flip_p':True,'percentile':False},}
343
+
344
+ test_df = exit_velo_df.sort_values(by='pitcher_name').drop_duplicates(subset='pitcher_id').reset_index(drop=True)[['pitcher_id','pitcher_name']]#['pitcher'].to_dict()
345
+ test_df = test_df.set_index('pitcher_id')
346
+
347
+ #test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])
348
+
349
+ batter_dict = test_df['pitcher_name'].to_dict()
350
+
351
+ level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A'}
352
+
353
+ plot_dict_small = {
354
+ 'k':'K%',
355
+ 'bb':'BB%',
356
+ 'csw':'CSW%',
357
+ 'launch_speed':'Exit Velocity',
358
+ 'launch_speed_90':'90th Percentile Exit Velocity',
359
+ 'sweet_spot':'SweetSpot%',
360
+ 'launch_angle':'Launch Angle',
361
+ 'zone_percent':'Zone%',
362
+ 'barrel':'Barrel%',
363
+ 'swing_percent':'Swing%',
364
+ 'whiff_percent':'Whiff%',
365
+ 'sw_str':'SwStr%',
366
+ 'zone_swing':'Z-Swing%',
367
+ 'zone_contact':'Z-Contact%',
368
+ 'chase_percent':'O-Swing%',
369
+ 'chase_contact':'O-Contact%',}
370
+
371
+
372
+ def server(input,output,session):
373
+
374
+ @output
375
+ @render.plot(alt="A histogram")
376
+ @reactive.event(input.go, ignore_none=False)
377
+ def plot():
378
+ # np.random.seed(19680801)
379
+ # x = 100 + 15 * np.random.randn(437)
380
+ # fig, ax = plt.subplots()
381
+ # ax.hist(x, input.n(), density=True)
382
+ # return fig
383
+ sns.set_theme(style="whitegrid", palette="pastel")
384
+ if input.id() is "":
385
+ fig = plt.figure(figsize=(12, 12))
386
+ fig.text(s='Please Select a Pitcher',x=0.5,y=0.5)
387
+ return
388
+
389
+ swing_min = int(input.n())
390
+ fig, ax = plt.subplots(1, 1, figsize=(10, 10))
391
+
392
+ fig.set_facecolor('white')
393
+ #ax.set_facecolor('white')
394
+ #fig.patch.set_facecolor('lightblue')
395
+
396
+ print(input.stat_id())
397
+
398
+ if input.stat_id() in pa_list:
399
+ print('we hAVE MADE IT TO THIS PART OF THE CODE')
400
+
401
+
402
+ if input.stat_id() in pa_list:
403
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.pa==1)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
404
+ divisor_x = 'pa'
405
+ print('this is short')
406
+ print(elly_zone_df)
407
+
408
+
409
+ if input.stat_id() in balls_in_play_list:
410
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.bip)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
411
+ divisor_x = 'bip'
412
+ #print('this is short')
413
+
414
+ if input.stat_id() in balls_in_play_list:
415
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.bip)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
416
+ divisor_x = 'bip'
417
+ print('this is short')
418
+
419
+ if input.stat_id() in pitches_list:
420
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.pitches == 1)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
421
+ divisor_x = 'pitches'
422
+
423
+ if input.stat_id() in swings_list:
424
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
425
+ divisor_x = 'swings'
426
+
427
+
428
+ if input.stat_id() in in_zone_pitches_list:
429
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.in_zone)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
430
+ divisor_x = 'in_zone'
431
+
432
+
433
+ if input.stat_id() in in_zone_swings_list:
434
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.zone_swing)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
435
+ divisor_x = 'zone_swing'
436
+
437
+
438
+ if input.stat_id() in out_zone_pitches_list:
439
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.in_zone == False)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
440
+ divisor_x = 'out_zone'
441
+
442
+
443
+ if input.stat_id() in out_zone_swings_list:
444
+ elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.ozone_swing)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
445
+ divisor_x = 'ozone_swing'
446
+
447
+ # penguins = sns.load_dataset("penguins")
448
+ # sns.histplot(data=penguins, x="flipper_length_mm")
449
+ # print('we made it here:')
450
+ # print(int(input.id()))
451
+ # print(input.stat_id())
452
+ # print(input.level_id())
453
+ # print(exit_velo_df_codes[(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())])
454
+ # print(exit_velo_df_codes.columns)
455
+ # print(elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum())
456
+
457
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ.copy()
458
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.set_index('pitcher_id','pitcher','level')
459
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new[divisor_x] >= int(input.n())]
460
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.level==input.level_id()]
461
+
462
+ exit_velo_df_codes_summ_rank = exit_velo_df_codes_summ_new.rank(method='max',ascending=False)
463
+ exit_velo_df_codes_summ_rank.columns = exit_velo_df_codes_summ_rank.columns+['_rank']
464
+
465
+ exit_velo_df_codes_summ_rank_percent = exit_velo_df_codes_summ_new.rank(pct=True)
466
+ exit_velo_df_codes_summ_rank_percent.columns = exit_velo_df_codes_summ_rank_percent.columns+['_percent']
467
+
468
+
469
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.reset_index()
470
+ exit_velo_df_codes_summ_rank = exit_velo_df_codes_summ_rank.reset_index()
471
+ exit_velo_df_codes_summ_rank_percent = exit_velo_df_codes_summ_rank_percent.reset_index()
472
+ print('Table columns:')
473
+
474
+ exit_velo_df_codes_summ_new.pitcher_id = exit_velo_df_codes_summ_new.pitcher_id.astype(int)
475
+ exit_velo_df_codes_summ_rank.pitcher_id = exit_velo_df_codes_summ_rank.pitcher_id.astype(int)
476
+ exit_velo_df_codes_summ_rank_percent.pitcher_id = exit_velo_df_codes_summ_rank_percent.pitcher_id.astype(int)
477
+
478
+ print('Table columns2:')
479
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.merge(exit_velo_df_codes_summ_rank,left_on=['pitcher_id'],right_on=['pitcher_id'],how='left',suffixes=['','_rank'])
480
+
481
+ exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.merge(exit_velo_df_codes_summ_rank_percent,left_on=['pitcher_id'],right_on=['pitcher_id'],how='left',suffixes=['','_percent'])
482
+
483
+
484
+ print(exit_velo_df_codes_summ_new)
485
+ print(exit_velo_df_codes_summ_rank)
486
+ print(exit_velo_df_codes_summ_rank_percent)
487
+
488
+
489
+
490
+
491
+ #sns.scatterplot(x=data_df.launch_speed_90,y=data_df.zone_contact,color=colour_palette[0],s=75,label=int(input.id()))
492
+
493
+ exit_velo_df_codes_summ_new_select = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.pitcher_id == int(input.id())].reset_index(drop=True)
494
+ print('whiffing')
495
+ print(exit_velo_df_codes)
496
+ print('Player _df:')
497
+ print(exit_velo_df_codes_summ_new_select)
498
+
499
+ if len(exit_velo_df_codes_summ_new_select) < 1:
500
+ ax.text(x=0.5,y=0.5,s='Please Select Different Parameters to Produce a plot',fontsize=18,ha='center')
501
+ return
502
+
503
+ p = inflect.engine()
504
+
505
+ exit_velo_df_codes_summ_new_select = exit_velo_df_codes_summ_new_select.loc[:,~exit_velo_df_codes_summ_new_select.columns.duplicated(keep='last')].copy()
506
+ print('Table for the player:')
507
+ print(list(exit_velo_df_codes_summ_new_select.columns))
508
+ print(plot_dict[input.stat_id()]["percentile_label"])
509
+ print(plot_dict[input.stat_id()]["percentile_label"]+'_percent')
510
+ print(exit_velo_df_codes_summ_new_select)
511
+ print(1*plot_dict[input.stat_id()]["flip_p"])
512
+ print(round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))
513
+ print((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)
514
+
515
+ # print(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+'_percent'])
516
+
517
+ if plot_dict[input.stat_id()]['percent']:
518
+ label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum():.1%}'
519
+ label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
520
+ #label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%}'
521
+ ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
522
+
523
+ else:
524
+ label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum():.1f}'
525
+ label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1f} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
526
+ #label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1f}'
527
+ #ax.yaxis.set_major_formatter(mtick.int)
528
+
529
+
530
+ if plot_dict[input.stat_id()]['percentile']:
531
+ label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].quantile(0.9):.1f}'
532
+ label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].quantile(0.9):.1f} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
533
+ #label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%}'
534
+ #ax.yaxis.set_major_formatter(mtick.int)
535
+
536
+
537
+ print(plot_dict[input.stat_id()]["x_value"])
538
+ print(divisor_x)
539
+
540
+ # exit_velo_df_codes_summ_new = exit_velo_df_codes_summ.copy()
541
+ # exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.balls_in_play >= int(input.n())]
542
+ # exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.level==input.level_id()]
543
+
544
+
545
+ print('this is here:')
546
+ print(exit_velo_df_codes_summ_new.head())
547
+ print(exit_velo_df_codes_summ_new.columns)
548
+
549
+
550
+ if plot_dict[input.stat_id()]["flip_p"] == False:
551
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[2],linestyle='dotted',alpha=0.5)
552
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[3],linestyle='dotted',alpha=0.5)
553
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[4],linestyle='dotted',alpha=0.5)
554
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[5],linestyle='dotted',alpha=0.5)
555
+
556
+
557
+ hard_hit_dates = [(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),
558
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),
559
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),
560
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1)]
561
+ hard_hit_text = ['90th %','75th %','25th %','10th %']
562
+ for i, x in enumerate(hard_hit_dates):
563
+ text(min(input.n()+input.n()/100,+input.n()+1), x ,hard_hit_text[i], rotation=0, ha='left',
564
+ bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[2+i], pad=2))
565
+
566
+
567
+
568
+ if plot_dict[input.stat_id()]["flip_p"] == True:
569
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[2],linestyle='dotted',alpha=0.5)
570
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[3],linestyle='dotted',alpha=0.5)
571
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[4],linestyle='dotted',alpha=0.5)
572
+ ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[5],linestyle='dotted',alpha=0.5)
573
+
574
+ hard_hit_dates = [(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),
575
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),
576
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),
577
+ (exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1)]
578
+ hard_hit_text = ['10th %','25th %','75th %','90th %']
579
+ for i, x in enumerate(hard_hit_dates):
580
+ text(min(input.n()+input.n()/100,input.n()+input.n()+3), x ,hard_hit_text[i], rotation=0, ha='left',
581
+ bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[2+i], pad=2))
582
+
583
+
584
+
585
+
586
+
587
+
588
+ if plot_dict[input.stat_id()]["percentile"] == False:
589
+ ax.hlines(y=exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum(),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[1],linestyle='-.',label=label_1)
590
+
591
+ ax.hlines(y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum(),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[0],linestyle='--',label=label_2)
592
+
593
+ sns.lineplot(x=range(1,len(elly_zone_df)+1),y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).rolling(window=swing_min).sum()/swing_min,color=colour_palette[0],linewidth=3,ax=ax)
594
+
595
+
596
+
597
+ if plot_dict[input.stat_id()]["percentile"] == True:
598
+
599
+ ax.hlines(y=exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[1],linestyle='-.',label=label_1)
600
+
601
+ ax.hlines(y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[0],linestyle='--',label=label_2)
602
+
603
+ sns.lineplot(x=range(1,len(elly_zone_df)+1),y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).rolling(window=swing_min).quantile(0.9),color=colour_palette[0],linewidth=3,ax=ax)
604
+
605
+
606
+ #ax.set_xlim(input.n(),exit_velo_df_small.pitch.max())
607
+ #plt.yticks([0,0.2,0.4,0.6,0.8,1])
608
+ #ax.set_ylim(math.floor((min(exit_velo_df_codes_summ.zone_contact)/5)*100)*5/100,1)
609
+ ax.set_xlim(math.floor(swing_min),len(elly_zone_df))
610
+ ax.set_title(f'{batter_dict[int(input.id())]} - {input.level_id()} - {swing_min} {plot_dict[input.stat_id()]["x_axis"]} Rolling {plot_dict[input.stat_id()]["title"]}', fontsize=16,fontname='Century Gothic',)
611
+ #vals = ax.get_yticks()
612
+ ax.set_xlabel(plot_dict[input.stat_id()]['x_axis'], fontsize=16,fontname='Century Gothic')
613
+ ax.set_ylabel(plot_dict[input.stat_id()]['y_axis'], fontsize=16,fontname='Century Gothic')
614
+
615
+ #fig.axes[0].invert_yaxis()
616
+
617
+ #fig.subplots_adjust(wspace=.02, hspace=.02)
618
+ #ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
619
+ ax.set_yticks(plot_dict[input.stat_id()]["x_range"])
620
+ #fig.colorbar(plot_dist, ax=ax)
621
+ #fig.colorbar(plot_dist)
622
+ #fig.axes[0].invert_yaxis()
623
+ ax.legend(fontsize='16')
624
+ fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
625
+ fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
626
+ fig.tight_layout()
627
+
628
+
629
+ rolling_pitcher = App(ui.page_fluid(
630
+ ui.tags.base(href=base_url),
631
+ ui.tags.div(
632
+ {"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
633
+ ui.tags.style(
634
+ """
635
+ h4 {
636
+ margin-top: 1em;font-size:35px;
637
+ }
638
+ h2{
639
+ font-size:25px;
640
+ }
641
+ """
642
+ ),
643
+ shinyswatch.theme.simplex(),
644
+ ui.tags.h4("TJStats"),
645
+ ui.tags.i("Baseball Analytics and Visualizations"),
646
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
647
+ ui.navset_tab(
648
+ ui.nav_control(
649
+ ui.a(
650
+ "Home",
651
+ href="home/"
652
+ ),
653
+ ),
654
+ ui.nav_menu(
655
+ "Batter Charts",
656
+ ui.nav_control(
657
+ ui.a(
658
+ "Batting Rolling",
659
+ href="rolling_batter/"
660
+ ),
661
+ ui.a(
662
+ "Spray",
663
+ href="spray/"
664
+ ),
665
+ ui.a(
666
+ "Decision Value",
667
+ href="decision_value/"
668
+ ),
669
+ ui.a(
670
+ "Damage Model",
671
+ href="damage_model/"
672
+ ),
673
+ ui.a(
674
+ "Batter Scatter",
675
+ href="batter_scatter/"
676
+ ),
677
+ ui.a(
678
+ "EV vs LA Plot",
679
+ href="ev_angle/"
680
+ ),
681
+ ui.a(
682
+ "Statcast Compare",
683
+ href="statcast_compare/"
684
+ )
685
+ ),
686
+ ),
687
+ ui.nav_menu(
688
+ "Pitcher Charts",
689
+ ui.nav_control(
690
+ ui.a(
691
+ "Pitcher Rolling",
692
+ href="rolling_pitcher/"
693
+ ),
694
+ ui.a(
695
+ "Pitcher Summary",
696
+ href="pitching_summary_graphic_new/"
697
+ ),
698
+ ui.a(
699
+ "Pitcher Scatter",
700
+ href="pitcher_scatter/"
701
+ )
702
+ ),
703
+ )),ui.row(
704
+ ui.layout_sidebar(
705
+
706
+ ui.panel_sidebar(
707
+ ui.input_select("id", "Select Pitcher",batter_dict,selected=675911,width=1,size=1,selectize=True),
708
+ ui.input_select("level_id", "Select Level",level_dict,width=1,size=1),
709
+ ui.input_select("stat_id", "Select Stat",plot_dict_small,width=1,size=1),
710
+ ui.input_numeric("n", "Rolling Window Size", value=50),
711
+ ui.input_action_button("go", "Generate",class_="btn-primary"),
712
+
713
+ ui.output_table("result")
714
+ ),
715
+
716
+ ui.panel_main(
717
+ ui.output_plot("plot",height = "1000px",width="1000px")
718
+ ),
719
+ )),)),server)
spray.py CHANGED
@@ -81,6 +81,7 @@ batter_dict = df_2023_bip.sort_values('batter_name').set_index('batter_id')['bat
81
  def server(input,output,session):
82
  @output
83
  @render.plot(alt="plot")
 
84
  def plot():
85
 
86
  batter_id_select = int(input.batter_id())
@@ -342,8 +343,9 @@ spray = App(ui.page_fluid(
342
  """
343
  ),
344
  shinyswatch.theme.simplex(),
345
- ui.tags.h4("Stats By Zach"),
346
- ui.tags.i("A website for hockey analytics"),
 
347
  ui.navset_tab(
348
  ui.nav_control(
349
  ui.a(
@@ -354,7 +356,11 @@ spray = App(ui.page_fluid(
354
  ui.nav_menu(
355
  "Batter Charts",
356
  ui.nav_control(
357
- ui.a(
 
 
 
 
358
  "Spray",
359
  href="spray/"
360
  ),
@@ -373,48 +379,29 @@ spray = App(ui.page_fluid(
373
  ui.a(
374
  "EV vs LA Plot",
375
  href="ev_angle/"
 
 
 
 
376
  )
377
  ),
378
  ),
379
  ui.nav_menu(
380
- "Goalie Charts",
381
  ui.nav_control(
382
  ui.a(
383
- "GSAx Timeline",
384
- href="gsax-timeline/"
385
  ),
386
  ui.a(
387
- "GSAx Leaderboard",
388
- href="gsax-leaderboard/"
389
  ),
390
  ui.a(
391
- "GSAx Comparison",
392
- href="gsax-comparison/"
393
  )
394
  ),
395
- ),ui.nav_menu(
396
- "Team Charts",
397
- ui.nav_control(
398
- ui.a(
399
- "Team xG Rates",
400
- href="team-xg-rates/"
401
- ),
402
- ),
403
- ),ui.nav_control(
404
- ui.a(
405
- "Games",
406
- href="games/"
407
- ),
408
- ),ui.nav_control(
409
- ui.a(
410
- "About",
411
- href="about/"
412
- ),
413
- ),ui.nav_control(
414
- ui.a(
415
- "Articles",
416
- href="articles/"
417
- ),
418
  )),ui.row(
419
  ui.layout_sidebar(
420
 
@@ -424,7 +411,9 @@ spray = App(ui.page_fluid(
424
  batter_dict,
425
  width=1,
426
  size=1,
427
- selectize=True)),
 
 
428
 
429
  ui.panel_main(
430
  ui.navset_tab(
 
81
  def server(input,output,session):
82
  @output
83
  @render.plot(alt="plot")
84
+ @reactive.event(input.go, ignore_none=False)
85
  def plot():
86
 
87
  batter_id_select = int(input.batter_id())
 
343
  """
344
  ),
345
  shinyswatch.theme.simplex(),
346
+ ui.tags.h4("TJStats"),
347
+ ui.tags.i("Baseball Analytics and Visualizations"),
348
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
349
  ui.navset_tab(
350
  ui.nav_control(
351
  ui.a(
 
356
  ui.nav_menu(
357
  "Batter Charts",
358
  ui.nav_control(
359
+ ui.a(
360
+ "Batting Rolling",
361
+ href="rolling_batter/"
362
+ ),
363
+ ui.a(
364
  "Spray",
365
  href="spray/"
366
  ),
 
379
  ui.a(
380
  "EV vs LA Plot",
381
  href="ev_angle/"
382
+ ),
383
+ ui.a(
384
+ "Statcast Compare",
385
+ href="statcast_compare/"
386
  )
387
  ),
388
  ),
389
  ui.nav_menu(
390
+ "Pitcher Charts",
391
  ui.nav_control(
392
  ui.a(
393
+ "Pitcher Rolling",
394
+ href="rolling_pitcher/"
395
  ),
396
  ui.a(
397
+ "Pitcher Summary",
398
+ href="pitching_summary_graphic_new/"
399
  ),
400
  ui.a(
401
+ "Pitcher Scatter",
402
+ href="pitcher_scatter/"
403
  )
404
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  )),ui.row(
406
  ui.layout_sidebar(
407
 
 
411
  batter_dict,
412
  width=1,
413
  size=1,
414
+ selectize=True),
415
+ ui.input_action_button("go", "Generate",class_="btn-primary",
416
+ )),
417
 
418
  ui.panel_main(
419
  ui.navset_tab(
statcast_20152023.csv ADDED
The diff for this file is too large to render. See raw diff
 
statcast_compare.py ADDED
@@ -0,0 +1,701 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import inflect
23
+ from matplotlib.pyplot import text
24
+
25
+ def percentile(n):
26
+ def percentile_(x):
27
+ return np.nanpercentile(x, n)
28
+ percentile_.__name__ = 'percentile_%s' % n
29
+ return percentile_
30
+
31
+ from matplotlib.colors import Normalize
32
+
33
+ print('Running')
34
+
35
+ cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"])
36
+ df = pd.read_csv('statcast_20152023.csv')
37
+ df['last_name'] = df['last_name, first_name'].str.split(',').str[0]
38
+ df['first_name'] = df['last_name, first_name'].str.split(',').str[1].str.strip(' ')
39
+ df['name'] = df['first_name'] +' ' +df['last_name']
40
+
41
+ df[[x for x in df if x[-7:] == 'percent']] = df[[x for x in df if x[-7:] == 'percent']]/100
42
+ df['barrel_batted_rate'] = df['barrel_batted_rate']/100
43
+
44
+ player_dict = df[['player_id','name']].drop_duplicates().sort_values('name').set_index('player_id').to_dict()['name']
45
+
46
+ df_median = df[df.pa>=400]
47
+
48
+ format_dict = {
49
+ 'k_percent':{'format':':.1%','average':df.strikeout.sum()/df.pa.sum(),'a_d_good':False,'tab_name':'K%'},
50
+ 'bb_percent':{'format':':.1%','average':df.walk.sum()/df.pa.sum(),'a_d_good':True,'tab_name':'BB%'},
51
+ 'batting_avg':{'format':':.3f','average':df.hit.sum()/df.ab.sum(),'a_d_good':True,'tab_name':'AVG'},
52
+ 'on_base_plus_slg':{'format':':.3f','average':df.on_base_plus_slg.mean()/df.pa.mean(),'a_d_good':True,'tab_name':'OPS'},
53
+ 'isolated_power':{'format':':.3f','average':(df.single.sum() + df.double.sum()*2 + df.triple.sum()*3 + df.home_run.sum()*4)/df.ab.sum() - df.hit.sum()/df.ab.sum(),'a_d_good':True,'tab_name':'ISO'},
54
+ 'xba':{'format':':.3f','average':df_median.xba.median(),'a_d_good':True,'tab_name':'xBA'},
55
+ 'xslg':{'format':':.3f','average':df_median.xslg.median(),'a_d_good':True,'tab_name':'xSLG'},
56
+ 'woba':{'format':':.3f','average':df_median.woba.median(),'a_d_good':True,'tab_name':'wOBA'},
57
+ 'xwoba':{'format':':.3f','average':df_median.xwoba.median(),'a_d_good':True,'tab_name':'xwOBA'},
58
+ 'xobp':{'format':':.3f','average':df_median.xobp.median(),'a_d_good':True,'tab_name':'xOBP'},
59
+ 'xiso':{'format':':.3f','average':df_median.xiso.median(),'a_d_good':True,'tab_name':'xISO'},
60
+ 'wobacon':{'format':':.3f','average':df_median.wobacon.median(),'a_d_good':True,'tab_name':'wOBACON'},
61
+ 'xwobacon':{'format':':.3f','average':df_median.xwobacon.median(),'a_d_good':True,'tab_name':'xwOBACON'},
62
+ 'bacon':{'format':':.1f','average':df_median.bacon.median(),'a_d_good':True,'tab_name':'BACON'},
63
+ 'xbacon':{'format':':.1f','average':df_median.xbacon.median(),'a_d_good':True,'tab_name':'xBACON'},
64
+ 'xbadiff':{'format':':.3f','average':df_median.xbadiff.median(),'a_d_good':True,'tab_name':'BA-xBA'},
65
+ 'xslgdiff':{'format':':.3f','average':df_median.xslgdiff.median(),'a_d_good':True,'tab_name':'SLG-xSLG'},
66
+ 'wobadiff':{'format':':.3f','average':df_median.wobadiff.median(),'a_d_good':True,'tab_name':'wOBA-xwOBA'},
67
+ 'exit_velocity_avg':{'format':':.1f','average':(df.exit_velocity_avg * df.batted_ball).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'EV'},
68
+ 'launch_angle_avg':{'format':':.1f','average':(df.launch_angle_avg * df.batted_ball).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'LA'},
69
+ 'barrel':{'format':':.0f','average':df_median.barrel.median(),'a_d_good':True,'tab_name':'Barrel'},
70
+ 'barrel_batted_rate':{'format':':.1%','average':(df.barrel).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'Barrel%'},
71
+ 'avg_best_speed':{'format':':.1f','average':(df.avg_best_speed * df.batted_ball).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'Best Speed'},
72
+ 'avg_hyper_speed':{'format':':.1f','average':(df.avg_hyper_speed * df.batted_ball).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'Hyper Speed'},
73
+ 'out_zone_swing_miss':{'format':':.0f','average':df_median.out_zone_swing_miss.mean(),'a_d_good':True,'tab_name':'O-Whiff%'},
74
+ 'out_zone_swing':{'format':':.0f','average':df_median.out_zone_swing.mean(),'a_d_good':True,'tab_name':'O-Swing'},
75
+ 'out_zone':{'format':':.0f','average':df_median.out_zone.mean(),'a_d_good':True,'tab_name':'O-Zone'},
76
+ 'pitch_count_offspeed':{'format':':.0f','average':df_median.pitch_count_offspeed.mean(),'a_d_good':True,'tab_name':'Pitch Off-Speed'},
77
+ 'pitch_count_fastball':{'format':':.0f','average':df_median.pitch_count_fastball.mean(),'a_d_good':True,'tab_name':'Pitch Fastball'},
78
+ 'pitch_count_breaking':{'format':':.0f','average':df_median.pitch_count_breaking.mean(),'a_d_good':True,'tab_name':'Pitch Breaking'},
79
+ 'pitch_count':{'format':':.0f','average':df_median.pitch_count.mean(),'a_d_good':True,'tab_name':'Pitches'},
80
+ 'in_zone_swing_miss':{'format':':.0f','average':df_median.in_zone_swing_miss.mean(),'a_d_good':False,'tab_name':'Z-Whiff'},
81
+ 'in_zone_swing':{'format':':.0f','average':df_median.in_zone_swing.mean(),'a_d_good':True,'tab_name':'Z-Swing'},
82
+ 'in_zone':{'format':':.0f','average':df_median.in_zone.mean(),'a_d_good':True,'tab_name':'Zone'},
83
+ 'edge':{'format':':.0f','average':df_median.edge.mean(),'a_d_good':True,'tab_name':'Edge'},
84
+ 'batted_ball':{'format':':.0f','average':df_median.batted_ball.mean(),'a_d_good':True,'tab_name':'Batted Balls'},
85
+ 'groundballs':{'format':':.0f','average':df_median.groundballs.mean(),'a_d_good':True,'tab_name':'Groundballs'},
86
+ 'flyballs':{'format':':.0f','average':df_median.flyballs.mean(),'a_d_good':True,'tab_name':'Flyballs'},
87
+ 'linedrives':{'format':':.0f','average':df_median.linedrives.mean(),'a_d_good':True,'tab_name':'Linedrives'},
88
+ 'popups':{'format':':.0f','average':df_median.popups.mean(),'a_d_good':True,'tab_name':'Popups'},
89
+ 'n_bolts':{'format':':.0f','average':df_median.n_bolts.mean(),'a_d_good':True,'tab_name':'Bolts'},
90
+ 'hp_to_1b':{'format':':.2f','average':df_median.hp_to_1b.mean(),'a_d_good':True,'tab_name':'Home Plate to 1st'},
91
+ 'sprint_speed':{'format':':.1f','average':df_median.sprint_speed.mean(),'a_d_good':True,'tab_name':'Sprint Speed'},
92
+
93
+ 'slg_percent':{'format':':.1%','average':(df.single.sum() + df.double.sum()*2 + df.triple.sum()*3 + df.home_run.sum()*4)/df.ab.sum(),'a_d_good':True,'tab_name':'SLG'},
94
+ 'on_base_percent':{'format':':.1%','average':df_median.on_base_percent.median(),'a_d_good':True,'tab_name':'OBP'},
95
+ 'sweet_spot_percent':{'format':':.1%','average':df_median.sweet_spot_percent.median(),'a_d_good':True,'tab_name':'SweetSpot%'},
96
+ 'solidcontact_percent':{'format':':.1%','average':df_median.solidcontact_percent.median(),'a_d_good':True,'tab_name':'Solid%'},
97
+ 'flareburner_percent':{'format':':.1%','average':df_median.flareburner_percent.median(),'a_d_good':False,'tab_name':'Flare/Burner%'},
98
+ 'poorlyunder_percent':{'format':':.1%','average':df_median.poorlyunder_percent.median(),'a_d_good':False,'tab_name':'Under%'},
99
+ 'poorlytopped_percent':{'format':':.1%','average':df_median.poorlytopped_percent.median(),'a_d_good':False,'tab_name':'Topped%'},
100
+ 'poorlyweak_percent':{'format':':.1%','average':df_median.poorlyweak_percent.median(),'a_d_good':False,'tab_name':'Weak%'},
101
+ 'hard_hit_percent':{'format':':.1%','average':df_median.hard_hit_percent.median(),'a_d_good':True,'tab_name':'HardHit%'},
102
+ 'z_swing_percent':{'format':':.1%','average':df.in_zone_swing.sum()/df.in_zone.sum(),'a_d_good':True,'tab_name':'Z-Swing%'},
103
+ 'z_swing_miss_percent':{'format':':.1%','average':df.in_zone_swing_miss.sum()/df.in_zone_swing.sum(),'a_d_good':False,'tab_name':'Z-Whiff%'},
104
+
105
+ 'out_zone_percent':{'format':':.1%','average':df.out_zone.sum()/df.pitch_count.sum(),'a_d_good':True,'tab_name':'O-Zone%'},
106
+ 'meatball_swing_percent':{'format':':.1%','average':df_median.meatball_swing_percent.median(),'a_d_good':True,'tab_name':'Meatball Swing%'},
107
+ 'meatball_percent':{'format':':.1%','average':df_median.meatball_percent.median(),'a_d_good':True,'tab_name':'Meatball%'},
108
+ 'iz_contact_percent':{'format':':.1%','average':1 - df.in_zone_swing_miss.sum()/df.in_zone_swing.sum(),'a_d_good':True,'tab_name':'Z-Contact%'},
109
+ 'in_zone_percent':{'format':':.1%','average':df.in_zone.mean()/df.pitch_count.sum(),'a_d_good':True,'tab_name':'Zone%'},
110
+ 'oz_swing_percent':{'format':':.1%','average':df.out_zone_swing.sum()/df.out_zone.sum(),'a_d_good':False,'tab_name':'O-Swing%'},
111
+ 'oz_swing_miss_percent':{'format':':.1%','average':df.out_zone_swing_miss.sum()/df.out_zone_swing.sum(),'a_d_good':False,'tab_name':'O-Whiff%'},
112
+ 'oz_contact_percent':{'format':':.1%','average':1 - df.out_zone_swing_miss.sum()/df.out_zone_swing.sum(),'a_d_good':True,'tab_name':'O-Contact%'},
113
+ 'edge_percent':{'format':':.1%','average':df_median.edge_percent.median(),'a_d_good':True,'tab_name':'Edge%'},
114
+ 'whiff_percent':{'format':':.1%','average':(df.in_zone_swing_miss.sum() + df.out_zone_swing_miss.sum()) / (df.in_zone_swing.sum() + df.out_zone_swing.sum()),'a_d_good':False,'tab_name':'Whiff%'},
115
+ 'swstr_percent':{'format':':.1%','average':(df.in_zone_swing_miss.sum() + df.out_zone_swing_miss.sum()) / (df.pitch_count.sum()),'a_d_good':False,'tab_name':'SwStr%'},
116
+ 'swing_percent':{'format':':.1%','average':(df.in_zone_swing.sum() + df.out_zone_swing.sum()) / (df.pitch_count.sum()),'a_d_good':True,'tab_name':'Swing%'},
117
+ 'pull_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Pull%'},
118
+ 'straightaway_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Straightaway%'},
119
+ 'opposite_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Opposite%'},
120
+ 'f_strike_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':False,'tab_name':'1st Strike%'},
121
+ 'groundballs_percent':{'format':':.1%','average':df.groundballs.sum()/df.batted_ball.sum(),'a_d_good':False,'tab_name':'GB%'},
122
+ 'flyballs_percent':{'format':':.1%','average':df.flyballs.sum()/df.batted_ball.sum(),'a_d_good':True,'tab_name':'FB%'},
123
+ 'linedrives_percent':{'format':':.1%','average':df.linedrives.sum()/df.batted_ball.sum(),'a_d_good':True,'tab_name':'LD%'},
124
+ 'popups_percent':{'format':':.1%','average':df.popups.sum()/df.batted_ball.sum(),'a_d_good':False,'tab_name':'PU%'},}
125
+
126
+ column_dict = pd.DataFrame(format_dict.keys(),[format_dict[x]['tab_name'] for x in format_dict.keys()]).reset_index().set_index(0).to_dict()['index']
127
+
128
+
129
+ def server(input,output,session):
130
+
131
+
132
+ @output
133
+ @render.text
134
+ @reactive.event(input.go, ignore_none=False)
135
+ def txt_title():
136
+ if input.player_id() == '':
137
+ return 'Select a Player'
138
+
139
+ player_input = int(input.player_id())
140
+ season_1 = max(2015,int(input.season_1()))
141
+ season_2 = min(2023,int(input.season_2()))
142
+
143
+ if season_1 < season_2:
144
+ season_pick = [season_1,season_2]
145
+
146
+ elif season_1 > season_2:
147
+ season_pick = [season_2,season_1]
148
+
149
+ if len(str(input.player_id())) == 0:
150
+ return 'Select a Batter'
151
+ if str(input.season_1()) == str(input.season_2()):
152
+ return 'Select Different Seasons'
153
+ if len(df[(df.player_id == player_input)&(df.year == season_pick[0])] )== 0 or len(df[(df.player_id == player_input)&(df.year == season_pick[1])] )== 0:
154
+ return 'Select Different Seasons'
155
+ return f'{player_dict[int(input.player_id())]} Statcast Season Comparison'
156
+
157
+ @output
158
+ @render.text
159
+ @reactive.event(input.go, ignore_none=False)
160
+ def txt_title_compare():
161
+ if type(input.player_id()) is int or input.player_id()=='':
162
+ return
163
+
164
+ if type(input.player_id_2()) is int or input.player_id_2()=='':
165
+ return
166
+
167
+ player_input_1 = int(input.player_id())
168
+ player_input_2 = int(input.player_id_2())
169
+
170
+
171
+ # season_pick = [input.season_1(),input.season_2()]
172
+ columns_i_want = list(input.row_select())
173
+ print(columns_i_want)
174
+ season_1 = max(2015,int(input.season_1()))
175
+ season_2 = min(2023,int(input.season_2()))
176
+
177
+ player_list = [player_input_1,player_input_2]
178
+ name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]
179
+ season_pick_list = [season_1,season_2]
180
+
181
+ if len(str(input.player_id())) == 0:
182
+ return 'Select a Batter'
183
+ if len(str(input.player_id_2())) == 0:
184
+ return 'Select a Batter'
185
+
186
+ if str(input.player_id()) == str(input.player_id_2()) and str(input.season_1()) == str(input.season_2()):
187
+ return 'Select Different Seasons'
188
+ if len(df[(df.player_id == player_list[0])&(df.year == season_pick_list[0])] )== 0 or len(df[(df.player_id == player_list[1])&(df.year == season_pick_list[1])])== 0:
189
+ return 'No Data for Specified Batter in Given Season'
190
+
191
+ return f'Statcast Season Comparison'
192
+
193
+
194
+
195
+
196
+ @output
197
+ @render.text
198
+ @reactive.event(input.go, ignore_none=False)
199
+ def text_2022():
200
+ if input.player_id() == '':
201
+ return 'Select a Player'
202
+ return f'{int(input.season_1())} Season Results Compares to MLB Average'
203
+
204
+
205
+ @output
206
+ @render.text
207
+ @reactive.event(input.go, ignore_none=False)
208
+ def text_2022_1():
209
+ if type(input.player_id()) is int or input.player_id()=='':
210
+ return
211
+
212
+ if type(input.player_id_2()) is int or input.player_id_2()=='':
213
+ return
214
+ season_1 = max(2015,int(input.season_1()))
215
+ season_2 = min(2023,int(input.season_2()))
216
+ season_pick_list = [season_1,season_2]
217
+ player_input_1 = int(input.player_id())
218
+ player_input_2 = int(input.player_id_2())
219
+ name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]
220
+ return f"{name_list[0]} '{str(season_pick_list[0])[2:]} Season Results Compares to MLB Average"
221
+
222
+ @output
223
+ @render.text
224
+ @reactive.event(input.go, ignore_none=False)
225
+ def text_2023():
226
+ if input.player_id() == '':
227
+ return 'Select a Player'
228
+ return f'{int(input.season_2())} Season Results Compares to MLB Average'
229
+
230
+ @output
231
+ @render.text
232
+ @reactive.event(input.go, ignore_none=False)
233
+ def text_2023_1():
234
+ if type(input.player_id()) is int or input.player_id()=='':
235
+ return
236
+
237
+ if type(input.player_id_2()) is int or input.player_id_2()=='':
238
+ return
239
+ season_1 = max(2015,int(input.season_1()))
240
+ season_2 = min(2023,int(input.season_2()))
241
+ season_pick_list = [season_1,season_2]
242
+ player_input_1 = int(input.player_id())
243
+ player_input_2 = int(input.player_id_2())
244
+ name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]
245
+ return f"{name_list[1]} '{str(season_pick_list[1])[2:]} Season Results Compares to MLB Average"
246
+
247
+ @output
248
+ @render.text
249
+ @reactive.event(input.go, ignore_none=False)
250
+ def text_diff():
251
+ if input.player_id() == '':
252
+ return 'Select a Player'
253
+ return f'Difference Compares {int(input.season_2())} Results to {int(input.season_1())} Results'
254
+
255
+ @output
256
+ @render.text
257
+ @reactive.event(input.go, ignore_none=False)
258
+ def text_diff_compare():
259
+ if type(input.player_id()) is int or input.player_id()=='':
260
+ return
261
+
262
+ if type(input.player_id_2()) is int or input.player_id_2()=='':
263
+ return
264
+ player_input_1 = int(input.player_id())
265
+ player_input_2 = int(input.player_id_2())
266
+ name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]
267
+ return f'Difference Compares {name_list[0]} Results to {name_list[1]} Results'
268
+
269
+
270
+
271
+ @output
272
+ @render.table
273
+ @reactive.event(input.go, ignore_none=False)
274
+ def statcast_compare():
275
+ if input.player_id() == '':
276
+ return
277
+
278
+ if len(str(input.player_id())) == 0:
279
+ return
280
+ if len(str(input.player_id_2())) == 0:
281
+ return
282
+
283
+
284
+ player_input = int(input.player_id())
285
+
286
+
287
+ # season_pick = [input.season_1(),input.season_2()]
288
+ columns_i_want = list(input.row_select())
289
+ print(columns_i_want)
290
+ season_1 = max(2015,int(input.season_1()))
291
+ season_2 = min(2023,int(input.season_2()))
292
+
293
+
294
+ if season_1 < season_2:
295
+ season_pick = [season_1,season_2]
296
+
297
+ elif season_1 > season_2:
298
+ season_pick = [season_2,season_1]
299
+
300
+ else:
301
+ return
302
+
303
+ print(df[(df.player_id == player_input)&(df.year == season_pick[0])])
304
+
305
+
306
+ if len(df[(df.player_id == player_input)&(df.year == season_pick[0])] )== 0 or len(df[(df.player_id == player_input)&(df.year == season_pick[1])] )== 0:
307
+ return
308
+
309
+ df_compare = pd.concat([df[(df.player_id == player_input)&(df.year == season_pick[0])][[ 'player_age', 'pa']+columns_i_want],
310
+ df[(df.player_id == player_input)&(df.year == season_pick[1])][[ 'player_age', 'pa']+columns_i_want]]).reset_index(drop=True).T
311
+
312
+
313
+ print('test')
314
+ print(sum(df.player_id == input.player_id()))
315
+ df_compare.columns = season_pick
316
+
317
+ df_compare['Difference'] = df_compare.loc[columns_i_want][season_pick[1]] - df_compare.loc[columns_i_want][season_pick[0]]
318
+
319
+ df_compare_style = df_compare.style.format(
320
+ "{:.0f}")
321
+ df_compare_style = df_compare_style.set_properties(**{'background-color': 'white',
322
+ 'color': 'white'},subset=(['player_age', 'pa'],df_compare_style.columns[2])).set_properties(
323
+ **{'min-width':'100px'},overwrite=False).set_table_styles(
324
+ [{'selector': 'th:first-child', 'text-align': 'center','props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
325
+ [{'selector': 'tr:first-child','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
326
+ [{'selector': 'index','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
327
+ [{'selector': 'th', 'text-align': 'center','props': [('line-height', '40px'),('min-width', '30px')]}],overwrite=False).set_properties(
328
+
329
+ **{'Height': '20px'},**{'text-align': 'center'},overwrite=False).set_table_styles([{
330
+ 'selector': 'caption',
331
+ 'props': [
332
+ ('color', ''),
333
+ ('fontname', 'Century Gothic'),
334
+ ('font-size', '20px'),
335
+ ('font-style', 'italic'),
336
+ ('font-weight', ''),
337
+ ('text-align', 'centre'),
338
+ ]
339
+
340
+ },{'selector' :'th', 'props':[('text-align', 'center'),('font-size', '20px'),('Height','20px'),('min-width','200px')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '20px'),('min-width','100px')]}],overwrite=False)
341
+
342
+
343
+ for r in columns_i_want:
344
+ if format_dict[r]['a_d_good']:
345
+ cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"])
346
+ else:
347
+ cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#FBBC04","white","#4285F4"])
348
+
349
+
350
+ colormap = plt.get_cmap(cmap)
351
+ norm = Normalize(vmin=0.7, vmax=1.3)
352
+
353
+ normalized_value = norm(df_compare[df_compare.columns[0]][r]/format_dict[r]['average'])
354
+ df_compare_style.format(
355
+ f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[0])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),
356
+ 'color': 'black'},subset=(r,df_compare_style.columns[0]))
357
+
358
+ norm = Normalize(vmin=0.7, vmax=1.3)
359
+ normalized_value = norm(df_compare[df_compare.columns[1]][r]/format_dict[r]['average'])
360
+ df_compare_style.format(
361
+ f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[1])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),
362
+ 'color': 'black'},subset=(r,df_compare_style.columns[1]))
363
+
364
+ norm = Normalize(vmin=0.7, vmax=1.3)
365
+ normalized_value = norm(df_compare[df_compare.columns[1]][r]/df_compare[df_compare.columns[0]][r])
366
+ df_compare_style.format(
367
+ f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[2])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),
368
+ 'color': 'black'},subset=(r,df_compare_style.columns[2]))
369
+
370
+
371
+
372
+
373
+ df_compare_style.relabel_index(['Age', 'PA']+[format_dict[x]['tab_name'] for x in columns_i_want]).set_properties(
374
+ **{'border': '1px black solid !important'},overwrite=False).set_table_styles(
375
+ [{"selector": "", "props": [("border", "1px solid")]},
376
+ {"selector": "tbody td", "props": [("border", "1px solid")]},
377
+ {"selector": "th", "props": [("border", "1px solid")]}],overwrite=False)
378
+
379
+ #df_compare = df_compare.fillna(np.nan)
380
+
381
+ return df_compare_style
382
+
383
+
384
+
385
+ @output
386
+ @render.table
387
+ @reactive.event(input.go, ignore_none=False)
388
+ def statcast_compare_2():
389
+
390
+ # if input.player_id() == 0:
391
+ # return
392
+ if type(input.player_id()) is int or input.player_id()=='':
393
+ return
394
+
395
+ if type(input.player_id_2()) is int or input.player_id_2()=='':
396
+ return
397
+ # if len(str(input.player_id())) == 0:
398
+ # return
399
+
400
+ player_input_1 = int(input.player_id())
401
+ player_input_2 = int(input.player_id_2())
402
+
403
+
404
+ # season_pick = [input.season_1(),input.season_2()]
405
+ columns_i_want = list(input.row_select())
406
+ print(columns_i_want)
407
+ season_1 = max(2015,int(input.season_1()))
408
+ season_2 = min(2023,int(input.season_2()))
409
+
410
+
411
+ # if season_1 < season_2:
412
+ # season_pick = [season_1,season_2]
413
+
414
+ # elif season_1 > season_2:
415
+ # season_pick = [season_2,season_1]
416
+
417
+ # else:
418
+ # return
419
+
420
+ #print(df[(df.player_id == player_input)&(df.year == season_pick[0])])
421
+ #player_list = ['Elly De La Cruz','Aaron Judge']
422
+ player_list = [player_input_1,player_input_2]
423
+ name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]
424
+ season_pick_list = [season_1,season_2]
425
+
426
+ if len(df[(df.player_id == player_list[0])&(df.year == season_pick_list[0])] )== 0 or len(df[(df.player_id == player_list[1])&(df.year == season_pick_list[1])] )== 0:
427
+ return
428
+ if str(input.player_id()) == str(input.player_id_2()) and str(input.season_1()) == str(input.season_2()):
429
+ return
430
+
431
+
432
+ df_compare = pd.concat([df[(df.player_id == player_list[0])&(df.year == season_pick_list[0])][[ 'year','player_age', 'pa']+columns_i_want],
433
+ df[(df.player_id == player_list[1])&(df.year == season_pick_list[1])][[ 'year','player_age', 'pa']+columns_i_want]]).reset_index(drop=True).T
434
+
435
+ df_compare.columns = [f"{name_list[0]} '{str(season_pick_list[0])[2:]}",f"{name_list[1]} '{str(season_pick_list[1])[2:]}"]
436
+ df_compare['Difference'] = df_compare.loc[columns_i_want][df_compare.columns [0]] - df_compare.loc[columns_i_want][df_compare.columns[1]]
437
+ #df_compare = df_compare.fillna(np.nan)
438
+
439
+ df_compare_style = df_compare.style.format(
440
+ "{:.0f}")
441
+ df_compare_style = df_compare_style.set_properties(**{'background-color': 'white',
442
+ 'color': 'white'},subset=(['year','player_age', 'pa'],df_compare_style.columns[2])).set_properties(
443
+ **{'min-width':'125px'},overwrite=False).set_table_styles(
444
+ [{'selector': 'th:first-child', 'text-align': 'center','props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
445
+ [{'selector': 'tr:first-child','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
446
+ [{'selector': 'index','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
447
+ [{'selector': 'th', 'text-align': 'center','props': [('line-height', '40px'),('min-width', '30px')]}],overwrite=False).set_properties(
448
+
449
+ **{'Height': '20px'},**{'text-align': 'center'},overwrite=False).set_table_styles([{
450
+ 'selector': 'caption',
451
+ 'props': [
452
+ ('color', ''),
453
+ ('fontname', 'Century Gothic'),
454
+ ('font-size', '20px'),
455
+ ('font-style', 'italic'),
456
+ ('font-weight', ''),
457
+ ('text-align', 'centre'),
458
+ ]
459
+
460
+ },{'selector' :'th', 'props':[('text-align', 'center'),('font-size', '20px'),('Height','20px'),('min-width','200px')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '20px'),('min-width','100px')]}],overwrite=False)
461
+
462
+
463
+ for r in columns_i_want:
464
+ if format_dict[r]['a_d_good']:
465
+ cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"])
466
+ else:
467
+ cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#FBBC04","white","#4285F4"])
468
+
469
+
470
+ colormap = plt.get_cmap(cmap)
471
+ norm = Normalize(vmin=0.5, vmax=1.5)
472
+ normalized_value = norm(df_compare[df_compare.columns[0]][r]/format_dict[r]['average'])
473
+ df_compare_style.format(
474
+ f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[0])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),
475
+ 'color': 'black'},subset=(r,df_compare_style.columns[0]))
476
+
477
+ norm = Normalize(vmin=0.5, vmax=1.5)
478
+ normalized_value = norm(df_compare[df_compare.columns[1]][r]/format_dict[r]['average'])
479
+ df_compare_style.format(
480
+ f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[1])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),
481
+ 'color': 'black'},subset=(r,df_compare_style.columns[1]))
482
+
483
+ norm = Normalize(vmin=0.8, vmax=1.2)
484
+ normalized_value = norm(df_compare[df_compare.columns[0]][r]/df_compare[df_compare.columns[1]][r])
485
+ df_compare_style.format(
486
+ f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[2])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),
487
+ 'color': 'black'},subset=(r,df_compare_style.columns[2]))
488
+
489
+ df_compare_style = df_compare_style
490
+
491
+
492
+ df_compare_style.relabel_index(['Year','Age', 'PA']+[format_dict[x]['tab_name'] for x in columns_i_want]).set_properties(
493
+ **{'border': '1px black solid !important'},overwrite=False).set_table_styles(
494
+ [{"selector": "", "props": [("border", "1px solid")]},
495
+ {"selector": "tbody td", "props": [("border", "1px solid")]},
496
+ {"selector": "th", "props": [("border", "1px solid")]}],overwrite=False)
497
+
498
+ #df_compare = df_compare.fillna(np.nan)
499
+
500
+ return df_compare_style
501
+
502
+ @output
503
+ @render.table
504
+ @reactive.event(input.go, ignore_none=False)
505
+ def colour_scale():
506
+ off_b2b_df = pd.DataFrame(data={'one':-0.30,'two':0,'three':0.30},index=[0])
507
+ off_b2b_df_style = off_b2b_df.style.set_properties(**{'border': '3 px'},overwrite=False).set_table_styles([{
508
+ 'selector': 'caption',
509
+ 'props': [
510
+ ('color', ''),
511
+ ('fontname', 'Century Gothic'),
512
+ ('font-size', '20px'),
513
+ ('font-style', 'italic'),
514
+ ('font-weight', ''),
515
+ ('text-align', 'centre'),
516
+ ]
517
+
518
+ },{'selector' :'th', 'props':[('text-align', 'center'),('Height','px'),('color','black'),(
519
+ 'border', '1px black solid !important')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '18px'),('color','black')]}],overwrite=False).set_properties(
520
+ **{'background-color':'White','index':'White','min-width':'150px'},overwrite=False).set_table_styles(
521
+ [{'selector': 'th:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
522
+ [{'selector': 'tr:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
523
+ [{'selector': 'tr', 'props': [('line-height', '20px')]}],overwrite=False).set_properties(
524
+ **{'Height': '8px'},**{'text-align': 'center'},overwrite=False).set_properties(
525
+ **{'background-color':'#4285F4'},subset=off_b2b_df.columns[0]).set_properties(
526
+ **{'background-color':'white'},subset=off_b2b_df.columns[1]).set_properties(
527
+ **{'background-color':'#FBBC04'},subset=off_b2b_df.columns[2]).set_properties(
528
+ **{'color':'black'},subset=off_b2b_df.columns[:]).hide_index().set_table_styles([
529
+ {'selector': 'thead', 'props': [('display', 'none')]}
530
+ ]).set_properties(**{'border': '3 px','color':'black'},overwrite=False).set_properties(
531
+ **{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))).set_properties(
532
+ **{'min-width':'130'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:])),overwrite=False).set_properties(**{
533
+ 'color': 'black'},overwrite=False).set_properties(
534
+ **{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))) .format(
535
+ "{:+.0%}")
536
+ return off_b2b_df_style
537
+
538
+ @output
539
+ @render.table
540
+ @reactive.event(input.go, ignore_none=False)
541
+ def colour_scale_2():
542
+ off_b2b_df = pd.DataFrame(data={'one':-0.30,'two':0,'three':0.30},index=[0])
543
+ off_b2b_df_style = off_b2b_df.style.set_properties(**{'border': '3 px'},overwrite=False).set_table_styles([{
544
+ 'selector': 'caption',
545
+ 'props': [
546
+ ('color', ''),
547
+ ('fontname', 'Century Gothic'),
548
+ ('font-size', '20px'),
549
+ ('font-style', 'italic'),
550
+ ('font-weight', ''),
551
+ ('text-align', 'centre'),
552
+ ]
553
+
554
+ },{'selector' :'th', 'props':[('text-align', 'center'),('Height','px'),('color','black'),(
555
+ 'border', '1px black solid !important')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '18px'),('color','black')]}],overwrite=False).set_properties(
556
+ **{'background-color':'White','index':'White','min-width':'150px'},overwrite=False).set_table_styles(
557
+ [{'selector': 'th:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
558
+ [{'selector': 'tr:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(
559
+ [{'selector': 'tr', 'props': [('line-height', '20px')]}],overwrite=False).set_properties(
560
+ **{'Height': '8px'},**{'text-align': 'center'},overwrite=False).set_properties(
561
+ **{'background-color':'#4285F4'},subset=off_b2b_df.columns[0]).set_properties(
562
+ **{'background-color':'white'},subset=off_b2b_df.columns[1]).set_properties(
563
+ **{'background-color':'#FBBC04'},subset=off_b2b_df.columns[2]).set_properties(
564
+ **{'color':'black'},subset=off_b2b_df.columns[:]).hide_index().set_table_styles([
565
+ {'selector': 'thead', 'props': [('display', 'none')]}
566
+ ]).set_properties(**{'border': '3 px','color':'black'},overwrite=False).set_properties(
567
+ **{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))).set_properties(
568
+ **{'min-width':'130'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:])),overwrite=False).set_properties(**{
569
+ 'color': 'black'},overwrite=False).set_properties(
570
+ **{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))) .format(
571
+ "{:+.0%}")
572
+
573
+
574
+ return off_b2b_df_style
575
+ # test = test.fillna(0)
576
+ #test['PP TOI'] = ["%d:%02d" % (int(x),(x*60)%60) if x>0 else '0:00' for x in test['PP TOI']]
577
+
578
+
579
+ statcast_compare = App(ui.page_fluid(
580
+ ui.tags.base(href=base_url),
581
+ ui.tags.div(
582
+ {"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
583
+ ui.tags.style(
584
+ """
585
+ h4 {
586
+ margin-top: 1em;font-size:35px;
587
+ }
588
+ h2{
589
+ font-size:25px;
590
+ }
591
+ """
592
+ ),
593
+ shinyswatch.theme.simplex(),
594
+ ui.tags.h4("TJStats"),
595
+ ui.tags.i("Baseball Analytics and Visualizations"),
596
+ ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
597
+ ui.navset_tab(
598
+ ui.nav_control(
599
+ ui.a(
600
+ "Home",
601
+ href="home/"
602
+ ),
603
+ ),
604
+ ui.nav_menu(
605
+ "Batter Charts",
606
+ ui.nav_control(
607
+ ui.a(
608
+ "Batting Rolling",
609
+ href="rolling_batter/"
610
+ ),
611
+ ui.a(
612
+ "Spray",
613
+ href="spray/"
614
+ ),
615
+ ui.a(
616
+ "Decision Value",
617
+ href="decision_value/"
618
+ ),
619
+ ui.a(
620
+ "Damage Model",
621
+ href="damage_model/"
622
+ ),
623
+ ui.a(
624
+ "Batter Scatter",
625
+ href="batter_scatter/"
626
+ ),
627
+ ui.a(
628
+ "EV vs LA Plot",
629
+ href="ev_angle/"
630
+ ),
631
+ ui.a(
632
+ "Statcast Compare",
633
+ href="statcast_compare/"
634
+ )
635
+ ),
636
+ ),
637
+ ui.nav_menu(
638
+ "Pitcher Charts",
639
+ ui.nav_control(
640
+ ui.a(
641
+ "Pitcher Rolling",
642
+ href="rolling_pitcher/"
643
+ ),
644
+ ui.a(
645
+ "Pitcher Summary",
646
+ href="pitching_summary_graphic_new/"
647
+ ),
648
+ ui.a(
649
+ "Pitcher Scatter",
650
+ href="pitcher_scatter/"
651
+ )
652
+ ),
653
+ )),ui.row(
654
+ ui.layout_sidebar(
655
+
656
+
657
+
658
+ ui.panel_sidebar(
659
+ #ui.input_date_range("date_range_id", "Date range input",start = statcast_df.game_date.min(), end = statcast_df.game_date.max()),
660
+ ui.input_select("player_id", "Select Player 1",player_dict,width=1,size=1,selectize=True,multiple=False,selected=592450),
661
+ ui.input_select("player_id_2", "Select Player 2 (For Player Compare Tab)",player_dict,width=1,size=1,selectize=True,multiple=False,selected=592450),
662
+ ui.input_numeric("season_1", "Season 1", value=2022,min=2015,max=2023),
663
+ ui.input_numeric("season_2", "Season 2", value=2023,min=2015,max=2023),
664
+ ui.input_select("row_select", "Select Stats",
665
+ column_dict,width=1,size=1,selectize=True,
666
+ multiple=True,
667
+ selected=['k_percent','bb_percent','woba','xwoba','iz_contact_percent','oz_swing_percent','whiff_percent']),
668
+ ui.input_action_button("go", "Generate",class_="btn-primary",
669
+ )),
670
+
671
+ ui.panel_main(ui.tags.h3(""),
672
+ ui.navset_tab(
673
+ ui.nav("Single Player",
674
+ ui.div({"style": "font-size:2.1em;"},ui.output_text("txt_title")),
675
+ #ui.tags.h2("Fantasy Hockey Schedule Summary"),
676
+ ui.tags.h5("Created By: @TJStats, Data: MLB"),
677
+ #ui.div({"style": "font-size:1.6em;"},ui.output_text("txt")),
678
+ ui.output_table("statcast_compare"),
679
+ #ui.tags.h5('Legend'),
680
+ ui.tags.h3(""),
681
+ ui.tags.h5('Colour Scale:'),
682
+ ui.output_table("colour_scale"),
683
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_2022")),
684
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_2023")),
685
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_diff"))),
686
+
687
+ ui.nav("Player Compare",
688
+ ui.div({"style": "font-size:2.1em;"},ui.output_text("txt_title_compare")),
689
+ #ui.tags.h2("Fantasy Hockey Schedule Summary"),
690
+ ui.tags.h5("Created By: @TJStats, Data: MLB"),
691
+ #ui.div({"style": "font-size:1.6em;"},ui.output_text("txt")),
692
+ ui.output_table("statcast_compare_2"),
693
+ ui.tags.h3(""),
694
+ ui.tags.h5('Colour Scale:'),
695
+ ui.output_table("colour_scale_2"),
696
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_2022_1")),
697
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_2023_1")),
698
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_diff_compare")))
699
+ )
700
+ ),
701
+ )),)),server)
statcast_pitch_summary.csv ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ,pitch_name,pitches,swings,whiff,csw,out_zone,chase,pitch_velocity,spin_rate,exit_velocity,pitch_velocity_std,spin_rate_std,exit_velocity_std,pfx_x,pfx_z,extension,release_x,release_z,zone_percent,xwOBA,pitch,whiff_rate,csw_rate,chase_percent,pitch_percent
2
+ 0,4-Seam Fastball,230929.0,111299.0,24750.0,64996.0,105087,25567.0,94.19093625756828,2282.8438012609377,83.74365680792623,2.4786532395740055,166.7404996241093,13.467960960835905,-0.2653508555392431,1.3087501028552622,6.512675018103453,-0.8060702360690668,5.833463472307033,0.5448427871770111,0.40198316316909566,,0.2223739656241296,0.28145447301984594,0.243293651926499,0.16085685746486877
3
+ 1,Slider,126071.0,60687.0,20453.0,39120.0,69402,21991.0,85.11885798600645,2408.7220697139937,81.64673187847214,3.3273820646525754,254.3806272913541,15.94645745407159,0.25503050110660713,0.1340754872641023,6.415392251066094,-0.9940703310302156,5.742570977542618,0.4494213578063155,0.36039362273548514,,0.3370244039085801,0.3103013381348605,0.31686406731794475,0.08781653615376793
4
+ 2,Sinker,110881.0,50193.0,7235.0,31514.0,49458,12493.0,93.31904199285688,2150.2386523758873,83.87984289276808,2.892717921060944,168.33013944189094,15.447823735442787,-0.5781807359307359,0.6536634199134199,6.419496957697072,-0.8578973665223666,5.636833874458874,0.5539452205517627,0.3630746667835847,,0.14414360568206722,0.28421460845410845,0.25259816409883135,0.07723572705273965
5
+ 3,Changeup,78512.0,39518.0,12295.0,19910.0,47870,16185.0,85.45176986078029,1789.468468007219,80.71900963512873,3.4111014745537154,305.36591130209456,16.47019099576866,-0.396463507833397,0.474831486434849,6.458975699314073,-0.568952999617883,5.749977200356643,0.3902588139392704,0.34566906762705085,,0.3111240447391062,0.2535918076217648,0.3381031961562565,0.054688642800522144
6
+ 4,Cutter,55805.0,27665.0,6714.0,15683.0,27167,7768.0,89.22766029952471,2387.958273978037,82.31847715218278,3.2905648267822385,206.66655976707708,15.415902082385763,0.12278660592581965,0.639279718774661,6.36341704390314,-0.9624070952005166,5.842086232871798,0.5123017650748141,0.36965886615098403,,0.24268931863365262,0.28103216557656124,0.2859351419000994,0.03887176115094684
7
+ 5,Curveball,51679.0,21553.0,6701.0,16148.0,29175,8309.0,79.2795228051164,2529.729456384324,81.09545750923714,3.845559953202193,303.1060225022905,16.897464856909227,0.3128750556318814,-0.7793070686352289,6.357315891472868,-0.6464800015480177,5.931275179473287,0.4354573424408367,0.36297365675534693,,0.3109079942467406,0.31246734650438285,0.2847986289631534,0.03599773755971296
8
+ 6,Sweeper,30983.0,14387.0,4714.0,9472.0,17497,5501.0,81.87104541200013,2573.167409915042,79.5424205782126,2.9169456469962296,242.3612934233338,16.05341300254306,0.626698834844915,0.10297743924087403,6.440382997994436,-1.0498637962753767,5.449356421263274,0.4352709550398606,0.34346382040489076,,0.32765691249044276,0.3057160378271956,0.3143967537292107,0.021581646371109865
9
+ 7,Split-Finger,15570.0,8130.0,2828.0,3995.0,9884,3589.0,86.61461143224149,1356.4218921536972,81.58194705023973,3.210384887902199,330.0858364738183,17.118239986777326,-0.8700796403339756,0.2713461785484907,6.4252829218107,-1.7122678227360306,5.850094412331407,0.36518946692357096,0.3443354243542435,,0.34784747847478475,0.25658317276814385,0.36311210036422503,0.01084550346958592
10
+ 8,Knuckle Curve,12154.0,5439.0,1805.0,3752.0,7059,2292.0,81.43667105479678,2469.610794751176,81.99410356180783,3.3353055020455646,284.5354624498529,16.265551322730666,0.4980812901102518,-0.7952756294224125,6.3597479614529275,-1.0805693598815205,5.938246667763699,0.41920355438538753,0.37180873734683006,,0.33186247471961755,0.30870495310185947,0.3246918827029324,0.00846604040907818
11
+ 9,Slurve,2330.0,984.0,273.0,767.0,1262,365.0,82.12420600858368,2524.4065886432595,80.52035398230089,2.558741806283268,306.1556785170377,16.356798085102586,0.3707424892703863,-0.25004291845493565,6.026536796536797,-0.7101459227467811,5.605351931330472,0.45836909871244635,0.3978255208333333,,0.2774390243902439,0.32918454935622316,0.28922345483359746,0.00162299441773508
12
+ 10,Other,1099.0,553.0,39.0,192.0,576,161.0,66.9722474977252,1636.2980856882407,84.21832669322708,9.112324944344907,265.8499548102452,16.429985208785887,-0.4521565059144677,1.3244676979071883,4.820714940421631,-1.647452229299363,6.3448225659690625,0.47588717015468607,0.44125391849529777,,0.0705244122965642,0.17470427661510465,0.2795138888888889,0.0007655239764338425
13
+ 11,Forkball,778.0,358.0,199.0,247.0,590,214.0,82.96773778920308,1079.639686684073,77.80469798657718,1.5813830914720153,180.5812639240975,16.635375590080205,-0.6015809768637532,0.10609254498714654,6.465681233933163,-1.8326992287917736,5.97146529562982,0.2416452442159383,0.31482432432432433,,0.5558659217877095,0.31748071979434445,0.36271186440677966,0.0005419268914154044
14
+ 12,Eephus,522.0,226.0,7.0,79.0,339,92.0,47.99444444444444,1168.9176245210729,85.22054794520548,6.869976110853827,235.4947805488423,16.73137435394275,0.008371647509578542,1.393352490421456,4.532567049808429,-1.4747318007662835,6.651915708812261,0.3505747126436782,0.3907325581395349,,0.030973451327433628,0.15134099616858238,0.2713864306784661,0.00036360647470288056
15
+ 13,Knuckleball,190.0,81.0,20.0,44.0,97,18.0,75.68631578947368,337.4578947368421,79.05087719298245,4.960630411514611,393.10082691661756,16.785560576270584,-0.2866315789473684,-0.16442105263157897,6.280526315789474,-1.598,5.561368421052632,0.48947368421052634,0.3248333333333333,,0.24691358024691357,0.23157894736842105,0.18556701030927836,0.00013234718427882627
16
+ 14,Screwball,74.0,34.0,5.0,10.0,41,10.0,80.18648648648649,2094.9594594594596,83.2423076923077,1.450679984566931,133.61049905317344,13.026010074521608,-0.7537837837837837,-0.37148648648648647,6.20945945945946,-1.2633783783783783,6.0683783783783785,0.44594594594594594,0.40330000000000005,,0.14705882352941177,0.13513513513513514,0.24390243902439024,5.1545745455963914e-05
17
+ 15,Slow Curve,51.0,21.0,1.0,2.0,36,8.0,59.17450980392157,2058.7551020408164,77.07000000000001,11.825867293983224,429.4794101880903,17.29673227428872,0.06490196078431372,-0.7588235294117648,5.652941176470589,-0.72,6.276274509803921,0.29411764705882354,0.21123076923076922,,0.047619047619047616,0.0392156862745098,0.2222222222222222,3.5524770516948104e-05
18
+ 16,Pitch Out,46.0,0.0,0.0,0.0,46,0.0,90.01086956521739,2212.1304347826085,,2.9954950072006916,143.40565751983308,,-0.6036956521739131,1.2219565217391304,6.254347826086956,-1.803478260869565,5.74195652173913,0.0,,,,0.0,0.0,3.204194987803162e-05
19
+ 17,All,717944.0,341263.0,88076.0,206012.0,365586,104563.0,88.99871914935127,2249.1345844165853,82.5959800585916,6.109454214467076,339.2167730889254,15.224075051082256,-0.12493170194679414,0.5903365570869159,6.43911718239724,-0.8595453392605805,5.7720100196491035,0.4902945076496217,0.37195099345233773,1.0,0.25808833656153757,0.2869471713671261,0.28601478174765993,0.5000940361572508
summary_pitcher.csv ADDED
The diff for this file is too large to render. See raw diff
 
summary_pitcher_level.csv ADDED
The diff for this file is too large to render. See raw diff
 
test_data_mlb.ipynb ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import time\n",
10
+ "import requests\n",
11
+ "import pandas as pd\n",
12
+ "import seaborn as sns\n",
13
+ "import matplotlib.pyplot as plt\n",
14
+ "from matplotlib.pyplot import figure\n",
15
+ "from matplotlib.offsetbox import OffsetImage, AnnotationBbox\n",
16
+ "from scipy import stats\n",
17
+ "import matplotlib.lines as mlines\n",
18
+ "import matplotlib.transforms as mtransforms\n",
19
+ "import numpy as np\n",
20
+ "import time\n",
21
+ "#import plotly.express as px\n",
22
+ "#!pip install chart_studio\n",
23
+ "#import chart_studio.tools as tls\n",
24
+ "from bs4 import BeautifulSoup\n",
25
+ "import matplotlib.pyplot as plt\n",
26
+ "import numpy as np\n",
27
+ "import matplotlib.font_manager as font_manager\n",
28
+ "from datetime import datetime\n",
29
+ "import pytz\n",
30
+ "from matplotlib.ticker import MaxNLocator\n",
31
+ "from matplotlib.patches import Ellipse\n",
32
+ "import matplotlib.transforms as transforms\n",
33
+ "from matplotlib.gridspec import GridSpec\n",
34
+ "from datasets import load_dataset"
35
+ ]
36
+ },
37
+ {
38
+ "cell_type": "code",
39
+ "execution_count": 2,
40
+ "metadata": {},
41
+ "outputs": [
42
+ {
43
+ "name": "stdout",
44
+ "output_type": "stream",
45
+ "text": [
46
+ "Starting Everything:\n"
47
+ ]
48
+ },
49
+ {
50
+ "name": "stderr",
51
+ "output_type": "stream",
52
+ "text": [
53
+ "Found cached dataset csv (C:/Users/thoma/.cache/huggingface/datasets/nesticot___csv/nesticot--mlb_data-a391519415fcbccf/0.0.0/6954658bab30a358235fa864b05cf819af0e179325c740e4bc853bcc7ec513e1)\n",
54
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 1/1 [00:00<00:00, 2.02it/s]\n"
55
+ ]
56
+ }
57
+ ],
58
+ "source": [
59
+ "\n",
60
+ "colour_palette = ['#FFB000','#648FFF','#785EF0',\n",
61
+ " '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']\n",
62
+ "\n",
63
+ "print('Starting Everything:')\n",
64
+ "# exit_velo_df = milb_a_ev_df.append([triple_a_ev_df,double_a_ev_df,a_high_a_ev_df,single_a_ev_df]).reset_index(drop=True)\n",
65
+ "# player_df_all = mlb_a_player_df.append([triple_a_player_df,double_a_player_df,a_high_a_player_df,single_a_player_df]).reset_index(drop=True)\n",
66
+ "# exit_velo_df = pd.read_csv('exit_velo_df_all.csv',index_col=[0])\n",
67
+ "# player_df_all = pd.read_csv('player_df_all.csv',index_col=[0])\n",
68
+ "\n",
69
+ "# pa_df = pd.read_csv('pa_df_all.csv',index_col=[0])\n",
70
+ "# pa_df_full_na = pa_df.dropna()\n",
71
+ "\n",
72
+ "### Import Datasets\n",
73
+ "dataset = load_dataset('nesticot/mlb_data', data_files=['a_pitch_data_2023.csv',\n",
74
+ " ])\n",
75
+ "dataset_train = dataset['train']\n",
76
+ "exit_velo_df = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)\n",
77
+ "colour_palette = ['#FFB000','#648FFF','#785EF0',\n",
78
+ " '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']\n",
79
+ "\n",
80
+ "\n"
81
+ ]
82
+ },
83
+ {
84
+ "cell_type": "code",
85
+ "execution_count": 9,
86
+ "metadata": {},
87
+ "outputs": [
88
+ {
89
+ "data": {
90
+ "text/plain": [
91
+ "0 True\n",
92
+ "1 True\n",
93
+ "2 True\n",
94
+ "3 True\n",
95
+ "4 True\n",
96
+ " ... \n",
97
+ "575260 True\n",
98
+ "575261 True\n",
99
+ "575262 True\n",
100
+ "575263 True\n",
101
+ "575264 True\n",
102
+ "Name: is_pitch, Length: 575265, dtype: bool"
103
+ ]
104
+ },
105
+ "execution_count": 9,
106
+ "metadata": {},
107
+ "output_type": "execute_result"
108
+ }
109
+ ],
110
+ "source": [
111
+ "exit_velo_df['is_pitch']"
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "code",
116
+ "execution_count": 18,
117
+ "metadata": {},
118
+ "outputs": [],
119
+ "source": [
120
+ "tl_df = exit_velo_df[exit_velo_df['batter_id'] == 699073].groupby(['batter_id','batter_name','batter_hand']).agg(\n",
121
+ " pitches = ('is_pitch','sum'),\n",
122
+ " swings = ('is_swing','sum'),\n",
123
+ " whiffs = ('is_whiff','sum')\n",
124
+ ")"
125
+ ]
126
+ },
127
+ {
128
+ "cell_type": "code",
129
+ "execution_count": 19,
130
+ "metadata": {},
131
+ "outputs": [],
132
+ "source": [
133
+ "tl_df['whiff_rate'] = tl_df['whiffs'] / tl_df['swings']"
134
+ ]
135
+ },
136
+ {
137
+ "cell_type": "code",
138
+ "execution_count": 20,
139
+ "metadata": {},
140
+ "outputs": [
141
+ {
142
+ "data": {
143
+ "text/html": [
144
+ "<div>\n",
145
+ "<style scoped>\n",
146
+ " .dataframe tbody tr th:only-of-type {\n",
147
+ " vertical-align: middle;\n",
148
+ " }\n",
149
+ "\n",
150
+ " .dataframe tbody tr th {\n",
151
+ " vertical-align: top;\n",
152
+ " }\n",
153
+ "\n",
154
+ " .dataframe thead th {\n",
155
+ " text-align: right;\n",
156
+ " }\n",
157
+ "</style>\n",
158
+ "<table border=\"1\" class=\"dataframe\">\n",
159
+ " <thead>\n",
160
+ " <tr style=\"text-align: right;\">\n",
161
+ " <th></th>\n",
162
+ " <th></th>\n",
163
+ " <th></th>\n",
164
+ " <th>pitches</th>\n",
165
+ " <th>swings</th>\n",
166
+ " <th>whiffs</th>\n",
167
+ " <th>whiff_rate</th>\n",
168
+ " </tr>\n",
169
+ " <tr>\n",
170
+ " <th>batter_id</th>\n",
171
+ " <th>batter_name</th>\n",
172
+ " <th>batter_hand</th>\n",
173
+ " <th></th>\n",
174
+ " <th></th>\n",
175
+ " <th></th>\n",
176
+ " <th></th>\n",
177
+ " </tr>\n",
178
+ " </thead>\n",
179
+ " <tbody>\n",
180
+ " <tr>\n",
181
+ " <th rowspan=\"2\" valign=\"top\">699073</th>\n",
182
+ " <th rowspan=\"2\" valign=\"top\">Thayron Liranzo</th>\n",
183
+ " <th>L</th>\n",
184
+ " <td>1344</td>\n",
185
+ " <td>554</td>\n",
186
+ " <td>189</td>\n",
187
+ " <td>0.341155</td>\n",
188
+ " </tr>\n",
189
+ " <tr>\n",
190
+ " <th>R</th>\n",
191
+ " <td>343</td>\n",
192
+ " <td>160</td>\n",
193
+ " <td>60</td>\n",
194
+ " <td>0.375</td>\n",
195
+ " </tr>\n",
196
+ " </tbody>\n",
197
+ "</table>\n",
198
+ "</div>"
199
+ ],
200
+ "text/plain": [
201
+ " pitches swings whiffs whiff_rate\n",
202
+ "batter_id batter_name batter_hand \n",
203
+ "699073 Thayron Liranzo L 1344 554 189 0.341155\n",
204
+ " R 343 160 60 0.375"
205
+ ]
206
+ },
207
+ "execution_count": 20,
208
+ "metadata": {},
209
+ "output_type": "execute_result"
210
+ }
211
+ ],
212
+ "source": [
213
+ "tl_df"
214
+ ]
215
+ }
216
+ ],
217
+ "metadata": {
218
+ "kernelspec": {
219
+ "display_name": "Python 3",
220
+ "language": "python",
221
+ "name": "python3"
222
+ },
223
+ "language_info": {
224
+ "codemirror_mode": {
225
+ "name": "ipython",
226
+ "version": 3
227
+ },
228
+ "file_extension": ".py",
229
+ "mimetype": "text/x-python",
230
+ "name": "python",
231
+ "nbconvert_exporter": "python",
232
+ "pygments_lexer": "ipython3",
233
+ "version": "3.9.13"
234
+ }
235
+ },
236
+ "nbformat": 4,
237
+ "nbformat_minor": 2
238
+ }