#import packages import pandas as pd import json import numpy as np import requests pd.options.mode.chained_assignment = None from datetime import datetime import time import difflib from datetime import datetime, timedelta import api_scraper import polars as pl import matplotlib.colors import matplotlib.colors as mcolors from shiny import App, ui, render, reactive def add_empty_rows(df, n_empty, period): """ adds 'n_empty' empty rows every 'period' rows to 'df'. Returns a new DataFrame. """ # to make sure that the DataFrame index is a RangeIndex(start=0, stop=len(df)) # and that the original df object is not mutated. df = df.reset_index(drop=True) # length of the new DataFrame containing the NaN rows len_new_index = len(df) + n_empty*(len(df) // period) # index of the new DataFrame new_index = pd.RangeIndex(len_new_index) # add an offset (= number of NaN rows up to that row) # to the current df.index to align with new_index. df.index += n_empty * (df.index .to_series() .groupby(df.index // period) .ngroup()) # reindex by aligning df.index with new_index. # Values of new_index not present in df.index are filled with NaN. new_df = df.reindex(new_index) return new_df unique_team_list = [120, 141, 140, 139, 138, 137, 136, 135, 134, 143, 133, 147, 121, 142, 158, 146, 119, 108, 118, 117, 116, 145, 115, 114, 113, 112, 111, 110, 109, 144] ## Create a dataframe of teams to assist with selecting a player to search #specifically for the case where multiple players share the same name #Make an api call to get a dictionary of all teams teams = requests.get(url='https://statsapi.mlb.com/api/v1/teams/').json() #Select only teams that are at the MLB level # mlb_teams_city = [x['franchiseName'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball'] # mlb_teams_name = [x['teamName'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball'] # mlb_teams_franchise = [x['name'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball'] # mlb_teams_id = [x['id'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball'] # mlb_teams_abb = [x['abbreviation'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball'] mlb_teams_city = [x.get('franchiseName', None) for x in teams.get('teams', [])] mlb_teams_name = [x.get('teamName', None) for x in teams.get('teams', [])] mlb_teams_franchise = [x.get('name', None) for x in teams.get('teams', [])] mlb_teams_id = [x.get('id', None) for x in teams.get('teams', [])] mlb_teams_abb = [x.get('abbreviation', None) for x in teams.get('teams', [])] #Create a dataframe of all the teams mlb_teams_df = pd.DataFrame(data={'team_id':mlb_teams_id,'city':mlb_teams_franchise,'name':mlb_teams_name,'franchise':mlb_teams_franchise,'abbreviation':mlb_teams_abb}) ##Create a dataframe of all players in the database #Make an api call to get a dictionary of all players player_data = requests.get(url='https://statsapi.mlb.com/api/v1/sports/11/players').json() #Select relevant data that will help distinguish players from one another fullName_list = [x['fullName'] for x in player_data['people']] id_list = [x['id'] for x in player_data['people']] position_list = [x['primaryPosition']['abbreviation'] for x in player_data['people']] team_list = [x['currentTeam']['id']for x in player_data['people']] #Create a dataframe of players and their current team ids player_df_all = pd.DataFrame(data={'id':id_list,'name':fullName_list,'position':position_list,'team_id':team_list}) #Use the teams dataframe to merge the team name to the players dataframe player_df_all = player_df_all.merge(right=mlb_teams_df[['team_id','franchise']],left_on='team_id',right_on='team_id',how='left',suffixes=['','_y']) #drop the duplicated id column player_df_all = player_df_all.drop(columns=['team_id']) #make a column of the names all uppercase to make lookups easier player_df_all['upper_name'] = player_df_all['name'].str.upper() #rename to make the data clearer player_df_all = player_df_all.rename(columns={'franchise':'currentTeam'}) import matplotlib.pyplot as plt colour_palette = ['#FFB000','#648FFF','#785EF0', '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED'] cmap_up = mcolors.LinearSegmentedColormap.from_list("", ['#FFFFFF', '#FFB000']) # 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))] # Function to apply thick border to data cells # Columns after which we want a thick vertical line scraper = api_scraper.MLB_Scrape() df_teams = scraper.get_teams() teams_mlb = df_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'])) {109: 'Arizona Diamondbacks', 144: 'Atlanta Braves', 110: 'Baltimore Orioles', 111: 'Boston Red Sox', 112: 'Chicago Cubs', 145: 'Chicago White Sox', 113: 'Cincinnati Reds', 114: 'Cleveland Guardians', 115: 'Colorado Rockies', 116: 'Detroit Tigers', 117: 'Houston Astros', 118: 'Kansas City Royals', 108: 'Los Angeles Angels', 119: 'Los Angeles Dodgers', 146: 'Miami Marlins', 158: 'Milwaukee Brewers', 142: 'Minnesota Twins', 121: 'New York Mets', 147: 'New York Yankees', #133: 'Athletics', 133: 'Oakland Athletics', 143: 'Philadelphia Phillies', 134: 'Pittsburgh Pirates', 135: 'San Diego Padres', 137: 'San Francisco Giants', 136: 'Seattle Mariners', 138: 'St. Louis Cardinals', 139: 'Tampa Bay Rays', 140: 'Texas Rangers', 141: 'Toronto Blue Jays', 120: 'Washington Nationals'} data_r_1 = requests.get("https://pub-api-ro.fantasysports.yahoo.com/fantasy/v2/league/458.l.1423;out=settings/players;position=ALL;start=0;count=5000;sort=rank_season;search=;out=percent_owned;out=auction_values,ranks;ranks=season;ranks_by_position=season;out=expert_ranks;expert_ranks.rank_type=projected_season_remaining/draft_analysis;cut_types=diamond;slices=last7days?format=json_f").json() #data_r_2 = requests.get("https://pub-api-ro.fantasysports.yahoo.com/fantasy/v2/league/422.l.1416;out=settings/players;position=ALL;start=758;count=5000;sort=rank_season;search=;out=auction_values,ranks;ranks=season;ranks_by_position=season;out=expert_ranks;expert_ranks.rank_type=projected_season_remaining/draft_analysis;cut_types=diamond;slices=last7days?format=json_f").json() # 757 is bad #https://pub-api-ro.fantasysports.yahoo.com/fantasy/v2/league/422.l.1416;out=settings/players;position=ALL;start=0;count=756;sort=rank_season;search=;out=auction_values,ranks;ranks=season;ranks_by_position=season;out=expert_ranks;expert_ranks.rank_type=projected_season_remaining/draft_analysis;cut_types=diamond;slices=last7days?format=json_f total_list = [] for x in data_r_1['fantasy_content']['league']['players']: single_list = [] single_list.append((x['player']['player_id'])) single_list.append(x['player']['name']['full']) single_list.append(x['player']['name']['first']) single_list.append(x['player']['name']['last']) single_list.append(x['player']['draft_analysis']['average_pick']) single_list.append(x['player']['average_auction_cost']) single_list.append(x['player']['display_position']) single_list.append(x['player']['editorial_team_abbr']) total_list.append(single_list) df_2023 = pd.DataFrame(data=total_list, columns=['player_id','full','first','last','average_pick', 'average_cost','display_position','editorial_team_abbr']) team_abb_list = ['ATL', 'AZ', 'BAL', 'BOS', 'CHC', 'CIN', 'CLE', 'COL', 'CWS', 'DET', 'HOU', 'KC', 'LAA', 'LAD', 'MIA', 'MIL', 'MIN', 'NYM', 'NYY', 'ATH', 'PHI', 'PIT', 'SD', 'SEA', 'SF', 'STL', 'TB', 'TEX', 'TOR', 'WSH'] team_abb_list_yahoo = ['ATL', 'ARI', 'BAL', 'BOS', 'CHC', 'CIN', 'CLE', 'COL', 'CWS', 'DET', 'HOU', 'KC', 'LAA', 'LAD', 'MIA', 'MIL', 'MIN', 'NYM', 'NYY', 'ATH', 'PHI', 'PIT', 'SD', 'SEA', 'SF', 'STL', 'TB', 'TEX', 'TOR', 'WSH'] team_abb_df = pd.DataFrame({'mlb_team':team_abb_list,'yahoo_team':team_abb_list_yahoo}) from shiny import App, ui, render import pandas as pd app_ui = ui.page_sidebar( ui.sidebar( ui.markdown("#### 2025 MLB Lineup Tracker"), ui.input_select( "team_id", "Select Team", choices=teams_name_dict ), ui.output_ui("player_select"), # ui.hr(), # Add a horizontal line for separation ui.div( ui.div( "By: ", # Plain text ui.tags.a( "@TJStats", # Only the handle is linked href="https://x.com/TJStats", target="_blank" ) ), ui.tags.p("Data: MLB"), ui.tags.p( ui.tags.a( "Support me on Patreon for more baseball content", href="https://www.patreon.com/TJ_Stats", target="_blank" ) ), style="color: #666; text-align: left;" ) ,width=500), ui.card( ui.output_text("selected_team_info"), ui.navset_card_tab( ui.nav_panel( "Lineups", ui.div({"style": "font-size:1.7em;"}, ui.output_text("lineup_title")), ui.output_table("table1") ), ui.nav_panel( "Summary", ui.div({"style": "font-size:1.7em;"}, ui.output_text("summary_title")), ui.output_table("table2") ), ui.nav_panel( "Fantasy Eligibility", ui.div({"style": "font-size:1.7em;"}, ui.output_text("fantasy_title")), ui.output_table("table3") ), ui.nav_panel( "Player Teammates", ui.div({"style": "font-size:1.7em;"}, ui.output_text("player_title")), ui.output_table("table4") ) ) ) ) def server(input, output, session): @render.ui def player_select(): df,batting_order_full_opp,statcast_dict = cached_data() df = df.sort(['Player']) player_dict = dict(zip(df['Player ID'],df['Player'])) return ui.input_select( "player_select", f"Select Player", choices=player_dict ) @render.text def lineup_title(): return f"{teams_name_dict[int(input.team_id())]} Lineups" @render.text def summary_title(): return f"{teams_name_dict[int(input.team_id())]} Lineup Summary" @render.text def fantasy_title(): return f"{teams_name_dict[int(input.team_id())]} Position Eligibility Tracker - Yahoo" @render.text def player_title(): return f"{teams_name_dict[int(input.team_id())]} Teammate Summary" @reactive.calc def cached_data(): team_id_select = int(input.team_id()) df_schedule = scraper.get_schedule(year_input=[2025],sport_id=[1],game_type=['R']) # df_schedule_p = scraper.get_schedule(year_input=[2024],sport_id=[21],game_type=['E']) # df_schedule_all = pl.concat([df_schedule,df_schedule_p]) df_schedule_all = pl.concat([df_schedule]) df_schedule_team = df_schedule_all.filter((pl.col('date') <= datetime.today().date())&((pl.col('home_id') == team_id_select)|(pl.col('away_id') == team_id_select))) if df_schedule_team.is_empty(): return None, None, None statcast_dict = dict(zip(df_schedule_team['game_id'],df_schedule_team['gameday_type'])) game_list = df_schedule_team['game_id'][:] game_data = scraper.get_data(game_list) print('Importing New Games.') lineup_data = [] game_id = [] date = [] player_id = [] player_name = [] position = [] team_id = [] batting_order = [] handedness_batter = [] away_home = [] handedness_pitcher = [] pitcher_name = [] for y in range(0,len(game_data)): game_id.append(len([x if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']])*[game_data[y]['gameData']['game']['pk']]) date.append(len([x if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']])*[game_data[y]['gameData']['datetime']['officialDate']]) player_id.append([x if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']]) player_name.append([game_data[y]['liveData']['boxscore']['teams']['away']['players'][x]['person']['fullName'] if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']]) position.append([game_data[y]['liveData']['boxscore']['teams']['away']['players'][x]['allPositions'][0]['code'] if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']]) team_id.append([df_schedule_team.filter(df_schedule_team['game_id']==game_data[y]['gamePk'])['away_id'][0]]*len([game_data[y]['liveData']['boxscore']['teams']['away']['players'][x]['parentTeamId'] if 'parentTeamId' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']])) batting_order.append([game_data[y]['liveData']['boxscore']['teams']['away']['players'][x]['battingOrder'] if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']]) away_home.append(['away' if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']]) handedness_batter.append([game_data[y]['gameData']['players'][x]['batSide']['code'] if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['away']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']]) handedness_pitcher.append([game_data[y]['gameData']['players']['ID'+str(game_data[y]['gameData']['probablePitchers']['home']['id'])]['pitchHand']['code']+'HP' if 'home' in game_data[y]['gameData']['probablePitchers'] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']]) pitcher_name.append([game_data[y]['gameData']['players']['ID'+str(game_data[y]['gameData']['probablePitchers']['home']['id'])]['fullName'] if 'home' in game_data[y]['gameData']['probablePitchers'] else None for x in game_data[y]['liveData']['boxscore']['teams']['away']['players']]) game_id.append(len([x if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']])*[game_data[y]['gameData']['game']['pk']]) date.append(len([x if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']])*[game_data[y]['gameData']['datetime']['officialDate']]) player_id.append([x if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']]) player_name.append([game_data[y]['liveData']['boxscore']['teams']['home']['players'][x]['person']['fullName'] if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']]) position.append([game_data[y]['liveData']['boxscore']['teams']['home']['players'][x]['allPositions'][0]['code'] if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']]) team_id.append([df_schedule_team.filter(df_schedule_team['game_id']==game_data[y]['gamePk'])['home_id'][0]]*len([game_data[y]['liveData']['boxscore']['teams']['home']['players'][x]['parentTeamId'] if 'parentTeamId' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']])) batting_order.append([game_data[y]['liveData']['boxscore']['teams']['home']['players'][x]['battingOrder'] if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']]) away_home.append(['home' if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']]) handedness_batter.append([game_data[y]['gameData']['players'][x]['batSide']['code'] if 'battingOrder' in game_data[y]['liveData']['boxscore']['teams']['home']['players'][x] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']]) handedness_pitcher.append([game_data[y]['gameData']['players']['ID'+str(game_data[y]['gameData']['probablePitchers']['away']['id'])]['pitchHand']['code']+'HP' if 'away' in game_data[y]['gameData']['probablePitchers'] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']]) pitcher_name.append([game_data[y]['gameData']['players']['ID'+str(game_data[y]['gameData']['probablePitchers']['away']['id'])]['fullName'] if 'away' in game_data[y]['gameData']['probablePitchers'] else None for x in game_data[y]['liveData']['boxscore']['teams']['home']['players']]) game_id_final = [item for sublist in game_id for item in sublist] date_final = [item for sublist in date for item in sublist] player_id_final = [item for sublist in player_id for item in sublist] player_name_final = [item for sublist in player_name for item in sublist] position_final = [item for sublist in position for item in sublist] team_id_final = [item for sublist in team_id for item in sublist] batting_order_final = [item for sublist in batting_order for item in sublist] away_home_final = [item for sublist in away_home for item in sublist] handedness_batter_final = [item for sublist in handedness_batter for item in sublist] handedness_pitcher_final = [item for sublist in handedness_pitcher for item in sublist] pitcher_name_final = [item for sublist in pitcher_name for item in sublist] position_df = pl.DataFrame(data={'position':[1,2,3,4,5,6,7,8,9,10],'position_name':['P','C','1B','2B','3B','SS','LF','CF','RF','DH']}) batting_order_full = pl.DataFrame(data={'game_id':game_id_final , 'date':date_final, 'player_id':player_id_final, 'player_name':player_name_final, 'position':position_final, 'team_id':team_id_final, 'batting_order':batting_order_final, 'away_home':away_home_final, 'handedness_batter':handedness_batter_final, 'handedness_pitcher':handedness_pitcher_final, 'pitcher_name':pitcher_name_final}) # batting_order_full = batting_order_full batting_order_full = batting_order_full.with_columns(pl.col('position').cast(pl.Int32)) batting_order_full = batting_order_full.join(df_teams[['team_id', 'franchise', 'abbreviation']], on='team_id', how='left') position_df = position_df.with_columns(pl.col('position').cast(pl.Int32)) batting_order_full = batting_order_full.join(position_df, on='position', how='left') batting_order_full_opp = batting_order_full.filter(pl.col('team_id') != team_id_select) batting_order_full = batting_order_full.filter(pl.col('team_id') == team_id_select) batting_order_full_filter = batting_order_full.filter(pl.col('batting_order').cast(pl.Int32) % 100 == 0) batting_order_full_filter = batting_order_full_filter.sort(by=['abbreviation', 'franchise', 'date', 'game_id', 'batting_order'], descending=[False, False, False, False, False]).with_row_count().drop('row_nr') batting_order_full_filter = batting_order_full_filter.unique(subset=['batting_order','game_id','away_home']) batting_order_full_filter = batting_order_full_filter.with_columns(pl.col('batting_order').cast(pl.Int32)) df_test_merge = batting_order_full_filter.clone() df_test_merge = df_test_merge.with_columns(pl.col('*').fill_null(np.nan)) df_test_merge = df_test_merge.sort(['player_id', 'date']).fill_null(strategy='forward') df_test_merge_small = df_test_merge.select([ 'game_id', 'date', 'abbreviation', 'batting_order', 'player_id', 'player_name', 'position_name', 'handedness_batter', 'handedness_pitcher','away_home', 'pitcher_name' ]).sort(['date', 'game_id', 'abbreviation', 'batting_order']) df_test_merge_small = df_test_merge_small.select(['game_id', 'date', 'abbreviation', 'batting_order', 'player_id', 'player_name', 'position_name', 'handedness_batter', 'handedness_pitcher','away_home', 'pitcher_name' ]).fill_null('') df_test_merge_small = df_test_merge_small.with_columns([ (pl.col('batting_order').cast(pl.Int32) / 100).cast(pl.Int32).alias('batting_order'), pl.lit(1).alias('count'), ]) df_test_merge_small = df_test_merge_small.rename({'game_id': 'Game ID', 'date': 'Date', 'abbreviation': 'Team', 'batting_order': 'Batting', 'player_id': 'Player ID', 'player_name': 'Player', 'position_name': 'Position', 'handedness_batter': 'Bats','away_home':'Home', 'handedness_pitcher': 'Pitcher Hand', 'pitcher_name': 'Pitcher Name' }) return df_test_merge_small,batting_order_full_opp,statcast_dict @output @render.table def table1(): df,batting_order_full_opp,statcast_dict = cached_data() if df is None: return pd.DataFrame({"Message": ["No Games as of this time"]}) team_opp = dict(zip(batting_order_full_opp['game_id'],batting_order_full_opp['abbreviation'])) df_test_merge_small_pd = df.to_pandas() df_test_merge_small_pd['Opponent'] = df_test_merge_small_pd['Game ID'].map(team_opp) # Create a new column with the date and opponent for the first occurrence of each game ID df_test_merge_small_pd['Game'] = np.where( ~df_test_merge_small_pd.duplicated(subset=['Game ID'], keep='first'), 0, None ) df_test_merge_small_pd['gameday_type'] = df_test_merge_small_pd['Game ID'].map(statcast_dict) df_test_merge_small_pd['Statcast'] = np.where( df_test_merge_small_pd['gameday_type'].isin(['E', 'P']), True, False ) df_test_merge_small_pd['vs_at'] = np.where( df_test_merge_small_pd['Home'].isin(['home']), ' vs ', ' @ ' ) df_test_merge_small_pd['Pitcher'] = df_test_merge_small_pd['Pitcher Name'] + ' (' + df_test_merge_small_pd['Pitcher Hand'] + ')' df_test_merge_small_pd['Batter'] = ''+df_test_merge_small_pd["Player"]+'' df_test_merge_small_pd['Gameday'] = 'Gameday' df_test_merge_small_pd['Savant'] = np.where( df_test_merge_small_pd['Statcast'], 'Statcast', '(No Statcast)' ) game_index = df_test_merge_small_pd.loc[~(df_test_merge_small_pd['Game'].isnull()), 'Game'].index df_test_merge_small_pd.loc[game_index+2,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Date']) df_test_merge_small_pd.loc[game_index+3,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Team'] + df_test_merge_small_pd.loc[game_index+0,'vs_at'] + df_test_merge_small_pd.loc[game_index+0,'Opponent']) df_test_merge_small_pd.loc[game_index+4,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Pitcher']) df_test_merge_small_pd.loc[game_index+5,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Gameday']) df_test_merge_small_pd.loc[game_index+6,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Savant']) df_test_merge_small_pd['Game'] = df_test_merge_small_pd['Game'].replace(0,None).fillna('') df_test_merge_small_output = df_test_merge_small_pd[['Game', 'Batting', 'Player', 'Position', 'Bats']] df_order_style = df_test_merge_small_output.style df_order_style = (df_order_style.set_precision(0) .set_table_styles( [ {'selector':'th', 'props' : [('border', '1px solid black')]}, ],overwrite=False) .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')] }, { 'selector': 'td', 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')] }], overwrite=False) .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '20px'}, overwrite=False) .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False) .set_table_styles([{'selector': 'th.col_heading.level0', 'props': [('background-color', '#d6d6d6')]}], overwrite=False) .set_table_styles([{'selector': 'th.col_heading.level1', 'props': [('background-color', '#a3a3a3')]}], 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', '225px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '100px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(3)', 'props': [('min-width', '225px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(4)', 'props': [('min-width', '100px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(5)', 'props': [('min-width', '100px')]}], overwrite=False) # .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '250px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th', 'props': [('height', '30px')]}], overwrite=False) .set_properties( **{'background-color':'#d6d6d6'}, # Apply only right border subset=df_test_merge_small_output.columns[0] # Only affects column 1 ) .set_properties( **{'border-top': 'none', 'border-bottom': 'none'}, subset=df_test_merge_small_output.columns[0] # Apply only to column 1 ) .apply(highlight_alternate_rows, axis=0, subset=df_test_merge_small_output.columns[1:]) ) def add_thick_border(s): return ['border-top: 3px solid black' if s['Batting'] == 1 else '' for _ in s] df_order_style = df_order_style.apply(add_thick_border, axis=1) return df_order_style @output @render.table def table2(): df_test_merge_small,batting_order_full_opp,statcast_dict = cached_data() import polars as pl df_test_merge_small = df_test_merge_small.with_columns( pl.col("Home").replace({"home": "H", "away": "A"}) ) print(df_test_merge_small) if df_test_merge_small is None: return pd.DataFrame({"Message": ["No Games as of this time"]}) df_pivot_sum = df_test_merge_small.group_by(['Player ID', 'Player','Bats']).agg([ pl.sum('count').alias('GP')]) df_pivot_order = df_test_merge_small.pivot(index=['Player ID', 'Player'], columns='Batting', values='count', aggregate_function='sum') df_pivot_position = df_test_merge_small.pivot(index=['Player ID', 'Player'], columns='Position', values='count', aggregate_function='sum') df_pivot_hand = df_test_merge_small.pivot(index=['Player ID', 'Player'], columns='Pitcher Hand', values='count', aggregate_function='sum') df_pivot_home = df_test_merge_small.pivot(index=['Player ID', 'Player'], columns='Home', values='count', aggregate_function='sum') df_test_merge = df_pivot_sum.join(df_pivot_order, on=['Player ID', 'Player'], how='left') df_test_merge = df_test_merge.join(df_pivot_position, on=['Player ID', 'Player'], how='left') df_test_merge = df_test_merge.join(df_pivot_hand, on=['Player ID', 'Player'], how='left') df_test_merge = df_test_merge.join(df_pivot_home, on=['Player ID', 'Player'], how='left').fill_null(0) df_test_merge = df_test_merge.sort(['GP']+[str(x) for x in list(range(1,10))] ,descending=[True]+[True]*9) df_test_merge = df_test_merge.with_columns( pl.concat_str( [ pl.lit(''), pl.col('Player'), pl.lit('') ] ).alias('Batter') ) # List of desired columns, including RHP and LHP desired_columns = [ 'Batter', 'Bats', 'GP', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'DH', 'LHP', 'RHP','H','A' ] # Ensure all desired columns exist in the Polars DataFrame. # If a column is missing, add it with a default value (e.g., empty string '') for col in desired_columns: if col not in df_test_merge.columns: df_test_merge = df_test_merge.with_columns(pl.lit('').alias(col)) # Now select the columns in the desired order df_test_merge = df_test_merge.select(desired_columns) # Define your mapping for hierarchical columns. cols = { # ('Player Info', 'Player ID'): 'Player ID', ('Player Info', 'Batter'): 'Batter', ('Player Info', 'Bats'): 'Bats', ('Player Info', 'GP'): 'GP', ('Batting Order', '1'): '1', ('Batting Order', '2'): '2', ('Batting Order', '3'): '3', ('Batting Order', '4'): '4', ('Batting Order', '5'): '5', ('Batting Order', '6'): '6', ('Batting Order', '7'): '7', ('Batting Order', '8'): '8', ('Batting Order', '9'): '9', ('Position', 'C'): 'C', ('Position', '1B'): '1B', ('Position', '2B'): '2B', ('Position', '3B'): '3B', ('Position', 'SS'): 'SS', ('Position', 'LF'): 'LF', ('Position', 'CF'): 'CF', ('Position', 'RF'): 'RF', ('Position', 'DH'): 'DH', ('Pitcher', 'LHP'): 'LHP', ('Pitcher', 'RHP'): 'RHP', ('Home', 'H'): 'H', ('Home', 'A'): 'A' } # Convert the Polars DataFrame to a pandas DataFrame df_pandas = df_test_merge.to_pandas() # Replace any zeros with empty strings (if needed) df_pandas = df_pandas.replace({0: ''}) # Create the MultiIndex for columns. # The order of tuples should correspond exactly to the order in desired_columns. # We can build the new MultiIndex by mapping the desired_columns using the `cols` dictionary. new_columns = [] for col in desired_columns: # Find the key in cols where value matches col. # This assumes your mapping has unique values. for key, value in cols.items(): if value == col: new_columns.append(key) break # Double-check that the length of new_columns matches the DataFrame columns. if len(new_columns) != len(df_pandas.columns): raise ValueError("Mismatch in number of columns between new MultiIndex and DataFrame columns.") # Apply the new MultiIndex to the DataFrame df_pandas.columns = pd.MultiIndex.from_tuples(new_columns) # Now your DataFrame has hierarchical columns and guaranteed 'LHP' and 'RHP' columns. df_pivot_style = df_pandas.style thick_border_cols = [2, 3, 12,21,23] # 0-based index # # Create a function to apply gradient only to integers # def apply_gradient(val): # if isinstance(val, int): # Check if the value is an integer # # Normalize the integer for the gradient (optional, here it's just a simple scale) # print(val) # return f'background-color: {norm(val)}' # return '' # No background for non-integer values norm = plt.Normalize(vmin=0, vmax=df_pandas.select_dtypes(include=['int']).max().max()) def apply_gradient(val): if isinstance(val, int): # Check if the value is an integer # Normalize the integer for the gradient return f'background-color: {mcolors.to_hex(cmap_up(norm(val)))}' return '' # No background for non-integer values df_pivot_style =(df_pivot_style.set_precision(0) .set_table_styles( [ {"selector": "td:nth-child(3)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column {"selector": "td:nth-child(12)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column {"selector": "td:nth-child(21)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column {"selector": "td:nth-child(23)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd {"selector": "td:nth-child(25)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column {'selector': 'thead th:nth-child(3)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header {'selector': 'thead th:nth-child(12)', 'props': [('border-right', '3px solid black')]}, {'selector': 'thead th:nth-child(21)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header {'selector': 'thead th:nth-child(23)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header {'selector': 'thead th:nth-child(25)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header {'selector': 'th.col_heading.level0', 'props': [('border-right', '3px solid black')]}, {'selector':'th', 'props' : [('border', '1px solid black')]}, ],overwrite=False) .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')] }, { 'selector': 'td', 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')] }], overwrite=False) .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '35px'}, overwrite=False) .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False) .set_table_styles([{'selector': 'th.col_heading.level0', 'props': [('background-color', '#b6b6b6')]}], overwrite=False) .set_table_styles([{'selector': 'th.col_heading.level1', 'props': [('background-color', '#a3a3a3')]}], 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', '100px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(1)', 'props': [('min-width', '225px')]}], overwrite=False) # .set_table_styles([{'selector': 'thead th:nth-child(2)', '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_pandas.columns[:]) .applymap(apply_gradient) ) return df_pivot_style @output @render.table def table3(): df_test_merge_small,batting_order_full_opp,statcast_dict = cached_data() if df_test_merge_small is None: return pd.DataFrame({"Message": ["No Games as of this time"]}) df_test_merge_small_ids = df_test_merge_small.to_pandas()[['Player ID','Player','Team']].drop_duplicates(subset='Player ID').reset_index(drop=True) df_test_merge_small_ids['yahoo_name'] = df_test_merge_small_ids['Player'].apply(lambda x: (difflib.get_close_matches(x, df_2023['full'])[:1] or [None])[0]) df_test_merge_small_ids = df_test_merge_small_ids.merge(right=team_abb_df,left_on=['Team'],right_on=['mlb_team'],how='left') #summary_2023 = summary_2023.merge(right=df_2023[['full','pos_new','percent_owned','display_position']],left_on=['cap_name','pos'],right_on=['full','pos_new'],how='left') df_test_merge_small_ids = df_test_merge_small_ids.merge(right=df_2023[['full','editorial_team_abbr','average_pick','display_position']],left_on=['yahoo_name','yahoo_team'],right_on=['full','editorial_team_abbr'],how='left').dropna() df_test_merge_small_eli = df_test_merge_small.to_pandas().merge(right=df_test_merge_small_ids[['Player ID','average_pick','display_position']],left_on=['Player ID'],right_on=['Player ID'],how='inner').fillna('') df_test_merge_small_eli['average_pick'] = df_test_merge_small_eli['average_pick'].replace('-','') df_test_merge_small_eli_small = df_test_merge_small_eli[df_test_merge_small_eli.apply(lambda x: x.Position not in x.display_position, axis=1)] df_test_merge_small_eli_small = df_test_merge_small_eli_small[df_test_merge_small_eli_small.Position != 'DH'].reset_index(drop=True) df_test_merge_small_eli_small_pivot = df_test_merge_small_eli_small.pivot_table(index=['Player ID','Player','Team','average_pick','display_position'], columns='Position', values='count', aggfunc='count').fillna(0) df_test_merge_small_eli_small_pivot['GP'] = df_test_merge_small_eli_small_pivot.sum(axis=1) df_test_merge_small_eli_small_pivot.index.names = ['Player ID','Player','Team','ADP','Yahoo Position'] elig_list = ['GP','C','1B','2B','3B','SS','LF','CF','RF'] for i in elig_list: if i not in df_test_merge_small_eli_small_pivot: df_test_merge_small_eli_small_pivot[i] = '' df_test_merge_small_eli_small_pivot = df_test_merge_small_eli_small_pivot[['GP','C','1B','2B','3B','SS','LF','CF','RF']].sort_values(by='GP',ascending=False) # df_test_merge_small_eli_small_pivot = df_test_merge_small_eli_small_pivot.astype({col: 'int' for col in df_test_merge_small_eli_small_pivot.columns}) # First convert your data to hierarchical columns cols = { ('Player Info', 'Player ID'): 'Player ID', ('Player Info', 'Player'): 'Player', ('Player Info', 'Team'): 'Team', ('Player Info', 'ADP'): 'ADP', ('Player Info', 'Yahoo Position '): 'Yahoo Position', ('Starts at New Position', 'GP'): 'GP', ('Starts at New Position', 'C'): 'C', ('Starts at New Position', '1B'): '1B', ('Starts at New Position', '2B'): '2B', ('Starts at New Position', '3B'): '3B', ('Starts at New Position', 'SS'): 'SS', ('Starts at New Position', 'LF'): 'LF', ('Starts at New Position', 'CF'): 'CF', ('Starts at New Position', 'RF'): 'RF', } # Assuming your polars DataFrame is called df # Convert to pandas df_yahoo_pandas = df_test_merge_small_eli_small_pivot.reset_index() df_yahoo_pandas = df_yahoo_pandas[~df_yahoo_pandas['Yahoo Position'].str.contains('P')] norm = plt.Normalize(vmin=0, vmax=df_yahoo_pandas['GP'].max()) df_yahoo_pandas = df_yahoo_pandas.replace({0: ''}) # Rename columns with multi-index df_yahoo_pandas.columns = pd.MultiIndex.from_tuples( [(k[0], k[1]) for k in cols.keys()] ) df_yahoo_style = df_yahoo_pandas.style def apply_gradient(val): if isinstance(val, float): # Check if the value is an integer # Normalize the integer for the gradient return f'background-color: {mcolors.to_hex(cmap_up(norm(val)))}' return '' # No background for non-integer values df_yahoo_style = (df_yahoo_style.set_precision(0) .set_table_styles( [ {"selector": "td:nth-child(5)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column {"selector": "td:nth-child(6)", "props": [("border-right", "3px solid black")]}, {"selector": "td:nth-child(14)", "props": [("border-right", "3px solid black")]}, {'selector': 'thead th:nth-child(5)', 'props': [('border-right', '3px solid black')]}, {'selector': 'thead th:nth-child(6)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header {'selector': 'thead th:nth-child(14)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header {'selector': 'th.col_heading.level0', 'props': [('border-right', '3px solid black')]}, {'selector':'th', 'props' : [('border', '1px solid black')]}, ],overwrite=False) .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')] }, { 'selector': 'td', 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')] }], overwrite=False) .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '35px'}, overwrite=False) .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False) .set_table_styles([{'selector': 'th.col_heading.level0', 'props': [('background-color', '#b6b6b6')]}], overwrite=False) .set_table_styles([{'selector': 'th.col_heading.level1', 'props': [('background-color', '#a3a3a3')]}], 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', '100px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '225px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(3)', 'props': [('min-width', '50px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(4)', 'props': [('min-width', '50px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(5)', 'props': [('min-width', '150px')]}], overwrite=False) # .set_table_styles([{'selector': 'thead th:nth-child(2)', '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_yahoo_pandas.columns[:]) .applymap(apply_gradient) ) return df_yahoo_style @output @render.table def table4(): df_test_merge_small,batting_order_full_opp,statcast_dict = cached_data() player_dict = dict(zip(df_test_merge_small['Player ID'],df_test_merge_small['Player'])) import polars as pl df_test_merge_small = df_test_merge_small.with_columns( pl.col("Home").replace({"home": "H", "away": "A"}) ) print(df_test_merge_small) if df_test_merge_small is None: return pd.DataFrame({"Message": ["No Games as of this time"]}) df_selected_player_games = df_test_merge_small.filter(pl.col('Player ID') == input.player_select())['Game ID'].unique() df_test_merge_small = df_test_merge_small.filter(pl.col('Game ID').is_in(df_selected_player_games)) df_test_merge_small = df_test_merge_small.with_columns( pl.lit(input.player_select()).alias('Selected Player ID'), pl.lit(player_dict[input.player_select()]).alias('Selected Player') ) df_pivot_sum = df_test_merge_small.group_by(['Selected Player ID','Selected Player', 'Player']).agg([ pl.sum('count').alias('GP')]).sort('GP',descending=True) # df_pivot_sum = df_pivot_sum.filter(pl.col('Player') != pl.col('Selected Player')) df_pivot_sum = df_pivot_sum.with_columns( pl.when(pl.col('Player') == pl.col('Selected Player')) .then(pl.lit(1)) .otherwise(pl.lit(0)) .alias('is_selected') ).sort(['is_selected', 'GP'], descending=[True, True]).drop('is_selected') df_pivot_sum = df_pivot_sum.with_columns(pl.col('GP').cast(pl.Float64)) df_pivot_sum_pd = df_pivot_sum.to_pandas() df_pivot_sum_pd.iloc[1:, 0] = '' df_pivot_sum_pd.iloc[1:, 1] = '' norm = plt.Normalize(vmin=0, vmax=df_pivot_sum_pd['GP'].max()) def apply_gradient(val): if isinstance(val, float): # Check if the value is an integer # Normalize the integer for the gradient return f'background-color: {mcolors.to_hex(cmap_up(norm(val)))}' return '' # No background for non-integer values def add_bottom_border(s): return ['border-bottom: 1px solid black' if i == len(s) - 1 else '' for i in range(len(s))] df_yahoo_style = (df_pivot_sum_pd.style.set_precision(0) .set_table_styles( [ # {"selector": "td:nth-child(2)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column # {"selector": "td:nth-child(4)", "props": [("border-right", "3px solid black")]}, # {"selector": "td:nth-child(14)", "props": [("border-right", "3px solid black")]}, # {'selector': 'thead th:nth-child(5)', 'props': [('border-right', '3px solid black')]}, # {'selector': 'thead th:nth-child(6)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header # {'selector': 'thead th:nth-child(14)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header # {'selector': 'th.col_heading.level0', 'props': [('border-right', '3px solid black')]}, {'selector':'th', 'props' : [('border', '1px solid black')]}, ],overwrite=False) .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')] }, { 'selector': 'td', 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')] }], overwrite=False) .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '35px'}, overwrite=False) .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False) .set_table_styles([{'selector': 'th.col_heading.level0', 'props': [('background-color', '#b6b6b6')]}], overwrite=False) .set_table_styles([{'selector': 'th.col_heading.level1', 'props': [('background-color', '#a3a3a3')]}], 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', '225px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(3)', 'props': [('min-width', '225px')]}], overwrite=False) .set_table_styles([{'selector': 'thead th:nth-child(4)', 'props': [('min-width', '50px')]}], overwrite=False) # .set_table_styles([{'selector': 'thead th:nth-child(5)', 'props': [('min-width', '150px')]}], overwrite=False) # .set_table_styles([{'selector': 'thead th:nth-child(2)', '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_pivot_sum_pd.columns[2:]) .applymap(apply_gradient) .set_properties( **{'border-top': 'none', 'border-bottom': 'none'}, subset=df_pivot_sum_pd.columns[0:2] # Apply only to column 1 ).apply(add_bottom_border, axis=0) ) return df_yahoo_style return df_pivot_sum_pd app = App(app_ui, server)