nesticot's picture
Update app.py
abb74cd verified
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
# Import the MLB_Scrape class from the module
from api_scraper import MLB_Scrape
# Initialize the scraper
scraper = MLB_Scrape()
# Call the get_teams method
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():
# Get the selected team's data
i = int(input.team_id())
url = f'https://statsapi.mlb.com/api/v1/teams/{i}/roster/40man?season=2025'
data = requests.get(url).json()
# Normalize the roster data
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()
# Normalize the roster data
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')
# # Insert blank rows with position group indicated
# blank_rows = []
# for idx, row in df_output_small.iterrows():
# if row['position_group']:
# blank_row = pd.Series([''] * len(df_output_small.columns), index=df_output_small.columns)
# blank_row['position_group'] = row['position_group']
# blank_rows.append((idx, blank_row))
# for idx, blank_row in reversed(blank_rows):
# df_output_small = pd.concat([df_output_small.iloc[:idx], pd.DataFrame([blank_row]), df_output_small.iloc[idx:]]).reset_index(drop=True)
# df_output_small.loc[(df_output_small['position_group'] != '') & (df_output_small['person.fullName'] != ''), 'position_group'] = ''
def highlight_nri(val):
color = 'yellow' if val else ''
return f'background-color: {color}'
# Function to alternate row colors
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'])
# .apply(lambda x: ['background-color: #bdbdbd' if x['Group'] != '' else '' for _ in x], axis=1)
.set_properties(
**{'background-color':'#bdbdbd'}, # Apply only right border
subset=df_output_small.columns[0] # Only affects column 1
)
.set_properties(
**{'border-top': 'none', 'border-bottom': 'none'},
subset=df_output_small.columns[0] # Apply only to column 1
)
# .format({'Age': '{:.0f}', 'Weight': '{:.0f}'})
)
def highlight_nri(s):
return ['background-color: #b7e1cd' if s.name != 'Status' and s['Status'] == 'NRI' else '' for _ in s]
# style_df = style_df.style.apply(highlight_nri, axis=1, subset=style_df.columns[1:])
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)