nesticot commited on
Commit
1c19008
·
verified ·
1 Parent(s): 469ba4a

Upload 2 files

Browse files
Files changed (2) hide show
  1. api_scraper.py +17 -9
  2. app.py +830 -153
api_scraper.py CHANGED
@@ -101,24 +101,32 @@ class MLB_Scrape:
101
  game_call = requests.get(url=f'https://statsapi.mlb.com/api/v1/schedule/?sportId={sport_id_str}&gameTypes={game_type_str}&season={year_input_str}&hydrate=lineup,players').json()
102
  try:
103
  # Extract relevant data from the API response
104
- game_list = [item for sublist in [[y.get('gamePk') for y in x['games']] for x in game_call['dates']] for item in sublist]
105
- time_list = [item for sublist in [[y.get('gameDate') for y in x['games']] for x in game_call['dates']] for item in sublist]
106
- date_list = [item for sublist in [[y.get('officialDate') for y in x['games']] for x in game_call['dates']] for item in sublist]
107
- away_team_list = [item for sublist in [[y['teams']['away']['team'].get('name') for y in x['games']] for x in game_call['dates']] for item in sublist]
108
- home_team_list = [item for sublist in [[y['teams']['home']['team'].get('name') for y in x['games']] for x in game_call['dates']] for item in sublist]
109
- state_list = [item for sublist in [[y['status'].get('codedGameState') for y in x['games']] for x in game_call['dates']] for item in sublist]
110
- venue_id = [item for sublist in [[y['venue'].get('id', None) for y in x['games']] for x in game_call['dates']] for item in sublist]
111
- venue_name = [item for sublist in [[y['venue'].get('name') for y in x['games']] for x in game_call['dates']] for item in sublist]
 
 
 
 
 
112
 
113
  # Create a Polars DataFrame with the extracted data
114
  game_df = pl.DataFrame(data={'game_id': game_list,
115
  'time': time_list,
116
  'date': date_list,
117
  'away': away_team_list,
 
118
  'home': home_team_list,
 
119
  'state': state_list,
120
  'venue_id': venue_id,
121
- 'venue_name': venue_name})
 
122
 
123
 
124
  # Check if the DataFrame is empty
 
101
  game_call = requests.get(url=f'https://statsapi.mlb.com/api/v1/schedule/?sportId={sport_id_str}&gameTypes={game_type_str}&season={year_input_str}&hydrate=lineup,players').json()
102
  try:
103
  # Extract relevant data from the API response
104
+ game_list = [item for sublist in [[y['gamePk'] for y in x['games']] for x in game_call['dates']] for item in sublist]
105
+ time_list = [item for sublist in [[y['gameDate'] for y in x['games']] for x in game_call['dates']] for item in sublist]
106
+ date_list = [item for sublist in [[y['officialDate'] for y in x['games']] for x in game_call['dates']] for item in sublist]
107
+ away_team_list = [item for sublist in [[y['teams']['away']['team']['name'] for y in x['games']] for x in game_call['dates']] for item in sublist]
108
+ away_team_id_list = [item for sublist in [[y['teams']['away']['team']['id'] for y in x['games']] for x in game_call['dates']] for item in sublist]
109
+ home_team_list = [item for sublist in [[y['teams']['home']['team']['name'] for y in x['games']] for x in game_call['dates']] for item in sublist]
110
+ home_team_id_list = [item for sublist in [[y['teams']['home']['team']['id'] for y in x['games']] for x in game_call['dates']] for item in sublist]
111
+ state_list = [item for sublist in [[y['status']['codedGameState'] for y in x['games']] for x in game_call['dates']] for item in sublist]
112
+ venue_id = [item for sublist in [[y['venue']['id'] for y in x['games']] for x in game_call['dates']] for item in sublist]
113
+ venue_name = [item for sublist in [[y['venue']['name'] for y in x['games']] for x in game_call['dates']] for item in sublist]
114
+ gameday_type = [item for sublist in [[y['gamedayType'] for y in x['games']] for x in game_call['dates']] for item in sublist]
115
+ # Create a Polars DataFrame with the extracted data
116
+
117
 
118
  # Create a Polars DataFrame with the extracted data
119
  game_df = pl.DataFrame(data={'game_id': game_list,
120
  'time': time_list,
121
  'date': date_list,
122
  'away': away_team_list,
123
+ 'away_id': away_team_id_list,
124
  'home': home_team_list,
125
+ 'home_id': home_team_id_list,
126
  'state': state_list,
127
  'venue_id': venue_id,
128
+ 'venue_name': venue_name,
129
+ 'gameday_type':gameday_type})
130
 
131
 
132
  # Check if the DataFrame is empty
