nesticot's picture
Update app.py
40eb83c verified
raw
history blame
16.3 kB
import polars as pl
import numpy as np
import pandas as pd
import api_scraper
scrape = api_scraper.MLB_Scrape()
from functions import df_update
from functions import pitch_summary_functions
update = df_update.df_update()
from stuff_model import feature_engineering as fe
from stuff_model import stuff_apply
import requests
import joblib
from matplotlib.gridspec import GridSpec
from shiny import App, reactive, ui, render
from shiny.ui import h2, tags
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
from functions.pitch_summary_functions import *
from shiny import App, reactive, ui, render
from shiny.ui import h2, tags
colour_palette = ['#FFB000','#648FFF','#785EF0',
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
year_list = [2017,2018,2019,2020,2021,2022,2023,2024]
level_dict = {'1':'MLB',
'11':'AAA',
'12':'AA',
'13':'A+',
'14':'A',
'17':'AFL',
'22':'College',
'21':'Prospects',
'51':'International' }
function_dict={
'velocity_kdes':'Velocity Distributions',
'break_plot':'Pitch Movement',
'tj_stuff_roling':'Rolling tjStuff+ by Pitch',
'tj_stuff_roling_game':'Rolling tjStuff+ by Game',
'location_plot_lhb':'Locations vs LHB',
'location_plot_rhb':'Locations vs RHB',
}
split_dict = {'all':'All',
'left':'LHH',
'right':'RHH'}
split_dict_hand = {'all':['L','R'],
'left':['L'],
'right':['R']}
type_dict = {'R':'Regular Season',
'S':'Spring',
'P':'Playoffs' }
# List of MLB teams and their corresponding ESPN logo URLs
mlb_teams = [
{"team": "AZ", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/ari.png&h=500&w=500"},
{"team": "ATH", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/oak.png&h=500&w=500"},
{"team": "ATL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/atl.png&h=500&w=500"},
{"team": "BAL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/bal.png&h=500&w=500"},
{"team": "BOS", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/bos.png&h=500&w=500"},
{"team": "CHC", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/chc.png&h=500&w=500"},
{"team": "CWS", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/chw.png&h=500&w=500"},
{"team": "CIN", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/cin.png&h=500&w=500"},
{"team": "CLE", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/cle.png&h=500&w=500"},
{"team": "COL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/col.png&h=500&w=500"},
{"team": "DET", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/det.png&h=500&w=500"},
{"team": "HOU", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/hou.png&h=500&w=500"},
{"team": "KC", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/kc.png&h=500&w=500"},
{"team": "LAA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/laa.png&h=500&w=500"},
{"team": "LAD", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/lad.png&h=500&w=500"},
{"team": "MIA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/mia.png&h=500&w=500"},
{"team": "MIL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/mil.png&h=500&w=500"},
{"team": "MIN", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/min.png&h=500&w=500"},
{"team": "NYM", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/nym.png&h=500&w=500"},
{"team": "NYY", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/nyy.png&h=500&w=500"},
{"team": "PHI", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/phi.png&h=500&w=500"},
{"team": "PIT", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/pit.png&h=500&w=500"},
{"team": "SD", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sd.png&h=500&w=500"},
{"team": "SF", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sf.png&h=500&w=500"},
{"team": "SEA", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/sea.png&h=500&w=500"},
{"team": "STL", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/stl.png&h=500&w=500"},
{"team": "TB", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tb.png&h=500&w=500"},
{"team": "TEX", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tex.png&h=500&w=500"},
{"team": "TOR", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/tor.png&h=500&w=500"},
{"team": "WSH", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/mlb/500/scoreboard/wsh.png&h=500&w=500"},
{"team": "ZZZ", "logo_url": "https://a.espncdn.com/combiner/i?img=/i/teamlogos/leagues/500/mlb.png&w=500&h=500"}
]
df_image = pd.DataFrame(mlb_teams)
image_dict = df_image.set_index('team')['logo_url'].to_dict()
image_dict_flip = df_image.set_index('logo_url')['team'].to_dict()
from shiny import App, reactive, ui, render
from shiny.ui import h2, tags
# Define the UI layout for the app
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
# Row for selecting season and level
ui.row(
ui.column(4, ui.input_select('year_input', 'Select Season', year_list, selected=2024)),
ui.column(4, ui.input_select('level_input', 'Select Level', level_dict)),
ui.column(4, ui.input_select('type_input', 'Select Type', type_dict,selected='R'))
),
# Row for the action button to get player list
ui.row(ui.input_action_button("player_button", "Get Player List", class_="btn-primary")),
# Row for selecting the player
ui.row(ui.column(12, ui.output_ui('player_select_ui', 'Select Player'))),
# Row for selecting the date range
ui.row(ui.column(12, ui.output_ui('date_id', 'Select Date'))),
# Rows for selecting plots and split options
ui.row(
ui.column(4, ui.input_select('plot_id_1', 'Plot Left', function_dict, multiple=False, selected='velocity_kdes')),
ui.column(4, ui.input_select('plot_id_2', 'Plot Middle', function_dict, multiple=False, selected='tj_stuff_roling')),
ui.column(4, ui.input_select('plot_id_3', 'Plot Right', function_dict, multiple=False, selected='break_plot'))
),
ui.row(
ui.column(6, ui.input_select('split_id', 'Select Split', split_dict, multiple=False)),
ui.column(6, ui.input_numeric('rolling_window', 'Rolling Window (for tjStuff+ Plot)', min=1, value=50))
),
ui.row(
ui.column(6, ui.input_switch("switch", "Custom Team?", False)),
ui.column(6, ui.input_select('logo_select', 'Select Custom Logo', image_dict_flip, multiple=False))
),
# Row for the action button to generate plot
ui.row(ui.input_action_button("generate_plot", "Generate Plot", class_="btn-primary")),
),
ui.panel_main(
ui.navset_tab(
# Tab for game summary plot
ui.nav("Pitching Summary",
ui.output_text("status"),
ui.output_plot('plot', width='2100px', height='2100px')
),
)
)
)
)
def server(input, output, session):
@reactive.calc
@reactive.event(input.pitcher_id, input.date_id,input.split_id)
def cached_data():
year_input = int(input.year_input())
sport_id = int(input.level_input())
player_input = int(input.pitcher_id())
start_date = str(input.date_id()[0])
end_date = str(input.date_id()[1])
# Simulate an expensive data operation
game_list = scrape.get_player_games_list(sport_id = sport_id,
season = year_input,
player_id = player_input,
start_date = start_date,
end_date = end_date,
game_type = [input.type_input()])
data_list = scrape.get_data(game_list_input = game_list[:])
df = (stuff_apply.stuff_apply(fe.feature_engineering(update.update(scrape.get_data_df(data_list = data_list).filter(
(pl.col("pitcher_id") == player_input)&
(pl.col("is_pitch") == True)&
(pl.col('batter_hand').is_in(split_dict_hand[input.split_id()]))
)))).with_columns(
pl.col('pitch_type').count().over('pitch_type').alias('pitch_count')
))
return df
@render.ui
@reactive.event(input.player_button, ignore_none=False)
def player_select_ui():
# Get the list of pitchers for the selected level and season
df_pitcher_info = scrape.get_players(sport_id=int(input.level_input()), season=int(input.year_input()), game_type = [input.type_input()]).filter(
pl.col("position").is_in(['P','TWP'])).sort("name")
# Create a dictionary of pitcher IDs and names
pitcher_dict = dict(zip(df_pitcher_info['player_id'], df_pitcher_info['name']))
# Return a select input for choosing a pitcher
return ui.input_select("pitcher_id", "Select Pitcher", pitcher_dict, selectize=True)
@render.ui
@reactive.event(input.player_button, ignore_none=False)
def date_id():
# Create a date range input for selecting the date range within the selected year
return ui.input_date_range("date_id", "Select Date Range",
start=f"{int(input.year_input())}-01-01",
end=f"{int(input.year_input())}-12-31",
min=f"{int(input.year_input())}-01-01",
max=f"{int(input.year_input())}-12-31")
@output
@render.text
def status():
# Only show status when generating
if input.generate == 0:
return ""
return ""
@output
@render.plot
@reactive.event(input.generate_plot, ignore_none=False)
def plot():
# Show progress/loading notification
with ui.Progress(min=0, max=1) as p:
p.set(message="Generating plot", detail="This may take a while...")
p.set(0.3, "Gathering data...")
year_input = int(input.year_input())
sport_id = int(input.level_input())
player_input = int(input.pitcher_id())
start_date = str(input.date_id()[0])
end_date = str(input.date_id()[1])
print(year_input, sport_id, player_input, start_date, end_date)
df = cached_data()
df = df.clone()
p.set(0.6, "Creating plot...")
#plt.rcParams["figure.figsize"] = [10,10]
fig = plt.figure(figsize=(26,26))
plt.rcParams.update({'figure.autolayout': True})
fig.set_facecolor('white')
sns.set_theme(style="whitegrid", palette=colour_palette)
print('this is the one plot')
gs = gridspec.GridSpec(6, 8,
height_ratios=[5,20,12,36,36,7],
width_ratios=[4,18,18,18,18,18,18,4])
gs.update(hspace=0.2, wspace=0.5)
# Define the positions of each subplot in the grid
ax_headshot = fig.add_subplot(gs[1,1:3])
ax_bio = fig.add_subplot(gs[1,3:5])
ax_logo = fig.add_subplot(gs[1,5:7])
ax_season_table = fig.add_subplot(gs[2,1:7])
ax_plot_1 = fig.add_subplot(gs[3,1:3])
ax_plot_2 = fig.add_subplot(gs[3,3:5])
ax_plot_3 = fig.add_subplot(gs[3,5:7])
ax_table = fig.add_subplot(gs[4,1:7])
ax_footer = fig.add_subplot(gs[-1,1:7])
ax_header = fig.add_subplot(gs[0,1:7])
ax_left = fig.add_subplot(gs[:,0])
ax_right = fig.add_subplot(gs[:,-1])
# Hide axes for footer, header, left, and right
ax_footer.axis('off')
ax_header.axis('off')
ax_left.axis('off')
ax_right.axis('off')
sns.set_theme(style="whitegrid", palette=colour_palette)
fig.set_facecolor('white')
df_teams = scrape.get_teams()
player_headshot(player_input=player_input, ax=ax_headshot,sport_id=sport_id,season=year_input)
player_bio(pitcher_id=player_input, ax=ax_bio,sport_id=sport_id,year_input=year_input)
if input.switch():
# Get the logo URL from the image dictionary using the team abbreviation
logo_url = input.logo_select()
# Send a GET request to the logo URL
response = requests.get(logo_url)
# Open the image from the response content
img = Image.open(BytesIO(response.content))
# Display the image on the axis
ax_logo.set_xlim(0, 1.3)
ax_logo.set_ylim(0, 1)
ax_logo.imshow(img, extent=[0.3, 1.3, 0, 1], origin='upper')
# Turn off the axis
ax_logo.axis('off')
else:
plot_logo(pitcher_id=player_input, ax=ax_logo, df_team=df_teams,df_players=scrape.get_players(sport_id,year_input))
stat_summary_table(df=df,
ax=ax_season_table,
player_input=player_input,
split=input.split_id(),
sport_id=sport_id,
game_type=[input.type_input()])
# break_plot(df=df_plot,ax=ax2)
for x,y,z in zip([input.plot_id_1(),input.plot_id_2(),input.plot_id_3()],[ax_plot_1,ax_plot_2,ax_plot_3],[1,3,5]):
if x == 'velocity_kdes':
velocity_kdes(df,
ax=y,
gs=gs,
gs_x=[3,4],
gs_y=[z,z+2],
fig=fig)
if x == 'tj_stuff_roling':
tj_stuff_roling(df=df,
window=int(input.rolling_window()),
ax=y)
if x == 'tj_stuff_roling_game':
tj_stuff_roling_game(df=df,
window=int(input.rolling_window()),
ax=y)
if x == 'break_plot':
break_plot(df = df,ax=y)
if x == 'location_plot_lhb':
location_plot(df = df,ax=y,hand='L')
if x == 'location_plot_rhb':
location_plot(df = df,ax=y,hand='R')
summary_table(df=df,
ax=ax_table)
plot_footer(ax_footer)
fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01)
app = App(app_ui, server)
app = App(app_ui, server)