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 from matplotlib.pyplot import text ### Import Datasets dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2023.csv' ]) dataset_train = dataset['train'] exit_velo_df = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True) colour_palette = ['#FFB000','#648FFF','#785EF0', '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED'] #exit_velo_df = pd.read_csv('exit_velo_df.csv',index_col=[0]) conditions = [ (exit_velo_df['launch_speed'].isna()), (exit_velo_df['launch_speed']*1.5 - exit_velo_df['launch_angle'] >= 117 ) & (exit_velo_df['launch_speed'] + exit_velo_df['launch_angle'] >= 124) & (exit_velo_df['launch_speed'] > 98) & (exit_velo_df['launch_angle'] >= 8) & (exit_velo_df['launch_angle'] <= 50) ] choices = [False,True] exit_velo_df['barrel'] = np.select(conditions, choices, default=np.nan) 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() test_df = test_df.set_index('batter_id') #test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt']) batter_dict = test_df['batter_name'].to_dict() colour_palette = ['#FFB000','#648FFF','#785EF0', '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED'] angle_ev_list_df = pd.read_csv('angle_ev_list_df.csv') ev_ranges = list(np.arange(97.5,130,0.1)) angle_ranges = list(range(8,51)) #print def server(input,output,session): @output @render.plot(alt="A histogram") @reactive.event(input.go, ignore_none=False) def plot(): data_df = exit_velo_df[exit_velo_df.batter_id==int(input.id())] #pitch_list = exit_velo_df_small.pitch_type.unique() sns.set_theme(style="whitegrid", palette="pastel") fig, ax = plt.subplots(1, 1, figsize=(10, 10)) if input.plot_id() == 'dist': sns.histplot(x=data_df.launch_angle,y=data_df.launch_speed,cbar=colour_palette,binwidth=(5,2.5),ax=ax,cbar_kws=dict(shrink=.75,label='Count'),binrange=( (math.floor((min(data_df.launch_angle.dropna())/5))*5,math.ceil((max(data_df.launch_angle.dropna())/5))*5),(math.floor((min(data_df.launch_speed.dropna())/5))*5,math.ceil((max(data_df.launch_speed.dropna())/5))*5))) if input.plot_id() == 'scatter': sns.scatterplot(x=data_df.launch_angle,y=data_df.launch_speed,color=colour_palette[1]) ax.set_xlim(math.floor((min(data_df.launch_angle.dropna())/10))*10,math.ceil((max(data_df.launch_angle.dropna())/10))*10) #ticks=np.arange(revels.values.min(),revels.values.max()+1 ) sns.lineplot(x=angle_ev_list_df.launch_angle,y=angle_ev_list_df.launch_speed,color=colour_palette[0]) ax.vlines(x=angle_ev_list_df.launch_angle[0],ymin=angle_ev_list_df.launch_speed[0],ymax=ev_ranges[-1],color=colour_palette[0]) ax.vlines(x=angle_ev_list_df.launch_angle[len(angle_ev_list_df)-1],ymin=angle_ev_list_df.launch_speed[len(angle_ev_list_df)-1],ymax=ev_ranges[-1],color=colour_palette[0]) groundball = f'{sum(data_df.launch_angle.dropna()<=10)/len(data_df.launch_angle.dropna()):.1%}' linedrive = f'{sum((data_df.launch_angle.dropna()<=25) & (data_df.launch_angle.dropna()>10))/len(data_df.launch_angle.dropna()):.1%}' flyball = f'{sum((data_df.launch_angle.dropna()<=50) & (data_df.launch_angle.dropna()>25))/len(data_df.launch_angle.dropna()):.1%}' popup = f'{sum(data_df.launch_angle.dropna()>50)/len(data_df.launch_angle.dropna()):.1%}' percentages_list = [groundball,linedrive,flyball,popup] hard_hit_percent = f'{sum(data_df.launch_speed.dropna()>=95)/len(data_df.launch_speed.dropna()):.1%}' barrel_percentage = f'{data_df.barrel.dropna().sum()/len(data_df.launch_angle.dropna()):.1%}' plt.text(x=27, y=math.ceil((max(data_df.launch_speed.dropna())/5))*5+5-3, s=f'Barrel% {barrel_percentage}',ha='left',bbox=dict(facecolor='white',alpha=0.8, edgecolor=colour_palette[4], pad=5)) sample_dates = np.array([math.floor((min(data_df.launch_angle.dropna())/10))*10,10,25,50]) sample_text = [f'Groundball ({groundball})',f'Line Drive ({linedrive})',f'Fly Ball ({flyball})',f'Pop-up ({popup})'] hard_hit_dates = [95] hard_hit_text = [f'Hard Hit% ({hard_hit_percent})'] #sample_dates = mdates.date2num(sample_dates) plt.hlines(y=hard_hit_dates,xmin=math.floor((min(data_df.launch_angle.dropna())/10))*10, xmax=math.ceil((max(data_df.launch_angle.dropna())/10))*10, color = colour_palette[4],linestyles='--') plt.vlines(x=sample_dates, ymin=0, ymax=130, color = colour_palette[3],linestyles='--') # ax.vlines(x=10,ymin=0,ymax=ev_ranges[-1],color=colour_palette[3],linestyles='--') # ax.vlines(x=25,ymin=0,ymax=ev_ranges[-1],color=colour_palette[3],linestyles='--') # ax.vlines(x=50,ymin=0,ymax=ev_ranges[-1],color=colour_palette[3],linestyles='--') for i, x in enumerate(hard_hit_dates): text(math.ceil((max(data_df.launch_angle.dropna())/10))*10-2.5, x+1.25,hard_hit_text[i], rotation=0, ha='right', bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[4], pad=5)) for i, x in enumerate(sample_dates): text(x+0.75, (math.floor((min(data_df.launch_speed.dropna())/5))*5)+1,sample_text[i], rotation=90, verticalalignment='bottom', bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[3], pad=5)) #ax.vlines(x=math.floor((min(data_df.launch_angle.dropna())/10))*10+1,ymin=0,ymax=ev_ranges[-1],color=colour_palette[3],linestyles='--') ax.set_xlim((math.floor((min(data_df.launch_angle.dropna())/10))*10,math.ceil((max(data_df.launch_angle.dropna())/10))*10)) ax.set_ylim((math.floor((min(data_df.launch_speed.dropna())/5))*5,math.ceil((max(data_df.launch_speed.dropna())/5))*5+5)) # ax.set_xlim(-90,90) # ax.set_ylim(0,125) ax.set_title(f'MLB - {data_df.batter_name.unique()[0]} Launch Angle vs EV Plot', fontsize=18,fontname='Century Gothic',) #vals = ax.get_yticks() ax.set_xlabel('Launch Angle', fontsize=16,fontname='Century Gothic') ax.set_ylabel('Exit Velocity', fontsize=16,fontname='Century Gothic') ax.fill_between(angle_ev_list_df.launch_angle, 130, angle_ev_list_df.launch_speed, interpolate=True, color=colour_palette[3],alpha=0.1,label='Barrel') #fig.colorbar(plot_dist, ax=ax) #fig.colorbar(plot_dist) #fig.axes[0].invert_yaxis() ax.legend(fontsize='16',loc='upper left') fig.text(x=0.03,y=0.02,s='By: @TJStats') fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right') # fig.text(x=0.25,y=0.02,s='Data: MLB',ha='right') # fig.text(x=0.25,y=0.02,s='Data: MLB',ha='right') # fig.text(x=0.25,y=0.02,s='Data: MLB',ha='right') #cbar = plt.colorbar() #fig.subplots_adjust(wspace=.02, hspace=.02) #ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x))) fig.set_facecolor('white') fig.tight_layout() #matplotlib.rcParams["figure.dpi"] = 300 # ax.set_xlim(input.n(),exit_velo_df_small.pitch.max()) #ax.axis('off') #fig.set_facecolor('white') #fig.tight_layout() #ax.hist(exit_velo_df[exit_velo_df.pitcher_id==int(input.id())]['pitch_velocity'],input.n(),density=True) #plt.show() #return g # This is a shiny.App object. It must be named `app`. # fig, ax = plt.subplots() #print(input.pitcher_id()) # print(input) # plt.hist(x=exit_velo_df[exit_velo_df.pitcher_id==input.x()]['pitch_velocity']) # plt.show() ev_angle = 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("""Support me on Patreon for Access to 2024 Apps1"""), 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 & Damage", 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( # "EV vs LA Plot", # href="ev_angle/" # ), 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("id", "Select Batter",batter_dict,width=1), ui.input_select("plot_id", "Select Plot",{'scatter':'Scatter Plot','dist':'Distribution Plot'},width=1), ui.input_action_button("go", "Generate",class_="btn-primary", )), ui.panel_main( ui.output_plot("plot",height = "1000px",width="1000px") ), )),)),server)