app.py CHANGED
@@ -1,153 +1,830 @@
1
- import polars as pl
2
- import numpy as np
3
- import joblib
4
- from shiny import App, reactive, render, ui
5
- import matplotlib.pyplot as plt
6
- import matplotlib.ticker as tkr
7
- import seaborn as sns
8
- import adjustText
9
- sns.set_style('whitegrid')
10
-
11
-
12
- import matplotlib
13
- cmap_sum = matplotlib.colors.LinearSegmentedColormap.from_list("", ['#FFFFFF','#FFB000','#FE6100'])
14
-
15
- xwoba_model = joblib.load('joblib_model/xwoba_model.joblib')
16
-
17
- x = np.arange(-30,90.5,.5)
18
- y = np.arange(0,120.5,0.1)
19
-
20
- xx, yy = np.meshgrid(x, y)
21
-
22
- df = pl.DataFrame({'launch_angle': xx.ravel(), 'launch_speed': yy.ravel()})
23
-
24
- df = df.with_columns(
25
- pl.Series('xwoba', xwoba_model.predict_proba(df.select(['launch_angle','launch_speed'])) @ [0, 0.883, 1.244, 1.569, 2.004])
26
- )
27
-
28
- df = df.with_columns(
29
- pl.Series('xslg', xwoba_model.predict_proba(df.select(['launch_angle','launch_speed'])) @ [0, 1, 2, 3, 4])
30
- )
31
-
32
- app_ui = ui.page_sidebar(
33
- ui.sidebar(
34
- ui.markdown("""
35
- ### How to use this app
36
-
37
- 1. Click anywhere on the plot to select a point, or manually enter coordinates
38
- 2. The selected point's coordinates will update automatically
39
- 3. The xwOBA value will be calculated based on these coordinates
40
- """),
41
- ui.hr(),
42
- ui.input_numeric("x_select", "Launch Speed (mph)", value=110),
43
- ui.input_numeric("y_select", "Launch Angle (°)", value=30),
44
- ui.input_switch("flip_stat", "xwOBA", value=False),
45
-
46
-
47
- ),
48
- ui.output_plot("plot",width='900px',height='900px', click=True)
49
- )
50
-
51
-
52
- def server(input, output, session):
53
- # Store the coordinates in reactive values
54
- x_coord = reactive.value(110)
55
- y_coord = reactive.value(30)
56
-
57
- @reactive.effect
58
- @reactive.event(input.plot_click)
59
- def _():
60
- # Update reactive values when plot is clicked
61
- click_data = input.plot_click()
62
- if click_data is not None:
63
- x_coord.set(click_data["x"])
64
- y_coord.set(click_data["y"])
65
- # Update the numeric inputs
66
- ui.update_numeric("x_select", value=round(click_data["x"],1))
67
- ui.update_numeric("y_select", value=round(click_data["y"],1))
68
-
69
- @reactive.effect
70
- @reactive.event(input.x_select, input.y_select)
71
- def _():
72
- # Update reactive values when numeric inputs change
73
- x_coord.set(round(input.x_select(),1))
74
- y_coord.set(round(input.y_select(),1))
75
-
76
-
77
- @render.plot
78
- def plot():
79
- switch = input.flip_stat()
80
- fig, ax = plt.subplots(1, 1, figsize=(9, 9))
81
-
82
-
83
- if switch:
84
- h = ax.hexbin(df['launch_speed'],
85
- df['launch_angle'],
86
- C=df['xwoba'],
87
- gridsize=(40,25),
88
- cmap=cmap_sum,
89
- vmin=0.0,
90
- vmax=2.0,)
91
- bounds=[0.0,0.4,0.8,1.2,1.6,2.0]
92
- fig.colorbar(h, ax=ax, label='xwOBA',format=tkr.FormatStrFormatter('%.3f'),shrink=0.5,
93
- ticks=bounds)
94
-
95
- else:
96
- h = ax.hexbin(df['launch_speed'],
97
- df['launch_angle'],
98
- C=df['xslg'],
99
- gridsize=(40,25),
100
- cmap=cmap_sum,
101
- vmin=0.0,
102
- vmax=4.0,)
103
- bounds=[0.0,0.5,1,1.5,2,2.5,3,3.5,4]
104
- fig.colorbar(h, ax=ax, label='xSLG',format=tkr.FormatStrFormatter('%.3f'),shrink=0.5,
105
- ticks=bounds)
106
-
107
-
108
-
109
- ax.set_xlabel('Launch Speed')
110
- ax.set_ylabel('Launch Angle')
111
- if switch:
112
- ax.set_title('Exit Velocity vs Launch Angle\nExpected Weighted On Base Average (xwOBA)\nBy: @TJStats, Data:MLB')
113
- else:
114
- ax.set_title('Exit Velocity vs Launch Angle\nExpected Total Bases (xSLG)\nBy: @TJStats, Data:MLB')
115
-
116
- ax.grid(False)
117
- ax.axis('square')
118
- ax.set_xlim(0, 120)
119
- ax.set_ylim(-30, 90)
120
-
121
- x_select = input.x_select()
122
- y_select = input.y_select()
123
-
124
-
125
- sns.scatterplot(x=[x_select],y=[y_select],color='#648FFF',s=50,ax=ax,edgecolor='k',zorder=100)
126
-
127
-
128
- if switch:
129
- xwoba_value = (xwoba_model.predict_proba([[y_select,x_select]]) @ [0, 0.883, 1.244, 1.569, 2.004])[0]
130
- texts = [ax.text(x_select+3, y_select+3, f'xwOBA: {xwoba_value:.3f}', color='black', fontsize=12, weight='bold',
131
- zorder=1000, bbox=dict(facecolor='white', alpha=0.5, edgecolor='black'))]
132
-
133
- else:
134
- xwoba_value = (xwoba_model.predict_proba([[y_select,x_select]]) @ [0, 1, 2, 3, 4])[0]
135
- texts = [ax.text(x_select+3, y_select+3, f'xSLG: {xwoba_value:.3f}', color='black', fontsize=12, weight='bold',
136
- zorder=1000, bbox=dict(facecolor='white', alpha=0.5, edgecolor='black'))]
137
-
138
-
139
-
140
-
141
- adjustText.adjust_text(texts,
142
-
143
- arrowprops=dict(arrowstyle='->', color='#DC267F'),avoid_self=True,
144
- min_arrow_len =5)
145
- # xwoba_value =
146
-
147
- ax.axhline(y=y_select, color='k', linestyle='--',linewidth=1,alpha=0.5)
148
- ax.axvline(x=x_select, color='k', linestyle='--',linewidth=1,alpha=0.5)
149
-
150
- # ax.axis('square')
151
-
152
-
153
- app = App(app_ui, server)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #import packages
2
+ import pandas as pd
3
+ import json
4
+ import numpy as np
5
+ import requests
6
+ pd.options.mode.chained_assignment = None
7
+ from datetime import datetime
8
+ import time
9
+ import difflib
10
+ from datetime import datetime, timedelta
11
+ import api_scraper
12
+ import polars as pl
13
+ import matplotlib.colors
14
+ import matplotlib.colors as mcolors
15
+ from shiny import App, ui, render, reactive
16
+
17
+ def add_empty_rows(df, n_empty, period):
18
+ """ adds 'n_empty' empty rows every 'period' rows to 'df'.
19
+ Returns a new DataFrame. """
20
+
21
+ # to make sure that the DataFrame index is a RangeIndex(start=0, stop=len(df))
22
+ # and that the original df object is not mutated.
23
+ df = df.reset_index(drop=True)
24
+
25
+ # length of the new DataFrame containing the NaN rows
26
+ len_new_index = len(df) + n_empty*(len(df) // period)
27
+ # index of the new DataFrame
28
+ new_index = pd.RangeIndex(len_new_index)
29
+
30
+ # add an offset (= number of NaN rows up to that row)
31
+ # to the current df.index to align with new_index.
32
+ df.index += n_empty * (df.index
33
+ .to_series()
34
+ .groupby(df.index // period)
35
+ .ngroup())
36
+
37
+ # reindex by aligning df.index with new_index.
38
+ # Values of new_index not present in df.index are filled with NaN.
39
+ new_df = df.reindex(new_index)
40
+
41
+ return new_df
42
+
43
+ unique_team_list = [120,
44
+ 141,
45
+ 140,
46
+ 139,
47
+ 138,
48
+ 137,
49
+ 136,
50
+ 135,
51
+ 134,
52
+ 143,
53
+ 133,
54
+ 147,
55
+ 121,
56
+ 142,
57
+ 158,
58
+ 146,
59
+ 119,
60
+ 108,
61
+ 118,
62
+ 117,
63
+ 116,
64
+ 145,
65
+ 115,
66
+ 114,
67
+ 113,
68
+ 112,
69
+ 111,
70
+ 110,
71
+ 109,
72
+ 144]
73
+
74
+ ## Create a dataframe of teams to assist with selecting a player to search
75
+ #specifically for the case where multiple players share the same name
76
+ #Make an api call to get a dictionary of all teams
77
+ teams = requests.get(url='https://statsapi.mlb.com/api/v1/teams/').json()
78
+ #Select only teams that are at the MLB level
79
+ mlb_teams_city = [x['franchiseName'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball']
80
+ mlb_teams_name = [x['teamName'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball']
81
+ mlb_teams_franchise = [x['name'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball']
82
+ mlb_teams_id = [x['id'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball']
83
+ mlb_teams_abb = [x['abbreviation'] for x in teams['teams'] if x['sport']['name'] == 'Major League Baseball']
84
+
85
+ #Create a dataframe of all the teams
86
+ 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})
87
+ ##Create a dataframe of all players in the database
88
+ #Make an api call to get a dictionary of all players
89
+ player_data = requests.get(url='https://statsapi.mlb.com/api/v1/sports/11/players').json()
90
+
91
+ #Select relevant data that will help distinguish players from one another
92
+ fullName_list = [x['fullName'] for x in player_data['people']]
93
+ id_list = [x['id'] for x in player_data['people']]
94
+ position_list = [x['primaryPosition']['abbreviation'] for x in player_data['people']]
95
+ team_list = [x['currentTeam']['id']for x in player_data['people']]
96
+
97
+ #Create a dataframe of players and their current team ids
98
+ player_df_all = pd.DataFrame(data={'id':id_list,'name':fullName_list,'position':position_list,'team_id':team_list})
99
+ #Use the teams dataframe to merge the team name to the players dataframe
100
+ 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'])
101
+ #drop the duplicated id column
102
+ player_df_all = player_df_all.drop(columns=['team_id'])
103
+ #make a column of the names all uppercase to make lookups easier
104
+ player_df_all['upper_name'] = player_df_all['name'].str.upper()
105
+ #rename to make the data clearer
106
+ player_df_all = player_df_all.rename(columns={'franchise':'currentTeam'})
107
+
108
+
109
+
110
+ import matplotlib.pyplot as plt
111
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
112
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
113
+
114
+ cmap_up = mcolors.LinearSegmentedColormap.from_list("", ['#FFFFFF', '#FFB000'])
115
+
116
+
117
+ # Function to alternate row colors
118
+ def highlight_alternate_rows(x):
119
+ return ['background-color: #ebebeb' if i % 2 == 0 else '' for i in range(len(x))]
120
+ # Function to apply thick border to data cells
121
+ # Columns after which we want a thick vertical line
122
+
123
+ scraper = api_scraper.MLB_Scrape()
124
+ df_teams = scraper.get_teams()
125
+
126
+ teams_mlb = df_teams.filter(pl.col("league_id").is_in([103,104])).sort("abbreviation")
127
+ teams_dict = dict(zip(teams_mlb['team_id'],teams_mlb['abbreviation']))
128
+
129
+ teams_name_dict = dict(zip(teams_mlb['team_id'],teams_mlb['franchise']))
130
+
131
+ {109: 'Arizona Diamondbacks',
132
+ 144: 'Atlanta Braves',
133
+ 110: 'Baltimore Orioles',
134
+ 111: 'Boston Red Sox',
135
+ 112: 'Chicago Cubs',
136
+ 145: 'Chicago White Sox',
137
+ 113: 'Cincinnati Reds',
138
+ 114: 'Cleveland Guardians',
139
+ 115: 'Colorado Rockies',
140
+ 116: 'Detroit Tigers',
141
+ 117: 'Houston Astros',
142
+ 118: 'Kansas City Royals',
143
+ 108: 'Los Angeles Angels',
144
+ 119: 'Los Angeles Dodgers',
145
+ 146: 'Miami Marlins',
146
+ 158: 'Milwaukee Brewers',
147
+ 142: 'Minnesota Twins',
148
+ 121: 'New York Mets',
149
+ 147: 'New York Yankees',
150
+ #133: 'Athletics',
151
+ 133: 'Oakland Athletics',
152
+ 143: 'Philadelphia Phillies',
153
+ 134: 'Pittsburgh Pirates',
154
+ 135: 'San Diego Padres',
155
+ 137: 'San Francisco Giants',
156
+ 136: 'Seattle Mariners',
157
+ 138: 'St. Louis Cardinals',
158
+ 139: 'Tampa Bay Rays',
159
+ 140: 'Texas Rangers',
160
+ 141: 'Toronto Blue Jays',
161
+ 120: 'Washington Nationals'}
162
+
163
+
164
+ 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()
165
+ #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()
166
+ # 757 is bad
167
+ #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
168
+
169
+ total_list = []
170
+
171
+ for x in data_r_1['fantasy_content']['league']['players']:
172
+ single_list = []
173
+
174
+ single_list.append((x['player']['player_id']))
175
+ single_list.append(x['player']['name']['full'])
176
+ single_list.append(x['player']['name']['first'])
177
+ single_list.append(x['player']['name']['last'])
178
+ single_list.append(x['player']['draft_analysis']['average_pick'])
179
+ single_list.append(x['player']['average_auction_cost'])
180
+ single_list.append(x['player']['display_position'])
181
+ single_list.append(x['player']['editorial_team_abbr'])
182
+ total_list.append(single_list)
183
+
184
+ df_2023 = pd.DataFrame(data=total_list, columns=['player_id','full','first','last','average_pick', 'average_cost','display_position','editorial_team_abbr'])
185
+
186
+
187
+ team_abb_list = ['ATL', 'AZ', 'BAL', 'BOS', 'CHC', 'CIN', 'CLE', 'COL', 'CWS',
188
+ 'DET', 'HOU', 'KC', 'LAA', 'LAD', 'MIA', 'MIL', 'MIN', 'NYM',
189
+ 'NYY', 'ATH', 'PHI', 'PIT', 'SD', 'SEA', 'SF', 'STL', 'TB', 'TEX',
190
+ 'TOR', 'WSH']
191
+
192
+ team_abb_list_yahoo = ['ATL', 'ARI', 'BAL', 'BOS', 'CHC', 'CIN', 'CLE', 'COL', 'CWS',
193
+ 'DET', 'HOU', 'KC', 'LAA', 'LAD', 'MIA', 'MIL', 'MIN', 'NYM',
194
+ 'NYY', 'ATH', 'PHI', 'PIT', 'SD', 'SEA', 'SF', 'STL', 'TB', 'TEX',
195
+ 'TOR', 'WSH']
196
+ team_abb_df = pd.DataFrame({'mlb_team':team_abb_list,'yahoo_team':team_abb_list_yahoo})
197
+
198
+
199
+
200
+ from shiny import App, ui, render
201
+ import pandas as pd
202
+
203
+ # Create sample data for the tables
204
+ data1 = pd.DataFrame({
205
+ 'Name': ['Alice', 'Bob', 'Charlie'],
206
+ 'Age': [25, 30, 35],
207
+ 'City': ['New York', 'London', 'Paris']
208
+ })
209
+
210
+ data2 = pd.DataFrame({
211
+ 'Product': ['Laptop', 'Phone', 'Tablet'],
212
+ 'Price': [1200, 800, 500],
213
+ 'Stock': [50, 100, 75]
214
+ })
215
+
216
+ data3 = pd.DataFrame({
217
+ 'Country': ['USA', 'UK', 'France'],
218
+ 'Population': ['331M', '67M', '67M'],
219
+ 'Language': ['English', 'English', 'French']
220
+ })
221
+
222
+ app_ui = ui.page_sidebar(
223
+ ui.sidebar(
224
+ ui.input_select(
225
+ "team_id",
226
+ "Select Team",
227
+ choices=teams_dict
228
+ ),
229
+ # ui.h3("Sidebar"),
230
+ # ui.p("This is a demonstration of a card with tabs containing tables.")
231
+ ),
232
+ ui.card(
233
+ ui.output_text("selected_team_info"),
234
+ ui.navset_card_tab(
235
+ ui.nav_panel(
236
+ "Lineups",
237
+ ui.div({"style": "font-size:1.7em;"}, ui.output_text("lineup_title")),
238
+ ui.output_table("table1")
239
+ ),
240
+ ui.nav_panel(
241
+ "Summary",
242
+ ui.div({"style": "font-size:1.7em;"}, ui.output_text("summary_title")),
243
+ ui.output_table("table2")
244
+ ),
245
+ ui.nav_panel(
246
+ "Fantasy Eligibility",
247
+ ui.div({"style": "font-size:1.7em;"}, ui.output_text("fantasy_title")),
248
+ ui.output_table("table3")
249
+ )
250
+ )
251
+ )
252
+ )
253
+
254
+ def server(input, output, session):
255
+
256
+ @render.text
257
+ def lineup_title():
258
+
259
+ return f"{teams_name_dict[int(input.team_id())]} Spring Training Lineups"
260
+
261
+ @render.text
262
+ def summary_title():
263
+
264
+ return f"{teams_name_dict[int(input.team_id())]} Spring Training Lineup Summary"
265
+
266
+ @render.text
267
+ def fantasy_title():
268
+
269
+ return f"{teams_name_dict[int(input.team_id())]} Spring Training Position Eligibility Tracker - Yahoo"
270
+
271
+
272
+ @reactive.calc
273
+ def cached_data():
274
+ team_id_select = int(input.team_id())
275
+
276
+ df_schedule = scraper.get_schedule(year_input=[2025],sport_id=[1],game_type=['S','E'])
277
+
278
+ # df_schedule_p = scraper.get_schedule(year_input=[2024],sport_id=[21],game_type=['E'])
279
+
280
+ # df_schedule_all = pl.concat([df_schedule,df_schedule_p])
281
+
282
+ df_schedule_all = pl.concat([df_schedule])
283
+
284
+
285
+ 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)))
286
+ if df_schedule_team.is_empty():
287
+ return None, None, None
288
+ statcast_dict = dict(zip(df_schedule_team['game_id'],df_schedule_team['gameday_type']))
289
+ game_list = df_schedule_team['game_id'][:]
290
+ game_data = scraper.get_data(game_list)
291
+
292
+ print('Importing New Games.')
293
+ lineup_data = []
294
+ game_id = []
295
+ date = []
296
+ player_id = []
297
+ player_name = []
298
+ position = []
299
+ team_id = []
300
+ batting_order = []
301
+ handedness_batter = []
302
+ away_home = []
303
+ handedness_pitcher = []
304
+ pitcher_name = []
305
+ for y in range(0,len(game_data)):
306
+ 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']])
307
+ 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']])
308
+ 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']])
309
+ 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']])
310
+ 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']])
311
+ team_id.append([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']])
312
+ 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']])
313
+ 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']])
314
+ 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']])
315
+ 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']])
316
+ 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']])
317
+
318
+ 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']])
319
+ 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']])
320
+ 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']])
321
+ 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']])
322
+ 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']])
323
+ team_id.append([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']])
324
+ 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']])
325
+ 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']])
326
+ 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']])
327
+ 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']])
328
+ 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']])
329
+
330
+
331
+
332
+ game_id_final = [item for sublist in game_id for item in sublist]
333
+ date_final = [item for sublist in date for item in sublist]
334
+ player_id_final = [item for sublist in player_id for item in sublist]
335
+ player_name_final = [item for sublist in player_name for item in sublist]
336
+ position_final = [item for sublist in position for item in sublist]
337
+ team_id_final = [item for sublist in team_id for item in sublist]
338
+ batting_order_final = [item for sublist in batting_order for item in sublist]
339
+ away_home_final = [item for sublist in away_home for item in sublist]
340
+ handedness_batter_final = [item for sublist in handedness_batter for item in sublist]
341
+ handedness_pitcher_final = [item for sublist in handedness_pitcher for item in sublist]
342
+ pitcher_name_final = [item for sublist in pitcher_name for item in sublist]
343
+
344
+ 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']})
345
+ batting_order_full = pl.DataFrame(data={'game_id':game_id_final ,
346
+ 'date':date_final,
347
+ 'player_id':player_id_final,
348
+ 'player_name':player_name_final,
349
+ 'position':position_final,
350
+ 'team_id':team_id_final,
351
+ 'batting_order':batting_order_final,
352
+ 'away_home':away_home_final,
353
+ 'handedness_batter':handedness_batter_final,
354
+ 'handedness_pitcher':handedness_pitcher_final,
355
+ 'pitcher_name':pitcher_name_final})
356
+
357
+ # batting_order_full = batting_order_full
358
+
359
+
360
+
361
+ batting_order_full = batting_order_full.with_columns(pl.col('position').cast(pl.Int32))
362
+ batting_order_full = batting_order_full.join(df_teams[['team_id', 'franchise', 'abbreviation']], on='team_id', how='left')
363
+ position_df = position_df.with_columns(pl.col('position').cast(pl.Int32))
364
+ batting_order_full = batting_order_full.join(position_df, on='position', how='left')
365
+ batting_order_full_opp = batting_order_full.filter(pl.col('team_id') != team_id_select)
366
+
367
+ batting_order_full = batting_order_full.filter(pl.col('team_id') == team_id_select)
368
+ batting_order_full_filter = batting_order_full.filter(pl.col('batting_order').cast(pl.Int32) % 100 == 0)
369
+
370
+ 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')
371
+ batting_order_full_filter = batting_order_full_filter.unique(subset=['batting_order','game_id','away_home'])
372
+
373
+ batting_order_full_filter = batting_order_full_filter.with_columns(pl.col('batting_order').cast(pl.Int32))
374
+
375
+
376
+ df_test_merge = batting_order_full_filter.clone()
377
+ df_test_merge = df_test_merge.with_columns(pl.col('*').fill_null(np.nan))
378
+
379
+ df_test_merge = df_test_merge.sort(['player_id', 'date']).fill_null(strategy='forward')
380
+ df_test_merge_small = df_test_merge.select([
381
+ 'game_id', 'date', 'abbreviation', 'batting_order', 'player_id', 'player_name',
382
+ 'position_name', 'handedness_batter', 'handedness_pitcher', 'pitcher_name'
383
+ ]).sort(['date', 'game_id', 'abbreviation', 'batting_order'])
384
+
385
+ df_test_merge_small = df_test_merge_small.select(['game_id',
386
+ 'date', 'abbreviation', 'batting_order', 'player_id', 'player_name',
387
+ 'position_name', 'handedness_batter', 'handedness_pitcher', 'pitcher_name'
388
+ ]).fill_null('')
389
+
390
+ df_test_merge_small = df_test_merge_small.with_columns([
391
+ (pl.col('batting_order').cast(pl.Int32) / 100).cast(pl.Int32).alias('batting_order'),
392
+ pl.lit(1).alias('count'),
393
+ ])
394
+
395
+ df_test_merge_small = df_test_merge_small.rename({'game_id': 'Game ID',
396
+ 'date': 'Date', 'abbreviation': 'Team', 'batting_order': 'Batting',
397
+ 'player_id': 'Player ID', 'player_name': 'Player', 'position_name': 'Position',
398
+ 'handedness_batter': 'Bats', 'handedness_pitcher': 'Pitcher Hand',
399
+ 'pitcher_name': 'Pitcher Name'
400
+ })
401
+
402
+ return df_test_merge_small,batting_order_full_opp,statcast_dict
403
+
404
+
405
+
406
+ @output
407
+ @render.table
408
+ def table1():
409
+ df,batting_order_full_opp,statcast_dict = cached_data()
410
+ if df is None:
411
+ return pd.DataFrame({"Message": ["No Games as of this time"]})
412
+ team_opp = dict(zip(batting_order_full_opp['game_id'],batting_order_full_opp['abbreviation']))
413
+
414
+ df_test_merge_small_pd = df.to_pandas()
415
+
416
+ df_test_merge_small_pd['Opponent'] = df_test_merge_small_pd['Game ID'].map(team_opp)
417
+
418
+ # Create a new column with the date and opponent for the first occurrence of each game ID
419
+ df_test_merge_small_pd['Game'] = np.where(
420
+ ~df_test_merge_small_pd.duplicated(subset=['Game ID'], keep='first'),
421
+ 0,
422
+ None
423
+ )
424
+
425
+ df_test_merge_small_pd['gameday_type'] = df_test_merge_small_pd['Game ID'].map(statcast_dict)
426
+ df_test_merge_small_pd['Statcast'] = np.where(
427
+ df_test_merge_small_pd['gameday_type'].isin(['E', 'P']),
428
+ True,
429
+ False
430
+ )
431
+
432
+
433
+ df_test_merge_small_pd['Pitcher'] = df_test_merge_small_pd['Pitcher Name'] + ' (' + df_test_merge_small_pd['Pitcher Hand'] + ')'
434
+
435
+
436
+ 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>'
437
+
438
+
439
+ 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>'
440
+ df_test_merge_small_pd['Savant'] = np.where(
441
+ df_test_merge_small_pd['Statcast'],
442
+ '<a href=https://baseballsavant.mlb.com/gamefeed?gamePk='+df_test_merge_small_pd["Game ID"].astype(int).astype(str)+' target="_blank">Statcast</a>',
443
+ '(No Statcast)'
444
+ )
445
+
446
+ game_index = df_test_merge_small_pd.loc[~(df_test_merge_small_pd['Game'].isnull()), 'Game'].index
447
+
448
+ df_test_merge_small_pd.loc[game_index+2,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Date'])
449
+
450
+ df_test_merge_small_pd.loc[game_index+3,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Team'] + ' vs ' + df_test_merge_small_pd.loc[game_index+0,'Opponent'])
451
+
452
+ df_test_merge_small_pd.loc[game_index+4,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Pitcher'])
453
+ df_test_merge_small_pd.loc[game_index+5,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Gameday'])
454
+ df_test_merge_small_pd.loc[game_index+6,'Game'] = list(df_test_merge_small_pd.loc[game_index+0,'Savant'])
455
+
456
+ df_test_merge_small_pd['Game'] = df_test_merge_small_pd['Game'].replace(0,None).fillna('')
457
+
458
+ df_test_merge_small_output = df_test_merge_small_pd[['Game',
459
+
460
+ 'Batting',
461
+ 'Player',
462
+ 'Position',
463
+ 'Bats']]
464
+
465
+ df_order_style = df_test_merge_small_output.style
466
+
467
+
468
+ df_order_style = (df_order_style.set_precision(0)
469
+ .set_table_styles(
470
+ [
471
+
472
+
473
+ {'selector':'th', 'props' : [('border', '1px solid black')]},
474
+
475
+ ],overwrite=False)
476
+ .set_properties(**{'border': '3 px'}, overwrite=False)
477
+ .set_table_styles([{
478
+ 'selector': 'caption',
479
+ 'props': [
480
+ ('color', ''),
481
+ ('fontname', 'Century Gothic'),
482
+ ('font-size', '16px'),
483
+ ('font-style', 'italic'),
484
+ ('font-weight', ''),
485
+ ('text-align', 'centre'),
486
+ ]
487
+ },
488
+ {
489
+ 'selector': 'th',
490
+ 'props': [('font-size', '16px'), ('text-align', 'center'), ('Height', 'px'), ('color', 'black')]
491
+ },
492
+ {
493
+ 'selector': 'td',
494
+ 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')]
495
+ }], overwrite=False)
496
+
497
+ .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '20px'}, overwrite=False)
498
+ .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False)
499
+
500
+ .set_table_styles([{'selector': 'th.col_heading.level0', 'props': [('background-color', '#d6d6d6')]}], overwrite=False)
501
+ .set_table_styles([{'selector': 'th.col_heading.level1', 'props': [('background-color', '#a3a3a3')]}], overwrite=False)
502
+ .set_table_styles([{'selector': 'tr', 'props': [('line-height', '20px')]}], overwrite=False)
503
+ .set_properties(**{'Height': '8px'}, **{'text-align': 'center'}, overwrite=False)
504
+ .hide_index()
505
+ .set_properties(**{'border': '1px black solid'})
506
+ .set_table_styles([{'selector': 'thead th:nth-child(1)', 'props': [('min-width', '225px')]}], overwrite=False)
507
+ .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '100px')]}], overwrite=False)
508
+ .set_table_styles([{'selector': 'thead th:nth-child(3)', 'props': [('min-width', '225px')]}], overwrite=False)
509
+ .set_table_styles([{'selector': 'thead th:nth-child(4)', 'props': [('min-width', '100px')]}], overwrite=False)
510
+ .set_table_styles([{'selector': 'thead th:nth-child(5)', 'props': [('min-width', '100px')]}], overwrite=False)
511
+ # .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '250px')]}], overwrite=False)
512
+ .set_table_styles([{'selector': 'thead th', 'props': [('height', '30px')]}], overwrite=False)
513
+ .set_properties(
514
+ **{'background-color':'#d6d6d6'}, # Apply only right border
515
+ subset=df_test_merge_small_output.columns[0] # Only affects column 1
516
+ )
517
+ .set_properties(
518
+ **{'border-top': 'none', 'border-bottom': 'none'},
519
+ subset=df_test_merge_small_output.columns[0] # Apply only to column 1
520
+ )
521
+ .apply(highlight_alternate_rows, axis=0, subset=df_test_merge_small_output.columns[1:])
522
+ )
523
+
524
+
525
+ def add_thick_border(s):
526
+ return ['border-top: 3px solid black' if s['Batting'] == 1 else '' for _ in s]
527
+
528
+ df_order_style = df_order_style.apply(add_thick_border, axis=1)
529
+
530
+
531
+ return df_order_style
532
+
533
+ @output
534
+ @render.table
535
+ def table2():
536
+ df_test_merge_small,batting_order_full_opp,statcast_dict = cached_data()
537
+ if df_test_merge_small is None:
538
+ return pd.DataFrame({"Message": ["No Games as of this time"]})
539
+
540
+ df_pivot_sum = df_test_merge_small.group_by(['Player ID', 'Player','Bats']).agg([
541
+ pl.sum('count').alias('GP')])
542
+
543
+ df_pivot_order = df_test_merge_small.pivot(index=['Player ID', 'Player'], columns='Batting', values='count', aggregate_function='sum')
544
+ df_pivot_position = df_test_merge_small.pivot(index=['Player ID', 'Player'], columns='Position', values='count', aggregate_function='sum')
545
+ df_pivot_hand = df_test_merge_small.pivot(index=['Player ID', 'Player'], columns='Pitcher Hand', values='count', aggregate_function='sum')
546
+
547
+ df_test_merge = df_pivot_sum.join(df_pivot_order, on=['Player ID', 'Player'], how='left')
548
+ df_test_merge = df_test_merge.join(df_pivot_position, on=['Player ID', 'Player'], how='left')
549
+ df_test_merge = df_test_merge.join(df_pivot_hand, on=['Player ID', 'Player'], how='left').fill_null(0)
550
+ df_test_merge = df_test_merge.sort(['GP']+[str(x) for x in list(range(1,10))]
551
+ ,descending=[True]+[True]*9)
552
+
553
+
554
+
555
+ df_test_merge = df_test_merge.with_columns(
556
+ pl.concat_str(
557
+ [
558
+ pl.lit('<a href=https://baseballsavant.mlb.com/savant-player/'),
559
+ pl.col('Player ID').cast(pl.Utf8).str.slice(2),
560
+ pl.lit(' target="_blank">'),
561
+ pl.col('Player'),
562
+ pl.lit('</a>')
563
+ ]
564
+ ).alias('Batter')
565
+ )
566
+
567
+
568
+
569
+ df_test_merge = df_test_merge.select([
570
+ 'Player ID','Batter','Bats', 'GP', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'DH', 'LHP', 'RHP'
571
+ ])
572
+
573
+ # First convert your data to hierarchical columns
574
+ cols = {
575
+ ('Player Info', 'Player ID'): 'Player ID',
576
+ ('Player Info', 'Batter'): 'Batter',
577
+ ('Player Info', 'Bats'): 'Bats',
578
+ ('Player Info', 'GP'): 'GP',
579
+ ('Batting Order', '1'): '1',
580
+ ('Batting Order', '2'): '2',
581
+ ('Batting Order', '3'): '3',
582
+ ('Batting Order', '4'): '4',
583
+ ('Batting Order', '5'): '5',
584
+ ('Batting Order', '6'): '6',
585
+ ('Batting Order', '7'): '7',
586
+ ('Batting Order', '8'): '8',
587
+ ('Batting Order', '9'): '9',
588
+ ('Position', 'C'): 'C',
589
+ ('Position', '1B'): '1B',
590
+ ('Position', '2B'): '2B',
591
+ ('Position', '3B'): '3B',
592
+ ('Position', 'SS'): 'SS',
593
+ ('Position', 'LF'): 'LF',
594
+ ('Position', 'CF'): 'CF',
595
+ ('Position', 'RF'): 'RF',
596
+ ('Position', 'DH'): 'DH',
597
+ ('Hand', 'LHP'): 'LHP',
598
+ ('Hand', 'RHP'): 'RHP'
599
+ }
600
+ # Assuming your polars DataFrame is called df
601
+ # Convert to pandas
602
+ df_pandas = df_test_merge.to_pandas()
603
+
604
+
605
+ df_pandas = df_pandas.replace({0: ''})
606
+
607
+ # Rename columns with multi-index
608
+ df_pandas.columns = pd.MultiIndex.from_tuples(
609
+ [(k[0], k[1]) for k in cols.keys()]
610
+ )
611
+
612
+
613
+ df_pivot_style = df_pandas.style
614
+ thick_border_cols = [3, 4, 13,22] # 0-based index
615
+
616
+
617
+
618
+ # # Create a function to apply gradient only to integers
619
+ # def apply_gradient(val):
620
+ # if isinstance(val, int): # Check if the value is an integer
621
+ # # Normalize the integer for the gradient (optional, here it's just a simple scale)
622
+ # print(val)
623
+ # return f'background-color: {norm(val)}'
624
+
625
+ # return '' # No background for non-integer values
626
+ norm = plt.Normalize(vmin=0, vmax=df_pandas.select_dtypes(include=['int']).max().max())
627
+
628
+
629
+ def apply_gradient(val):
630
+ if isinstance(val, int): # Check if the value is an integer
631
+ # Normalize the integer for the gradient
632
+ return f'background-color: {mcolors.to_hex(cmap_up(norm(val)))}'
633
+ return '' # No background for non-integer values
634
+
635
+ df_pivot_style =(df_pivot_style.set_precision(0)
636
+ .set_table_styles(
637
+ [
638
+
639
+ {"selector": "td:nth-child(4)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column
640
+ {"selector": "td:nth-child(13)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column
641
+ {"selector": "td:nth-child(22)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column
642
+ {"selector": "td:nth-child(24)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column
643
+
644
+
645
+ {'selector': 'thead th:nth-child(4)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header
646
+ {'selector': 'thead th:nth-child(13)', 'props': [('border-right', '3px solid black')]},
647
+ {'selector': 'thead th:nth-child(22)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header
648
+ {'selector': 'thead th:nth-child(24)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header
649
+
650
+
651
+ {'selector': 'th.col_heading.level0', 'props': [('border-right', '3px solid black')]},
652
+
653
+
654
+ {'selector':'th', 'props' : [('border', '1px solid black')]},
655
+
656
+ ],overwrite=False)
657
+ .set_properties(**{'border': '3 px'}, overwrite=False)
658
+ .set_table_styles([{
659
+ 'selector': 'caption',
660
+ 'props': [
661
+ ('color', ''),
662
+ ('fontname', 'Century Gothic'),
663
+ ('font-size', '16px'),
664
+ ('font-style', 'italic'),
665
+ ('font-weight', ''),
666
+ ('text-align', 'centre'),
667
+ ]
668
+ },
669
+ {
670
+ 'selector': 'th',
671
+ 'props': [('font-size', '16px'), ('text-align', 'center'), ('Height', 'px'), ('color', 'black')]
672
+ },
673
+ {
674
+ 'selector': 'td',
675
+ 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')]
676
+ }], overwrite=False)
677
+
678
+ .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '35px'}, overwrite=False)
679
+ .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False)
680
+
681
+ .set_table_styles([{'selector': 'th.col_heading.level0', 'props': [('background-color', '#b6b6b6')]}], overwrite=False)
682
+ .set_table_styles([{'selector': 'th.col_heading.level1', 'props': [('background-color', '#a3a3a3')]}], overwrite=False)
683
+ .set_table_styles([{'selector': 'tr', 'props': [('line-height', '20px')]}], overwrite=False)
684
+ .set_properties(**{'Height': '8px'}, **{'text-align': 'center'}, overwrite=False)
685
+ .hide_index()
686
+ .set_properties(**{'border': '1px black solid'})
687
+ .set_table_styles([{'selector': 'thead th:nth-child(1)', 'props': [('min-width', '100px')]}], overwrite=False)
688
+ .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '225px')]}], overwrite=False)
689
+ # .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '250px')]}], overwrite=False)
690
+ .set_table_styles([{'selector': 'thead th', 'props': [('height', '30px')]}], overwrite=False)
691
+ .apply(highlight_alternate_rows, axis=0, subset=df_pandas.columns[:])
692
+ .applymap(apply_gradient)
693
+ )
694
+ return df_pivot_style
695
+
696
+ @output
697
+ @render.table
698
+ def table3():
699
+
700
+ df_test_merge_small,batting_order_full_opp,statcast_dict = cached_data()
701
+ if df_test_merge_small is None:
702
+ return pd.DataFrame({"Message": ["No Games as of this time"]})
703
+ df_test_merge_small_ids = df_test_merge_small.to_pandas()[['Player ID','Player','Team']].drop_duplicates(subset='Player ID').reset_index(drop=True)
704
+ 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])
705
+ df_test_merge_small_ids = df_test_merge_small_ids.merge(right=team_abb_df,left_on=['Team'],right_on=['mlb_team'],how='left')
706
+
707
+
708
+ #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')
709
+ 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()
710
+
711
+ 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('')
712
+ df_test_merge_small_eli['average_pick'] = df_test_merge_small_eli['average_pick'].replace('-','')
713
+ 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)]
714
+ df_test_merge_small_eli_small = df_test_merge_small_eli_small[df_test_merge_small_eli_small.Position != 'DH'].reset_index(drop=True)
715
+ 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)
716
+ df_test_merge_small_eli_small_pivot['GP'] = df_test_merge_small_eli_small_pivot.sum(axis=1)
717
+ df_test_merge_small_eli_small_pivot.index.names = ['Player ID','Player','Team','ADP','Yahoo Position']
718
+
719
+ elig_list = ['GP','C','1B','2B','3B','SS','LF','CF','RF']
720
+ for i in elig_list:
721
+ if i not in df_test_merge_small_eli_small_pivot:
722
+ df_test_merge_small_eli_small_pivot[i] = ''
723
+ 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)
724
+
725
+ # 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})
726
+
727
+ # First convert your data to hierarchical columns
728
+ cols = {
729
+ ('Player Info', 'Player ID'): 'Player ID',
730
+ ('Player Info', 'Player'): 'Player',
731
+ ('Player Info', 'Team'): 'Team',
732
+ ('Player Info', 'ADP'): 'ADP',
733
+ ('Player Info', 'Yahoo Position '): 'Yahoo Position',
734
+ ('Starts at New Position', 'GP'): 'GP',
735
+ ('Starts at New Position', 'C'): 'C',
736
+ ('Starts at New Position', '1B'): '1B',
737
+ ('Starts at New Position', '2B'): '2B',
738
+ ('Starts at New Position', '3B'): '3B',
739
+ ('Starts at New Position', 'SS'): 'SS',
740
+ ('Starts at New Position', 'LF'): 'LF',
741
+ ('Starts at New Position', 'CF'): 'CF',
742
+ ('Starts at New Position', 'RF'): 'RF',
743
+
744
+ }
745
+ # Assuming your polars DataFrame is called df
746
+ # Convert to pandas
747
+ df_yahoo_pandas = df_test_merge_small_eli_small_pivot.reset_index()
748
+
749
+ df_yahoo_pandas = df_yahoo_pandas[~df_yahoo_pandas['Yahoo Position'].str.contains('P')]
750
+
751
+ norm = plt.Normalize(vmin=0, vmax=df_yahoo_pandas['GP'].max())
752
+ df_yahoo_pandas = df_yahoo_pandas.replace({0: ''})
753
+
754
+ # Rename columns with multi-index
755
+ df_yahoo_pandas.columns = pd.MultiIndex.from_tuples(
756
+ [(k[0], k[1]) for k in cols.keys()]
757
+ )
758
+
759
+ df_yahoo_style = df_yahoo_pandas.style
760
+
761
+
762
+
763
+ def apply_gradient(val):
764
+ if isinstance(val, float): # Check if the value is an integer
765
+ # Normalize the integer for the gradient
766
+ return f'background-color: {mcolors.to_hex(cmap_up(norm(val)))}'
767
+ return '' # No background for non-integer values
768
+
769
+ df_yahoo_style = (df_yahoo_style.set_precision(0)
770
+ .set_table_styles(
771
+ [
772
+
773
+ {"selector": "td:nth-child(5)", "props": [("border-right", "3px solid black")]}, # Thick right border for the 3rd column
774
+ {"selector": "td:nth-child(6)", "props": [("border-right", "3px solid black")]},
775
+ {"selector": "td:nth-child(14)", "props": [("border-right", "3px solid black")]},
776
+
777
+ {'selector': 'thead th:nth-child(5)', 'props': [('border-right', '3px solid black')]},
778
+ {'selector': 'thead th:nth-child(6)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header
779
+ {'selector': 'thead th:nth-child(14)', 'props': [('border-right', '3px solid black')]}, # Thick right border for the 3rd header
780
+
781
+ {'selector': 'th.col_heading.level0', 'props': [('border-right', '3px solid black')]},
782
+
783
+
784
+ {'selector':'th', 'props' : [('border', '1px solid black')]},
785
+
786
+ ],overwrite=False)
787
+ .set_properties(**{'border': '3 px'}, overwrite=False)
788
+ .set_table_styles([{
789
+ 'selector': 'caption',
790
+ 'props': [
791
+ ('color', ''),
792
+ ('fontname', 'Century Gothic'),
793
+ ('font-size', '16px'),
794
+ ('font-style', 'italic'),
795
+ ('font-weight', ''),
796
+ ('text-align', 'centre'),
797
+ ]
798
+ },
799
+ {
800
+ 'selector': 'th',
801
+ 'props': [('font-size', '16px'), ('text-align', 'center'), ('Height', 'px'), ('color', 'black')]
802
+ },
803
+ {
804
+ 'selector': 'td',
805
+ 'props': [('text-align', 'center'), ('font-size', '16px'), ('color', 'black')]
806
+ }], overwrite=False)
807
+
808
+ .set_properties(**{'background-color': 'White', 'index': 'White', 'min-width': '35px'}, overwrite=False)
809
+ .set_table_styles([{'selector': 'th:first-child', 'props': [('background-color', 'white')]}], overwrite=False)
810
+
811
+ .set_table_styles([{'selector': 'th.col_heading.level0', 'props': [('background-color', '#b6b6b6')]}], overwrite=False)
812
+ .set_table_styles([{'selector': 'th.col_heading.level1', 'props': [('background-color', '#a3a3a3')]}], overwrite=False)
813
+ .set_table_styles([{'selector': 'tr', 'props': [('line-height', '20px')]}], overwrite=False)
814
+ .set_properties(**{'Height': '8px'}, **{'text-align': 'center'}, overwrite=False)
815
+ .hide_index()
816
+ .set_properties(**{'border': '1px black solid'})
817
+
818
+ .set_table_styles([{'selector': 'thead th:nth-child(1)', 'props': [('min-width', '100px')]}], overwrite=False)
819
+ .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '225px')]}], overwrite=False)
820
+ .set_table_styles([{'selector': 'thead th:nth-child(3)', 'props': [('min-width', '50px')]}], overwrite=False)
821
+ .set_table_styles([{'selector': 'thead th:nth-child(4)', 'props': [('min-width', '50px')]}], overwrite=False)
822
+ .set_table_styles([{'selector': 'thead th:nth-child(5)', 'props': [('min-width', '150px')]}], overwrite=False)
823
+ # .set_table_styles([{'selector': 'thead th:nth-child(2)', 'props': [('min-width', '250px')]}], overwrite=False)
824
+ .set_table_styles([{'selector': 'thead th', 'props': [('height', '30px')]}], overwrite=False)
825
+ .apply(highlight_alternate_rows, axis=0, subset=df_yahoo_pandas.columns[:])
826
+ .applymap(apply_gradient)
827
+ )
828
+ return df_yahoo_style
829
+
830
+ app = App(app_ui, server)