Spaces:
Running
Running
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)) | |
def server(input,output,session): | |
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("""<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 & 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) |