|
import polars as pl |
|
import api_scraper |
|
import pandas as pd |
|
scrape = api_scraper.MLB_Scrape() |
|
|
|
|
|
|
|
from matplotlib.colors import LinearSegmentedColormap, Normalize |
|
import numpy as np |
|
import requests |
|
from io import BytesIO |
|
from PIL import Image |
|
from matplotlib.gridspec import GridSpec |
|
|
|
import matplotlib.pyplot as plt |
|
import matplotlib.patches as patches |
|
import PIL |
|
|
|
level_dict = { |
|
'11':'AAA', |
|
'14':'A',} |
|
|
|
|
|
|
|
def player_bio(pitcher_id: str, ax: plt.Axes, sport_id: int, year_input: int): |
|
""" |
|
Display the player's bio information on the given axis. |
|
Parameters |
|
---------- |
|
pitcher_id : str |
|
The player's ID. |
|
ax : plt.Axes |
|
The axis to display the bio information on. |
|
sport_id : int |
|
The sport ID (1 for MLB, other for minor leagues). |
|
year_input : int |
|
The season year. |
|
""" |
|
|
|
url = f"https://statsapi.mlb.com/api/v1/people?personIds={pitcher_id}&hydrate=currentTeam" |
|
|
|
|
|
data = requests.get(url).json() |
|
|
|
|
|
player_name = data['people'][0]['fullName'] |
|
position = data['people'][0]['primaryPosition']['abbreviation'] |
|
pitcher_hand = data['people'][0]['pitchHand']['code'] |
|
age = data['people'][0]['currentAge'] |
|
height = data['people'][0]['height'] |
|
weight = data['people'][0]['weight'] |
|
|
|
|
|
ax.text(0.5, 1, f'{player_name}', va='top', ha='center', fontsize=30) |
|
ax.text(0.5, 0.65, f'{position}, Age:{age}, {height}/{weight}', va='top', ha='center', fontsize=20) |
|
ax.text(0.5, 0.4, f'Season Batting Percentiles', va='top', ha='center', fontsize=16) |
|
|
|
|
|
response = requests.get(url='https://statsapi.mlb.com/api/v1/sports').json() |
|
|
|
|
|
df_sport_id = pl.DataFrame(response['sports']) |
|
abb = df_sport_id.filter(pl.col('id') == sport_id)['abbreviation'][0] |
|
|
|
|
|
ax.text(0.5, 0.20, f'{year_input} {abb} Season', va='top', ha='center', fontsize=14, fontstyle='italic') |
|
|
|
|
|
ax.axis('off') |
|
|
|
|
|
df_teams = scrape.get_teams() |
|
team_dict = dict(zip(df_teams['team_id'],df_teams['parent_org_abbreviation'])) |
|
|
|
|
|
|
|
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() |
|
|
|
|
|
merged_dict = { |
|
"woba_percent": { "format": '.3f', "percentile_flip": False, "stat_title": "wOBA" }, |
|
"xwoba_percent": { "format": '.3f', "percentile_flip": False, "stat_title": "xwOBA" }, |
|
"launch_speed": { "format": '.1f', "percentile_flip": False, "stat_title": "Average EV"}, |
|
"launch_speed_90": { "format": '.1f', "percentile_flip": False, "stat_title": "90th% EV"}, |
|
"max_launch_speed": { "format": '.1f', "percentile_flip": False, "stat_title": "Max EV"}, |
|
"barrel_percent": { "format": '.1%', "percentile_flip": False, "stat_title": "Barrel%" }, |
|
"hard_hit_percent": { "format": '.1%', "percentile_flip": False, "stat_title": "Hard-Hit%" }, |
|
"sweet_spot_percent": { "format": '.1%', "percentile_flip": False, "stat_title": "LA Sweet-Spot%" }, |
|
"zone_percent": { "format": '.1%', "percentile_flip": False, "stat_title": "Zone%" }, |
|
"zone_swing_percent": { "format": '.1%', "percentile_flip": False, "stat_title": "Z-Swing%" }, |
|
"chase_percent": { "format": '.1%', "percentile_flip": True, "stat_title": "O-Swing%" }, |
|
"whiff_rate": { "format": '.1%', "percentile_flip": True, "stat_title": "Whiff%" }, |
|
"k_percent": { "format": '.1%', "percentile_flip": True, "stat_title": "K%" }, |
|
"bb_percent": { "format": '.1%', "percentile_flip": False, "stat_title": "BB%" }, |
|
"pull_percent": { "format": '.1%', "percentile_flip": False, "stat_title": "Pull%" }, |
|
"pulled_fly_ball_percent": { "format": '.1%', "percentile_flip": False, "stat_title": "Pull FB%" }, |
|
} |
|
|
|
|
|
|
|
|
|
|
|
level_dict = { |
|
'11':'AAA', |
|
'14':'A (FSL)',} |
|
|
|
|
|
level_dict_file = { |
|
'11':'aaa', |
|
'14':'a',} |
|
|
|
|
|
|
|
year_list = [2024] |
|
|
|
|
|
from shiny import App, reactive, ui, render |
|
from shiny.ui import h2, tags |
|
|
|
|
|
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; |
|
} |
|
""" |
|
), |
|
|
|
ui.tags.h4("TJStats"), |
|
ui.tags.i("Baseball Analytics and Visualizations"), |
|
ui.markdown("""<a href='https://x.com/TJStats'>Follow me on Twitter</a><sup>1</sup>"""), |
|
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""), |
|
|
|
ui.markdown("### Statcast Batting Summaries"), |
|
ui.markdown("""This Shiny App allows you to generate Baseball Savant-style percentile bars for MiLB players in the 2024 Season. |
|
Currently, MiLB Statcast is only available for AAA and and A (Florida State League) levels."""), |
|
|
|
ui.layout_sidebar( |
|
ui.panel_sidebar( |
|
|
|
ui.row( |
|
ui.column(6, ui.input_select('year_input', 'Select Season', year_list, selected=2024)), |
|
ui.column(6, ui.input_select('level_input', 'Select Level', level_dict)), |
|
), |
|
|
|
ui.row(ui.input_action_button("player_button", "Get Player List", class_="btn-primary")), |
|
|
|
ui.row(ui.column(12, ui.output_ui('player_select_ui', 'Select Player'))), |
|
|
|
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)) |
|
), |
|
|
|
|
|
ui.row(ui.input_action_button("generate_plot", "Generate Plot", class_="btn-primary")), |
|
width=3, |
|
), |
|
|
|
ui.panel_main( |
|
ui.navset_tab( |
|
|
|
ui.nav("Batter Summary", |
|
ui.output_text("status"), |
|
ui.output_plot('plot', width='1200px', height='1200px') |
|
), |
|
) |
|
) |
|
) |
|
) |
|
) |
|
|
|
def server(input, output, session): |
|
@render.ui |
|
@reactive.event(input.player_button, ignore_none=False) |
|
def player_select_ui(): |
|
|
|
df_pitcher_info = scrape.get_players(sport_id=int(input.level_input()), season=int(input.year_input())).filter( |
|
~pl.col("position").is_in(['P','TWP'])).sort("name") |
|
|
|
|
|
|
|
|
|
batter_dict_pos = dict(zip(df_pitcher_info['player_id'], df_pitcher_info['position'])) |
|
|
|
year = int(input.year_input()) |
|
sport_id = int(input.level_input()) |
|
batter_summary = pl.read_csv(f'data/statcast/batter_summary_{level_dict_file[str(sport_id)]}_{year}.csv').sort('batter_name',descending=False) |
|
|
|
batter_summary = batter_summary.with_columns( |
|
pl.col("batter_id").map_elements(lambda x: batter_dict_pos.get(x, x)).alias("position") |
|
) |
|
|
|
|
|
batter_dict_pos = dict(zip(batter_summary['batter_id'], batter_summary['batter_name'])) |
|
|
|
batter_dict = dict(zip(batter_summary['batter_id'], batter_summary['batter_name'] + ' - ' + batter_summary['position'])) |
|
|
|
|
|
return ui.input_select("batter_id", "Select Batter", batter_dict, selectize=True) |
|
|
|
|
|
|
|
|
|
@output |
|
@render.plot |
|
@reactive.event(input.generate_plot, ignore_none=False) |
|
def plot(): |
|
|
|
with ui.Progress(min=0, max=1) as p: |
|
|
|
def draw_baseball_savant_percentiles(new_player_metrics, new_player_percentiles, colors=None, |
|
sport_id=None, |
|
year_input=None): |
|
""" |
|
Draw Baseball Savant-style percentile bars with proper alignment and scaling. |
|
|
|
:param new_player_metrics: DataFrame containing new player metrics. |
|
:param new_player_percentiles: DataFrame containing new player percentiles. |
|
:param colors: List of colors for bars (optional, red/blue default). |
|
""" |
|
|
|
batter_id = new_player_metrics['batter_id'][0] |
|
player_name = batter_name_id[batter_id] |
|
stats = [merged_dict[x]['stat_title'] for x in merged_dict.keys()] |
|
|
|
|
|
percentiles = [int((1 - x) * 100) if merged_dict[stat]["percentile_flip"] else int(x * 100) for x, stat in zip(new_player_percentiles.select(merged_dict.keys()).to_numpy()[0], merged_dict.keys())] |
|
percentiles = np.clip(percentiles, 1, 100) |
|
values = [str(f'{x:{merged_dict[stat]["format"]}}').strip('%') for x, stat in zip(new_player_metrics.select(merged_dict.keys()).to_numpy()[0], merged_dict.keys())] |
|
|
|
|
|
logo_url = image_dict[team_dict[player_team_dict[batter_id]]] |
|
|
|
|
|
color_list = ['#3661AD', '#B4CFD1', '#D82129'] |
|
cmap = LinearSegmentedColormap.from_list("custom_cmap", color_list) |
|
norm = Normalize(vmin=0.1, vmax=0.9) |
|
norm_percentiles = norm(percentiles / 100) |
|
colors = [cmap(p) for p in norm_percentiles] |
|
|
|
|
|
num_stats = len(stats) |
|
bar_height = 4.5 |
|
spacing = 1 |
|
fig_height = (bar_height + spacing) * num_stats |
|
fig = plt.figure(figsize=(12, 12)) |
|
gs = GridSpec(6, 5, height_ratios=[0.1, 1.5, 0.9, 0.9, 7.6, 0.1], width_ratios=[0.2, 1.5, 7, 1.5, 0.2]) |
|
|
|
|
|
ax_title = fig.add_subplot(gs[1, 2]) |
|
ax_table = fig.add_subplot(gs[2, :]) |
|
ax_fv_table = fig.add_subplot(gs[3, :]) |
|
ax = fig.add_subplot(gs[4, :]) |
|
ax_logo = fig.add_subplot(gs[1, 3]) |
|
|
|
ax.set_xlim(-1, 99) |
|
ax.set_ylim(-1, 99) |
|
ax.set_aspect("equal") |
|
ax.axis("off") |
|
|
|
|
|
for i, (stat, percentile, value, color) in enumerate(zip(stats, percentiles, values, colors)): |
|
y = fig_height - (i + 1) * (bar_height + spacing) |
|
ax.add_patch(patches.Rectangle((0, y + bar_height / 4), 100, bar_height / 2, color="#C7DCDC", lw=0)) |
|
ax.add_patch(patches.Rectangle((0, y), percentile, bar_height, color=color, lw=0)) |
|
circle_y = y + bar_height - bar_height / 2 |
|
circle = plt.Circle((percentile, circle_y), bar_height / 2, color=color, ec='white', lw=1.5, zorder=10) |
|
ax.add_patch(circle) |
|
fs = 14 |
|
ax.text(percentile, circle_y, f"{percentile}", ha="center", va="center", fontsize=10, color='white', zorder=10, fontweight='bold') |
|
ax.text(-5, y + bar_height / 2, stat, ha="right", va="center", fontsize=fs) |
|
ax.text(115, y + bar_height / 2, str(value), ha="right", va="center", fontsize=fs, zorder=5) |
|
if i < len(stats) and i > 0: |
|
ax.hlines(y=y + bar_height + spacing / 2, color='#399098', linestyle=(0, (5, 5)), linewidth=1, xmin=-33, xmax=0) |
|
ax.hlines(y=y + bar_height + spacing / 2, color='#399098', linestyle=(0, (5, 5)), linewidth=1, xmin=100, xmax=115) |
|
|
|
|
|
for x, label, align, color in zip([10, 50, 90], ["Poor", "Average", "Great"], ['center', 'center', 'center'], color_list): |
|
ax.axvline(x=x, ymin=0, ymax=1, color='#FFF', linestyle='-', lw=1, zorder=1, alpha=0.5) |
|
ax.text(x, fig_height + 4, label, ha=align, va='center', fontsize=12, fontweight='bold', color=color) |
|
triangle = patches.RegularPolygon((x, fig_height + 1), 3, radius=1, orientation=0, color=color, zorder=2) |
|
ax.add_patch(triangle) |
|
|
|
|
|
|
|
|
|
|
|
player_bio(batter_id, ax=ax_title, sport_id=sport_id, year_input=year_input) |
|
|
|
|
|
|
|
if input.switch(): |
|
response = requests.get(input.logo_select()) |
|
else: |
|
response = requests.get(logo_url) |
|
img = Image.open(BytesIO(response.content)) |
|
ax_logo.imshow(img) |
|
ax_logo.axis("off") |
|
ax.axis('equal') |
|
|
|
|
|
metrics_data = { |
|
"Pitches": new_player_metrics['pitches'][0], |
|
"PA": new_player_metrics['pa'][0], |
|
"BIP": new_player_metrics['bip'][0], |
|
"HR": f"{new_player_metrics['home_run'][0]:.0f}", |
|
"AVG": f"{new_player_metrics['avg'][0]:.3f}", |
|
"OBP": f"{new_player_metrics['obp'][0]:.3f}", |
|
"SLG": f"{new_player_metrics['slg'][0]:.3f}", |
|
"OPS": f"{new_player_metrics['obp'][0] + new_player_metrics['slg'][0]:.3f}", |
|
} |
|
df_table = pd.DataFrame(metrics_data, index=[0]) |
|
ax_table.axis('off') |
|
table = ax_table.table(cellText=df_table.values, colLabels=df_table.columns, cellLoc='center', loc='bottom', bbox=[0.07, 0, 0.86, 1]) |
|
for key, cell in table.get_celld().items(): |
|
if key[0] == 0: |
|
cell.set_text_props(fontweight='bold') |
|
table.auto_set_font_size(False) |
|
table.set_fontsize(12) |
|
table.scale(1, 1.5) |
|
|
|
|
|
ax_top = fig.add_subplot(gs[0, :]) |
|
ax_bot = fig.add_subplot(gs[-1, :]) |
|
ax_top.axis('off') |
|
ax_bot.axis('off') |
|
ax_bot.text(0.05, 2, "By: Thomas Nestico (@TJStats)", ha="left", va="center", fontsize=14) |
|
ax_bot.text(0.95, 2, "Data: MLB, Fangraphs", ha="right", va="center", fontsize=14) |
|
fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01) |
|
|
|
|
|
ax_headshot = fig.add_subplot(gs[1, 1]) |
|
try: |
|
url = f'https://img.mlbstatic.com/mlb-photos/image/upload/c_fill,g_auto/w_640/v1/people/{batter_id}/headshot/milb/current.png' |
|
response = requests.get(url) |
|
img = Image.open(BytesIO(response.content)) |
|
ax_headshot.set_xlim(0, 1) |
|
ax_headshot.set_ylim(0, 1) |
|
ax_headshot.imshow(img, extent=[1/6, 5/6, 0, 1], origin='upper') |
|
except PIL.UnidentifiedImageError: |
|
ax_headshot.axis('off') |
|
return |
|
ax_headshot.axis('off') |
|
ax_table.set_title('Season Summary', style='italic') |
|
|
|
|
|
print(batter_id) |
|
ax_fv_table.axis('off') |
|
if batter_id not in dict_mlb_fg.keys(): |
|
ax_fv_table.text(x=0.5, y=0.5, s='No Scouting Data', style='italic', ha='center', va='center', fontsize=20, bbox=dict(facecolor='white', alpha=1, pad=10)) |
|
return |
|
df_fv_table = df_prospects[(df_prospects['minorMasterId'] == dict_mlb_fg[batter_id])][['cFV', 'Hit', 'Game', 'Raw', 'Spd', 'Fld']].reset_index(drop=True) |
|
ax_fv_table.axis('off') |
|
if df_fv_table.empty: |
|
ax_fv_table.text(x=0.5, y=0.5, s='No Scouting Data', style='italic', ha='center', va='center', fontsize=20, bbox=dict(facecolor='white', alpha=1, pad=10)) |
|
return |
|
df_fv_table.columns = ['FV', 'Hit', 'Game', 'Raw', 'Spd', 'Fld'] |
|
table_fv = ax_fv_table.table(cellText=df_fv_table.values, colLabels=df_fv_table.columns, cellLoc='center', loc='bottom', bbox=[0.07, 0, 0.86, 1]) |
|
for key, cell in table_fv.get_celld().items(): |
|
if key[0] == 0: |
|
cell.set_text_props(fontweight='bold') |
|
table_fv.auto_set_font_size(False) |
|
table_fv.set_fontsize(12) |
|
table_fv.scale(1, 1.5) |
|
ax_fv_table.set_title('Fangraphs Scouting Grades', style='italic') |
|
|
|
|
|
|
|
|
|
|
|
|
|
def calculate_new_player_percentiles(player_id, new_player_metrics, player_summary_filtered): |
|
""" |
|
Calculate percentiles for a new player's metrics. |
|
|
|
:param player_id: ID of the player. |
|
:param new_player_metrics: DataFrame containing new player metrics. |
|
:param player_summary_filtered: Filtered player summary DataFrame. |
|
:return: DataFrame containing new player percentiles. |
|
""" |
|
filtered_summary_clone = player_summary_filtered[['batter_id'] + stat_list].filter(pl.col('batter_id') != player_id).clone() |
|
combined_data = pl.concat([filtered_summary_clone, new_player_metrics], how="vertical").to_pandas() |
|
combined_percentiles = pl.DataFrame(pd.concat([combined_data['batter_id'], combined_data[stat_list].rank(pct=True)], axis=1)) |
|
new_player_percentiles = combined_percentiles.filter(pl.col('batter_id') == player_id) |
|
return new_player_percentiles |
|
|
|
|
|
|
|
p.set(message="Generating plot", detail="This may take a while...") |
|
|
|
|
|
p.set(0.3, "Gathering data...") |
|
|
|
|
|
year = int(input.year_input()) |
|
sport_id = int(input.level_input()) |
|
batter_id = int(input.batter_id()) |
|
|
|
|
|
df_player = scrape.get_players(sport_id=sport_id,season=year) |
|
batter_name_id = dict(zip(df_player['player_id'],df_player['name'])) |
|
player_team_dict = dict(zip(df_player['player_id'],df_player['team'])) |
|
player_position_dict = dict(zip(df_player['player_id'],df_player['position'])) |
|
|
|
|
|
batter_summary = pl.read_csv(f'data/statcast/batter_summary_{level_dict_file[str(sport_id)]}_{year}.csv') |
|
df_prospects = pd.read_csv(f'data/prospects/prospects_{year}.csv') |
|
df_rosters = pd.read_csv(f'data/rosters/fangraphs_rosters_{year}.csv') |
|
df_small = df_rosters[['minorbamid','minormasterid']].dropna() |
|
dict_mlb_fg=dict(zip(df_small['minorbamid'].astype(int),df_small['minormasterid'])) |
|
|
|
|
|
|
|
|
|
batter_summary_filter = batter_summary.filter((pl.col('pa') >= 300) & (pl.col('launch_speed') >= 0)) |
|
stat_list = batter_summary.columns[2:] |
|
batter_summary_filter_pd = batter_summary_filter.to_pandas() |
|
new_player_metrics = batter_summary.filter(pl.col('batter_id') == batter_id)[['batter_id'] + stat_list] |
|
|
|
|
|
new_player_percentiles = calculate_new_player_percentiles(batter_id, new_player_metrics, batter_summary_filter) |
|
|
|
p.set(0.6, "Creating plot...") |
|
|
|
draw_baseball_savant_percentiles(new_player_metrics=new_player_metrics, |
|
new_player_percentiles=new_player_percentiles, |
|
sport_id=sport_id, |
|
year_input=year) |
|
|
|
app = App(app_ui, server) |