nesticot commited on
Commit
4f3c380
·
verified ·
1 Parent(s): f998b2e

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +706 -0
app.py ADDED
@@ -0,0 +1,706 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ app = 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
+
598
+ ui.navset_tab(
599
+ ui.nav_control(
600
+ ui.a(
601
+ "Home",
602
+ href="https://nesticot-tjstats-site.hf.space/home/"
603
+ ),
604
+ ),
605
+ ui.nav_menu(
606
+ "Batter Charts",
607
+ ui.nav_control(
608
+ ui.a(
609
+ "Batting Rolling",
610
+ href="https://nesticot-tjstats-site-rolling-batter.hf.space/"
611
+ ),
612
+ ui.a(
613
+ "Spray",
614
+ href="https://nesticot-tjstats-site-spray.hf.space/"
615
+ ),
616
+ ui.a(
617
+ "Decision Value",
618
+ href="https://nesticot-tjstats-site-decision-value.hf.space/"
619
+ ),
620
+ ui.a(
621
+ "Damage Model",
622
+ href="https://nesticot-tjstats-site-damage.hf.space/"
623
+ ),
624
+ ui.a(
625
+ "Batter Scatter",
626
+ href="https://nesticot-tjstats-site-batter-scatter.hf.space/"
627
+ ),
628
+ ui.a(
629
+ "EV vs LA Plot",
630
+ href="https://nesticot-tjstats-site-ev-angle.hf.space/"
631
+ ),
632
+ ui.a(
633
+ "Statcast Compare",
634
+ href="https://nesticot-tjstats-site-statcast-compare.hf.space/"
635
+ ),
636
+ ui.a(
637
+ "MLB/MiLB Cards",
638
+ href="https://nesticot-tjstats-site-mlb-cards.hf.space/"
639
+ )
640
+ ),
641
+ ),
642
+ ui.nav_menu(
643
+ "Pitcher Charts",
644
+ ui.nav_control(
645
+ ui.a(
646
+ "Pitcher Rolling",
647
+ href="https://nesticot-tjstats-site-rolling-pitcher.hf.space/"
648
+ ),
649
+ ui.a(
650
+ "Pitcher Summary",
651
+ href="https://nesticot-tjstats-site-pitching-summary-graphic-new.hf.space/"
652
+ ),
653
+ ui.a(
654
+ "Pitcher Scatter",
655
+ href="https://nesticot-tjstats-site-pitcher-scatter.hf.space"
656
+ )
657
+ ),
658
+ )), ui.row(
659
+ ui.layout_sidebar(
660
+
661
+
662
+
663
+ ui.panel_sidebar(
664
+ #ui.input_date_range("date_range_id", "Date range input",start = statcast_df.game_date.min(), end = statcast_df.game_date.max()),
665
+ ui.input_select("player_id", "Select Player 1",player_dict,width=1,size=1,selectize=True,multiple=False,selected=592450),
666
+ 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),
667
+ ui.input_numeric("season_1", "Season 1", value=2022,min=2015,max=2023),
668
+ ui.input_numeric("season_2", "Season 2", value=2023,min=2015,max=2023),
669
+ ui.input_select("row_select", "Select Stats",
670
+ column_dict,width=1,size=1,selectize=True,
671
+ multiple=True,
672
+ selected=['k_percent','bb_percent','woba','xwoba','iz_contact_percent','oz_swing_percent','whiff_percent']),
673
+ ui.input_action_button("go", "Generate",class_="btn-primary",
674
+ )),
675
+
676
+ ui.panel_main(ui.tags.h3(""),
677
+ ui.navset_tab(
678
+ ui.nav("Single Player",
679
+ ui.div({"style": "font-size:2.1em;"},ui.output_text("txt_title")),
680
+ #ui.tags.h2("Fantasy Hockey Schedule Summary"),
681
+ ui.tags.h5("Created By: @TJStats, Data: MLB"),
682
+ #ui.div({"style": "font-size:1.6em;"},ui.output_text("txt")),
683
+ ui.output_table("statcast_compare"),
684
+ #ui.tags.h5('Legend'),
685
+ ui.tags.h3(""),
686
+ ui.tags.h5('Colour Scale:'),
687
+ ui.output_table("colour_scale"),
688
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_2022")),
689
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_2023")),
690
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_diff"))),
691
+
692
+ ui.nav("Player Compare",
693
+ ui.div({"style": "font-size:2.1em;"},ui.output_text("txt_title_compare")),
694
+ #ui.tags.h2("Fantasy Hockey Schedule Summary"),
695
+ ui.tags.h5("Created By: @TJStats, Data: MLB"),
696
+ #ui.div({"style": "font-size:1.6em;"},ui.output_text("txt")),
697
+ ui.output_table("statcast_compare_2"),
698
+ ui.tags.h3(""),
699
+ ui.tags.h5('Colour Scale:'),
700
+ ui.output_table("colour_scale_2"),
701
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_2022_1")),
702
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_2023_1")),
703
+ ui.div({"style": "font-size:1em;"},ui.output_text("text_diff_compare")))
704
+ )
705
+ ),
706
+ )),)),server)