nesticot's picture
Update app.py
9a21941 verified
raw
history blame
38.4 kB
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
#import pitch_summary_functions as psf
import requests
import matplotlib
from api_scraper import MLB_Scrape
from shinywidgets import output_widget, render_widget
import shinyswatch
season = 2024
level = 'mlb'
colour_palette = ['#FFB000','#648FFF','#785EF0',
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
import datasets
from datasets import load_dataset
### Import Datasets
dataset = load_dataset('nesticot/mlb_data', data_files=[f'{level}_pitch_data_{season}.csv' ])
dataset_train = dataset['train']
df_2024 = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True).drop_duplicates(subset=['play_id'],keep='last')
# df_2024 = pd.read_csv('C:/Users/thoma/Google Drive/Python/Baseball/season_stats/2024/2024_regular_data.csv',index_col=[0])
# ### Import Datasets
# import datasets
# from datasets import load_dataset
# dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2020.csv' ])
# dataset_train = dataset['train']
# df_2024 = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
### PITCH COLOURS ###
pitch_colours = {
'Four-Seam Fastball':'#FF007D',#BC136F
'Sinker':'#98165D',#DC267F
'Cutter':'#BE5FA0',
'Changeup':'#F79E70',#F75233
'Splitter':'#FE6100',#F75233
'Screwball':'#F08223',
'Forkball':'#FFB000',
'Slider':'#67E18D',#1BB999#785EF0
'Sweeper':'#1BB999',#37CD85#904039
'Slurve':'#376748',#785EF0#549C07#BEABD8
'Knuckle Curve':'#311D8B',
'Curveball':'#3025CE',
'Slow Curve':'#274BFC',
'Eephus':'#648FFF',
'Knuckleball':'#867A08',
'Pitch Out':'#472C30',
'Other':'#9C8975',
}
import pitcher_update as pu
df_2024 = pu.df_update(df_2024)
# DEFINE STRIKE ZONE
strike_zone = pd.DataFrame({
'PlateLocSide': [-0.9, -0.9, 0.9, 0.9, -0.9],
'PlateLocHeight': [1.5, 3.5, 3.5, 1.5, 1.5]
})
### STRIKE ZONE ###
def draw_line(axis,alpha_spot=1,catcher_p = True):
axis.plot(strike_zone['PlateLocSide'], strike_zone['PlateLocHeight'], color='black', linewidth=1.3,zorder=3,alpha=alpha_spot,)
# ax.plot([-0.2833333, -0.2833333], [1.6, 3.5], color='black', linestyle='dashed',alpha=alpha_spot,zorder=3)
# ax.plot([0.2833333, 0.2833333], [1.6, 3.5], color='black', linestyle='dashed',alpha=alpha_spot,zorder=3)
# ax.plot([-0.85, 0.85], [2.2, 2.2], color='black', linestyle='dashed',alpha=alpha_spot,zorder=3)
# ax.plot([-0.85, 0.85], [2.9, 2.9], color='black', linestyle='dashed',alpha=alpha_spot,zorder=3)
if catcher_p:
# Add dashed line
# Add home plate
axis.plot([-0.708, 0.708], [0.15, 0.15], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
axis.plot([-0.708, -0.708], [0.15, 0.3], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
axis.plot([-0.708, 0], [0.3, 0.5], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
axis.plot([0, 0.708], [0.5, 0.3], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
axis.plot([0.708, 0.708], [0.3, 0.15], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
else:
axis.plot([-0.708, 0.708], [0.4, 0.4], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
axis.plot([-0.708, -0.9], [0.4, -0.1], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
axis.plot([-0.9, 0], [-0.1, -0.35], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
axis.plot([0, 0.9], [-.35, -0.1], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
axis.plot([0.9, 0.708], [-0.1,0.4], color='black', linewidth=1,alpha=alpha_spot,zorder=1)
pitcher_dicts = df_2024.set_index('pitcher_id')['pitcher_name'].sort_values().to_dict()
team_logos = pd.read_csv('team_logos.csv')
cmap_sum = matplotlib.colors.LinearSegmentedColormap.from_list("", ['#FFFFFF','#FFB000','#FE6100'])
cmap_sum2 = matplotlib.colors.LinearSegmentedColormap.from_list("", ['#FFFFFF','#FFB000','#FE6100'])
from urllib.request import Request, urlopen
from shiny import App, reactive, ui, render
from shiny.ui import h2, tags
# importing OpenCV(cv2) module
app_ui = ui.page_fluid(
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.tags.h5("Pitcher Heat Maps"),
ui.row(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_select('player_id','Select Player',pitcher_dicts,selectize=True,multiple=False),
ui.output_ui('game_id_select','Date Range'),
ui.output_ui('pitch_type_select','Select Pitch Type'),
ui.input_action_button("go", "Generate",class_="btn-primary"),width=2
),
ui.panel_main(
ui.navset_tab(
# ui.nav("Raw Data",
# ui.output_data_frame("raw_table")),
ui.nav("Season Summary",
ui.output_plot('plot',
width='1600px',
height='900px')),id="my_tabs"))))))
#print(app_ui)
def server(input, output, session):
@render.ui
def game_id_select():
# @reactive.Effect
if input.my_tabs() == 'Season Summary':
return ui.input_date_range("date_range_id", "Date range input",start = df_2024.game_date.min(),
end = df_2024.game_date.max(),width=2,min=df_2024.game_date.min(),
max=df_2024.game_date.max()),
@render.ui
def pitch_type_select():
if input.player_id() == '':
return ui.input_select('pitch_type','Select a Pitcher',{'pitch':''},selectize=True,multiple=False)
else:
pitch_dicts = df_2024[(df_2024['pitcher_id']==int(input.player_id()))].set_index('pitch_type')['pitch_description'].sort_values().to_dict()
# @reactive.Effect
return ui.input_select('pitch_type','Select Pitch Type',pitch_dicts,selectize=True,multiple=False)
@output
@render.plot
@reactive.event(input.go, ignore_none=False)
def plot():
#fig, ax = plt.subplots(3, 2, figsize=(9, 9))
font_properties = {'family': 'calibi', 'size': 12}
font_properties_titles = {'family': 'calibi', 'size': 20}
font_properties_axes = {'family': 'calibi', 'size': 16}
if len((input.player_id()))<1:
fig, ax = plt.subplots(1, 1, figsize=(9, 9))
ax.text(x=0.5,y=0.5,s='Please Select\nA Player',fontsize=150,ha='center')
ax.grid('off')
return
pitcher_input = int(input.player_id())
pitch_input = input.pitch_type()
df_plot_full = df_2024[(df_2024['pitcher_id']==pitcher_input)&
(pd.to_datetime(df_2024['game_date']).dt.date>=input.date_range_id()[0])&
(pd.to_datetime(df_2024['game_date']).dt.date<=input.date_range_id()[1])]
df_plot_full['pitch_count_hand'] = df_plot_full.groupby(['pitcher_id','batter_hand'])['start_speed'].transform('count')
df_plot_full['h_s_b'] = df_plot_full.groupby(['batter_hand','strikes', 'balls']).transform('count')['pitcher_id']
df_plot_full['h_s_b_pitch'] = df_plot_full.groupby(['batter_hand','strikes', 'balls','pitch_type']).transform('count')['pitcher_id']
df_plot_full['h_s_b_pitch_percent'] = df_plot_full['h_s_b_pitch']/df_plot_full['h_s_b']
df_plot = df_plot_full[(df_plot_full['pitch_type']==pitch_input)]
print("THIS IS HERE")
print(df_plot)
pivot_table_l = df_plot[df_plot['batter_hand'].isin(['L'])].groupby(['batter_hand','strikes', 'balls'])[['h_s_b_pitch_percent']].mean().reset_index().pivot('strikes','balls','h_s_b_pitch_percent')#.fillna(0).style.background_gradient(cmap=cmap_sum2, axis=None).format("{:.0%}")
# Create a new index and columns range
new_index = range(3)
new_columns = range(4)
# Reindex the pivot table
pivot_table_l = pivot_table_l.reindex(index=new_index, columns=new_columns)
# Fill any missing values with 0
pivot_table_l = pivot_table_l.fillna(0)
pivot_table_l = df_plot[df_plot['batter_hand']=='L'].groupby(['batter_hand','strikes', 'balls'])[['h_s_b_pitch_percent']].mean().reset_index().pivot('strikes','balls','h_s_b_pitch_percent')#.fillna(0).style.background_gradient(cmap=cmap_sum2, axis=None).format("{:.0%}")
# Create a new index and columns range
new_index = range(3)
new_columns = range(4)
# Reindex the pivot table
pivot_table_l = pivot_table_l.reindex(index=new_index, columns=new_columns)
# Fill any missing values with 0
pivot_table_l = pivot_table_l.fillna(0)
pivot_table_r = df_plot[df_plot['batter_hand']=='R'].groupby(['batter_hand','strikes', 'balls'])[['h_s_b_pitch_percent']].mean().reset_index().pivot('strikes','balls','h_s_b_pitch_percent')#.fillna(0).style.background_gradient(cmap=cmap_sum2, axis=None).format("{:.0%}")
# Create a new index and columns range
new_index = range(3)
new_columns = range(4)
# Reindex the pivot table
pivot_table_r = pivot_table_r.reindex(index=new_index, columns=new_columns)
# Fill any missing values with 0
pivot_table_r = pivot_table_r.fillna(0)
# Assuming you have a DataFrame called 'df_plot_full' with columns 'pitch_type', 'strikes', and 'balls'
# Filter the dataset to include only slider pitches
# slider_pitches = df_plot_full[df_plot_full['pitch_type'] == 'SL']
# Group the filtered dataset by strike and ball counts
# grouped_counts = slider_pitches.groupby(['pitcher_hand','strikes', 'balls']).size().reset_index(name='total_pitches')
# Calculate the proportion of slider pitches for each strike and ball count
# grouped_counts['proportion'] = grouped_counts['total_pitches'] / grouped_counts['total_pitches'].sum()
# Print the resulting DataFrame
df_summ = df_plot.groupby(['batter_hand']).agg(
pitch_count = ('pitch_count_hand','max'),
pa = ('pa','sum'),
ab = ('ab','sum'),
obp_pa = ('obp','sum'),
hits = ('hits','sum'),
on_base = ('on_base','sum'),
k = ('k','sum'),
bb = ('bb','sum'),
bb_minus_k = ('bb_minus_k','sum'),
csw = ('csw','sum'),
bip = ('bip','sum'),
bip_div = ('bip_div','sum'),
tb = ('tb','sum'),
woba = ('woba','sum'),
woba_contact = ('woba_contact','sum'),
xwoba = ('woba_pred','sum'),
xwoba_contact = ('woba_pred_contact','sum'),
woba_codes = ('woba_codes','sum'),
hard_hit = ('hard_hit','sum'),
barrel = ('barrel','sum'),
sweet_spot = ('sweet_spot','sum'),
max_launch_speed = ('launch_speed','max'),
launch_speed = ('launch_speed','mean'),
launch_angle = ('launch_angle','mean'),
pitches = ('is_pitch','sum'),
swings = ('swings','sum'),
in_zone = ('in_zone','sum'),
out_zone = ('out_zone','sum'),
whiffs = ('whiffs','sum'),
zone_swing = ('zone_swing','sum'),
zone_contact = ('zone_contact','sum'),
ozone_swing = ('ozone_swing','sum'),
ozone_contact = ('ozone_contact','sum'),
ground_ball = ('trajectory_ground_ball','sum'),
line_drive = ('trajectory_line_drive','sum'),
fly_ball =('trajectory_fly_ball','sum'),
pop_up = ('trajectory_popup','sum'),
attack_zone = ('attack_zone','count'),
heart = ('heart','sum'),
shadow = ('shadow','sum'),
chase = ('chase','sum'),
waste = ('waste','sum'),
heart_swing = ('heart_swing','sum'),
shadow_swing = ('shadow_swing','sum'),
chase_swing = ('chase_swing','sum'),
waste_swing = ('waste_swing','sum'),
heart_whiff = ('heart_whiff','sum'),
shadow_whiff = ('shadow_whiff','sum'),
chase_whiff = ('chase_whiff','sum'),
waste_whiff = ('waste_whiff','sum'),
).reset_index()
df_summ['avg'] = [df_summ.hits[x]/df_summ.ab[x] if df_summ.ab[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['obp'] = [df_summ.on_base[x]/df_summ.obp_pa[x] if df_summ.obp_pa[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['slg'] = [df_summ.tb[x]/df_summ.ab[x] if df_summ.ab[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['ops'] = df_summ['obp']+df_summ['slg']
df_summ['k_percent'] = [df_summ.k[x]/df_summ.pa[x] if df_summ.pa[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['bb_percent'] =[df_summ.bb[x]/df_summ.pa[x] if df_summ.pa[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['bb_minus_k_percent'] =[(df_summ.bb_minus_k[x])/df_summ.pa[x] if df_summ.pa[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['bb_over_k_percent'] =[df_summ.bb[x]/df_summ.k[x] if df_summ.k[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['csw_percent'] =[df_summ.csw[x]/df_summ.pitches[x] if df_summ.pitches[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['sweet_spot_percent'] = [df_summ.sweet_spot[x]/df_summ.bip_div[x] if df_summ.bip_div[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['woba_percent'] = [df_summ.woba[x]/df_summ.woba_codes[x] if df_summ.woba_codes[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['woba_percent_contact'] = [df_summ.woba_contact[x]/df_summ.bip[x] if df_summ.bip[x] != 0 else np.nan for x in range(len(df_summ))]
#df_summ['hard_hit_percent'] = [df_summ.sweet_spot[x]/df_summ.bip[x] if df_summ.bip[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['hard_hit_percent'] = [df_summ.hard_hit[x]/df_summ.bip_div[x] if df_summ.bip_div[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['barrel_percent'] = [df_summ.barrel[x]/df_summ.bip_div[x] if df_summ.bip_div[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['zone_contact_percent'] = [df_summ.zone_contact[x]/df_summ.zone_swing[x] if df_summ.zone_swing[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['zone_swing_percent'] = [df_summ.zone_swing[x]/df_summ.in_zone[x] if df_summ.in_zone[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['zone_percent'] = [df_summ.in_zone[x]/df_summ.pitches[x] if df_summ.pitches[x] > 0 else np.nan for x in range(len(df_summ))]
df_summ['chase_percent'] = [df_summ.ozone_swing[x]/(df_summ.pitches[x] - df_summ.in_zone[x]) if (df_summ.pitches[x]- df_summ.in_zone[x]) != 0 else np.nan for x in range(len(df_summ))]
df_summ['chase_contact'] = [df_summ.ozone_contact[x]/df_summ.ozone_swing[x] if df_summ.ozone_swing[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['swing_percent'] = [df_summ.swings[x]/df_summ.pitches[x] if df_summ.pitches[x] > 0 else np.nan for x in range(len(df_summ))]
df_summ['whiff_rate'] = [df_summ.whiffs[x]/df_summ.swings[x] if df_summ.swings[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['swstr_rate'] = [df_summ.whiffs[x]/df_summ.pitches[x] if df_summ.pitches[x] > 0 else np.nan for x in range(len(df_summ))]
df_summ['ground_ball_percent'] = [df_summ.ground_ball[x]/df_summ.bip[x] if df_summ.bip[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['line_drive_percent'] = [df_summ.line_drive[x]/df_summ.bip[x] if df_summ.bip[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['fly_ball_percent'] = [df_summ.fly_ball[x]/df_summ.bip[x] if df_summ.bip[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['pop_up_percent'] = [df_summ.pop_up[x]/df_summ.bip[x] if df_summ.bip[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['heart_zone_percent'] = [df_summ.heart[x]/df_summ.attack_zone[x] if df_summ.attack_zone[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['shadow_zone_percent'] = [df_summ.shadow[x]/df_summ.attack_zone[x] if df_summ.attack_zone[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['chase_zone_percent'] = [df_summ.chase[x]/df_summ.attack_zone[x] if df_summ.attack_zone[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['waste_zone_percent'] = [df_summ.waste[x]/df_summ.attack_zone[x] if df_summ.attack_zone[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['heart_zone_swing_percent'] = [df_summ.heart_swing[x]/df_summ.heart[x] if df_summ.heart[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['shadow_zone_swing_percent'] = [df_summ.shadow_swing[x]/df_summ.shadow[x] if df_summ.shadow[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['chase_zone_swing_percent'] = [df_summ.chase_swing[x]/df_summ.chase[x] if df_summ.chase[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['waste_zone_swing_percent'] = [df_summ.waste_swing[x]/df_summ.waste[x] if df_summ.waste[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['heart_zone_whiff_percent'] = [df_summ.heart_whiff[x]/df_summ.heart_swing[x] if df_summ.heart_swing[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['shadow_zone_whiff_percent'] = [df_summ.shadow_whiff[x]/df_summ.shadow_swing[x] if df_summ.shadow_swing[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['chase_zone_whiff_percent'] = [df_summ.chase_whiff[x]/df_summ.chase_swing[x] if df_summ.chase_swing[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['waste_zone_whiff_percent'] = [df_summ.waste_whiff[x]/df_summ.waste_swing[x] if df_summ.waste_swing[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['xwoba_percent'] = [df_summ.xwoba[x]/df_summ.woba_codes[x] if df_summ.woba_codes[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['xwoba_percent_contact'] = [df_summ.xwoba_contact[x]/df_summ.bip[x] if df_summ.bip[x] != 0 else np.nan for x in range(len(df_summ))]
df_summ['pitch_percent'] = [df_summ.pitches[x]/df_summ.pitch_count[x] if df_summ.pitch_count[x] != 0 else np.nan for x in range(len(df_summ))]
table_left = df_summ[df_summ['batter_hand']=='L'][['pitch_percent',
'pitches',
'heart_zone_percent',
'shadow_zone_percent',
'chase_zone_percent',
'waste_zone_percent',
'csw_percent',
'whiff_rate',
'chase_percent',
'bip',
'xwoba_percent_contact'
]]
### GET COLOURS##
import matplotlib.colors
import matplotlib.colors as mcolors
def get_color(value,normalize):
color = cmap_sum(normalize(value))
return mcolors.to_hex(color)
try:
normalize = mcolors.Normalize(vmin=table_left['pitch_percent']*0.5,
vmax=table_left['pitch_percent']*1.5) # Define the range of values
except ValueError:
normalize = mcolors.Normalize(vmin=0,
vmax=1) # Define the range of values
df_colour_left = pd.DataFrame(data=[[get_color(x,normalize) for x in pivot_table_l.loc[0]],
[get_color(x,normalize) for x in pivot_table_l.loc[1]],
[get_color(x,normalize) for x in pivot_table_l.loc[2]]],)
table_left['pitch_percent'] = table_left['pitch_percent'].map('{:.1%}'.format)
table_left['pitches'] = table_left['pitches'].astype(int).astype(str)
# table_left['pa'] = table_left['pa'].astype(int).astype(str)
# table_left['k_percent'] = table_left['k_percent'].map('{:.1%}'.format)
# table_left['bb_percent'] = table_left['bb_percent'].map('{:.1%}'.format)
table_left['heart_zone_percent'] = table_left['heart_zone_percent'].map('{:.1%}'.format)
table_left['shadow_zone_percent'] = table_left['shadow_zone_percent'].map('{:.1%}'.format)
table_left['chase_zone_percent'] = table_left['chase_zone_percent'].map('{:.1%}'.format)
table_left['waste_zone_percent'] = table_left['waste_zone_percent'].map('{:.1%}'.format)
table_left['csw_percent'] = table_left['csw_percent'].map('{:.1%}'.format)
table_left['whiff_rate'] = table_left['whiff_rate'].map('{:.1%}'.format)
table_left['chase_percent'] = table_left['chase_percent'].map('{:.1%}'.format)
table_left['bip'] = table_left['bip'].astype(int).astype(str)
table_left['xwoba_percent_contact'] = table_left['xwoba_percent_contact'].map('{:.3f}'.format)
table_left.columns = ['Usage%','Pitches','Heart%','Shadow%','Chase%','Waste%','CSW%','Whiff%','O-Swing%','BBE','xwOBACON']
table_left = table_left.replace({'nan%':'—'})
table_left = table_left.replace({'nan':'—'})
table_left = table_left.T
table_right = df_summ[df_summ['batter_hand']=='R'][['pitch_percent',
'pitches',
'heart_zone_percent',
'shadow_zone_percent',
'chase_zone_percent',
'waste_zone_percent',
'csw_percent',
'whiff_rate',
'chase_percent',
'bip',
'xwoba_percent_contact'
]]
try:
normalize = mcolors.Normalize(vmin=table_right['pitch_percent']*0.5,
vmax=table_right['pitch_percent']*1.5) # Define the range of values
except ValueError:
normalize = mcolors.Normalize(vmin=0,
vmax=1) # Define the range of values
df_colour_right = pd.DataFrame(data=[[get_color(x,normalize) for x in pivot_table_r.loc[0]],
[get_color(x,normalize) for x in pivot_table_r.loc[1]],
[get_color(x,normalize) for x in pivot_table_r.loc[2]]],)
table_right['pitch_percent'] = table_right['pitch_percent'].map('{:.1%}'.format)
table_right['pitches'] = table_right['pitches'].astype(int).astype(str)
# table_right['pa'] = table_right['pa'].astype(int).astype(str)
# table_right['k_percent'] = table_right['k_percent'].map('{:.1%}'.format)
# table_right['bb_percent'] = table_right['bb_percent'].map('{:.1%}'.format)
table_right['heart_zone_percent'] = table_right['heart_zone_percent'].map('{:.1%}'.format)
table_right['shadow_zone_percent'] = table_right['shadow_zone_percent'].map('{:.1%}'.format)
table_right['chase_zone_percent'] = table_right['chase_zone_percent'].map('{:.1%}'.format)
table_right['waste_zone_percent'] = table_right['waste_zone_percent'].map('{:.1%}'.format)
table_right['csw_percent'] = table_right['csw_percent'].map('{:.1%}'.format)
table_right['whiff_rate'] = table_right['whiff_rate'].map('{:.1%}'.format)
table_right['chase_percent'] = table_right['chase_percent'].map('{:.1%}'.format)
table_right['bip'] = table_right['bip'].astype(int).astype(str)
table_right['xwoba_percent_contact'] = table_right['xwoba_percent_contact'].map('{:.3f}'.format)
table_right.columns = ['Usage%','Pitches','Heart%','Shadow%','Chase%','Waste%','CSW%','Whiff%','O-Swing%','BBE','xwOBACON']
table_right = table_right.replace({'nan%':'—'})
table_right = table_right.replace({'nan':'—'})
table_right = table_right.T
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.gridspec as gridspec
from matplotlib.gridspec import GridSpec
# Assuming you have a list of pitch locations called 'pitch_locations'
# where each location is a tuple of (x, y) coordinates
fig = plt.figure(figsize=(16, 9))
fig.set_facecolor('white')
sns.set_theme(style="whitegrid", palette=colour_palette)
gs = GridSpec(3, 5, height_ratios=[2,9,1],width_ratios=[2,9,0.5,9,2])
gs.update(hspace=0.2, wspace=0.2)
# Add subplots to the grid
axheader = fig.add_subplot(gs[0, :])
ax_left = fig.add_subplot(gs[1, 1])
ax_right = fig.add_subplot(gs[1, 3])
axfooter = fig.add_subplot(gs[-1, :])
if df_plot[df_plot['batter_hand']=='L'].shape[0] > 3:
sns.kdeplot(data=df_plot[df_plot['batter_hand']=='L'],
x='px',
y='pz',
cmap=cmap_sum2,
shade=True,
ax=ax_left,
thresh=0.3,
bw_adjust=1)
else:
sns.scatterplot(data=df_plot[df_plot['batter_hand']=='L'],
x='px',
y='pz',
cmap=cmap_sum2,
ax=ax_left,
s=125)
if df_plot[df_plot['batter_hand']=='R'].shape[0] > 3:
sns.kdeplot(data=df_plot[df_plot['batter_hand']=='R'],
x='px',
y='pz',
cmap=cmap_sum2,
shade=True,
ax=ax_right,
thresh=0.3,
bw_adjust=1)
else:
sns.scatterplot(data=df_plot[df_plot['batter_hand']=='R'],
x='px',
y='pz',
cmap=cmap_sum2,
ax=ax_right,
s=125)
draw_line(ax_left,alpha_spot=1,catcher_p = False)
draw_line(ax_right,alpha_spot=1,catcher_p = False)
ax_left.axis('off')
ax_right.axis('off')
ax_left.axis('square')
ax_right.axis('square')
ax_left.set_xlim(-2.75,2.75)
ax_right.set_xlim(-2.75,2.75)
ax_left.set_ylim(-0.5,5)
ax_right.set_ylim(-0.5,5)
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
# Load the image
img = mpimg.imread('left.png')
imagebox = OffsetImage(img, zoom=0.7) # adjust zoom as needed
ab = AnnotationBbox(imagebox, (1.25, -0.5), box_alignment=(0, 0), frameon=False)
ax_left.add_artist(ab)
# Load the image
img = mpimg.imread('right.png')
imagebox = OffsetImage(img, zoom=0.7) # adjust zoom as needed
# Create an AnnotationBbox
ab = AnnotationBbox(imagebox, (-1.25, -0.5), box_alignment=(1, 0), frameon=False)
ax_right.add_artist(ab)
from matplotlib.transforms import Bbox
# Create a transformation that converts from data coordinates to axes coordinates
trans = ax_left.transData + ax_left.transAxes.inverted()
# Calculate the bbox in axes coordinates
bbox_data = Bbox.from_bounds(-4.2, -0.5, 2.5, 5) # replace width and height with the desired values
bbox_axes = trans.transform_bbox(bbox_data)
table_left_plot = ax_left.table(cellText=table_left.reset_index().values,
loc='right',
cellLoc='center',
colWidths=[0.52,0.3],
bbox=bbox_axes.bounds,zorder=100)
min_font_size = 14
# Set table properties
table_left_plot.auto_set_font_size(False)
#table.set_fontsize(min(min_font_size,max(min_font_size/((len(label_labels)/4)),10)))
table_left_plot.set_fontsize(min_font_size)
#table_left_plot.scale(1,3)
# Calculate the bbox in axes coordinates
bbox_data = Bbox.from_bounds(-0.75, 5, 2.5, 1) # replace width and height with the desired values
bbox_axes = trans.transform_bbox(bbox_data)
def format_as_percentage(val):
return f'{val * 100:.0f}%'
table_left_plot_pivot = ax_left.table(cellText=[[format_as_percentage(val) for val in row] for row in pivot_table_l.values],
colLabels =pivot_table_l.columns,
rowLabels =[' 0 ',' 1 ',' 2 '],
loc='center',
cellLoc='center',
colWidths=[0.3,0.3,0.30,0.3],
bbox=bbox_axes.bounds,zorder=100,cellColours =df_colour_left.values)
min_font_size = 11
# Set table properties
table_left_plot_pivot.auto_set_font_size(False)
#table.set_fontsize(min(min_font_size,max(min_font_size/((len(label_labels)/4)),10)))
table_left_plot_pivot.set_fontsize(min_font_size)
# Create a transformation that converts from data coordinates to axes coordinates
trans = ax_right.transData + ax_right.transAxes.inverted()
# Calculate the bbox in axes coordinates
bbox_data = Bbox.from_bounds(1.7, -0.5, 2.5, 5) # replace width and height with the desired values
bbox_axes = trans.transform_bbox(bbox_data)
table_right_plot = ax_right.table(cellText=table_right.reset_index().values,
loc='right',
cellLoc='center',
colWidths=[0.52,0.3],
bbox=bbox_axes.bounds,zorder=100)
min_font_size = 14
# Set table properties
table_right_plot.auto_set_font_size(False)
#table.set_fontsize(min(min_font_size,max(min_font_size/((len(label_labels)/4)),10)))
table_right_plot.set_fontsize(min_font_size)
table_right_plot.scale(0.5,3)
# Calculate the bbox in axes coordinates
# Create a transformation that converts from data coordinates to axes coordinates
trans = ax_right.transData + ax_right.transAxes.inverted()
bbox_data = Bbox.from_bounds(-0.75, 5, 2.5, 1) # replace width and height with the desired values
bbox_axes = trans.transform_bbox(bbox_data)
table_right_plot_pivot = ax_right.table(cellText=[[format_as_percentage(val) for val in row] for row in pivot_table_r.values],
colLabels =pivot_table_r.columns,
rowLabels =[' 0 ',' 1 ',' 2 '],
loc='center',
cellLoc='center',
colWidths=[0.3,0.3,0.30,0.3],
bbox=bbox_axes.bounds,zorder=100,cellColours =df_colour_right.values)
min_font_size = 11
# Set table properties
table_right_plot_pivot.auto_set_font_size(False)
#table.set_fontsize(min(min_font_size,max(min_font_size/((len(label_labels)/4)),10)))
table_right_plot_pivot.set_fontsize(min_font_size)
from matplotlib.cm import ScalarMappable
from matplotlib.colors import Normalize
# Create a ScalarMappable with the same colormap and normalization
sm = ScalarMappable(cmap=cmap_sum2, norm=Normalize(vmin=0, vmax=1))
#from mpl_toolkits.axes_grid1.inset_locator import inset_axes
#######################
# Create a new Subplot object for the colorbar
# Create a new Axes object for the colorbar at the bottom middle of the figure
cbar = fig.colorbar(sm, ax=axfooter, orientation='horizontal',aspect=100)
# cbar.ax.set_aspect(20)
# cbar = plt.colorbar(batter_plot, cax=ax12, orientation='vertical',shrink=1, cmap=cmap_hue)
# cbar = plt.colorbar(batter_plot, cax=ax12, orientation='vertical',shrink=1)
cbar.set_ticks([])
# # Create an inset axes for the colorbar
# cax = inset_axes(axfooter,
# width="50%", # width = 50% of parent_bbox width
# height="100%", # height : 5%
# loc='center')
# # Add the colorbar to the inset axes
# cbar = fig.colorbar(sm, cax=cax, orientation='horizontal')
# # Set the labels on the low and high ends of the colorbar
# # Set the xticks to only include the low and high ends of the colorbar
cbar.set_ticks([sm.norm.vmin, sm.norm.vmax])
# # Set the labels on the low and high ends of the colorbar
cbar.ax.set_xticklabels(['Least', 'Most'])
# # Place the xticks on top of the colorbar
cbar.ax.tick_params(labeltop=True, labelbottom=False, labelsize=14)
# # Get the labels
labels = cbar.ax.get_xticklabels()
# # Set the alignment of the labels
labels[0].set_horizontalalignment('left')
labels[-1].set_horizontalalignment('right')
# # Get the labels
labels = cbar.ax.get_xticklabels()
# # Set the font size of the labels
# for label in labels:
# label.set_fontsize(16)
# # Set the labels
cbar.ax.set_xticklabels(labels)
# # Remove the tick lines on the colorbar
cbar.ax.tick_params(length=0)
axfooter.text(x=0.02,y=0.5,s='By: Thomas Nestico\n @TJStats',fontname='Calibri',ha='left',fontsize=18,va='top')
axfooter.text(x=1-0.02,y=0.5,s='Data: MLB',ha='right',fontname='Calibri',fontsize=18,va='top')
axfooter.axis('off')
axheader.text(x=0.5,y=1.2,s=f"{df_plot['pitcher_name'].values[0]} - {df_plot['pitcher_hand'].values[0]}HP\n{season} {df_plot['pitch_description'].values[0]} Pitch Frequency",ha='center',fontsize=24,va='top')
axheader.text(x=0.5,y=0.5,s=f"{input.date_range_id()[0]} to {input.date_range_id()[1]}",ha='center',fontsize=16,va='top')
axheader.axis('off')
import urllib
import urllib.request
import urllib.error
from urllib.error import HTTPError
try:
url = f'https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png/w_213,q_auto:best/v1/people/{df_plot["pitcher_id"].values[0]}/headshot/67/current.png'
test_mage = plt.imread(url)
except urllib.error.HTTPError as err:
url = f'https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png/w_213,q_auto:best/v1/people/1/headshot/67/current.png'
imagebox = OffsetImage(test_mage, zoom = 0.4)
ab = AnnotationBbox(imagebox, (0.075, 0.4), frameon = False)
axheader.add_artist(ab)
player_bio = requests.get(url=f"https://statsapi.mlb.com/api/v1/people?personIds={df_plot['pitcher_id'].values[0]}&hydrate=currentTeam").json()
team_logos = pd.read_csv('team_logos.csv')
mlb_stats = MLB_Scrape()
teams_df = mlb_stats.get_teams()
team_logo_dict = teams_df.set_index(['team_id'])['parent_org_id'].to_dict()
if 'currentTeam' in player_bio['people'][0]:
try:
url = team_logos[team_logos['id'] == team_logo_dict[player_bio['people'][0]['currentTeam']['id']]]['imageLink'].values[0]
im = plt.imread(url)
# response = requests.get(url)
# im = Image.open(BytesIO(response.content))
# im = plt.imread(team_logos[team_logos['id'] == player_bio['people'][0]['currentTeam']['parentOrgId']]['imageLink'].values[0])
# ax = fig.add_axes([0,0,1,0.85], anchor='C', zorder=1)
imagebox = OffsetImage(im, zoom = 0.3)
ab = AnnotationBbox(imagebox, (0.925, 0.40), frameon = False)
axheader.add_artist(ab)
except IndexError:
print()
ax_left.text(s='Against LHH',x=-2.95,y=4.65,fontsize=18,fontweight='bold',ha='center')
ax_right.text(s='Against RHH',x=2.95,y=4.65,fontsize=18,fontweight='bold',ha='center')
# Center the labels
ax_left.text(x=-1.76, y=5.08, s='Strikes', rotation=90,fontweight='bold')
ax_right.text(x=-1.76, y=5.08, s='Strikes', rotation=90,fontweight='bold')
ax_left.text(x=0, y=6.03, s='Balls',ha='center',fontweight='bold')
ax_right.text(x=0, y=6.03, s='Balls',ha='center',fontweight='bold')
#cbar.ax.set_xticklabels(cbar.ax.get_xticklabels(), ha='center')
fig.subplots_adjust(left=0.01, right=0.99, top=0.95, bottom=0.05)
return
app = App(app_ui, server)