Spaces:
Runtime error
Runtime error
| 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 Datasets | |
| dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2023.csv' ]) | |
| dataset_train = dataset['train'] | |
| df_2023_mlb = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True) | |
| ### Import Datasets | |
| dataset = load_dataset('nesticot/mlb_data', data_files=['aaa_pitch_data_2023.csv' ]) | |
| dataset_train = dataset['train'] | |
| df_2023_aaa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True) | |
| df_2023_mlb['level'] = 'MLB' | |
| df_2023_aaa['level'] = 'AAA' | |
| df_2023 = pd.concat([df_2023_mlb,df_2023_aaa]) | |
| #print(df_2023) | |
| ### Normalize Hit Locations | |
| import joblib | |
| swing_model = joblib.load('swing.joblib') | |
| no_swing_model = joblib.load('no_swing.joblib') | |
| # Now you can use the loaded model for prediction or any other task | |
| batter_dict = df_2023.sort_values('batter_name').set_index('batter_id')['batter_name'].to_dict() | |
| ## Make Predictions | |
| ## Define Features and Target | |
| features = ['px','pz','strikes','balls'] | |
| ## Set up 2023 Data for Prediction of Run Expectancy | |
| df_model_2023_no_swing = df_2023[df_2023.is_swing != 1].dropna(subset=features) | |
| df_model_2023_swing = df_2023[df_2023.is_swing == 1].dropna(subset=features) | |
| import xgboost as xgb | |
| df_model_2023_no_swing['y_pred'] = no_swing_model.predict(xgb.DMatrix(df_model_2023_no_swing[features])) | |
| df_model_2023_swing['y_pred'] = swing_model.predict(xgb.DMatrix(df_model_2023_swing[features])) | |
| df_model_2023 = pd.concat([df_model_2023_no_swing,df_model_2023_swing]) | |
| import joblib | |
| # # Dump the model to a file named 'model.joblib' | |
| # model = joblib.load('xtb_model.joblib') | |
| # ## Create a Dataset to calculate xRV/100 Pitches | |
| # df_model_2023['pitcher_name'] = df_model_2023.pitcher.map(pitcher_dict) | |
| # df_model_2023['player_team'] = df_model_2023.batter.map(team_player_dict) | |
| df_model_2023_group = df_model_2023.groupby(['batter_id','batter_name','level']).agg( | |
| pitches = ('start_speed','count'), | |
| y_pred = ('y_pred','mean'), | |
| ) | |
| ## Minimum 500 pitches faced | |
| #min_pitches = 300 | |
| #df_model_2023_group = df_model_2023_group[df_model_2023_group.pitches >= min_pitches] | |
| ## Calculate 20-80 Scale | |
| df_model_2023_group['decision_value'] = zscore(df_model_2023_group['y_pred']) | |
| df_model_2023_group['decision_value'] = (50+df_model_2023_group['decision_value']*10) | |
| ## Create a Dataset to calculate xRV/100 for Pitches Taken | |
| df_model_2023_group_no_swing = df_model_2023[df_model_2023.is_swing!=1].groupby(['batter_id','batter_name','level']).agg( | |
| pitches = ('start_speed','count'), | |
| y_pred = ('y_pred','mean') | |
| ) | |
| # Select Pitches with 500 total pitches | |
| df_model_2023_group_no_swing = df_model_2023_group_no_swing[df_model_2023_group_no_swing.index.get_level_values(1).isin(df_model_2023_group.index.get_level_values(1))] | |
| ## Calculate 20-80 Scale | |
| df_model_2023_group_no_swing['iz_awareness'] = zscore(df_model_2023_group_no_swing['y_pred']) | |
| df_model_2023_group_no_swing['iz_awareness'] = (((50+df_model_2023_group_no_swing['iz_awareness']*10))) | |
| ## Create a Dataset for xRV/100 Pitches Swung At | |
| df_model_2023_group_swing = df_model_2023[df_model_2023.is_swing==1].groupby(['batter_id','batter_name','level']).agg( | |
| pitches = ('start_speed','count'), | |
| y_pred = ('y_pred','mean') | |
| ) | |
| # Select Pitches with 500 total pitches | |
| df_model_2023_group_swing = df_model_2023_group_swing[df_model_2023_group_swing.index.get_level_values(1).isin(df_model_2023_group.index.get_level_values(1))] | |
| ## Calculate 20-80 Scale | |
| df_model_2023_group_swing['oz_awareness'] = zscore(df_model_2023_group_swing['y_pred']) | |
| df_model_2023_group_swing['oz_awareness'] = (((50+df_model_2023_group_swing['oz_awareness']*10))) | |
| ## Create df for plotting | |
| # Merge Datasets | |
| df_model_2023_group_swing_plus_no = df_model_2023_group_swing.merge(df_model_2023_group_no_swing,left_index=True,right_index=True,suffixes=['_swing','_no_swing']) | |
| df_model_2023_group_swing_plus_no['pitches'] = df_model_2023_group_swing_plus_no.pitches_swing + df_model_2023_group_swing_plus_no.pitches_no_swing | |
| # Calculate xRV/100 Pitches | |
| df_model_2023_group_swing_plus_no['y_pred'] = (df_model_2023_group_swing_plus_no.y_pred_swing*df_model_2023_group_swing_plus_no.pitches_swing + \ | |
| df_model_2023_group_swing_plus_no.y_pred_no_swing*df_model_2023_group_swing_plus_no.pitches_no_swing) / \ | |
| df_model_2023_group_swing_plus_no.pitches | |
| df_model_2023_group_swing_plus_no = df_model_2023_group_swing_plus_no.merge(right=df_model_2023_group, | |
| left_index=True, | |
| right_index=True, | |
| suffixes=['','_y']) | |
| df_model_2023_group_swing_plus_no = df_model_2023_group_swing_plus_no.reset_index() | |
| team_dict = df_2023.groupby(['batter_name'])[['batter_id','batter_team']].tail().set_index('batter_id')['batter_team'].to_dict() | |
| df_model_2023_group_swing_plus_no['team'] = df_model_2023_group_swing_plus_no['batter_id'].map(team_dict) | |
| df_model_2023_group_swing_plus_no = df_model_2023_group_swing_plus_no.set_index(['batter_id','batter_name','level','team']) | |
| df_model_2023_group_swing_plus_no = df_model_2023_group_swing_plus_no[df_model_2023_group_swing_plus_no['pitches']>=250] | |
| df_model_2023_group_swing_plus_no_copy = df_model_2023_group_swing_plus_no.copy() | |
| import matplotlib | |
| colour_palette = ['#FFB000','#648FFF','#785EF0', | |
| '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED'] | |
| cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],'#ffffff',colour_palette[0]]) | |
| cmap_hue2 = matplotlib.colors.LinearSegmentedColormap.from_list("",['#ffffff',colour_palette[0]]) | |
| from matplotlib.pyplot import text | |
| import inflect | |
| from scipy.stats import percentileofscore | |
| p = inflect.engine() | |
| def server(input,output,session): | |
| def scatter_plot(): | |
| if input.batter_id() is "": | |
| fig = plt.figure(figsize=(12, 12)) | |
| fig.text(s='Please Select a Batter',x=0.5,y=0.5) | |
| return | |
| print(df_model_2023_group_swing_plus_no_copy) | |
| print(input.level_list()) | |
| df_model_2023_group_swing_plus_no = df_model_2023_group_swing_plus_no_copy[df_model_2023_group_swing_plus_no_copy.index.get_level_values(2) == input.level_list()] | |
| print('this one') | |
| print(df_model_2023_group_swing_plus_no) | |
| batter_select_id = int(input.batter_id()) | |
| # batter_select_name = 'Edouard Julien' | |
| #max(1,int(input.pitch_min())) | |
| plot_min = max(250,int(input.pitch_min())) | |
| df_model_2023_group_swing_plus_no = df_model_2023_group_swing_plus_no[df_model_2023_group_swing_plus_no.pitches >= plot_min] | |
| ## Plot In-Zone vs Out-of-Zone Awareness | |
| sns.set_theme(style="whitegrid", palette="pastel") | |
| # fig, ax = plt.subplots(1,1,figsize=(12,12)) | |
| fig = plt.figure(figsize=(12,12)) | |
| gs = GridSpec(3, 3, height_ratios=[0.6,10,0.2], width_ratios=[0.25,0.50,0.25]) | |
| axheader = fig.add_subplot(gs[0, :]) | |
| #ax10 = fig.add_subplot(gs[1, 0]) | |
| ax = fig.add_subplot(gs[1, :]) # Subplot at the top-right position | |
| #ax12 = fig.add_subplot(gs[1, 2]) | |
| axfooter1 = fig.add_subplot(gs[-1, 0]) | |
| axfooter2 = fig.add_subplot(gs[-1, 1]) | |
| axfooter3 = fig.add_subplot(gs[-1, 2]) | |
| cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],colour_palette[3],colour_palette[0]]) | |
| norm = plt.Normalize(df_model_2023_group_swing_plus_no['y_pred'].min()*100, df_model_2023_group_swing_plus_no['y_pred'].max()*100) | |
| sns.scatterplot( | |
| x=df_model_2023_group_swing_plus_no['y_pred_swing']*100, | |
| y=df_model_2023_group_swing_plus_no['y_pred_no_swing']*100, | |
| hue=df_model_2023_group_swing_plus_no['y_pred']*100, | |
| size=df_model_2023_group_swing_plus_no['pitches_swing']/df_model_2023_group_swing_plus_no['pitches'], | |
| palette=cmap_hue,ax=ax) | |
| sm = plt.cm.ScalarMappable(cmap=cmap_hue, norm=norm) | |
| cbar = plt.colorbar(sm, cax=axfooter2, orientation='horizontal',shrink=1) | |
| cbar.set_label('Decision Value xRV/100 Pitches',fontsize=12) | |
| ax.hlines(xmin=(math.floor((df_model_2023_group_swing_plus_no['y_pred_swing'].min()*100*100-0.01)/5))*5/100, | |
| xmax= (math.ceil((df_model_2023_group_swing_plus_no['y_pred_swing'].max()**100100+0.01)/5))*5/100, | |
| y=df_model_2023_group_swing_plus_no['y_pred_no_swing'].mean()*100,color='gray',linewidth=3,linestyle='dotted',alpha=0.4) | |
| ax.vlines(ymin=(math.floor((df_model_2023_group_swing_plus_no['y_pred_no_swing'].min()*100*100-0.01)/5))*5/100, | |
| ymax= (math.ceil((df_model_2023_group_swing_plus_no['y_pred_no_swing'].max()*100*100+0.01)/5))*5/100, | |
| x=df_model_2023_group_swing_plus_no['y_pred_swing'].mean()*100,color='gray',linewidth=3,linestyle='dotted',alpha=0.4) | |
| x_lim_min = (math.floor((df_model_2023_group_swing_plus_no['y_pred_swing'].min()*100*100)/5))*5/100 | |
| x_lim_max = (math.ceil((df_model_2023_group_swing_plus_no['y_pred_swing'].max()*100*100)/5))*5/100 | |
| y_lim_min = (math.floor((df_model_2023_group_swing_plus_no['y_pred_no_swing'].min()*100*100)/5))*5/100 | |
| y_lim_max = (math.ceil((df_model_2023_group_swing_plus_no['y_pred_no_swing'].max()*100*100)/5))*5/100 | |
| ax.set_xlim(x_lim_min,x_lim_max) | |
| ax.set_ylim(y_lim_min,y_lim_max) | |
| ax.tick_params(axis='both', which='major', labelsize=12) | |
| ax.set_xlabel('Out-of-Zone Awareness Value xRV/100 Swings',fontsize=16) | |
| ax.set_ylabel('In-Zone Awareness Value xRV/100 Takes',fontsize=16) | |
| ax.get_legend().remove() | |
| ts=[] | |
| # thresh = 0.5 | |
| # thresh_2 = -0.9 | |
| # for i in range(len(df_model_2023_group_swing_plus_no)): | |
| # if (df_model_2023_group_swing_plus_no['y_pred'].values[i]*100) >= thresh or \ | |
| # (df_model_2023_group_swing_plus_no['y_pred'].values[i]*100) <= thresh_2 or \ | |
| # (str(df_model_2023_group_swing_plus_no.index.get_level_values(0).values[i]) in (input.name_list())) : | |
| # ts.append(ax.text(x=df_model_2023_group_swing_plus_no['y_pred_swing'].values[i]*100, | |
| # y=df_model_2023_group_swing_plus_no['y_pred_no_swing'].values[i]*100, | |
| # s=df_model_2023_group_swing_plus_no.index.get_level_values(1).values[i], | |
| # fontsize=8)) | |
| thresh = 0.5 | |
| thresh_2 = -0.9 | |
| for i in range(len(df_model_2023_group_swing_plus_no)): | |
| if (df_model_2023_group_swing_plus_no['y_pred_swing'].values[i]) >= df_model_2023_group_swing_plus_no['y_pred_swing'].quantile(0.98) or \ | |
| (df_model_2023_group_swing_plus_no['y_pred_swing'].values[i]) <= df_model_2023_group_swing_plus_no['y_pred_swing'].quantile(0.02) or \ | |
| (df_model_2023_group_swing_plus_no['y_pred_no_swing'].values[i]) >= df_model_2023_group_swing_plus_no['y_pred_no_swing'].quantile(0.98) or \ | |
| (df_model_2023_group_swing_plus_no['y_pred_no_swing'].values[i]) <= df_model_2023_group_swing_plus_no['y_pred_no_swing'].quantile(0.02) or \ | |
| (df_model_2023_group_swing_plus_no['y_pred'].values[i]) >= df_model_2023_group_swing_plus_no['y_pred'].quantile(0.98) or \ | |
| (df_model_2023_group_swing_plus_no['y_pred'].values[i]) <= df_model_2023_group_swing_plus_no['y_pred'].quantile(0.02) or \ | |
| (str(df_model_2023_group_swing_plus_no.index.get_level_values(0).values[i]) in (input.name_list())) : | |
| ts.append(ax.text(x=df_model_2023_group_swing_plus_no['y_pred_swing'].values[i]*100, | |
| y=df_model_2023_group_swing_plus_no['y_pred_no_swing'].values[i]*100, | |
| s=df_model_2023_group_swing_plus_no.index.get_level_values(1).values[i], | |
| fontsize=8)) | |
| ax.text(x=x_lim_min+abs(x_lim_min)*0.02,y=y_lim_max-abs(y_lim_max-y_lim_min)*0.02,s=f'Min. {plot_min} Pitches',fontsize='10',fontstyle='oblique',va='top', | |
| bbox=dict(facecolor='white', edgecolor='black')) | |
| # ax.text(x=x_lim_min+abs(x_lim_min)*0.02,y=y_lim_max-abs(y_lim_max-y_lim_min)*0.06,s=f'Labels for Batters with\nDescion Value xRV/100 > {thresh:.2f}\nDescion Value xRV/100 < {thresh_2:.2f}',fontsize='10',fontstyle='oblique',va='top', | |
| # bbox=dict(facecolor='white', edgecolor='black')) | |
| ax.text(x=x_lim_min+abs(x_lim_min)*0.02,y=y_lim_max-abs(y_lim_max-y_lim_min)*0.06,s=f'Point Size Represents Swing%',fontsize='10',fontstyle='oblique',va='top', | |
| bbox=dict(facecolor='white', edgecolor='black')) | |
| adjust_text(ts, | |
| arrowprops=dict(arrowstyle="-", color=colour_palette[4], lw=1),ax=ax) | |
| axfooter1.axis('off') | |
| axfooter3.axis('off') | |
| axheader.axis('off') | |
| axheader.text(s=f'{input.level_list()} In-Zone vs Out-of-Zone Awareness Value',fontsize=24,x=0.5,y=0,va='top',ha='center') | |
| axfooter1.text(0.05, -0.5,"By: Thomas Nestico\n @TJStats",ha='left', va='bottom',fontsize=12) | |
| axfooter3.text(0.95, -0.5, "Data: MLB",ha='right', va='bottom',fontsize=12) | |
| fig.subplots_adjust(left=0.01, right=0.99, top=0.975, bottom=0.025) | |
| def dv_plot(): | |
| if input.batter_id() is "": | |
| fig = plt.figure(figsize=(12, 12)) | |
| fig.text(s='Please Select a Batter',x=0.5,y=0.5) | |
| return | |
| player_select = int(input.batter_id()) | |
| player_select_full = batter_dict[player_select] | |
| df_will = df_model_2023[df_model_2023.batter_id == player_select].sort_values(by=['game_date','start_time']) | |
| df_will = df_will[df_will['level']==input.level_list()] | |
| # df_will['y_pred'] = df_will['y_pred'] - df_will['y_pred'].mean() | |
| win = max(1,int(input.rolling_window())) | |
| sns.set_theme(style="whitegrid", palette="pastel") | |
| #fig, ax = plt.subplots(1, 1, figsize=(10, 10),dpi=300) | |
| from matplotlib.gridspec import GridSpec | |
| # fig,ax = plt.subplots(figsize=(12, 12),dpi=150) | |
| fig = plt.figure(figsize=(12,12)) | |
| gs = GridSpec(3, 3, height_ratios=[0.3,10,0.2], width_ratios=[0.01,2,0.01]) | |
| axheader = fig.add_subplot(gs[0, :]) | |
| ax10 = fig.add_subplot(gs[1, 0]) | |
| ax = fig.add_subplot(gs[1, 1]) # Subplot at the top-right position | |
| ax12 = fig.add_subplot(gs[1, 2]) | |
| axfooter1 = fig.add_subplot(gs[-1, :]) | |
| axheader.axis('off') | |
| ax10.axis('off') | |
| ax12.axis('off') | |
| axfooter1.axis('off') | |
| sns.lineplot( x= range(win,len(df_will.y_pred.rolling(window=win).mean())+1), | |
| y= df_will.y_pred.rolling(window=win).mean().dropna()*100, | |
| color=colour_palette[0],linewidth=2,ax=ax,zorder=100) | |
| ax.hlines(y=df_will.y_pred.mean()*100,xmin=win,xmax=len(df_will),color=colour_palette[0],linestyle='--', | |
| label=f'{player_select_full} Average: {df_will.y_pred.mean()*100:.2} xRV/100 ({p.ordinal(int(np.around(percentileofscore(df_model_2023_group_swing_plus_no.y_pred,df_will.y_pred.mean(), kind="strict"))))} Percentile)') | |
| # ax.hlines(y=df_model_2023.y_pred.std()*100,xmin=win,xmax=len(df_will)) | |
| # sns.scatterplot( x= [976], | |
| # y= df_will.y_pred.rolling(window=win).mean().min()*100, | |
| # color=colour_palette[0],linewidth=2,ax=ax,zorder=100,s=100,edgecolor=colour_palette[7]) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred.mean()*100,xmin=win,xmax=len(df_will),color=colour_palette[1],linestyle='-.',alpha=1, | |
| label = f'{input.level_list()} Average: {df_model_2023_group_swing_plus_no.y_pred.mean()*100:.2f} xRV/100') | |
| ax.legend() | |
| hard_hit_dates = [df_model_2023_group_swing_plus_no.y_pred.quantile(0.9)*100, | |
| df_model_2023_group_swing_plus_no.y_pred.quantile(0.75)*100, | |
| df_model_2023_group_swing_plus_no.y_pred.quantile(0.25)*100, | |
| df_model_2023_group_swing_plus_no.y_pred.quantile(0.1)*100] | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred.quantile(0.9)*100,xmin=win,xmax=len(df_will),color=colour_palette[2],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred.quantile(0.75)*100,xmin=win,xmax=len(df_will),color=colour_palette[3],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred.quantile(0.25)*100,xmin=win,xmax=len(df_will),color=colour_palette[4],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred.quantile(0.1)*100,xmin=win,xmax=len(df_will),color=colour_palette[5],linestyle='dotted',alpha=0.5,zorder=1) | |
| hard_hit_text = ['90th %','75th %','25th %','10th %'] | |
| for i, x in enumerate(hard_hit_dates): | |
| ax.text(min(win+win/1000,win+win+5), x ,hard_hit_text[i], rotation=0,va='center', ha='left', | |
| bbox=dict(facecolor='white',alpha=0.7, edgecolor=colour_palette[2+i], pad=2),zorder=11) | |
| # # Annotate with an arrow | |
| # ax.annotate('June 6, 2023\nSeason Worst Decision Value', xy=(976, df_will.y_pred.rolling(window=win).mean().min()*100-0.03), | |
| # xytext=(976 - 150, df_will.y_pred.rolling(window=win).mean().min()*100 - 0.2), | |
| # arrowprops=dict(facecolor=colour_palette[7], shrink=0.01),zorder=150,fontsize=10, | |
| # bbox=dict(facecolor='white', edgecolor='black'),va='top') | |
| ax.set_xlim(win,len(df_will)) | |
| #ax.set_ylim(-1.5,1.5) | |
| ax.set_yticks([-1.5,-1,-0.5,0,0.5,1,1.5]) | |
| ax.set_xlabel('Pitch') | |
| ax.set_ylabel('Expected Run Value Added per 100 Pitches (xRV/100)') | |
| axheader.text(s=f'{player_select_full} - {input.level_list()} - {win} Pitch Rolling Swing Decision Expected Run Value Added',x=0.5,y=-0.5,ha='center',va='bottom',fontsize=14) | |
| axfooter1.text(.05, 0.2, "By: Thomas Nestico",ha='left', va='bottom',fontsize=12) | |
| axfooter1.text(0.95, 0.2, "Data: MLB",ha='right', va='bottom',fontsize=12) | |
| fig.subplots_adjust(left=0.01, right=0.99, top=0.98, bottom=0.02) | |
| #fig.set_facecolor(colour_palette[5]) | |
| def iz_plot(): | |
| if input.batter_id() is "": | |
| fig = plt.figure(figsize=(12, 12)) | |
| fig.text(s='Please Select a Batter',x=0.5,y=0.5) | |
| return | |
| player_select = int(input.batter_id()) | |
| player_select_full = batter_dict[player_select] | |
| df_will = df_model_2023[df_model_2023.batter_id == player_select].sort_values(by=['game_date','start_time']) | |
| df_will = df_will[df_will['level']==input.level_list()] | |
| df_will = df_will[df_will['is_swing'] != 1] | |
| win = max(1,int(input.rolling_window())) | |
| sns.set_theme(style="whitegrid", palette="pastel") | |
| #fig, ax = plt.subplots(1, 1, figsize=(10, 10),dpi=300) | |
| from matplotlib.gridspec import GridSpec | |
| # fig,ax = plt.subplots(figsize=(12, 12),dpi=150) | |
| fig = plt.figure(figsize=(12,12)) | |
| gs = GridSpec(3, 3, height_ratios=[0.3,10,0.2], width_ratios=[0.01,2,0.01]) | |
| axheader = fig.add_subplot(gs[0, :]) | |
| ax10 = fig.add_subplot(gs[1, 0]) | |
| ax = fig.add_subplot(gs[1, 1]) # Subplot at the top-right position | |
| ax12 = fig.add_subplot(gs[1, 2]) | |
| axfooter1 = fig.add_subplot(gs[-1, :]) | |
| axheader.axis('off') | |
| ax10.axis('off') | |
| ax12.axis('off') | |
| axfooter1.axis('off') | |
| sns.lineplot( x= range(win,len(df_will.y_pred.rolling(window=win).mean())+1), | |
| y= df_will.y_pred.rolling(window=win).mean().dropna()*100, | |
| color=colour_palette[0],linewidth=2,ax=ax,zorder=100) | |
| ax.hlines(y=df_will.y_pred.mean()*100,xmin=win,xmax=len(df_will),color=colour_palette[0],linestyle='--', | |
| label=f'{player_select_full} Average: {df_will.y_pred.mean()*100:.2} xRV/100 ({p.ordinal(int(np.around(percentileofscore(df_model_2023_group_swing_plus_no.y_pred_no_swing,df_will.y_pred.mean(), kind="strict"))))} Percentile)') | |
| # ax.hlines(y=df_model_2023.y_pred_no_swing.std()*100,xmin=win,xmax=len(df_will)) | |
| # sns.scatterplot( x= [976], | |
| # y= df_will.y_pred.rolling(window=win).mean().min()*100, | |
| # color=colour_palette[0],linewidth=2,ax=ax,zorder=100,s=100,edgecolor=colour_palette[7]) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_no_swing.mean()*100,xmin=win,xmax=len(df_will),color=colour_palette[1],linestyle='-.',alpha=1, | |
| label = f'{input.level_list()} Average: {df_model_2023_group_swing_plus_no.y_pred_no_swing.mean()*100:.2} xRV/100') | |
| ax.legend() | |
| hard_hit_dates = [df_model_2023_group_swing_plus_no.y_pred_no_swing.quantile(0.9)*100, | |
| df_model_2023_group_swing_plus_no.y_pred_no_swing.quantile(0.75)*100, | |
| df_model_2023_group_swing_plus_no.y_pred_no_swing.quantile(0.25)*100, | |
| df_model_2023_group_swing_plus_no.y_pred_no_swing.quantile(0.1)*100] | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_no_swing.quantile(0.9)*100,xmin=win,xmax=len(df_will),color=colour_palette[2],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_no_swing.quantile(0.75)*100,xmin=win,xmax=len(df_will),color=colour_palette[3],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_no_swing.quantile(0.25)*100,xmin=win,xmax=len(df_will),color=colour_palette[4],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_no_swing.quantile(0.1)*100,xmin=win,xmax=len(df_will),color=colour_palette[5],linestyle='dotted',alpha=0.5,zorder=1) | |
| hard_hit_text = ['90th %','75th %','25th %','10th %'] | |
| for i, x in enumerate(hard_hit_dates): | |
| ax.text(min(win+win/1000,win+win+5), x ,hard_hit_text[i], rotation=0,va='center', ha='left', | |
| bbox=dict(facecolor='white',alpha=0.7, edgecolor=colour_palette[2+i], pad=2),zorder=11) | |
| # # Annotate with an arrow | |
| # ax.annotate('June 6, 2023\nSeason Worst Decision Value', xy=(976, df_will.y_pred.rolling(window=win).mean().min()*100-0.03), | |
| # xytext=(976 - 150, df_will.y_pred.rolling(window=win).mean().min()*100 - 0.2), | |
| # arrowprops=dict(facecolor=colour_palette[7], shrink=0.01),zorder=150,fontsize=10, | |
| # bbox=dict(facecolor='white', edgecolor='black'),va='top') | |
| ax.set_xlim(win,len(df_will)) | |
| ax.set_yticks([1.0,1.5,2.0,2.5,3.0]) | |
| # ax.set_ylim(1,3) | |
| ax.set_xlabel('Takes') | |
| ax.set_ylabel('Expected Run Value Added per 100 Pitches (xRV/100)') | |
| axheader.text(s=f'{player_select_full} - {input.level_list()} - {win} Pitch Rolling In-Zone Awareness Expected Run Value Added',x=0.5,y=-0.5,ha='center',va='bottom',fontsize=14) | |
| axfooter1.text(.05, 0.2, "By: Thomas Nestico",ha='left', va='bottom',fontsize=12) | |
| axfooter1.text(0.95, 0.2, "Data: MLB",ha='right', va='bottom',fontsize=12) | |
| fig.subplots_adjust(left=0.01, right=0.99, top=0.98, bottom=0.02) | |
| def oz_plot(): | |
| if input.batter_id() is "": | |
| fig = plt.figure(figsize=(12, 12)) | |
| fig.text(s='Please Select a Batter',x=0.5,y=0.5) | |
| return | |
| player_select = int(input.batter_id()) | |
| player_select_full = batter_dict[player_select] | |
| df_will = df_model_2023[df_model_2023.batter_id == player_select].sort_values(by=['game_date','start_time']) | |
| df_will = df_will[df_will['level']==input.level_list()] | |
| df_will = df_will[df_will['is_swing'] == 1] | |
| win = max(1,int(input.rolling_window())) | |
| sns.set_theme(style="whitegrid", palette="pastel") | |
| #fig, ax = plt.subplots(1, 1, figsize=(10, 10),dpi=300) | |
| from matplotlib.gridspec import GridSpec | |
| # fig,ax = plt.subplots(figsize=(12, 12),dpi=150) | |
| fig = plt.figure(figsize=(12,12)) | |
| gs = GridSpec(3, 3, height_ratios=[0.3,10,0.2], width_ratios=[0.01,2,0.01]) | |
| axheader = fig.add_subplot(gs[0, :]) | |
| ax10 = fig.add_subplot(gs[1, 0]) | |
| ax = fig.add_subplot(gs[1, 1]) # Subplot at the top-right position | |
| ax12 = fig.add_subplot(gs[1, 2]) | |
| axfooter1 = fig.add_subplot(gs[-1, :]) | |
| axheader.axis('off') | |
| ax10.axis('off') | |
| ax12.axis('off') | |
| axfooter1.axis('off') | |
| sns.lineplot( x= range(win,len(df_will.y_pred.rolling(window=win).mean())+1), | |
| y= df_will.y_pred.rolling(window=win).mean().dropna()*100, | |
| color=colour_palette[0],linewidth=2,ax=ax,zorder=100) | |
| ax.hlines(y=df_will.y_pred.mean()*100,xmin=win,xmax=len(df_will),color=colour_palette[0],linestyle='--', | |
| label=f'{player_select_full} Average: {df_will.y_pred.mean()*100:.2} xRV/100 ({p.ordinal(int(np.around(percentileofscore(df_model_2023_group_swing_plus_no.y_pred_swing,df_will.y_pred.mean(), kind="strict"))))} Percentile)') | |
| # ax.hlines(y=df_model_2023.y_pred_swing.std()*100,xmin=win,xmax=len(df_will)) | |
| # sns.scatterplot( x= [976], | |
| # y= df_will.y_pred.rolling(window=win).mean().min()*100, | |
| # color=colour_palette[0],linewidth=2,ax=ax,zorder=100,s=100,edgecolor=colour_palette[7]) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_swing.mean()*100,xmin=win,xmax=len(df_will),color=colour_palette[1],linestyle='-.',alpha=1, | |
| label = f'{input.level_list()} Average: {df_model_2023_group_swing_plus_no.y_pred_swing.mean()*100:.2} xRV/100') | |
| ax.legend() | |
| hard_hit_dates = [df_model_2023_group_swing_plus_no.y_pred_swing.quantile(0.9)*100, | |
| df_model_2023_group_swing_plus_no.y_pred_swing.quantile(0.75)*100, | |
| df_model_2023_group_swing_plus_no.y_pred_swing.quantile(0.25)*100, | |
| df_model_2023_group_swing_plus_no.y_pred_swing.quantile(0.1)*100] | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_swing.quantile(0.9)*100,xmin=win,xmax=len(df_will),color=colour_palette[2],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_swing.quantile(0.75)*100,xmin=win,xmax=len(df_will),color=colour_palette[3],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_swing.quantile(0.25)*100,xmin=win,xmax=len(df_will),color=colour_palette[4],linestyle='dotted',alpha=0.5,zorder=1) | |
| ax.hlines(y=df_model_2023_group_swing_plus_no.y_pred_swing.quantile(0.1)*100,xmin=win,xmax=len(df_will),color=colour_palette[5],linestyle='dotted',alpha=0.5,zorder=1) | |
| hard_hit_text = ['90th %','75th %','25th %','10th %'] | |
| for i, x in enumerate(hard_hit_dates): | |
| ax.text(min(win+win/1000,win+win+5), x ,hard_hit_text[i], rotation=0,va='center', ha='left', | |
| bbox=dict(facecolor='white',alpha=0.7, edgecolor=colour_palette[2+i], pad=2),zorder=11) | |
| # # Annotate with an arrow | |
| # ax.annotate('June 6, 2023\nSeason Worst Decision Value', xy=(976, df_will.y_pred.rolling(window=win).mean().min()*100-0.03), | |
| # xytext=(976 - 150, df_will.y_pred.rolling(window=win).mean().min()*100 - 0.2), | |
| # arrowprops=dict(facecolor=colour_palette[7], shrink=0.01),zorder=150,fontsize=10, | |
| # bbox=dict(facecolor='white', edgecolor='black'),va='top') | |
| ax.set_xlim(win,len(df_will)) | |
| #ax.set_ylim(-3.25,-1.25) | |
| ax.set_yticks([-3.25,-2.75,-2.25,-1.75,-1.25]) | |
| ax.set_xlabel('Swing') | |
| ax.set_ylabel('Expected Run Value Added per 100 Pitches (xRV/100)') | |
| axheader.text(s=f'{player_select_full} - {input.level_list()} - {win} Pitch Rolling Out of Zone Awareness Expected Run Value Added',x=0.5,y=-0.5,ha='center',va='bottom',fontsize=14) | |
| axfooter1.text(.05, 0.2, "By: Thomas Nestico",ha='left', va='bottom',fontsize=12) | |
| axfooter1.text(0.95, 0.2, "Data: MLB",ha='right', va='bottom',fontsize=12) | |
| fig.subplots_adjust(left=0.01, right=0.99, top=0.98, bottom=0.02) | |
| decision_value = 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_numeric("pitch_min", | |
| "Select Pitch Minimum [min. 250] (Scatter)", | |
| value=500, | |
| min=250), | |
| ui.input_select("name_list", | |
| "Select Players to List (Scatter)", | |
| batter_dict, | |
| selectize=True, | |
| multiple=True), | |
| ui.input_select("batter_id", | |
| "Select Batter (Rolling)", | |
| batter_dict, | |
| width=1, | |
| size=1, | |
| selectize=True), | |
| ui.input_numeric("rolling_window", | |
| "Select Rolling Window (Rolling)", | |
| value=100, | |
| min=1), | |
| ui.input_select("level_list", | |
| "Select Level", | |
| ['MLB','AAA'], | |
| selected='MLB'), | |
| ui.input_action_button("go", "Generate",class_="btn-primary"), | |
| ), | |
| ui.panel_main( | |
| ui.navset_tab( | |
| ui.nav("Scatter Plot", | |
| ui.output_plot('scatter_plot', | |
| width='1000px', | |
| height='1000px')), | |
| ui.nav("Rolling DV", | |
| ui.output_plot('dv_plot', | |
| width='1000px', | |
| height='1000px')), | |
| ui.nav("Rolling In-Zone", | |
| ui.output_plot('iz_plot', | |
| width='1000px', | |
| height='1000px')), | |
| ui.nav("Rolling Out-of-Zone", | |
| ui.output_plot('oz_plot', | |
| width='1000px', | |
| height='1000px')) | |
| )) | |
| )),)),server) |