nesticot commited on
Commit
438298c
·
verified ·
1 Parent(s): 24a438d

Upload 27 files

Browse files
app.py CHANGED
@@ -1,284 +1,280 @@
1
- import polars as pl
2
- import numpy as np
3
- import pandas as pd
4
- import api_scraper
5
- scrape = api_scraper.MLB_Scrape()
6
- from functions import df_update
7
- from functions import pitch_summary_functions
8
- update = df_update.df_update()
9
- from stuff_model import feature_engineering as fe
10
- from stuff_model import stuff_apply
11
- import requests
12
- import joblib
13
- from matplotlib.gridspec import GridSpec
14
- from shiny import App, reactive, ui, render
15
- from shiny.ui import h2, tags
16
- import matplotlib.pyplot as plt
17
- import matplotlib.gridspec as gridspec
18
- import seaborn as sns
19
- from functions.pitch_summary_functions import *
20
- from shiny import App, reactive, ui, render
21
- from shiny.ui import h2, tags
22
-
23
- colour_palette = ['#FFB000','#648FFF','#785EF0',
24
- '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
25
-
26
-
27
- year_list = [2017,2018,2019,2020,2021,2022,2023,2024]
28
-
29
-
30
-
31
- level_dict = {'1':'MLB',
32
- '11':'AAA',
33
- '12':'AA',
34
- '13':'A+',
35
- '14':'A',
36
- '17':'AFL',
37
- '22':'College',
38
- '21':'Prospects',
39
- '51':'International' }
40
-
41
- function_dict={
42
- 'velocity_kdes':'Velocity Distributions',
43
- 'break_plot':'Pitch Movement',
44
- 'tj_stuff_roling':'Rolling tjStuff+ by Pitch',
45
- 'tj_stuff_roling_game':'Rolling tjStuff+ by Game',
46
- 'location_plot_lhb':'Locations vs LHB',
47
- 'location_plot_rhb':'Locations vs RHB',
48
- }
49
-
50
-
51
- split_dict = {'all':'All',
52
- 'left':'LHH',
53
- 'right':'RHH'}
54
-
55
- split_dict_hand = {'all':['L','R'],
56
- 'left':['L'],
57
- 'right':['R']}
58
-
59
- from shiny import App, reactive, ui, render
60
- from shiny.ui import h2, tags
61
-
62
- # Define the UI layout for the app
63
- app_ui = ui.page_fluid(
64
- ui.layout_sidebar(
65
- ui.panel_sidebar(
66
- # Row for selecting season and level
67
- ui.row(
68
- ui.column(6, ui.input_select('year_input', 'Select Season', year_list, selected=2024)),
69
- ui.column(6, ui.input_select('level_input', 'Select Level', level_dict))
70
- ),
71
- # Row for the action button to get player list
72
- ui.row(ui.input_action_button("player_button", "Get Player List", class_="btn-primary")),
73
- # Row for selecting the player
74
- ui.row(ui.column(12, ui.output_ui('player_select_ui', 'Select Player'))),
75
- # Row for selecting the date range
76
- ui.row(ui.column(12, ui.output_ui('date_id', 'Select Date'))),
77
-
78
- # Rows for selecting plots and split options
79
- ui.row(
80
- ui.column(4, ui.input_select('plot_id_1', 'Plot Left', function_dict, multiple=False, selected='velocity_kdes')),
81
- ui.column(4, ui.input_select('plot_id_2', 'Plot Middle', function_dict, multiple=False, selected='tj_stuff_roling')),
82
- ui.column(4, ui.input_select('plot_id_3', 'Plot Right', function_dict, multiple=False, selected='break_plot'))
83
- ),
84
- ui.row(
85
- ui.column(6, ui.input_select('split_id', 'Select Split', split_dict, multiple=False)),
86
- ui.column(6, ui.input_numeric('rolling_window', 'Rolling Window (for tjStuff+ Plot)', min=1, value=50))
87
- ),
88
-
89
- # Row for the action button to generate plot
90
- ui.row(ui.input_action_button("generate_plot", "Generate Plot", class_="btn-primary")),
91
- ),
92
-
93
- ui.panel_main(
94
- ui.navset_tab(
95
- # Tab for game summary plot
96
- ui.nav("Pitching Summary",
97
- ui.output_text("status"),
98
- ui.output_plot('plot', width='2100px', height='2100px')
99
- ),
100
- )
101
- )
102
- )
103
- )
104
-
105
-
106
- def server(input, output, session):
107
-
108
- @reactive.calc
109
- @reactive.event(input.pitcher_id, input.date_id,input.split_id)
110
- def cached_data():
111
-
112
- year_input = int(input.year_input())
113
- sport_id = int(input.level_input())
114
- player_input = int(input.pitcher_id())
115
- start_date = str(input.date_id()[0])
116
- end_date = str(input.date_id()[1])
117
- # Simulate an expensive data operation
118
- game_list = scrape.get_player_games_list(sport_id = sport_id,
119
- season = year_input,
120
- player_id = player_input,
121
- start_date = start_date,
122
- end_date = end_date)
123
-
124
- data_list = scrape.get_data(game_list_input = game_list[:])
125
- df = (stuff_apply.stuff_apply(fe.feature_engineering(update.update(scrape.get_data_df(data_list = data_list).filter(
126
- (pl.col("pitcher_id") == player_input)&
127
- (pl.col("is_pitch") == True)&
128
- (pl.col('batter_hand').is_in(split_dict_hand[input.split_id()]))
129
-
130
- )))).with_columns(
131
- pl.col('pitch_type').count().over('pitch_type').alias('pitch_count')
132
- ))
133
- return df
134
-
135
- @render.ui
136
- @reactive.event(input.player_button, ignore_none=False)
137
- def player_select_ui():
138
- # Get the list of pitchers for the selected level and season
139
- df_pitcher_info = scrape.get_players(sport_id=int(input.level_input()), season=int(input.year_input())).filter(
140
- pl.col("position").is_in(['P','TWP'])).sort("name")
141
-
142
- # Create a dictionary of pitcher IDs and names
143
- pitcher_dict = dict(zip(df_pitcher_info['player_id'], df_pitcher_info['name']))
144
-
145
- # Return a select input for choosing a pitcher
146
- return ui.input_select("pitcher_id", "Select Pitcher", pitcher_dict, selectize=True)
147
-
148
- @render.ui
149
- @reactive.event(input.player_button, ignore_none=False)
150
- def date_id():
151
- # Create a date range input for selecting the date range within the selected year
152
- return ui.input_date_range("date_id", "Select Date Range",
153
- start=f"{int(input.year_input())}-01-01",
154
- end=f"{int(input.year_input())}-12-31",
155
- min=f"{int(input.year_input())}-01-01",
156
- max=f"{int(input.year_input())}-12-31")
157
- @output
158
- @render.text
159
- def status():
160
- # Only show status when generating
161
- if input.generate == 0:
162
- return ""
163
- return ""
164
-
165
- @output
166
- @render.plot
167
- @reactive.event(input.generate_plot, ignore_none=False)
168
- def plot():
169
- # Show progress/loading notification
170
- with ui.Progress(min=0, max=1) as p:
171
- p.set(message="Generating plot", detail="This may take a while...")
172
-
173
-
174
- p.set(0.3, "Gathering data...")
175
- year_input = int(input.year_input())
176
- sport_id = int(input.level_input())
177
- player_input = int(input.pitcher_id())
178
- start_date = str(input.date_id()[0])
179
- end_date = str(input.date_id()[1])
180
-
181
- print(year_input, sport_id, player_input, start_date, end_date)
182
-
183
- df = cached_data()
184
- df = df.clone()
185
-
186
- p.set(0.6, "Creating plot...")
187
-
188
-
189
- #plt.rcParams["figure.figsize"] = [10,10]
190
- fig = plt.figure(figsize=(26,26))
191
- plt.rcParams.update({'figure.autolayout': True})
192
- fig.set_facecolor('white')
193
- sns.set_theme(style="whitegrid", palette=colour_palette)
194
- print('this is the one plot')
195
-
196
- gs = gridspec.GridSpec(6, 8,
197
- height_ratios=[5,20,12,36,36,7],
198
- width_ratios=[4,18,18,18,18,18,18,4])
199
-
200
-
201
- gs.update(hspace=0.2, wspace=0.5)
202
-
203
- # Define the positions of each subplot in the grid
204
- ax_headshot = fig.add_subplot(gs[1,1:3])
205
- ax_bio = fig.add_subplot(gs[1,3:5])
206
- ax_logo = fig.add_subplot(gs[1,5:7])
207
-
208
- ax_season_table = fig.add_subplot(gs[2,1:7])
209
-
210
- ax_plot_1 = fig.add_subplot(gs[3,1:3])
211
- ax_plot_2 = fig.add_subplot(gs[3,3:5])
212
- ax_plot_3 = fig.add_subplot(gs[3,5:7])
213
-
214
- ax_table = fig.add_subplot(gs[4,1:7])
215
-
216
- ax_footer = fig.add_subplot(gs[-1,1:7])
217
- ax_header = fig.add_subplot(gs[0,1:7])
218
- ax_left = fig.add_subplot(gs[:,0])
219
- ax_right = fig.add_subplot(gs[:,-1])
220
-
221
- # Hide axes for footer, header, left, and right
222
- ax_footer.axis('off')
223
- ax_header.axis('off')
224
- ax_left.axis('off')
225
- ax_right.axis('off')
226
-
227
- sns.set_theme(style="whitegrid", palette=colour_palette)
228
- fig.set_facecolor('white')
229
-
230
- df_teams = scrape.get_teams()
231
-
232
- player_headshot(player_input=player_input, ax=ax_headshot,sport_id=sport_id,season=year_input)
233
- player_bio(pitcher_id=player_input, ax=ax_bio,sport_id=sport_id,year_input=year_input)
234
- plot_logo(pitcher_id=player_input, ax=ax_logo, df_team=df_teams,df_players=scrape.get_players(sport_id,year_input))
235
-
236
- stat_summary_table(df=df,
237
- ax=ax_season_table,
238
- player_input=player_input,
239
- split=input.split_id(),
240
- sport_id=sport_id)
241
-
242
- # break_plot(df=df_plot,ax=ax2)
243
- for x,y,z in zip([input.plot_id_1(),input.plot_id_2(),input.plot_id_3()],[ax_plot_1,ax_plot_2,ax_plot_3],[1,3,5]):
244
- if x == 'velocity_kdes':
245
- velocity_kdes(df,
246
- ax=y,
247
- gs=gs,
248
- gs_x=[3,4],
249
- gs_y=[z,z+2],
250
- fig=fig)
251
- if x == 'tj_stuff_roling':
252
- tj_stuff_roling(df=df,
253
- window=int(input.rolling_window()),
254
- ax=y)
255
-
256
- if x == 'tj_stuff_roling_game':
257
- tj_stuff_roling_game(df=df,
258
- window=int(input.rolling_window()),
259
- ax=y)
260
-
261
- if x == 'break_plot':
262
- break_plot(df = df,ax=y)
263
-
264
- if x == 'location_plot_lhb':
265
- location_plot(df = df,ax=y,hand='L')
266
-
267
- if x == 'location_plot_rhb':
268
- location_plot(df = df,ax=y,hand='R')
269
-
270
- summary_table(df=df,
271
- ax=ax_table)
272
-
273
- plot_footer(ax_footer)
274
-
275
- fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01)
276
-
277
-
278
-
279
-
280
- app = App(app_ui, server)
281
-
282
-
283
-
284
  app = App(app_ui, server)
 
