|
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui |
|
import datasets |
|
from datasets import load_dataset |
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import seaborn as sns |
|
import numpy as np |
|
from scipy.stats import gaussian_kde |
|
import matplotlib |
|
from matplotlib.ticker import MaxNLocator |
|
from matplotlib.gridspec import GridSpec |
|
from scipy.stats import zscore |
|
import math |
|
import matplotlib |
|
from adjustText import adjust_text |
|
import matplotlib.ticker as mtick |
|
from shinywidgets import output_widget, render_widget |
|
import pandas as pd |
|
from configure import base_url |
|
import shinyswatch |
|
import inflect |
|
from matplotlib.pyplot import text |
|
|
|
def percentile(n): |
|
def percentile_(x): |
|
return np.nanpercentile(x, n) |
|
percentile_.__name__ = 'percentile_%s' % n |
|
return percentile_ |
|
|
|
from matplotlib.colors import Normalize |
|
|
|
print('Running') |
|
|
|
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"]) |
|
df = pd.read_csv('statcast_20152023.csv') |
|
df['last_name'] = df['last_name, first_name'].str.split(',').str[0] |
|
df['first_name'] = df['last_name, first_name'].str.split(',').str[1].str.strip(' ') |
|
df['name'] = df['first_name'] +' ' +df['last_name'] |
|
|
|
df[[x for x in df if x[-7:] == 'percent']] = df[[x for x in df if x[-7:] == 'percent']]/100 |
|
df['barrel_batted_rate'] = df['barrel_batted_rate']/100 |
|
|
|
player_dict = df[['player_id','name']].drop_duplicates().sort_values('name').set_index('player_id').to_dict()['name'] |
|
|
|
df_median = df[df.pa>=400] |
|
|
|
format_dict = { |
|
'k_percent':{'format':':.1%','average':df.strikeout.sum()/df.pa.sum(),'a_d_good':False,'tab_name':'K%'}, |
|
'bb_percent':{'format':':.1%','average':df.walk.sum()/df.pa.sum(),'a_d_good':True,'tab_name':'BB%'}, |
|
'batting_avg':{'format':':.3f','average':df.hit.sum()/df.ab.sum(),'a_d_good':True,'tab_name':'AVG'}, |
|
'on_base_plus_slg':{'format':':.3f','average':df.on_base_plus_slg.mean()/df.pa.mean(),'a_d_good':True,'tab_name':'OPS'}, |
|
'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'}, |
|
'xba':{'format':':.3f','average':df_median.xba.median(),'a_d_good':True,'tab_name':'xBA'}, |
|
'xslg':{'format':':.3f','average':df_median.xslg.median(),'a_d_good':True,'tab_name':'xSLG'}, |
|
'woba':{'format':':.3f','average':df_median.woba.median(),'a_d_good':True,'tab_name':'wOBA'}, |
|
'xwoba':{'format':':.3f','average':df_median.xwoba.median(),'a_d_good':True,'tab_name':'xwOBA'}, |
|
'xobp':{'format':':.3f','average':df_median.xobp.median(),'a_d_good':True,'tab_name':'xOBP'}, |
|
'xiso':{'format':':.3f','average':df_median.xiso.median(),'a_d_good':True,'tab_name':'xISO'}, |
|
'wobacon':{'format':':.3f','average':df_median.wobacon.median(),'a_d_good':True,'tab_name':'wOBACON'}, |
|
'xwobacon':{'format':':.3f','average':df_median.xwobacon.median(),'a_d_good':True,'tab_name':'xwOBACON'}, |
|
'bacon':{'format':':.1f','average':df_median.bacon.median(),'a_d_good':True,'tab_name':'BACON'}, |
|
'xbacon':{'format':':.1f','average':df_median.xbacon.median(),'a_d_good':True,'tab_name':'xBACON'}, |
|
'xbadiff':{'format':':.3f','average':df_median.xbadiff.median(),'a_d_good':True,'tab_name':'BA-xBA'}, |
|
'xslgdiff':{'format':':.3f','average':df_median.xslgdiff.median(),'a_d_good':True,'tab_name':'SLG-xSLG'}, |
|
'wobadiff':{'format':':.3f','average':df_median.wobadiff.median(),'a_d_good':True,'tab_name':'wOBA-xwOBA'}, |
|
'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'}, |
|
'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'}, |
|
'barrel':{'format':':.0f','average':df_median.barrel.median(),'a_d_good':True,'tab_name':'Barrel'}, |
|
'barrel_batted_rate':{'format':':.1%','average':(df.barrel).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'Barrel%'}, |
|
'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'}, |
|
'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'}, |
|
'out_zone_swing_miss':{'format':':.0f','average':df_median.out_zone_swing_miss.mean(),'a_d_good':True,'tab_name':'O-Whiff%'}, |
|
'out_zone_swing':{'format':':.0f','average':df_median.out_zone_swing.mean(),'a_d_good':True,'tab_name':'O-Swing'}, |
|
'out_zone':{'format':':.0f','average':df_median.out_zone.mean(),'a_d_good':True,'tab_name':'O-Zone'}, |
|
'pitch_count_offspeed':{'format':':.0f','average':df_median.pitch_count_offspeed.mean(),'a_d_good':True,'tab_name':'Pitch Off-Speed'}, |
|
'pitch_count_fastball':{'format':':.0f','average':df_median.pitch_count_fastball.mean(),'a_d_good':True,'tab_name':'Pitch Fastball'}, |
|
'pitch_count_breaking':{'format':':.0f','average':df_median.pitch_count_breaking.mean(),'a_d_good':True,'tab_name':'Pitch Breaking'}, |
|
'pitch_count':{'format':':.0f','average':df_median.pitch_count.mean(),'a_d_good':True,'tab_name':'Pitches'}, |
|
'in_zone_swing_miss':{'format':':.0f','average':df_median.in_zone_swing_miss.mean(),'a_d_good':False,'tab_name':'Z-Whiff'}, |
|
'in_zone_swing':{'format':':.0f','average':df_median.in_zone_swing.mean(),'a_d_good':True,'tab_name':'Z-Swing'}, |
|
'in_zone':{'format':':.0f','average':df_median.in_zone.mean(),'a_d_good':True,'tab_name':'Zone'}, |
|
'edge':{'format':':.0f','average':df_median.edge.mean(),'a_d_good':True,'tab_name':'Edge'}, |
|
'batted_ball':{'format':':.0f','average':df_median.batted_ball.mean(),'a_d_good':True,'tab_name':'Batted Balls'}, |
|
'groundballs':{'format':':.0f','average':df_median.groundballs.mean(),'a_d_good':True,'tab_name':'Groundballs'}, |
|
'flyballs':{'format':':.0f','average':df_median.flyballs.mean(),'a_d_good':True,'tab_name':'Flyballs'}, |
|
'linedrives':{'format':':.0f','average':df_median.linedrives.mean(),'a_d_good':True,'tab_name':'Linedrives'}, |
|
'popups':{'format':':.0f','average':df_median.popups.mean(),'a_d_good':True,'tab_name':'Popups'}, |
|
'n_bolts':{'format':':.0f','average':df_median.n_bolts.mean(),'a_d_good':True,'tab_name':'Bolts'}, |
|
'hp_to_1b':{'format':':.2f','average':df_median.hp_to_1b.mean(),'a_d_good':True,'tab_name':'Home Plate to 1st'}, |
|
'sprint_speed':{'format':':.1f','average':df_median.sprint_speed.mean(),'a_d_good':True,'tab_name':'Sprint Speed'}, |
|
|
|
'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'}, |
|
'on_base_percent':{'format':':.1%','average':df_median.on_base_percent.median(),'a_d_good':True,'tab_name':'OBP'}, |
|
'sweet_spot_percent':{'format':':.1%','average':df_median.sweet_spot_percent.median(),'a_d_good':True,'tab_name':'SweetSpot%'}, |
|
'solidcontact_percent':{'format':':.1%','average':df_median.solidcontact_percent.median(),'a_d_good':True,'tab_name':'Solid%'}, |
|
'flareburner_percent':{'format':':.1%','average':df_median.flareburner_percent.median(),'a_d_good':False,'tab_name':'Flare/Burner%'}, |
|
'poorlyunder_percent':{'format':':.1%','average':df_median.poorlyunder_percent.median(),'a_d_good':False,'tab_name':'Under%'}, |
|
'poorlytopped_percent':{'format':':.1%','average':df_median.poorlytopped_percent.median(),'a_d_good':False,'tab_name':'Topped%'}, |
|
'poorlyweak_percent':{'format':':.1%','average':df_median.poorlyweak_percent.median(),'a_d_good':False,'tab_name':'Weak%'}, |
|
'hard_hit_percent':{'format':':.1%','average':df_median.hard_hit_percent.median(),'a_d_good':True,'tab_name':'HardHit%'}, |
|
'z_swing_percent':{'format':':.1%','average':df.in_zone_swing.sum()/df.in_zone.sum(),'a_d_good':True,'tab_name':'Z-Swing%'}, |
|
'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%'}, |
|
|
|
'out_zone_percent':{'format':':.1%','average':df.out_zone.sum()/df.pitch_count.sum(),'a_d_good':True,'tab_name':'O-Zone%'}, |
|
'meatball_swing_percent':{'format':':.1%','average':df_median.meatball_swing_percent.median(),'a_d_good':True,'tab_name':'Meatball Swing%'}, |
|
'meatball_percent':{'format':':.1%','average':df_median.meatball_percent.median(),'a_d_good':True,'tab_name':'Meatball%'}, |
|
'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%'}, |
|
'in_zone_percent':{'format':':.1%','average':df.in_zone.mean()/df.pitch_count.sum(),'a_d_good':True,'tab_name':'Zone%'}, |
|
'oz_swing_percent':{'format':':.1%','average':df.out_zone_swing.sum()/df.out_zone.sum(),'a_d_good':False,'tab_name':'O-Swing%'}, |
|
'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%'}, |
|
'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%'}, |
|
'edge_percent':{'format':':.1%','average':df_median.edge_percent.median(),'a_d_good':True,'tab_name':'Edge%'}, |
|
'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%'}, |
|
'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%'}, |
|
'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%'}, |
|
'pull_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Pull%'}, |
|
'straightaway_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Straightaway%'}, |
|
'opposite_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Opposite%'}, |
|
'f_strike_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':False,'tab_name':'1st Strike%'}, |
|
'groundballs_percent':{'format':':.1%','average':df.groundballs.sum()/df.batted_ball.sum(),'a_d_good':False,'tab_name':'GB%'}, |
|
'flyballs_percent':{'format':':.1%','average':df.flyballs.sum()/df.batted_ball.sum(),'a_d_good':True,'tab_name':'FB%'}, |
|
'linedrives_percent':{'format':':.1%','average':df.linedrives.sum()/df.batted_ball.sum(),'a_d_good':True,'tab_name':'LD%'}, |
|
'popups_percent':{'format':':.1%','average':df.popups.sum()/df.batted_ball.sum(),'a_d_good':False,'tab_name':'PU%'},} |
|
|
|
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'] |
|
|
|
|
|
def server(input,output,session): |
|
|
|
|
|
@output |
|
@render.text |
|
@reactive.event(input.go, ignore_none=False) |
|
def txt_title(): |
|
if input.player_id() == '': |
|
return 'Select a Player' |
|
|
|
player_input = int(input.player_id()) |
|
season_1 = max(2015,int(input.season_1())) |
|
season_2 = min(2023,int(input.season_2())) |
|
|
|
if season_1 < season_2: |
|
season_pick = [season_1,season_2] |
|
|
|
elif season_1 > season_2: |
|
season_pick = [season_2,season_1] |
|
|
|
if len(str(input.player_id())) == 0: |
|
return 'Select a Batter' |
|
if str(input.season_1()) == str(input.season_2()): |
|
return 'Select Different Seasons' |
|
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: |
|
return 'Select Different Seasons' |
|
return f'{player_dict[int(input.player_id())]} Statcast Season Comparison' |
|
|
|
@output |
|
@render.text |
|
@reactive.event(input.go, ignore_none=False) |
|
def txt_title_compare(): |
|
if type(input.player_id()) is int or input.player_id()=='': |
|
return |
|
|
|
if type(input.player_id_2()) is int or input.player_id_2()=='': |
|
return |
|
|
|
player_input_1 = int(input.player_id()) |
|
player_input_2 = int(input.player_id_2()) |
|
|
|
|
|
|
|
columns_i_want = list(input.row_select()) |
|
print(columns_i_want) |
|
season_1 = max(2015,int(input.season_1())) |
|
season_2 = min(2023,int(input.season_2())) |
|
|
|
player_list = [player_input_1,player_input_2] |
|
name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]] |
|
season_pick_list = [season_1,season_2] |
|
|
|
if len(str(input.player_id())) == 0: |
|
return 'Select a Batter' |
|
if len(str(input.player_id_2())) == 0: |
|
return 'Select a Batter' |
|
|
|
if str(input.player_id()) == str(input.player_id_2()) and str(input.season_1()) == str(input.season_2()): |
|
return 'Select Different Seasons' |
|
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: |
|
return 'No Data for Specified Batter in Given Season' |
|
|
|
return f'Statcast Season Comparison' |
|
|
|
|
|
|
|
|
|
@output |
|
@render.text |
|
@reactive.event(input.go, ignore_none=False) |
|
def text_2022(): |
|
if input.player_id() == '': |
|
return 'Select a Player' |
|
return f'{int(input.season_1())} Season Results Compares to MLB Average' |
|
|
|
|
|
@output |
|
@render.text |
|
@reactive.event(input.go, ignore_none=False) |
|
def text_2022_1(): |
|
if type(input.player_id()) is int or input.player_id()=='': |
|
return |
|
|
|
if type(input.player_id_2()) is int or input.player_id_2()=='': |
|
return |
|
season_1 = max(2015,int(input.season_1())) |
|
season_2 = min(2023,int(input.season_2())) |
|
season_pick_list = [season_1,season_2] |
|
player_input_1 = int(input.player_id()) |
|
player_input_2 = int(input.player_id_2()) |
|
name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]] |
|
return f"{name_list[0]} '{str(season_pick_list[0])[2:]} Season Results Compares to MLB Average" |
|
|
|
@output |
|
@render.text |
|
@reactive.event(input.go, ignore_none=False) |
|
def text_2023(): |
|
if input.player_id() == '': |
|
return 'Select a Player' |
|
return f'{int(input.season_2())} Season Results Compares to MLB Average' |
|
|
|
@output |
|
@render.text |
|
@reactive.event(input.go, ignore_none=False) |
|
def text_2023_1(): |
|
if type(input.player_id()) is int or input.player_id()=='': |
|
return |
|
|
|
if type(input.player_id_2()) is int or input.player_id_2()=='': |
|
return |
|
season_1 = max(2015,int(input.season_1())) |
|
season_2 = min(2023,int(input.season_2())) |
|
season_pick_list = [season_1,season_2] |
|
player_input_1 = int(input.player_id()) |
|
player_input_2 = int(input.player_id_2()) |
|
name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]] |
|
return f"{name_list[1]} '{str(season_pick_list[1])[2:]} Season Results Compares to MLB Average" |
|
|
|
@output |
|
@render.text |
|
@reactive.event(input.go, ignore_none=False) |
|
def text_diff(): |
|
if input.player_id() == '': |
|
return 'Select a Player' |
|
return f'Difference Compares {int(input.season_2())} Results to {int(input.season_1())} Results' |
|
|
|
@output |
|
@render.text |
|
@reactive.event(input.go, ignore_none=False) |
|
def text_diff_compare(): |
|
if type(input.player_id()) is int or input.player_id()=='': |
|
return |
|
|
|
if type(input.player_id_2()) is int or input.player_id_2()=='': |
|
return |
|
player_input_1 = int(input.player_id()) |
|
player_input_2 = int(input.player_id_2()) |
|
name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]] |
|
return f'Difference Compares {name_list[0]} Results to {name_list[1]} Results' |
|
|
|
|
|
|
|
@output |
|
@render.table |
|
@reactive.event(input.go, ignore_none=False) |
|
def statcast_compare(): |
|
if input.player_id() == '': |
|
return |
|
|
|
if len(str(input.player_id())) == 0: |
|
return |
|
if len(str(input.player_id_2())) == 0: |
|
return |
|
|
|
|
|
player_input = int(input.player_id()) |
|
|
|
|
|
|
|
columns_i_want = list(input.row_select()) |
|
print(columns_i_want) |
|
season_1 = max(2015,int(input.season_1())) |
|
season_2 = min(2023,int(input.season_2())) |
|
|
|
|
|
if season_1 < season_2: |
|
season_pick = [season_1,season_2] |
|
|
|
elif season_1 > season_2: |
|
season_pick = [season_2,season_1] |
|
|
|
else: |
|
return |
|
|
|
print(df[(df.player_id == player_input)&(df.year == season_pick[0])]) |
|
|
|
|
|
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: |
|
return |
|
|
|
df_compare = pd.concat([df[(df.player_id == player_input)&(df.year == season_pick[0])][[ 'player_age', 'pa']+columns_i_want], |
|
df[(df.player_id == player_input)&(df.year == season_pick[1])][[ 'player_age', 'pa']+columns_i_want]]).reset_index(drop=True).T |
|
|
|
|
|
print('test') |
|
print(sum(df.player_id == input.player_id())) |
|
df_compare.columns = season_pick |
|
|
|
df_compare['Difference'] = df_compare.loc[columns_i_want][season_pick[1]] - df_compare.loc[columns_i_want][season_pick[0]] |
|
|
|
df_compare_style = df_compare.style.format( |
|
"{:.0f}") |
|
df_compare_style = df_compare_style.set_properties(**{'background-color': 'white', |
|
'color': 'white'},subset=(['player_age', 'pa'],df_compare_style.columns[2])).set_properties( |
|
**{'min-width':'100px'},overwrite=False).set_table_styles( |
|
[{'selector': 'th:first-child', 'text-align': 'center','props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'tr:first-child','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'index','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'th', 'text-align': 'center','props': [('line-height', '40px'),('min-width', '30px')]}],overwrite=False).set_properties( |
|
|
|
**{'Height': '20px'},**{'text-align': 'center'},overwrite=False).set_table_styles([{ |
|
'selector': 'caption', |
|
'props': [ |
|
('color', ''), |
|
('fontname', 'Century Gothic'), |
|
('font-size', '20px'), |
|
('font-style', 'italic'), |
|
('font-weight', ''), |
|
('text-align', 'centre'), |
|
] |
|
|
|
},{'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) |
|
|
|
|
|
for r in columns_i_want: |
|
if format_dict[r]['a_d_good']: |
|
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"]) |
|
else: |
|
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#FBBC04","white","#4285F4"]) |
|
|
|
|
|
colormap = plt.get_cmap(cmap) |
|
norm = Normalize(vmin=0.7, vmax=1.3) |
|
|
|
normalized_value = norm(df_compare[df_compare.columns[0]][r]/format_dict[r]['average']) |
|
df_compare_style.format( |
|
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)), |
|
'color': 'black'},subset=(r,df_compare_style.columns[0])) |
|
|
|
norm = Normalize(vmin=0.7, vmax=1.3) |
|
normalized_value = norm(df_compare[df_compare.columns[1]][r]/format_dict[r]['average']) |
|
df_compare_style.format( |
|
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)), |
|
'color': 'black'},subset=(r,df_compare_style.columns[1])) |
|
|
|
norm = Normalize(vmin=0.7, vmax=1.3) |
|
normalized_value = norm(df_compare[df_compare.columns[1]][r]/df_compare[df_compare.columns[0]][r]) |
|
df_compare_style.format( |
|
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)), |
|
'color': 'black'},subset=(r,df_compare_style.columns[2])) |
|
|
|
|
|
|
|
|
|
df_compare_style.relabel_index(['Age', 'PA']+[format_dict[x]['tab_name'] for x in columns_i_want]).set_properties( |
|
**{'border': '1px black solid !important'},overwrite=False).set_table_styles( |
|
[{"selector": "", "props": [("border", "1px solid")]}, |
|
{"selector": "tbody td", "props": [("border", "1px solid")]}, |
|
{"selector": "th", "props": [("border", "1px solid")]}],overwrite=False) |
|
|
|
|
|
|
|
return df_compare_style |
|
|
|
|
|
|
|
@output |
|
@render.table |
|
@reactive.event(input.go, ignore_none=False) |
|
def statcast_compare_2(): |
|
|
|
|
|
|
|
if type(input.player_id()) is int or input.player_id()=='': |
|
return |
|
|
|
if type(input.player_id_2()) is int or input.player_id_2()=='': |
|
return |
|
|
|
|
|
|
|
player_input_1 = int(input.player_id()) |
|
player_input_2 = int(input.player_id_2()) |
|
|
|
|
|
|
|
columns_i_want = list(input.row_select()) |
|
print(columns_i_want) |
|
season_1 = max(2015,int(input.season_1())) |
|
season_2 = min(2023,int(input.season_2())) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
player_list = [player_input_1,player_input_2] |
|
name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]] |
|
season_pick_list = [season_1,season_2] |
|
|
|
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: |
|
return |
|
if str(input.player_id()) == str(input.player_id_2()) and str(input.season_1()) == str(input.season_2()): |
|
return |
|
|
|
|
|
df_compare = pd.concat([df[(df.player_id == player_list[0])&(df.year == season_pick_list[0])][[ 'year','player_age', 'pa']+columns_i_want], |
|
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 |
|
|
|
df_compare.columns = [f"{name_list[0]} '{str(season_pick_list[0])[2:]}",f"{name_list[1]} '{str(season_pick_list[1])[2:]}"] |
|
df_compare['Difference'] = df_compare.loc[columns_i_want][df_compare.columns [0]] - df_compare.loc[columns_i_want][df_compare.columns[1]] |
|
|
|
|
|
df_compare_style = df_compare.style.format( |
|
"{:.0f}") |
|
df_compare_style = df_compare_style.set_properties(**{'background-color': 'white', |
|
'color': 'white'},subset=(['year','player_age', 'pa'],df_compare_style.columns[2])).set_properties( |
|
**{'min-width':'125px'},overwrite=False).set_table_styles( |
|
[{'selector': 'th:first-child', 'text-align': 'center','props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'tr:first-child','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'index','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'th', 'text-align': 'center','props': [('line-height', '40px'),('min-width', '30px')]}],overwrite=False).set_properties( |
|
|
|
**{'Height': '20px'},**{'text-align': 'center'},overwrite=False).set_table_styles([{ |
|
'selector': 'caption', |
|
'props': [ |
|
('color', ''), |
|
('fontname', 'Century Gothic'), |
|
('font-size', '20px'), |
|
('font-style', 'italic'), |
|
('font-weight', ''), |
|
('text-align', 'centre'), |
|
] |
|
|
|
},{'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) |
|
|
|
|
|
for r in columns_i_want: |
|
if format_dict[r]['a_d_good']: |
|
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"]) |
|
else: |
|
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#FBBC04","white","#4285F4"]) |
|
|
|
|
|
colormap = plt.get_cmap(cmap) |
|
norm = Normalize(vmin=0.5, vmax=1.5) |
|
normalized_value = norm(df_compare[df_compare.columns[0]][r]/format_dict[r]['average']) |
|
df_compare_style.format( |
|
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)), |
|
'color': 'black'},subset=(r,df_compare_style.columns[0])) |
|
|
|
norm = Normalize(vmin=0.5, vmax=1.5) |
|
normalized_value = norm(df_compare[df_compare.columns[1]][r]/format_dict[r]['average']) |
|
df_compare_style.format( |
|
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)), |
|
'color': 'black'},subset=(r,df_compare_style.columns[1])) |
|
|
|
norm = Normalize(vmin=0.8, vmax=1.2) |
|
normalized_value = norm(df_compare[df_compare.columns[0]][r]/df_compare[df_compare.columns[1]][r]) |
|
df_compare_style.format( |
|
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)), |
|
'color': 'black'},subset=(r,df_compare_style.columns[2])) |
|
|
|
df_compare_style = df_compare_style |
|
|
|
|
|
df_compare_style.relabel_index(['Year','Age', 'PA']+[format_dict[x]['tab_name'] for x in columns_i_want]).set_properties( |
|
**{'border': '1px black solid !important'},overwrite=False).set_table_styles( |
|
[{"selector": "", "props": [("border", "1px solid")]}, |
|
{"selector": "tbody td", "props": [("border", "1px solid")]}, |
|
{"selector": "th", "props": [("border", "1px solid")]}],overwrite=False) |
|
|
|
|
|
|
|
return df_compare_style |
|
|
|
@output |
|
@render.table |
|
@reactive.event(input.go, ignore_none=False) |
|
def colour_scale(): |
|
off_b2b_df = pd.DataFrame(data={'one':-0.30,'two':0,'three':0.30},index=[0]) |
|
off_b2b_df_style = off_b2b_df.style.set_properties(**{'border': '3 px'},overwrite=False).set_table_styles([{ |
|
'selector': 'caption', |
|
'props': [ |
|
('color', ''), |
|
('fontname', 'Century Gothic'), |
|
('font-size', '20px'), |
|
('font-style', 'italic'), |
|
('font-weight', ''), |
|
('text-align', 'centre'), |
|
] |
|
|
|
},{'selector' :'th', 'props':[('text-align', 'center'),('Height','px'),('color','black'),( |
|
'border', '1px black solid !important')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '18px'),('color','black')]}],overwrite=False).set_properties( |
|
**{'background-color':'White','index':'White','min-width':'150px'},overwrite=False).set_table_styles( |
|
[{'selector': 'th:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'tr:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'tr', 'props': [('line-height', '20px')]}],overwrite=False).set_properties( |
|
**{'Height': '8px'},**{'text-align': 'center'},overwrite=False).set_properties( |
|
**{'background-color':'#4285F4'},subset=off_b2b_df.columns[0]).set_properties( |
|
**{'background-color':'white'},subset=off_b2b_df.columns[1]).set_properties( |
|
**{'background-color':'#FBBC04'},subset=off_b2b_df.columns[2]).set_properties( |
|
**{'color':'black'},subset=off_b2b_df.columns[:]).hide_index().set_table_styles([ |
|
{'selector': 'thead', 'props': [('display', 'none')]} |
|
]).set_properties(**{'border': '3 px','color':'black'},overwrite=False).set_properties( |
|
**{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))).set_properties( |
|
**{'min-width':'130'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:])),overwrite=False).set_properties(**{ |
|
'color': 'black'},overwrite=False).set_properties( |
|
**{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))) .format( |
|
"{:+.0%}") |
|
return off_b2b_df_style |
|
|
|
@output |
|
@render.table |
|
@reactive.event(input.go, ignore_none=False) |
|
def colour_scale_2(): |
|
off_b2b_df = pd.DataFrame(data={'one':-0.30,'two':0,'three':0.30},index=[0]) |
|
off_b2b_df_style = off_b2b_df.style.set_properties(**{'border': '3 px'},overwrite=False).set_table_styles([{ |
|
'selector': 'caption', |
|
'props': [ |
|
('color', ''), |
|
('fontname', 'Century Gothic'), |
|
('font-size', '20px'), |
|
('font-style', 'italic'), |
|
('font-weight', ''), |
|
('text-align', 'centre'), |
|
] |
|
|
|
},{'selector' :'th', 'props':[('text-align', 'center'),('Height','px'),('color','black'),( |
|
'border', '1px black solid !important')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '18px'),('color','black')]}],overwrite=False).set_properties( |
|
**{'background-color':'White','index':'White','min-width':'150px'},overwrite=False).set_table_styles( |
|
[{'selector': 'th:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'tr:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles( |
|
[{'selector': 'tr', 'props': [('line-height', '20px')]}],overwrite=False).set_properties( |
|
**{'Height': '8px'},**{'text-align': 'center'},overwrite=False).set_properties( |
|
**{'background-color':'#4285F4'},subset=off_b2b_df.columns[0]).set_properties( |
|
**{'background-color':'white'},subset=off_b2b_df.columns[1]).set_properties( |
|
**{'background-color':'#FBBC04'},subset=off_b2b_df.columns[2]).set_properties( |
|
**{'color':'black'},subset=off_b2b_df.columns[:]).hide_index().set_table_styles([ |
|
{'selector': 'thead', 'props': [('display', 'none')]} |
|
]).set_properties(**{'border': '3 px','color':'black'},overwrite=False).set_properties( |
|
**{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))).set_properties( |
|
**{'min-width':'130'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:])),overwrite=False).set_properties(**{ |
|
'color': 'black'},overwrite=False).set_properties( |
|
**{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))) .format( |
|
"{:+.0%}") |
|
|
|
|
|
return off_b2b_df_style |
|
|
|
|
|
|
|
|
|
statcast_compare = App(ui.page_fluid( |
|
ui.tags.base(href=base_url), |
|
ui.tags.div( |
|
{"style": "width:90%;margin: 0 auto;max-width: 1600px;"}, |
|
ui.tags.style( |
|
""" |
|
h4 { |
|
margin-top: 1em;font-size:35px; |
|
} |
|
h2{ |
|
font-size:25px; |
|
} |
|
""" |
|
), |
|
shinyswatch.theme.simplex(), |
|
ui.tags.h4("TJStats"), |
|
ui.tags.i("Baseball Analytics and Visualizations"), |
|
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""), |
|
ui.navset_tab( |
|
ui.nav_control( |
|
ui.a( |
|
"Home", |
|
href="home/" |
|
), |
|
), |
|
ui.nav_menu( |
|
"Batter Charts", |
|
ui.nav_control( |
|
ui.a( |
|
"Batting Rolling", |
|
href="rolling_batter/" |
|
), |
|
ui.a( |
|
"Spray", |
|
href="spray/" |
|
), |
|
ui.a( |
|
"Decision Value", |
|
href="decision_value/" |
|
), |
|
ui.a( |
|
"Damage Model", |
|
href="damage_model/" |
|
), |
|
ui.a( |
|
"Batter Scatter", |
|
href="batter_scatter/" |
|
), |
|
|
|
|
|
|
|
|
|
ui.a( |
|
"Statcast Compare", |
|
href="statcast_compare/" |
|
) |
|
), |
|
), |
|
ui.nav_menu( |
|
"Pitcher Charts", |
|
ui.nav_control( |
|
ui.a( |
|
"Pitcher Rolling", |
|
href="rolling_pitcher/" |
|
), |
|
ui.a( |
|
"Pitcher Summary", |
|
href="pitching_summary_graphic_new/" |
|
), |
|
ui.a( |
|
"Pitcher Scatter", |
|
href="pitcher_scatter/" |
|
) |
|
), |
|
)),ui.row( |
|
ui.layout_sidebar( |
|
|
|
|
|
|
|
ui.panel_sidebar( |
|
|
|
ui.input_select("player_id", "Select Player 1",player_dict,width=1,size=1,selectize=True,multiple=False,selected=592450), |
|
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), |
|
ui.input_numeric("season_1", "Season 1", value=2022,min=2015,max=2023), |
|
ui.input_numeric("season_2", "Season 2", value=2023,min=2015,max=2023), |
|
ui.input_select("row_select", "Select Stats", |
|
column_dict,width=1,size=1,selectize=True, |
|
multiple=True, |
|
selected=['k_percent','bb_percent','woba','xwoba','iz_contact_percent','oz_swing_percent','whiff_percent']), |
|
ui.input_action_button("go", "Generate",class_="btn-primary", |
|
)), |
|
|
|
ui.panel_main(ui.tags.h3(""), |
|
ui.navset_tab( |
|
ui.nav("Single Player", |
|
ui.div({"style": "font-size:2.1em;"},ui.output_text("txt_title")), |
|
|
|
ui.tags.h5("Created By: @TJStats, Data: MLB"), |
|
|
|
ui.output_table("statcast_compare"), |
|
|
|
ui.tags.h3(""), |
|
ui.tags.h5('Colour Scale:'), |
|
ui.output_table("colour_scale"), |
|
ui.div({"style": "font-size:1em;"},ui.output_text("text_2022")), |
|
ui.div({"style": "font-size:1em;"},ui.output_text("text_2023")), |
|
ui.div({"style": "font-size:1em;"},ui.output_text("text_diff"))), |
|
|
|
ui.nav("Player Compare", |
|
ui.div({"style": "font-size:2.1em;"},ui.output_text("txt_title_compare")), |
|
|
|
ui.tags.h5("Created By: @TJStats, Data: MLB"), |
|
|
|
ui.output_table("statcast_compare_2"), |
|
ui.tags.h3(""), |
|
ui.tags.h5('Colour Scale:'), |
|
ui.output_table("colour_scale_2"), |
|
ui.div({"style": "font-size:1em;"},ui.output_text("text_2022_1")), |
|
ui.div({"style": "font-size:1em;"},ui.output_text("text_2023_1")), |
|
ui.div({"style": "font-size:1em;"},ui.output_text("text_diff_compare"))) |
|
) |
|
), |
|
)),)),server) |