| import polars as pl |
| import numpy as np |
| import pandas as pd |
| import api_scraper |
| scrape = api_scraper.MLB_Scrape() |
| import requests |
| from shiny import App, reactive, ui, render |
| from shiny.ui import h2, tags |
| from shiny import App, reactive, ui, render |
| from shiny.ui import h2, tags |
|
|
|
|
| from shiny import App, ui, render |
|
|
|
|
| |
| from api_scraper import MLB_Scrape |
|
|
| |
| scraper = MLB_Scrape() |
|
|
|
|
| |
| teams = scraper.get_teams() |
| print(teams) |
|
|
| df_player = pl.concat([ |
| scraper.get_players(sport_id=1,season=2025,game_type=['R']), |
| scraper.get_players(sport_id=1,season=2024,game_type=['R']), |
| scraper.get_players(sport_id=1,season=2023,game_type=['R']), |
| scraper.get_players(sport_id=11,season=2025,game_type=['R']), |
| scraper.get_players(sport_id=12,season=2025,game_type=['R']), |
| scraper.get_players(sport_id=13,season=2025,game_type=['R']), |
| scraper.get_players(sport_id=14,season=2025,game_type=['R']), |
| scraper.get_players(sport_id=22,season=2024,game_type=['R']) |
| ]).unique(subset=['player_id']) |
|
|
|
|
|
|
| teams_mlb = teams.filter(pl.col("league_id").is_in([103,104])).sort("abbreviation") |
| teams_dict = dict(zip(teams_mlb['team_id'],teams_mlb['abbreviation'])) |
|
|
| teams_name_dict = dict(zip(teams_mlb['team_id'],teams_mlb['franchise'])) |
|
|
| app_ui = ui.page_sidebar( |
| ui.sidebar( |
| ui.input_select( |
| "team_id", |
| "Select Team", |
| choices=teams_dict |
| ), |
| ui.input_switch("nri_only", "NRI Only"), |
| ui.div( |
| ui.div({"style": "font-size:1.2em;"}, ui.markdown("Legend")), |
| ui.div( |
| style="display: inline-block; width: 20px; height: 20px; background-color: #b7e1cd; margin-right: 10px;" |
| ), |
| ui.span("NRI", style="vertical-align: top;"), |
| style="padding: 10px;" |
| ), |
| ), |
| ui.card( |
| ui.div({"style": "font-size:2em;"}, ui.output_text("card_title")), |
| ui.div({"style": "font-size:1.2em;"}, ui.markdown("By: [@TJStats](https://x.com/TJStats), Data: MLB")), |
| ui.output_table("team_stats") |
| ) |
| ) |
|
|
| def server(input, output, session): |
|
|
| @render.text |
| def card_title(): |
| if input.nri_only(): |
| return f"{teams_name_dict[int(input.team_id())]} — Spring Training Roster Non-Roster Invitees" |
| else: |
| return f"{teams_name_dict[int(input.team_id())]} — Spring Training Roster" |
|
|
| @render.table |
| def team_stats(): |
| |
| i = int(input.team_id()) |
|
|
| url = f'https://statsapi.mlb.com/api/v1/teams/{i}/roster/40man?season=2025' |
| data = requests.get(url).json() |
| |
| roster_df = pd.json_normalize(data['roster']) |
| roster_df['nri'] = False |
| roster_df['status.code'] = '' |
| roster_df = roster_df.fillna('') |
|
|
|
|
| url = f'https://statsapi.mlb.com/api/v1/teams/{i}/roster/nonRosterInvitees?season=2025' |
| data = requests.get(url).json() |
| |
| nri_roster_df = pd.json_normalize(data['roster']) |
| nri_roster_df['nri'] = True |
| nri_roster_df['parentTeamId'] = i |
| nri_roster_df = nri_roster_df.fillna('') |
|
|
|
|
|
|
| df_output = pd.concat([roster_df,nri_roster_df]) |
|
|
| df_output.loc[df_output['position.abbreviation'] == 'DH', 'position.code'] = '6.5' |
| df_output.loc[df_output['position.abbreviation'] == 'IF', 'position.code'] = '6.5' |
| df_output.loc[df_output['position.abbreviation'] == 'TWP', 'position.code'] = '1' |
| df_output = df_output.sort_values(by=['position.code', 'status.code']).reset_index(drop=True) |
| if input.nri_only(): |
| df_output = df_output[df_output['status.code'] == 'NRI'] |
|
|
| |
| df_output = df_output.merge(df_player.to_pandas(),left_on='person.id',right_on='player_id',how='left') |
|
|
| conditions = [ |
| (df_output['position.abbreviation'].isin(['P'])) & (~df_output.duplicated(subset=['position.abbreviation'], keep='first')), |
| (df_output['position.abbreviation'] == 'C') & (~df_output.duplicated(subset=['position.abbreviation'], keep='first')), |
| (df_output['position.abbreviation'].isin(['LF','CF','RF','OF'])) & (~df_output.duplicated(subset=['position.abbreviation'], keep='first')) |
| ] |
|
|
| choices = ['Pitchers', 'Infielders', 'Outfielders'] |
|
|
| df_output['position_group'] = np.select(conditions, choices, default='') |
|
|
| df_output.loc[df_output.duplicated(subset=['position_group'], keep='first'), 'position_group'] = '' |
|
|
|
|
| df_output['team'] = df_output['parentTeamId'].map(teams_dict) |
| df_output.loc[df_output['position.abbreviation'] == 'P', 'position.abbreviation'] = df_output['pitchHand'] + 'H' + df_output['position.abbreviation'] |
| df_output['bat_throw'] = df_output['batSide'] + '/' + df_output['pitchHand'] |
| df_output_small = df_output[['position_group','person.id', 'person.fullName', |
| 'position.abbreviation','team', 'status.code', 'age','weight', 'height', 'bat_throw']] |
| df_output_small['age'] = df_output_small['age'].replace('', np.nan).astype('Int64') |
| df_output_small['weight'] = df_output_small['weight'].replace('', np.nan).astype('Int64') |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| def highlight_nri(val): |
| color = 'yellow' if val else '' |
| return f'background-color: {color}' |
| |
| def highlight_alternate_rows(x): |
| return ['background-color: #ebebeb' if i % 2 == 0 else '' for i in range(len(x))] |
|
|
| |
|
|
|
|
| df_output_small.columns = ['Group','Player ID', 'Name', 'Pos','Team', 'Status','Age','Weight', 'Height', 'B/T'] |
|
|
|
|
|
|
| style_df = (df_output_small.style.set_precision(1) |
| .set_properties(**{'border': '3 px'}, overwrite=False) |
| .set_table_styles([{ |
| 'selector': 'caption', |
| 'props': [ |
| ('color', ''), |
| ('fontname', 'Century Gothic'), |
| ('font-size', '16px'), |
| ('font-style', 'italic'), |
| ('font-weight', ''), |
| ('text-align', 'centre'), |
| ] |
| }, { |
| 'selector': 'th', |
| 'props': [('font-size', '16px'), ('text-align', 'center'), ('Height', 'px'), ('color', 'black'), ('border', '1px black solid !important')] |
| }, { |
| 'selector': 'td', |
| 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')] |
| }], overwrite=False) |
| .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '72px'}, overwrite=False) |
| .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False) |
| .set_table_styles([{'selector': 'tr:first-child', 'props': [('background-color', 'white')]}], overwrite=False) |
| .set_table_styles([{'selector': 'tr', 'props': [('line-height', '20px')]}], overwrite=False) |
| .set_properties(**{'Height': '8px'}, **{'text-align': 'center'}, overwrite=False) |
| .hide_index() |
| .set_properties(**{'border': '1px black solid'}) |
| .set_table_styles([{'selector': 'thead th:nth-child(1)', 'props': [('min-width', '150px')]}], overwrite=False) |
| .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '150px')]}], overwrite=False) |
| .set_table_styles([{'selector': 'thead th:nth-child(3)', 'props': [('min-width', '250px')]}], overwrite=False) |
| .set_table_styles([{'selector': 'thead th', 'props': [('height', '30px')]}], overwrite=False) |
| .apply(highlight_alternate_rows, axis=0, subset=df_output_small.columns[1:]) |
| .applymap(lambda x: 'background-color: #bdbdbd' if x != '' else '', subset=['Group']) |
| .applymap(lambda x: 'background-color: #bdbdbd' if x else '', subset=['Group']) |
| |
| .set_properties( |
| **{'background-color':'#bdbdbd'}, |
| subset=df_output_small.columns[0] |
| ) |
| .set_properties( |
| **{'border-top': 'none', 'border-bottom': 'none'}, |
| subset=df_output_small.columns[0] |
| ) |
| |
| |
| ) |
| def highlight_nri(s): |
| return ['background-color: #b7e1cd' if s.name != 'Status' and s['Status'] == 'NRI' else '' for _ in s] |
|
|
| |
| |
| if not input.nri_only(): |
| style_df = style_df.apply(highlight_nri, axis=1, subset=df_output_small.columns[1:]) |
|
|
| def add_top_border(s): |
| return ['border-top: 3px solid black' if s['Group'] != '' else '' for _ in s] |
|
|
| styled_df = style_df.apply(add_top_border, axis=1) |
| |
| def add_bottom_border(s): |
| return ['border-bottom: 3px solid black' if s.name == len(df_output_small) - 1 else '' for _ in s] |
|
|
| styled_df = style_df.apply(add_bottom_border, axis=1) |
|
|
|
|
| return style_df |
| app = App(app_ui, server) |