1
+ import polars as pl
2
+ import numpy as np
3
+ import pandas as pd
4
+ import api_scraper
5
+ scrape = api_scraper.MLB_Scrape()
6
+ from functions import df_update
7
+ from functions import pitch_summary_functions
8
+ update = df_update.df_update()
9
+ from stuff_model import feature_engineering as fe
10
+ from stuff_model import stuff_apply
11
+ import requests
12
+ import joblib
13
+ from matplotlib.gridspec import GridSpec
14
+ from shiny import App, reactive, ui, render
15
+ from shiny.ui import h2, tags
16
+ import matplotlib.pyplot as plt
17
+ import matplotlib.gridspec as gridspec
18
+ import seaborn as sns
19
+ from functions.pitch_summary_functions import *
20
+ from shiny import App, reactive, ui, render
21
+ from shiny.ui import h2, tags
22
+
23
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
24
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
25
+
26
+
27
+ year_list = [2017,2018,2019,2020,2021,2022,2023,2024]
28
+
29
+
30
+
31
+ level_dict = {'1':'MLB',
32
+ '11':'AAA',
33
+ '12':'AA',
34
+ '13':'A+',
35
+ '14':'A',
36
+ '17':'AFL',
37
+ '22':'College',
38
+ '21':'Prospects',
39
+ '51':'International' }
40
+
41
+ function_dict={
42
+ 'velocity_kdes':'Velocity Distributions',
43
+ 'break_plot':'Pitch Movement',
44
+ 'tj_stuff_roling':'Rolling tjStuff+ by Pitch',
45
+ 'tj_stuff_roling_game':'Rolling tjStuff+ by Game',
46
+ 'location_plot_lhb':'Locations vs LHB',
47
+ 'location_plot_rhb':'Locations vs RHB',
48
+ }
49
+
50
+
51
+ split_dict = {'all':'All',
52
+ 'left':'LHH',
53
+ 'right':'RHH'}
54
+
55
+ split_dict_hand = {'all':['L','R'],
56
+ 'left':['L'],
57
+ 'right':['R']}
58
+
59
+ from shiny import App, reactive, ui, render
60
+ from shiny.ui import h2, tags
61
+
62
+ # Define the UI layout for the app
63
+ app_ui = ui.page_fluid(
64
+ ui.layout_sidebar(
65
+ ui.panel_sidebar(
66
+ # Row for selecting season and level
67
+ ui.row(
68
+
69
+ ui.column(6, ui.input_date('date_input', 'Select Date')),
70
+ ui.column(6, ui.input_select('level_input', 'Select Level', level_dict))
71
+ ),
72
+ ui.row(ui.input_action_button("game_button", "Get Games", class_="btn-primary")),
73
+ ui.row(
74
+
75
+ ui.row(ui.column(12, ui.output_ui('game_select_ui', 'Select Game'))),
76
+ ui.row(ui.column(12, ui.output_ui('player_select_ui', 'Select Player'))),
77
+ ),
78
+
79
+ # Rows for selecting plots and split options
80
+ ui.row(
81
+ ui.column(4, ui.input_select('plot_id_1', 'Plot Left', function_dict, multiple=False, selected='velocity_kdes')),
82
+ ui.column(4, ui.input_select('plot_id_2', 'Plot Middle', function_dict, multiple=False, selected='tj_stuff_roling')),
83
+ ui.column(4, ui.input_select('plot_id_3', 'Plot Right', function_dict, multiple=False, selected='break_plot'))
84
+ ),
85
+ ui.row(
86
+ ui.column(6, ui.input_select('split_id', 'Select Split', split_dict, multiple=False)),
87
+ ui.column(6, ui.input_numeric('rolling_window', 'Rolling Window (for tjStuff+ Plot)', min=1, value=50))
88
+ ),
89
+
90
+ # Row for the action button to generate plot
91
+ ui.row(ui.input_action_button("generate_plot", "Generate Plot", class_="btn-primary"))
92
+ ),
93
+
94
+ ui.panel_main(
95
+ ui.navset_tab(
96
+ # Tab for game summary plot
97
+ ui.nav("Pitching Summary",
98
+ ui.output_text("status"),
99
+ ui.output_plot('plot', width='2100px', height='2100px')
100
+ ),
101
+ )
102
+ )
103
+ )
104
+ )
105
+
106
+
107
+ def server(input, output, session):
108
+
109
+ @render.ui
110
+ @reactive.event(input.game_button, ignore_none=False)
111
+ def game_select_ui():
112
+ df = (scrape.get_schedule(year_input=[int(str(input.date_input())[:4])],
113
+ sport_id=[int(input.level_input())],
114
+ game_type=['S','R','P','E','A']).with_columns(pl.col('date').cast(pl.Utf8)).
115
+ filter(pl.col('date') == str(input.date_input()))).with_columns(
116
+ (pl.col('away')+' @ '+pl.col('home')).alias('matchup'))
117
+ game_dict = dict(zip(df['game_id'], df['matchup']))
118
+
119
+ return ui.input_select("game_id", "Select Game", game_dict, selectize=True)
120
+
121
+
122
+ @render.ui
123
+ @reactive.event(input.game_id)
124
+ def player_select_ui():
125
+ try:
126
+ # Get the list of pitchers for the selected level and season
127
+ data_list = scrape.get_data(game_list_input = [int(input.game_id())])
128
+ df = (stuff_apply.stuff_apply(fe.feature_engineering(update.update(scrape.get_data_df(data_list = data_list).filter(
129
+ (pl.col("is_pitch") == True)&
130
+ (pl.col('batter_hand').is_in(split_dict_hand[input.split_id()]))
131
+
132
+ )))).with_columns(
133
+ pl.col('pitch_type').count().over('pitch_type').alias('pitch_count')
134
+ ).with_columns(
135
+ (pl.col('pitcher_name')+' - '+pl.col('pitcher_team')).alias('pitcher_name'))
136
+ )
137
+ pitcher_dict = dict(zip(df['pitcher_id'], df['pitcher_name']))
138
+ return ui.input_select("pitcher_id", "Select Pitcher", pitcher_dict, selectize=True)
139
+ except Exception as e:
140
+ print(e)
141
+ return ui.output_text('pitcher_id',"No pitchers available for this game")
142
+
143
+ @output
144
+ @render.text
145
+ def status():
146
+ # Only show status when generating
147
+ if input.generate == 0:
148
+ return ""
149
+ return ""
150
+
151
+ @output
152
+ @render.plot
153
+ @reactive.event(input.generate_plot, ignore_none=False)
154
+ def plot():
155
+ # Show progress/loading notification
156
+ with ui.Progress(min=0, max=1) as p:
157
+ p.set(message="Generating plot", detail="This may take a while...")
158
+
159
+
160
+ p.set(0.3, "Gathering data...")
161
+
162
+ data_list = scrape.get_data(game_list_input = [int(input.game_id())])
163
+ df = (stuff_apply.stuff_apply(fe.feature_engineering(update.update(scrape.get_data_df(data_list = data_list).filter(
164
+ (pl.col("pitcher_id") == int(input.pitcher_id()))&
165
+ (pl.col("is_pitch") == True)&
166
+ (pl.col('batter_hand').is_in(split_dict_hand[input.split_id()]))
167
+
168
+ )))).with_columns(
169
+ pl.col('pitch_type').count().over('pitch_type').alias('pitch_count')
170
+ ))
171
+ df = df.clone()
172
+
173
+ p.set(0.6, "Creating plot...")
174
+
175
+
176
+ #plt.rcParams["figure.figsize"] = [10,10]
177
+ fig = plt.figure(figsize=(26,26))
178
+ plt.rcParams.update({'figure.autolayout': True})
179
+ fig.set_facecolor('white')
180
+ sns.set_theme(style="whitegrid", palette=colour_palette)
181
+ print('this is the one plot')
182
+
183
+ gs = gridspec.GridSpec(6, 8,
184
+ height_ratios=[5,20,12,36,36,7],
185
+ width_ratios=[4,18,18,18,18,18,18,4])
186
+
187
+
188
+ gs.update(hspace=0.2, wspace=0.5)
189
+
190
+ # Define the positions of each subplot in the grid
191
+ ax_headshot = fig.add_subplot(gs[1,1:3])
192
+ ax_bio = fig.add_subplot(gs[1,3:5])
193
+ ax_logo = fig.add_subplot(gs[1,5:7])
194
+
195
+ ax_season_table = fig.add_subplot(gs[2,1:7])
196
+
197
+ ax_plot_1 = fig.add_subplot(gs[3,1:3])
198
+ ax_plot_2 = fig.add_subplot(gs[3,3:5])
199
+ ax_plot_3 = fig.add_subplot(gs[3,5:7])
200
+
201
+ ax_table = fig.add_subplot(gs[4,1:7])
202
+
203
+
204
+ ax_footer = fig.add_subplot(gs[-1,1:7])
205
+ ax_header = fig.add_subplot(gs[0,1:7])
206
+ ax_left = fig.add_subplot(gs[:,0])
207
+ ax_right = fig.add_subplot(gs[:,-1])
208
+
209
+ # Hide axes for footer, header, left, and right
210
+ ax_footer.axis('off')
211
+ ax_header.axis('off')
212
+ ax_left.axis('off')
213
+ ax_right.axis('off')
214
+
215
+ sns.set_theme(style="whitegrid", palette=colour_palette)
216
+ fig.set_facecolor('white')
217
+
218
+ df_teams = scrape.get_teams()
219
+
220
+ year_input = int(str(input.date_input())[:4])
221
+ sport_id = int(input.level_input())
222
+ player_input = int(input.pitcher_id())
223
+
224
+ player_headshot(player_input=player_input, ax=ax_headshot,sport_id=sport_id,season=year_input)
225
+ player_bio(pitcher_id=player_input, ax=ax_bio,sport_id=sport_id,year_input=year_input)
226
+ plot_logo(pitcher_id=player_input, ax=ax_logo, df_team=df_teams,df_players=scrape.get_players(sport_id,year_input))
227
+
228
+ # stat_summary_table(df=df,
229
+ # ax=ax_season_table,
230
+ # player_input=player_input,
231
+ # split=input.split_id(),
232
+ # sport_id=sport_id)
233
+
234
+ stat_daily_summary(df=df,
235
+ data=data_list,
236
+ player_input=int(input.pitcher_id()),
237
+ sport_id=int(input.level_input()),
238
+ ax=ax_season_table)
239
+
240
+
241
+ # break_plot(df=df_plot,ax=ax2)
242
+ for x,y,z in zip([input.plot_id_1(),input.plot_id_2(),input.plot_id_3()],[ax_plot_1,ax_plot_2,ax_plot_3],[1,3,5]):
243
+ if x == 'velocity_kdes':
244
+ velocity_kdes(df,
245
+ ax=y,
246
+ gs=gs,
247
+ gs_x=[3,4],
248
+ gs_y=[z,z+2],
249
+ fig=fig)
250
+ if x == 'tj_stuff_roling':
251
+ tj_stuff_roling(df=df,
252
+ window=int(input.rolling_window()),
253
+ ax=y)
254
+
255
+ if x == 'tj_stuff_roling_game':
256
+ tj_stuff_roling_game(df=df,
257
+ window=int(input.rolling_window()),
258
+ ax=y)
259
+
260
+ if x == 'break_plot':
261
+ break_plot(df = df,ax=y)
262
+
263
+ if x == 'location_plot_lhb':
264
+ location_plot(df = df,ax=y,hand='L')
265
+
266
+ if x == 'location_plot_rhb':
267
+ location_plot(df = df,ax=y,hand='R')
268
+
269
+ summary_table(df=df,
270
+ ax=ax_table)
271
+
272
+ plot_footer(ax_footer)
273
+
274
+ fig.subplots_adjust(left=0.01, right=0.99, top=0.99, bottom=0.01)
275
+
276
+
277
+
278
+
279
+
 
 
 
 
280
  app = App(app_ui, server)
