2025_mlb_lineup / app.py
nesticot's picture
Update app.py
12fa48a verified
#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'] = '<a href=https://baseballsavant.mlb.com/savant-player/'+'df_test_merge_small_pd["Player ID"].astype(str).str[2:]'+'target="_blank">'+df_test_merge_small_pd["Player"]+'</a>'
df_test_merge_small_pd['Gameday'] = '<a href=https://www.mlb.com/gameday/'+df_test_merge_small_pd["Game ID"].astype(int).astype(str)+' target="_blank">Gameday</a>'
df_test_merge_small_pd['Savant'] = np.where(
df_test_merge_small_pd['Statcast'],
'<a href=https://baseballsavant.mlb.com/gamefeed?gamePk='+df_test_merge_small_pd["Game ID"].astype(int).astype(str)+' target="_blank">Statcast</a>',
'(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('<a href=https://baseballsavant.mlb.com/savant-player/'),
pl.col('Player ID').cast(pl.Utf8).str.slice(2),
pl.lit(' target="_blank">'),
pl.col('Player'),
pl.lit('</a>')
]
).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)