2024_spray / ev_angle.py
nesticot's picture
Upload 13 files
30629a5 verified
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("""<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)