app_streamlit.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import seaborn as sns
2
+ import streamlit as st
3
+ from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode
4
+ import PitchPlotFunctions as ppf
5
+ import requests
6
+ import polars as pl
7
+ from datetime import date
8
+ import api_scraper
9
+
10
+
11
+
12
+ # Display the app title and description
13
+ st.markdown("""
14
+ ## MLB & AAA Pitch Plots App
15
+
16
+ ##### By: Thomas Nestico ([@TJStats](https://x.com/TJStats))
17
+ ##### Code: [GitHub Repo](https://github.com/tnestico/streamlit_pitch_plots)
18
+ ##### Data: [MLB](https://baseballsavant.mlb.com/)
19
+
20
+ #### About
21
+ This Streamlit app retrieves MLB and AAA Pitching Data for a selected pitcher from the MLB Stats API and is accessed using my [MLB Stats API Scraper](https://github.com/tnestico/mlb_scraper).
22
+
23
+ The app outputs the pitcher's data into both a plot and table to illustrate and summarize the data.
24
+ It can also display data for games currently in progress.
25
+
26
+ *More information about the data and plots is shown at the bottom of this page.*
27
+
28
+ """
29
+ )
30
+
31
+ # Initialize the plotter object from PitchPlotFunctions
32
+ ploter = ppf.PitchPlotFunctions()
33
+ # Initialize the scraper object
34
+ scraper = api_scraper.MLB_Scrape()
35
+
36
+ # Dictionary mapping league names to sport IDs
37
+ sport_id_dict = {'MLB': 1, 'AAA': 11}
38
+
39
+ # Create two columns for league and pitcher selection
40
+ st.write("#### Plot")
41
+ col_1, col_2 = st.columns(2)
42
+ with col_1:
43
+ # Select league
44
+ selected_league = st.selectbox('##### Select League', list(sport_id_dict.keys()))
45
+ selected_sport_id = sport_id_dict[selected_league]
46
+
47
+ with col_2:
48
+ # Get player data and filter for pitchers
49
+ df_player = scraper.get_players(sport_id=selected_sport_id)
50
+ df_player = df_player.filter(pl.col('position').str.contains('P'))
51
+ df_player = df_player.with_columns(
52
+ (pl.concat_str(["name", "player_id"], separator=" - ").alias("pitcher_name_id"))
53
+ )
54
+
55
+ # Select specific columns and convert to dictionary
56
+ pitcher_name_id_dict = dict(df_player.select(['pitcher_name_id', 'player_id']).iter_rows())
57
+
58
+ # Initialize session state for previous selection
59
+ if 'prev_pitcher_id' not in st.session_state:
60
+ st.session_state.prev_pitcher_id = None
61
+
62
+ # Display a selectbox for pitcher selection
63
+ selected_pitcher = st.selectbox("##### Select Pitcher", list(pitcher_name_id_dict.keys()))
64
+ pitcher_id = pitcher_name_id_dict[selected_pitcher]
65
+
66
+ # Clear cache if selection changes
67
+ if pitcher_id != st.session_state.prev_pitcher_id:
68
+ st.cache_data.clear()
69
+ st.session_state.prev_pitcher_id = pitcher_id
70
+ st.session_state.cache_cleared = False
71
+ st.write('Cache cleared!')
72
+
73
+ # Initialize session state for cache status
74
+ if 'cache_cleared' not in st.session_state:
75
+ st.session_state.cache_cleared = False
76
+
77
+ # Dictionary for batter hand selection
78
+ batter_hand_picker = {
79
+ 'All': ['L', 'R'],
80
+ 'LHH': ['L'],
81
+ 'RHH': ['R']
82
+ }
83
+
84
+ # Define date range for the season
85
+ min_date = date(2024, 3, 20)
86
+ max_date = date(2024, 11, 30)
87
+
88
+ # Create columns for input widgets
89
+ st.write("##### Filters")
90
+ col1, col2, col3 = st.columns(3)
91
+ with col1:
92
+ # Selectbox for batter handedness
93
+ batter_hand_select = st.selectbox('Batter Handedness:', list(batter_hand_picker.keys()))
94
+ batter_hand = batter_hand_picker[batter_hand_select]
95
+ with col2:
96
+ # Date input for start date
97
+ start_date = st.date_input('Start Date:',
98
+ value=min_date,
99
+ min_value=min_date,
100
+ max_value=max_date,
101
+ format="YYYY-MM-DD")
102
+ with col3:
103
+ # Date input for end date
104
+ end_date = st.date_input('End Date:',
105
+ value="default_value_today",
106
+ min_value=min_date,
107
+ max_value=max_date,
108
+ format="YYYY-MM-DD")
109
+
110
+ # Dictionary for plot type selection
111
+ plot_picker_dict = {
112
+ 'Short Form Movement': 'short_form_movement',
113
+ 'Long Form Movement': 'long_form_movement',
114
+ 'Release Points': 'release_point'
115
+ }
116
+
117
+ # Selectbox for plot type
118
+ plot_picker_select = st.selectbox('Select Plot Type:', list(plot_picker_dict.keys()))
119
+ plot_picker = plot_picker_dict[plot_picker_select]
120
+
121
+ # Extract season from start date
122
+ season = str(start_date)[0:4]
123
+
124
+ # Get list of games for the selected player and date range
125
+ player_games = scraper.get_player_games_list(player_id=pitcher_id, season=season,
126
+ start_date=str(start_date), end_date=str(end_date),
127
+ sport_id=selected_sport_id,
128
+ game_type = ['R','P'])
129
+
130
+ # Function to fetch data and cache it
131
+ @st.cache_data
132
+ def fetch_data():
133
+ data = scraper.get_data(game_list_input=player_games)
134
+ df = scraper.get_data_df(data_list=data)
135
+ return df
136
+
137
+ # Fetch data and manage cache status
138
+ if not st.session_state.cache_cleared:
139
+ df_original = fetch_data()
140
+ st.session_state.cache_cleared = True
141
+ else:
142
+ df_original = fetch_data()
143
+
144
+ # Button to generate plot
145
+ if st.button('Generate Plot'):
146
+ try:
147
+ # Convert dataframe to polars and filter based on inputs
148
+ df = ploter.df_to_polars(df_original=df_original,
149
+ pitcher_id=pitcher_id,
150
+ start_date=str(start_date),
151
+ end_date=str(end_date),
152
+ batter_hand=batter_hand)
153
+ print(df)
154
+ if len(df) == 0:
155
+ st.write('Please select different parameters.')
156
+ else:
157
+ # Generate the final plot
158
+ ploter.final_plot(
159
+ df=df,
160
+ pitcher_id=pitcher_id,
161
+ plot_picker=plot_picker,
162
+ sport_id=selected_sport_id)
163
+
164
+ # Use a container to control the width of the AgGrid display
165
+ with st.container():
166
+ # Group the data by pitch type
167
+ grouped_df = (
168
+ df.group_by(['pitcher_id', 'pitch_description'])
169
+ .agg([
170
+ pl.col('is_pitch').drop_nans().count().alias('pitches'),
171
+ pl.col('start_speed').drop_nans().mean().round(1).alias('start_speed'),
172
+ pl.col('vb').drop_nans().mean().round(1).alias('vb'),
173
+ pl.col('ivb').drop_nans().mean().round(1).alias('ivb'),
174
+ pl.col('hb').drop_nans().mean().round(1).alias('hb'),
175
+ pl.col('spin_rate').drop_nans().mean().round(0).alias('spin_rate'),
176
+ pl.col('x0').drop_nans().mean().round(1).alias('x0'),
177
+ pl.col('z0').drop_nans().mean().round(1).alias('z0'),
178
+ ])
179
+ .with_columns(
180
+ (pl.col('pitches') / pl.col('pitches').sum().over('pitcher_id') * 100).round(3).alias('proportion')
181
+ )).sort('proportion', descending=True).select(["pitch_description", "pitches", "proportion", "start_speed", "vb", "ivb", "hb",
182
+ "spin_rate", "x0", "z0"])
183
+
184
+ st.write("#### Pitching Data")
185
+ column_config_dict = {
186
+ 'pitcher_id': 'Pitcher ID',
187
+ 'pitch_description': 'Pitch Type',
188
+ 'pitches': 'Pitches',
189
+ 'start_speed': 'Velocity',
190
+ 'vb': 'VB',
191
+ 'ivb': 'iVB',
192
+ 'hb': 'HB',
193
+ 'spin_rate': 'Spin Rate',
194
+ 'proportion': st.column_config.NumberColumn("Pitch%", format="%.1f%%"),
195
+ 'x0': 'hRel',
196
+ 'z0': 'vRel',
197
+ }
198
+
199
+ st.markdown(f"""##### {selected_pitcher.split('-')[0]} {selected_league} Pitch Data""")
200
+ st.dataframe(grouped_df,
201
+ hide_index=True,
202
+ column_config=column_config_dict,
203
+ width=1500)
204
+
205
+ # Configure the AgGrid options
206
+ # gb = GridOptionsBuilder.from_dataframe(grouped_df)
207
+ # # Set display names for columns
208
+ # for col, display_name in zip(grouped_df.columns, grouped_df.columns):
209
+ # gb.configure_column(col, headerName=display_name)
210
+
211
+ # grid_options = gb.build()
212
+
213
+ # # Display the dataframe using AgGrid
214
+ # grid_response = AgGrid(
215
+ # grouped_df,
216
+ # gridOptions=grid_options,
217
+ # height=300,
218
+ # allow_unsafe_jscode=True,
219
+ # )
220
+
221
+ except IndexError:
222
+ st.write('Please select different parameters.')
223
+
224
+ # Display column and plot descriptions
225
+ st.markdown("""
226
+ #### Column Descriptions
227
+
228
+ - **`Pitch Type`**: Describes the type of pitch thrown (e.g., 4-Seam Fastball, Curveball, Slider).
229
+ - **`Pitches`**: The total number of pitches thrown by the pitcher.
230
+ - **`Pitch%`**: Proportion of pitch thrown.
231
+ - **`Velocity`**: The initial velocity of the pitch as it leaves the pitcher's hand, measured in miles per hour (mph).
232
+ - **`VB`**: Vertical Break (VB), representing the amount movement of a pitch due to spin and gravity, measured in inches (in).
233
+ - **`iVB`**: Induced Vertical Break (iVB), representing the amount movement of a pitch strictly due to the spin imparted on the ball, measured in inches (in).
234
+ - **`HB`**: Horizontal Break (HB), indicating the amount of horizontal movement of a pitch, measured in inches (in).
235
+ - **`Spin Rate`**: The rate of spin of the pitch as it is released, measured in revolutions per minute (rpm).
236
+ - **`hRel`**: The horizontal release point of the pitch, measured in feet from the center of the pitcher's mound (ft).
237
+ - **`vRel`**: The vertical release point of the pitch, measured in feet above the ground (ft).
238
+
239
+ #### Plot Descriptions
240
+
241
+ - **`Short Form Movement`**: Illustrates the movement of the pitch due to spin, where (0,0) indicates a pitch with perfect gyro-spin (e.g. Like a Football).
242
+ - **`Long Form Movement`**: Illustrates the movement of the pitch due to spin and gravity.
243
+ - **`Release Points`**: Illustrates a pitchers release points from the catcher's perspective.
244
+
245
+ #### Acknowledgements
246
+
247
+ Big thanks to [Michael Rosen](https://twitter.com/bymichaelrosen) and [Jeremy Maschino](https://twitter.com/pitchprofiler) for inspiration for this project
248
+
249
+ Check Out Michael's [Pitch Plotting App](https://pitchplotgenerator.streamlit.app/)
250
+
251
+ Check Out Jeremy's Website [Pitch Profiler](http://www.mlbpitchprofiler.com/)
252
+ """
253
+ )
functions/__pycache__/pitch_summary_functions.cpython-39.pyc CHANGED
Binary files a/functions/__pycache__/pitch_summary_functions.cpython-39.pyc and b/functions/__pycache__/pitch_summary_functions.cpython-39.pyc differ
 
