|
|
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 |
|
|
from shiny import req |
|
|
|
|
|
colour_palette = ['#FFB000','#648FFF','#785EF0', |
|
|
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED'] |
|
|
|
|
|
|
|
|
year_list = [2017,2018,2019,2020,2021,2022,2023,2024,2025] |
|
|
|
|
|
|
|
|
|
|
|
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_title = {'all':'', |
|
|
'left':' - vs LHH', |
|
|
'right':' - vs RHH'} |
|
|
|
|
|
|
|
|
split_dict_hand = {'all':['L','R'], |
|
|
'left':['L'], |
|
|
'right':['R']} |
|
|
|
|
|
|
|
|
type_dict = {'R':'Regular Season', |
|
|
'S':'Spring', |
|
|
'P':'Playoffs' } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
import requests |
|
|
|
|
|
import os |
|
|
CAMPAIGN_ID = os.getenv("CAMPAIGN_ID") |
|
|
ACCESS_TOKEN = os.getenv("ACCESS_TOKEN") |
|
|
BACKUP_PW = os.getenv("BACKUP_PW") |
|
|
ADMIN_PW = os.getenv("ADMIN_PW") |
|
|
|
|
|
url = f"https://www.patreon.com/api/oauth2/v2/campaigns/{CAMPAIGN_ID}/members" |
|
|
|
|
|
headers = { |
|
|
"Authorization": f"Bearer {ACCESS_TOKEN}" |
|
|
} |
|
|
|
|
|
|
|
|
params = { |
|
|
"fields[member]": "full_name,email", |
|
|
"include": "currently_entitled_tiers", |
|
|
"page[size]": 10000 |
|
|
} |
|
|
|
|
|
response = requests.get(url, headers=headers, params=params) |
|
|
|
|
|
|
|
|
VALID_PASSWORDS = [] |
|
|
if response.status_code == 200: |
|
|
data = response.json() |
|
|
for patron in data['data']: |
|
|
try: |
|
|
tiers = patron['relationships']['currently_entitled_tiers']['data'] |
|
|
if any(tier['id'] == '9078921' for tier in tiers): |
|
|
full_name = patron['attributes']['email'] |
|
|
VALID_PASSWORDS.append(full_name) |
|
|
except KeyError: |
|
|
continue |
|
|
VALID_PASSWORDS.append(BACKUP_PW) |
|
|
VALID_PASSWORDS.append(ADMIN_PW) |
|
|
|
|
|
from datetime import datetime, timedelta |
|
|
from shiny import App, reactive, ui, render |
|
|
from shiny.ui import h2, tags |
|
|
|
|
|
|
|
|
login_ui = ui.page_fluid( |
|
|
ui.card( |
|
|
ui.h2([ |
|
|
"TJStats Pitching Summary Compare App ", |
|
|
ui.tags.a("(@TJStats)", href="https://twitter.com/TJStats", target="_blank") |
|
|
]), |
|
|
ui.p( |
|
|
"This App is available to Superstar Patrons. Please enter your Patreon email address in the box below. If you're having trouble, please refer to the ", |
|
|
ui.tags.a("Patreon post", href="https://www.patreon.com/posts/119993776", target="_blank"), |
|
|
"." |
|
|
), |
|
|
ui.input_password("password", "Enter Patreon Email (or Password from Link):", width="25%"), |
|
|
ui.tags.input( |
|
|
type="checkbox", |
|
|
id="authenticated", |
|
|
value=False, |
|
|
disabled=True |
|
|
), |
|
|
ui.input_action_button("login", "Login", class_="btn-primary"), |
|
|
ui.output_text("login_message"), |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
main_ui = ui.page_sidebar( |
|
|
ui.sidebar( |
|
|
|
|
|
ui.row(ui.markdown("### Select Pitcher 1")), |
|
|
ui.row( |
|
|
ui.column(4, ui.input_select('year_input_1', 'Season', year_list, selected=2025)), |
|
|
ui.column(4, ui.input_select('level_input_1', 'Level', level_dict)), |
|
|
ui.column(4, ui.input_select('type_input_1', 'Type', type_dict,selected='R')) |
|
|
), |
|
|
|
|
|
ui.row(ui.input_action_button("player_button_1", "Get Player List", class_="btn-primary"), |
|
|
), |
|
|
|
|
|
|
|
|
ui.row(ui.column(8, ui.output_ui('player_select_ui_1', 'Select Player')), |
|
|
ui.column(4, ui.input_select('split_id_1', 'Select Split', split_dict, multiple=False)) |
|
|
|
|
|
|
|
|
), |
|
|
|
|
|
ui.row(ui.column(8, ui.output_ui('date_id_1', 'Select Date')), |
|
|
ui.column(4, ui.input_select('select_date_time1', 'Quick Time',['Season','Last 10 Days','Last 30 Days'])), |
|
|
|
|
|
|
|
|
), |
|
|
ui.row( |
|
|
ui.column(6, ui.input_switch("switch_1", "Custom Team?", False)), |
|
|
ui.column(6, ui.input_select('logo_select_1', 'Select Custom Logo', image_dict_flip, multiple=False)) |
|
|
), |
|
|
|
|
|
|
|
|
|
|
|
ui.row(ui.markdown("### Select Pitcher 2")), |
|
|
ui.row( |
|
|
ui.column(4, ui.input_select('year_input_2', 'Season', year_list, selected=2025)), |
|
|
ui.column(4, ui.input_select('level_input_2', 'Level', level_dict)), |
|
|
ui.column(4, ui.input_select('type_input_2', 'Type', type_dict,selected='R')) |
|
|
), |
|
|
|
|
|
ui.row(ui.input_action_button("player_button_2", "Get Player List", class_="btn-primary"), |
|
|
), |
|
|
|
|
|
|
|
|
ui.row(ui.column(8, ui.output_ui('player_select_ui_2', 'Select Player')), |
|
|
ui.column(4, ui.input_select('split_id_2', 'Select Split', split_dict, multiple=False)) |
|
|
|
|
|
|
|
|
), |
|
|
|
|
|
ui.row(ui.column(8, ui.output_ui('date_id_2', 'Select Date')), |
|
|
ui.column(4, ui.input_select('select_date_time2', 'Quick Time',['Season','Last 10 Days','Last 30 Days'])), |
|
|
), |
|
|
|
|
|
ui.row( |
|
|
ui.column(6, ui.input_switch("switch_2", "Custom Team?", False)), |
|
|
ui.column(6, ui.input_select('logo_select_2', 'Select Custom Logo', image_dict_flip, multiple=False)) |
|
|
), |
|
|
|
|
|
|
|
|
ui.row(ui.input_action_button("generate_plot", "Generate Plot", class_="btn-primary")), |
|
|
width="400px" |
|
|
), |
|
|
|
|
|
|
|
|
ui.navset_tab( |
|
|
ui.nav_panel("Full Compare", |
|
|
ui.output_text("status"), |
|
|
ui.output_plot('plot', width='2100px', height='2100px')), |
|
|
ui.nav_panel("Break Plots", |
|
|
ui.output_text("status_break"), |
|
|
ui.output_plot('plot_break', width='2100px', height='1300px')), |
|
|
ui.nav_panel("Tables", |
|
|
ui.output_text("status_table"), |
|
|
ui.output_plot('plot_table', width='2100px', height='1300px')), |
|
|
id="tabset_id", |
|
|
|
|
|
|
|
|
|
|
|
), |
|
|
) |
|
|
|
|
|
|
|
|
app_ui = ui.page_fluid( |
|
|
ui.tags.head( |
|
|
ui.tags.script(src="script.js") |
|
|
), |
|
|
|
|
|
ui.panel_conditional( |
|
|
"!input.authenticated", |
|
|
login_ui |
|
|
), |
|
|
ui.panel_conditional( |
|
|
"input.authenticated", |
|
|
main_ui |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
def server(input, output, session): |
|
|
|
|
|
@reactive.Effect |
|
|
@reactive.event(input.login) |
|
|
def check_password(): |
|
|
if input.password() in VALID_PASSWORDS: |
|
|
ui.update_checkbox("authenticated", value=True) |
|
|
ui.update_text("login_message", value="") |
|
|
else: |
|
|
ui.update_text("login_message", value="Invalid password!") |
|
|
ui.update_text("password", value="") |
|
|
|
|
|
@output |
|
|
@render.text |
|
|
def login_message(): |
|
|
return "" |
|
|
@reactive.calc |
|
|
@reactive.event(input.pitcher_id_1, input.date_id_1,input.split_id_1) |
|
|
def cached_data_1(): |
|
|
|
|
|
year_input = int(input.year_input_1()) |
|
|
sport_id = int(input.level_input_1()) |
|
|
player_input = int(input.pitcher_id_1()) |
|
|
start_date = str(input.date_id_1()[0]) |
|
|
end_date = str(input.date_id_1()[1]) |
|
|
|
|
|
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_1()]) |
|
|
|
|
|
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_1()])) |
|
|
|
|
|
)))).with_columns( |
|
|
pl.col('pitch_type').count().over('pitch_type').alias('pitch_count') |
|
|
)) |
|
|
return df |
|
|
|
|
|
def cached_data_2(): |
|
|
|
|
|
year_input = int(input.year_input_2()) |
|
|
sport_id = int(input.level_input_2()) |
|
|
player_input = int(input.pitcher_id_2()) |
|
|
start_date = str(input.date_id_2()[0]) |
|
|
end_date = str(input.date_id_2()[1]) |
|
|
|
|
|
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_2()]) |
|
|
|
|
|
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_2()])) |
|
|
|
|
|
)))).with_columns( |
|
|
pl.col('pitch_type').count().over('pitch_type').alias('pitch_count') |
|
|
)) |
|
|
return df |
|
|
|
|
|
@render.ui |
|
|
@reactive.event(input.player_button_1, input.year_input_1, input.level_input_1, input.type_input_1,ignore_none=False) |
|
|
def player_select_ui_1(): |
|
|
|
|
|
|
|
|
df_pitcher_info = scrape.get_players(sport_id=int(input.level_input_1()), season=int(input.year_input_1()), game_type = [input.type_input_1()]).filter( |
|
|
pl.col("position").is_in(['P','TWP'])).sort("name") |
|
|
|
|
|
|
|
|
pitcher_dict = dict(zip(df_pitcher_info['player_id'], df_pitcher_info['name'])) |
|
|
|
|
|
|
|
|
return ui.input_select("pitcher_id_1", "Select Pitcher", pitcher_dict, selectize=True) |
|
|
|
|
|
|
|
|
@render.ui |
|
|
@reactive.event(input.player_button_2, input.year_input_2, input.level_input_2, input.type_input_2,ignore_none=False) |
|
|
def player_select_ui_2(): |
|
|
|
|
|
df_pitcher_info = scrape.get_players(sport_id=int(input.level_input_2()), season=int(input.year_input_2()), game_type = [input.type_input_2()]).filter( |
|
|
pl.col("position").is_in(['P','TWP'])).sort("name") |
|
|
|
|
|
|
|
|
pitcher_dict = dict(zip(df_pitcher_info['player_id'], df_pitcher_info['name'])) |
|
|
|
|
|
|
|
|
return ui.input_select("pitcher_id_2", "Select Pitcher", pitcher_dict, selectize=True) |
|
|
|
|
|
@render.ui |
|
|
@reactive.event(input.player_button_1, input.year_input_1, input.select_date_time1, ignore_none=False) |
|
|
def date_id_1(): |
|
|
|
|
|
if input.select_date_time1() == 'Season': |
|
|
|
|
|
return ui.input_date_range("date_id_1", "Select Date Range", |
|
|
start=f"{int(input.year_input_1())}-01-01", |
|
|
end=f"{int(input.year_input_1())}-12-31", |
|
|
min=f"{int(input.year_input_1())}-01-01", |
|
|
max=f"{int(input.year_input_1())}-12-31") |
|
|
elif input.select_date_time1() == 'Last 10 Days': |
|
|
today = datetime.today().date() |
|
|
last_10_days = today - timedelta(days=10) |
|
|
|
|
|
return ui.input_date_range("date_id_1", "Select Date Range", |
|
|
start=last_10_days.strftime("%Y-%m-%d"), |
|
|
end=today.strftime("%Y-%m-%d"), |
|
|
min=last_10_days.strftime("%Y-%m-%d"), |
|
|
max=today.strftime("%Y-%m-%d")) |
|
|
|
|
|
elif input.select_date_time1() == 'Last 30 Days': |
|
|
today = datetime.today().date() |
|
|
last_10_days = today - timedelta(days=30) |
|
|
|
|
|
return ui.input_date_range("date_id_1", "Select Date Range", |
|
|
start=last_10_days.strftime("%Y-%m-%d"), |
|
|
end=today.strftime("%Y-%m-%d"), |
|
|
min=last_10_days.strftime("%Y-%m-%d"), |
|
|
max=today.strftime("%Y-%m-%d")) |
|
|
|
|
|
@render.ui |
|
|
@reactive.event(input.player_button_2, input.year_input_2, input.select_date_time2, ignore_none=False) |
|
|
def date_id_2(): |
|
|
|
|
|
if input.select_date_time2() == 'Season': |
|
|
|
|
|
return ui.input_date_range("date_id_2", "Select Date Range", |
|
|
start=f"{int(input.year_input_2())}-01-01", |
|
|
end=f"{int(input.year_input_2())}-12-31", |
|
|
min=f"{int(input.year_input_2())}-01-01", |
|
|
max=f"{int(input.year_input_2())}-12-31") |
|
|
elif input.select_date_time2() == 'Last 10 Days': |
|
|
today = datetime.today().date() |
|
|
last_10_days = today - timedelta(days=10) |
|
|
|
|
|
return ui.input_date_range("date_id_2", "Select Date Range", |
|
|
start=last_10_days.strftime("%Y-%m-%d"), |
|
|
end=today.strftime("%Y-%m-%d"), |
|
|
min=last_10_days.strftime("%Y-%m-%d"), |
|
|
max=today.strftime("%Y-%m-%d")) |
|
|
|
|
|
elif input.select_date_time2() == 'Last 30 Days': |
|
|
today = datetime.today().date() |
|
|
last_10_days = today - timedelta(days=30) |
|
|
|
|
|
return ui.input_date_range("date_id_2", "Select Date Range", |
|
|
start=last_10_days.strftime("%Y-%m-%d"), |
|
|
end=today.strftime("%Y-%m-%d"), |
|
|
min=last_10_days.strftime("%Y-%m-%d"), |
|
|
max=today.strftime("%Y-%m-%d")) |
|
|
|
|
|
@output |
|
|
@render.text |
|
|
def status(): |
|
|
|
|
|
if input.generate == 0: |
|
|
return "" |
|
|
return "" |
|
|
|
|
|
@output |
|
|
@render.text |
|
|
def status_break(): |
|
|
|
|
|
if input.generate == 0: |
|
|
return "" |
|
|
return "" |
|
|
@output |
|
|
@render.text |
|
|
def status_table(): |
|
|
|
|
|
if input.generate == 0: |
|
|
return "" |
|
|
return "" |
|
|
|
|
|
@output |
|
|
@render.plot |
|
|
@reactive.event(input.generate_plot, ignore_none=False) |
|
|
def plot(): |
|
|
req(input.generate_plot()) |
|
|
|
|
|
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_1()) |
|
|
sport_id = int(input.level_input_1()) |
|
|
player_input = int(input.pitcher_id_1()) |
|
|
start_date = str(input.date_id_1()[0]) |
|
|
end_date = str(input.date_id_1()[1]) |
|
|
|
|
|
print(year_input, sport_id, player_input, start_date, end_date) |
|
|
|
|
|
year_input_2 = int(input.year_input_2()) |
|
|
sport_id_2 = int(input.level_input_2()) |
|
|
player_input_2 = int(input.pitcher_id_2()) |
|
|
start_date_2 = str(input.date_id_2()[0]) |
|
|
end_date_2 = str(input.date_id_2()[1]) |
|
|
|
|
|
print(year_input_2, sport_id_2, player_input_2, start_date_2, end_date_2) |
|
|
|
|
|
|
|
|
|
|
|
df = cached_data_1() |
|
|
print(df) |
|
|
df_2 = cached_data_2() |
|
|
print(df_2) |
|
|
df = df.clone() |
|
|
df_2 = df_2.clone() |
|
|
|
|
|
p.set(0.6, "Creating plot...") |
|
|
|
|
|
|
|
|
|
|
|
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, 12, |
|
|
height_ratios=[1,10,40,25,25,5], |
|
|
width_ratios=[3,3,10,24,10,3,3,10,24,10,3,3]) |
|
|
|
|
|
|
|
|
gs.update(hspace=0.15, wspace=0.15) |
|
|
|
|
|
ax_headshot_1 = fig.add_subplot(gs[1,2]) |
|
|
ax_bio_1 = fig.add_subplot(gs[1,3]) |
|
|
ax_logo_1 = fig.add_subplot(gs[1,4]) |
|
|
ax_break_1 = fig.add_subplot(gs[2,1:6]) |
|
|
ax_table_1 = fig.add_subplot(gs[3,1:11]) |
|
|
|
|
|
|
|
|
ax_headshot_2 = fig.add_subplot(gs[1,7]) |
|
|
ax_bio_2 = fig.add_subplot(gs[1,8]) |
|
|
ax_logo_2 = fig.add_subplot(gs[1,9]) |
|
|
ax_break_2 = fig.add_subplot(gs[2,6:11]) |
|
|
ax_table_2 = fig.add_subplot(gs[4,1:11]) |
|
|
|
|
|
|
|
|
|
|
|
ax_footer = fig.add_subplot(gs[-1,1:11]) |
|
|
ax_header = fig.add_subplot(gs[0,1:11]) |
|
|
ax_left = fig.add_subplot(gs[:,0]) |
|
|
ax_right = fig.add_subplot(gs[:,-1]) |
|
|
|
|
|
|
|
|
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') |
|
|
|
|
|
|
|
|
|
|
|
player_headshot(player_input=player_input, ax=ax_headshot_1,sport_id=sport_id,season=year_input) |
|
|
player_bio(pitcher_id=player_input, ax=ax_bio_1,sport_id=sport_id,year_input=year_input, |
|
|
df=df,split=input.split_id_1(),game_type=input.type_input_1()) |
|
|
|
|
|
df_teams = scrape.get_teams() |
|
|
if input.switch_1(): |
|
|
|
|
|
|
|
|
logo_url = input.logo_select_1() |
|
|
|
|
|
|
|
|
response = requests.get(logo_url) |
|
|
|
|
|
|
|
|
img = Image.open(BytesIO(response.content)) |
|
|
|
|
|
|
|
|
ax_logo_1.set_xlim(0, 1) |
|
|
ax_logo_1.set_ylim(0, 1) |
|
|
ax_logo_1.imshow(img, extent=[0, 1, 0, 1], origin='upper') |
|
|
|
|
|
|
|
|
ax_logo_1.axis('off') |
|
|
|
|
|
else: |
|
|
plot_logo(pitcher_id=player_input, ax=ax_logo_1, df_team=df_teams,df_players=scrape.get_players(sport_id,year_input)) |
|
|
|
|
|
break_plot(df = df,ax=ax_break_1) |
|
|
summary_table(df=df, |
|
|
ax=ax_table_1) |
|
|
ax_table_1.text(x=0.5,y=0.83,s=f'{df["pitcher_name"][0]} - {year_input} - {level_dict[str(sport_id)]} - {type_dict[input.type_input_1()]}{split_dict_title[input.split_id_1()]}', |
|
|
fontsize=30, ha='center', va='bottom',fontstyle='italic') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
player_headshot(player_input=player_input_2, ax=ax_headshot_2,sport_id=sport_id_2,season=year_input_2) |
|
|
player_bio(pitcher_id=player_input_2, ax=ax_bio_2,sport_id=sport_id_2,year_input=year_input_2, |
|
|
df=df_2,split=input.split_id_2(),game_type=input.type_input_2()) |
|
|
|
|
|
|
|
|
if input.switch_2(): |
|
|
|
|
|
|
|
|
logo_url = input.logo_select_2() |
|
|
|
|
|
|
|
|
response = requests.get(logo_url) |
|
|
|
|
|
|
|
|
img = Image.open(BytesIO(response.content)) |
|
|
|
|
|
|
|
|
ax_logo_2.set_xlim(0, 1) |
|
|
ax_logo_2.set_ylim(0, 1) |
|
|
ax_logo_2.imshow(img, extent=[0, 1, 0, 1], origin='upper') |
|
|
|
|
|
|
|
|
ax_logo_2.axis('off') |
|
|
|
|
|
else: |
|
|
plot_logo(pitcher_id=player_input_2, ax=ax_logo_2, df_team=df_teams,df_players=scrape.get_players(sport_id_2,year_input_2)) |
|
|
|
|
|
|
|
|
break_plot(df = df_2,ax=ax_break_2) |
|
|
summary_table(df=df_2, |
|
|
ax=ax_table_2) |
|
|
ax_table_2.text(x=0.5,y=0.83,s=f'{df_2["pitcher_name"][0]} - {year_input_2} - {level_dict[str(sport_id_2)]} - {type_dict[input.type_input_2()]}{split_dict_title[input.split_id_2()]}', |
|
|
fontsize=30, ha='center', va='bottom',fontstyle='italic') |
|
|
|
|
|
|
|
|
plot_footer(ax_footer) |
|
|
|
|
|
fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01) |
|
|
|
|
|
|
|
|
line = plt.Line2D([0.5, 0.5], [0.55, 0.95], transform=fig.transFigure, color="grey",alpha=0.4, linewidth=2) |
|
|
fig.add_artist(line) |
|
|
|
|
|
@output |
|
|
@render.plot |
|
|
@reactive.event(input.generate_plot, ignore_none=False) |
|
|
def plot_break(): |
|
|
req(input.generate_plot()) |
|
|
|
|
|
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_1()) |
|
|
sport_id = int(input.level_input_1()) |
|
|
player_input = int(input.pitcher_id_1()) |
|
|
start_date = str(input.date_id_1()[0]) |
|
|
end_date = str(input.date_id_1()[1]) |
|
|
|
|
|
print(year_input, sport_id, player_input, start_date, end_date) |
|
|
|
|
|
year_input_2 = int(input.year_input_2()) |
|
|
sport_id_2 = int(input.level_input_2()) |
|
|
player_input_2 = int(input.pitcher_id_2()) |
|
|
start_date_2 = str(input.date_id_2()[0]) |
|
|
end_date_2 = str(input.date_id_2()[1]) |
|
|
|
|
|
print(year_input_2, sport_id_2, player_input_2, start_date_2, end_date_2) |
|
|
|
|
|
|
|
|
|
|
|
df = cached_data_1() |
|
|
print(df) |
|
|
df_2 = cached_data_2() |
|
|
print(df_2) |
|
|
df = df.clone() |
|
|
df_2 = df_2.clone() |
|
|
|
|
|
p.set(0.6, "Creating plot...") |
|
|
|
|
|
|
|
|
|
|
|
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(4, 12, |
|
|
height_ratios=[1,10,40,10], |
|
|
width_ratios=[3,3,10,24,10,3,3,10,24,10,3,3]) |
|
|
|
|
|
|
|
|
gs.update(hspace=0.15, wspace=0.15) |
|
|
|
|
|
ax_headshot_1 = fig.add_subplot(gs[1,2]) |
|
|
ax_bio_1 = fig.add_subplot(gs[1,3]) |
|
|
ax_logo_1 = fig.add_subplot(gs[1,4]) |
|
|
ax_break_1 = fig.add_subplot(gs[2,1:6]) |
|
|
|
|
|
|
|
|
|
|
|
ax_headshot_2 = fig.add_subplot(gs[1,7]) |
|
|
ax_bio_2 = fig.add_subplot(gs[1,8]) |
|
|
ax_logo_2 = fig.add_subplot(gs[1,9]) |
|
|
ax_break_2 = fig.add_subplot(gs[2,6:11]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ax_footer = fig.add_subplot(gs[-1,1:11]) |
|
|
ax_header = fig.add_subplot(gs[0,1:11]) |
|
|
ax_left = fig.add_subplot(gs[:,0]) |
|
|
ax_right = fig.add_subplot(gs[:,-1]) |
|
|
|
|
|
|
|
|
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') |
|
|
|
|
|
|
|
|
|
|
|
player_headshot(player_input=player_input, ax=ax_headshot_1,sport_id=sport_id,season=year_input) |
|
|
player_bio(pitcher_id=player_input, ax=ax_bio_1,sport_id=sport_id,year_input=year_input, |
|
|
df=df,split=input.split_id_1(),game_type=input.type_input_1()) |
|
|
|
|
|
df_teams = scrape.get_teams() |
|
|
if input.switch_1(): |
|
|
|
|
|
|
|
|
logo_url = input.logo_select_1() |
|
|
|
|
|
|
|
|
response = requests.get(logo_url) |
|
|
|
|
|
|
|
|
img = Image.open(BytesIO(response.content)) |
|
|
|
|
|
|
|
|
ax_logo_1.set_xlim(0, 1) |
|
|
ax_logo_1.set_ylim(0, 1) |
|
|
ax_logo_1.imshow(img, extent=[0, 1, 0, 1], origin='upper') |
|
|
|
|
|
|
|
|
ax_logo_1.axis('off') |
|
|
|
|
|
else: |
|
|
plot_logo(pitcher_id=player_input, ax=ax_logo_1, df_team=df_teams,df_players=scrape.get_players(sport_id,year_input)) |
|
|
|
|
|
break_plot(df = df,ax=ax_break_1) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
player_headshot(player_input=player_input_2, ax=ax_headshot_2,sport_id=sport_id_2,season=year_input_2) |
|
|
player_bio(pitcher_id=player_input_2, ax=ax_bio_2,sport_id=sport_id_2,year_input=year_input_2, |
|
|
df=df_2,split=input.split_id_2(),game_type=input.type_input_2()) |
|
|
|
|
|
|
|
|
if input.switch_2(): |
|
|
|
|
|
|
|
|
logo_url = input.logo_select_2() |
|
|
|
|
|
|
|
|
response = requests.get(logo_url) |
|
|
|
|
|
|
|
|
img = Image.open(BytesIO(response.content)) |
|
|
|
|
|
|
|
|
ax_logo_2.set_xlim(0, 1) |
|
|
ax_logo_2.set_ylim(0, 1) |
|
|
ax_logo_2.imshow(img, extent=[0, 1, 0, 1], origin='upper') |
|
|
|
|
|
|
|
|
ax_logo_2.axis('off') |
|
|
|
|
|
else: |
|
|
plot_logo(pitcher_id=player_input_2, ax=ax_logo_2, df_team=df_teams,df_players=scrape.get_players(sport_id_2,year_input_2)) |
|
|
|
|
|
|
|
|
break_plot(df = df_2,ax=ax_break_2) |
|
|
|
|
|
|
|
|
plot_footer_break(ax_footer) |
|
|
|
|
|
fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01) |
|
|
|
|
|
|
|
|
line = plt.Line2D([0.5, 0.5], [0.15, 0.95], transform=fig.transFigure, color="grey",alpha=0.4, linewidth=2) |
|
|
fig.add_artist(line) |
|
|
|
|
|
print(df.schema) |
|
|
print(df_2.schema) |
|
|
columns_to_cast = [ |
|
|
"trajectory_fly_ball", "trajectory_ground_ball", |
|
|
"trajectory_line_drive", "trajectory_popup", |
|
|
"count", "pitch_count" |
|
|
] |
|
|
|
|
|
df = df.with_columns([pl.col(col).cast(pl.Int32) for col in columns_to_cast]) |
|
|
df_2 = df_2.with_columns([pl.col(col).cast(pl.Int32) for col in columns_to_cast]) |
|
|
|
|
|
common_cols = list(set(df.columns) & set(df_2.columns)) |
|
|
|
|
|
|
|
|
|
|
|
df_legend = pl.concat([df.select(common_cols), df_2.select(common_cols)], rechunk=True) |
|
|
|
|
|
items_in_order = (df_legend.sort("pitch_count", descending=True)['pitch_type'].unique(maintain_order=True).to_numpy()) |
|
|
colour_pitches = [dict_colour[x] for x in items_in_order] |
|
|
label = [dict_pitch[x] for x in items_in_order] |
|
|
handles = [plt.scatter([], [], color=color, marker='o', s=100) for color in colour_pitches] |
|
|
|
|
|
if len(label) > 10: |
|
|
ax_footer.legend(handles, label, bbox_to_anchor=(0.2, 0.2, 0.6, 0.3), ncol=5, |
|
|
fancybox=True, loc='lower center', fontsize=16, framealpha=1.0, markerscale=1.7, prop={'family': 'calibi', 'size': 16}) |
|
|
if len(label) > 5: |
|
|
ax_footer.legend(handles, label, bbox_to_anchor=(0.2, 0.2, 0.6, 0.2), ncol=5, |
|
|
fancybox=True, loc='lower center', fontsize=16, framealpha=1.0, markerscale=1.7, prop={'family': 'calibi', 'size': 16}) |
|
|
else: |
|
|
ax_footer.legend(handles, label, bbox_to_anchor=(0.2, 0.2, 0.6, 0.14), ncol=5, |
|
|
fancybox=True, loc='lower center', fontsize=16, framealpha=1.0, markerscale=1.7, prop={'family': 'calibi', 'size': 16}) |
|
|
ax_footer.axis('off') |
|
|
|
|
|
|
|
|
@output |
|
|
@render.plot |
|
|
@reactive.event(input.generate_plot, ignore_none=False) |
|
|
def plot_table(): |
|
|
req(input.generate_plot()) |
|
|
|
|
|
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_1()) |
|
|
sport_id = int(input.level_input_1()) |
|
|
player_input = int(input.pitcher_id_1()) |
|
|
start_date = str(input.date_id_1()[0]) |
|
|
end_date = str(input.date_id_1()[1]) |
|
|
|
|
|
print(year_input, sport_id, player_input, start_date, end_date) |
|
|
|
|
|
year_input_2 = int(input.year_input_2()) |
|
|
sport_id_2 = int(input.level_input_2()) |
|
|
player_input_2 = int(input.pitcher_id_2()) |
|
|
start_date_2 = str(input.date_id_2()[0]) |
|
|
end_date_2 = str(input.date_id_2()[1]) |
|
|
|
|
|
print(year_input_2, sport_id_2, player_input_2, start_date_2, end_date_2) |
|
|
|
|
|
|
|
|
|
|
|
df = cached_data_1() |
|
|
print(df) |
|
|
df_2 = cached_data_2() |
|
|
print(df_2) |
|
|
df = df.clone() |
|
|
df_2 = df_2.clone() |
|
|
|
|
|
p.set(0.6, "Creating plot...") |
|
|
|
|
|
|
|
|
|
|
|
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(4, 12, |
|
|
height_ratios=[1,25,25,5], |
|
|
width_ratios=[3,3,10,24,10,3,3,10,24,10,3,3]) |
|
|
|
|
|
|
|
|
gs.update(hspace=0.15, wspace=0.15) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ax_table_1 = fig.add_subplot(gs[1,1:11]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ax_table_2 = fig.add_subplot(gs[2,1:11]) |
|
|
|
|
|
|
|
|
|
|
|
ax_footer = fig.add_subplot(gs[-1,1:11]) |
|
|
ax_header = fig.add_subplot(gs[0,1:11]) |
|
|
ax_left = fig.add_subplot(gs[:,0]) |
|
|
ax_right = fig.add_subplot(gs[:,-1]) |
|
|
|
|
|
|
|
|
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') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
summary_table(df=df, |
|
|
ax=ax_table_1) |
|
|
ax_table_1.text(x=0.5,y=0.83,s=f'{df["pitcher_name"][0]} - {year_input} - {level_dict[str(sport_id)]} - {type_dict[input.type_input_1()]}{split_dict_title[input.split_id_1()]} - {start_date} to {end_date}', |
|
|
fontsize=30, ha='center', va='bottom',fontstyle='italic') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
summary_table(df=df_2, |
|
|
ax=ax_table_2) |
|
|
ax_table_2.text(x=0.5,y=0.83,s=f'{df_2["pitcher_name"][0]} - {year_input_2} - {level_dict[str(sport_id_2)]} - {type_dict[input.type_input_2()]}{split_dict_title[input.split_id_2()]} - {start_date_2} to {end_date_2}', |
|
|
fontsize=30, ha='center', va='bottom',fontstyle='italic') |
|
|
|
|
|
|
|
|
plot_footer(ax_footer) |
|
|
|
|
|
fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = App(app_ui, server) |
|
|
|
|
|
|