functions/pitch_summary_functions.py CHANGED
@@ -866,7 +866,7 @@ def plot_logo(pitcher_id: str, ax: plt.Axes, df_team: pl.DataFrame, df_players:
866
 
867
  # Turn off the axis
868
  ax.axis('off')
869
- except KeyError:
870
  ax.axis('off')
871
  return
872
 
@@ -1121,3 +1121,149 @@ def stat_summary_table(df: pl.DataFrame,
1121
  # Add title to the plot
1122
  ax.text(0.5, 0.9, title, va='bottom', ha='center', fontsize=36, fontstyle='italic')
1123
  ax.axis('off')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
866
 
867
  # Turn off the axis
868
  ax.axis('off')
869
+ except (KeyError,IndexError) as e:
870
  ax.axis('off')
871
  return
872
 
 
1121
  # Add title to the plot
1122
  ax.text(0.5, 0.9, title, va='bottom', ha='center', fontsize=36, fontstyle='italic')
1123
  ax.axis('off')
1124
+
1125
+
1126
+
1127
+ def stat_daily_summary(df: pl.DataFrame,
1128
+ data: list,
1129
+ player_input: int,
1130
+ sport_id: int,
1131
+ ax: plt.Axes):
1132
+
1133
+
1134
+ pk_list = []
1135
+ pitcher_id_list = []
1136
+ summary_list = []
1137
+ ip_list = []
1138
+ pa_list = []
1139
+ er_list = []
1140
+ hit_list = []
1141
+ k_list = []
1142
+ bb_list = []
1143
+ hbp_list = []
1144
+ strikes_list = []
1145
+ hr_list = []
1146
+ test_list = []
1147
+ game_pk_list = []
1148
+ pitches_list = []
1149
+
1150
+
1151
+ # 'inningsPitched','battersFaced','earnedRuns','hits','strikeOuts','baseOnBalls','hitByPitch'
1152
+
1153
+ for y in range(0,len(data)):
1154
+
1155
+ pk_list.append([data[y]['gameData']['game']['pk'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1156
+ pk_list.append([data[y]['gameData']['game']['pk'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1157
+
1158
+ pitcher_id_list.append([x for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1159
+ pitcher_id_list.append([x for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1160
+
1161
+
1162
+ ip_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['inningsPitched'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1163
+ ip_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['inningsPitched'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1164
+
1165
+ pa_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['battersFaced'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1166
+ pa_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['battersFaced'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1167
+
1168
+ er_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['earnedRuns'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1169
+ er_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['earnedRuns'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1170
+
1171
+ hit_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['hits'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1172
+ hit_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['hits'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1173
+
1174
+ k_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['strikeOuts'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1175
+ k_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['strikeOuts'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1176
+
1177
+ bb_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['baseOnBalls'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1178
+ bb_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['baseOnBalls'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1179
+
1180
+ hbp_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['hitByPitch'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1181
+ hbp_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['hitByPitch'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1182
+
1183
+ strikes_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['strikes'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1184
+ strikes_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['strikes'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1185
+
1186
+ pitches_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['pitchesThrown'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1187
+ pitches_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['pitchesThrown'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1188
+
1189
+
1190
+ hr_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['homeRuns'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1191
+ hr_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['homeRuns'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1192
+
1193
+ summary_list.append([data[y]['liveData']['boxscore']['teams']['away']['players']['ID'+str(x)]['stats']['pitching']['summary'] for x in data[y]['liveData']['boxscore']['teams']['away']['pitchers']])
1194
+ summary_list.append([data[y]['liveData']['boxscore']['teams']['home']['players']['ID'+str(x)]['stats']['pitching']['summary'] for x in data[y]['liveData']['boxscore']['teams']['home']['pitchers']])
1195
+
1196
+ test_list.append([x for x in data[y]['liveData']['plays']['allPlays']])
1197
+ game_pk_list.append([data[y]['gameData']['game']['pk'] for x in data[y]['liveData']['plays']['allPlays']])
1198
+
1199
+ flat_list_pk = [item for sublist in pk_list for item in sublist]
1200
+ flat_list_pitcher_id = [item for sublist in pitcher_id_list for item in sublist]
1201
+ flat_list_summary = [item for sublist in summary_list for item in sublist]
1202
+ flat_list_hits = [item for sublist in hit_list for item in sublist]
1203
+ flat_list_k = [item for sublist in k_list for item in sublist]
1204
+ flat_list_bb = [item for sublist in bb_list for item in sublist]
1205
+ flat_list_pa = [item for sublist in pa_list for item in sublist]
1206
+ flat_list_ip = [item for sublist in ip_list for item in sublist]
1207
+ flat_list_hbp= [item for sublist in hbp_list for item in sublist]
1208
+ flat_list_strikes = [item for sublist in strikes_list for item in sublist]
1209
+ flat_list_hr= [item for sublist in hr_list for item in sublist]
1210
+ flat_list_er= [item for sublist in er_list for item in sublist]
1211
+ flat_list_pitches= [item for sublist in pitches_list for item in sublist]
1212
+
1213
+
1214
+
1215
+ pitcher_summary_df = pl.DataFrame(data={'game_id':flat_list_pk,
1216
+ 'pitcher_id':flat_list_pitcher_id,
1217
+ 'summary':flat_list_summary,
1218
+ 'hits':flat_list_hits,
1219
+ 'k':flat_list_k,
1220
+ 'bb':flat_list_bb,
1221
+ 'pa':flat_list_pa,
1222
+ 'hbp':flat_list_hbp,
1223
+ 'strikes':flat_list_strikes,
1224
+ 'hr':flat_list_hr,
1225
+ 'ip':flat_list_ip,
1226
+ 'er':flat_list_er,
1227
+ 'pitches':flat_list_pitches})
1228
+
1229
+
1230
+
1231
+ # Add additional calculated columns
1232
+ pitcher_summary_df = pitcher_summary_df.filter(pl.col('pitcher_id')==player_input).with_columns(
1233
+ pl.lit(df['is_whiff'].sum()).alias('whiffs'),
1234
+ ((pl.col('strikes'))/(pl.col('pitches'))*100).round(1).cast(pl.Utf8).str.concat('%').alias('strikePercentage')
1235
+ )
1236
+
1237
+ # Determine columns and title based on game count and sport ID
1238
+
1239
+ pitcher_stats_call_df_small = pitcher_summary_df.select(['ip',
1240
+ 'pa',
1241
+ 'er',
1242
+ 'hits',
1243
+ 'k',
1244
+ 'bb',
1245
+ 'hbp',
1246
+ 'hr',
1247
+ 'strikePercentage',
1248
+ 'whiffs'])
1249
+
1250
+ new_column_names = ['$\\bf{IP}$', '$\\bf{PA}$', '$\\bf{ER}$', '$\\bf{H}$', '$\\bf{K}$', '$\\bf{BB}$', '$\\bf{HBP}$', '$\\bf{HR}$', '$\\bf{Strike\%}$', '$\\bf{Whiffs}$']
1251
+ title = f'{df["game_date"][0]} vs {df["batter_team"][0]}'
1252
+
1253
+ table_fg = ax.table(cellText=pitcher_stats_call_df_small.to_numpy(), colLabels=pitcher_stats_call_df_small.columns, cellLoc='center',
1254
+ bbox=[0.0, 0.1, 1, 0.7])
1255
+
1256
+ min_font_size = 20
1257
+ table_fg.set_fontsize(min_font_size)
1258
+
1259
+
1260
+ new_column_names = ['$\\bf{IP}$','$\\bf{PA}$','$\\bf{ER}$','$\\bf{H}$','$\\bf{K}$','$\\bf{BB}$','$\\bf{HBP}$','$\\bf{HR}$','$\\bf{Strike\%}$','$\\bf{Whiffs}$']
1261
+ # #new_column_names = ['Pitch Name', 'Pitch%', 'Velocity', 'Spin Rate','Exit Velocity', 'Whiff%', 'CSW%']
1262
+ for i, col_name in enumerate(new_column_names):
1263
+ table_fg.get_celld()[(0, i)].get_text().set_text(col_name)
1264
+
1265
+ ax.axis('off')
1266
+
1267
+ # Add title to the plot
1268
+ ax.text(0.5, 0.9, title, va='bottom', ha='center', fontsize=36, fontstyle='italic')
1269
+ ax.axis('off')
pitching_summary_api.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt CHANGED
@@ -1,14 +1,12 @@
1
- joblib==1.3.2
2
- lightgbm
3
- matplotlib==3.5.1
4
- numpy==1.23.5
5
- pandas==1.5.2
6
- polars==1.12.0
7
- Requests==2.31.0
8
- scipy==1.11.1
9
- seaborn==0.11.1
10
- scikit-learn==1.0.1
11
- shiny==0.6.1
12
- Jinja2==3.1.4
13
- tqdm==4.62.3
14
-
 
1
+ joblib==1.4.2
2
+ matplotlib==3.5.1
3
+ numpy==1.22.1
4
+ pandas==2.0.3
5
+ Pillow==11.0.0
6
+ polars==1.12.0
7
+ pytz==2022.7.1
8
+ Requests==2.32.3
9
+ seaborn==0.11.1
10
+ shiny==0.7.1
11
+ streamlit==1.37.1
12
+ tqdm==4.62.3