nesticot commited on
Commit
8ae8da9
·
verified ·
1 Parent(s): e93dde0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +505 -505
app.py CHANGED
@@ -1,505 +1,505 @@
1
- from shiny import App, ui, render, reactive
2
- import polars as pl
3
- import numpy as np
4
- import pandas as pd
5
- import api_scraper
6
- scrape = api_scraper.MLB_Scrape()
7
- from functions import df_update
8
- from functions import pitch_summary_functions
9
- update = df_update.df_update()
10
- from stuff_model import feature_engineering as fe
11
- from stuff_model import stuff_apply
12
- import requests
13
- import joblib
14
- from matplotlib.gridspec import GridSpec
15
- import math
16
- from pytabulator import TableOptions, Tabulator, output_tabulator, render_tabulator, theme
17
- theme.tabulator_site()
18
-
19
- colour_palette = ['#FFB000','#648FFF','#785EF0',
20
- '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
21
-
22
- # df = pl.read_csv("data.csv")
23
- # df = pl.read_parquet("data_small.parquet")[:]
24
- # df = pl.read_parquet("data.parquet")[:]
25
- # print('df')
26
- season = 2024
27
-
28
- df_mlb = pl.read_parquet("data/data_mlb_2024.parquet")[:]
29
- df_aaa = pl.read_parquet("data/data_aaa_2024.parquet")[:]
30
- df_a = pl.read_parquet("data/data_a_2024.parquet")[:]
31
-
32
- # df_mlb = pl.read_parquet("data_small.parquet")[:]
33
- # df_aaa = pl.read_parquet("data_small_aaa.parquet")[:]
34
- # df_a = pl.read_parquet("data_small_a.parquet")[:]
35
-
36
-
37
-
38
-
39
- def df_final(df:pl.dataframe,year_input:int,sport_id:int):
40
-
41
- df_schedule = scrape.get_schedule(year_input=[year_input],sport_id=[sport_id])
42
- df = df.join(df_schedule, on='game_id', how='left')
43
-
44
- df = df.with_columns(
45
- pl.when((pl.col('batter_team_id') == pl.col('away_id')))
46
- .then(pl.lit('Away'))
47
- .when((pl.col('batter_team_id') == pl.col('home_id')))
48
- .then(pl.lit('Home'))
49
- .otherwise(None)
50
- .alias('home_away')
51
- )
52
-
53
- df = df.with_columns(
54
- pl.when((pl.col('pitcher_team_id') == pl.col('away_id')))
55
- .then(pl.lit('Away'))
56
- .when((pl.col('pitcher_team_id') == pl.col('home_id')))
57
- .then(pl.lit('Home'))
58
- .otherwise(None)
59
- .alias('home_away_pitcher')
60
- )
61
-
62
-
63
- print('schedule')
64
-
65
- df_stuff = stuff_apply.stuff_apply(fe.feature_engineering(df))
66
- print('stuff')
67
- df_up = update.update(df)
68
- print('update')
69
- df_total = df_up.join(df_stuff[['play_id','tj_stuff_plus']], on='play_id', how='left')
70
- print('total')
71
- return df_total
72
-
73
-
74
- df_mlb_total = df_final(df=df_mlb,year_input=season,sport_id=1)
75
- df_aaa_total = df_final(df=df_aaa,year_input=season,sport_id=11)
76
- df_a_total = df_final(df=df_a.drop_nulls(subset=['start_speed']),year_input=season,sport_id=14)
77
-
78
- rounding_dict = {
79
- 'pa': 0,
80
- 'bip': 0,
81
- 'hits': 0,
82
- 'k': 0,
83
- 'bb': 0,
84
- 'max_launch_speed': 1,
85
- 'launch_speed_90': 1,
86
- 'launch_speed': 1,
87
- 'pitches': 0,
88
- 'tj_stuff_plus_avg': 0,
89
- 'avg': 3,
90
- 'obp': 3,
91
- 'slg': 3,
92
- 'ops': 3,
93
- 'k_percent': 3,
94
- 'bb_percent': 3,
95
- 'k_minus_bb_percent': 3,
96
- 'sweet_spot_percent': 3,
97
- 'woba_percent': 3,
98
- 'xwoba_percent': 3,
99
- 'woba_percent_contact': 3,
100
- 'xwoba_percent_contact': 3,
101
- 'hard_hit_percent': 3,
102
- 'barrel_percent': 3,
103
- 'zone_contact_percent': 3,
104
- 'zone_swing_percent': 3,
105
- 'zone_percent': 3,
106
- 'chase_percent': 3,
107
- 'chase_contact': 3,
108
- 'swing_percent': 3,
109
- 'whiff_rate': 3,
110
- 'swstr_rate': 3,
111
- 'ground_ball_percent': 3,
112
- 'line_drive_percent': 3,
113
- 'fly_ball_percent': 3,
114
- 'pop_up_percent': 3,
115
- 'pulled_fly_ball_percent': 3,
116
- 'heart_zone_swing_percent': 3,
117
- 'shadow_zone_swing_percent': 3,
118
- 'chase_zone_swing_percent': 3,
119
- 'waste_zone_swing_percent': 3,
120
- 'heart_zone_whiff_percent': 3,
121
- 'shadow_zone_whiff_percent': 3,
122
- 'chase_zone_whiff_percent': 3,
123
- 'waste_zone_whiff_percent': 3,
124
- 'start_speed_avg': 1,
125
- 'vb_avg': 1,
126
- 'ivb_avg': 1,
127
- 'hb_avg': 1,
128
- 'z0_avg': 1,
129
- 'x0_avg': 1,
130
- 'vaa_avg': 1,
131
- 'haa_avg': 1,
132
- 'spin_rate_avg': 0,
133
- 'extension_avg': 1
134
- }
135
-
136
- columns = [
137
- { "title": "PA", "field": "pa", "width": 150},
138
- { "title": "BBE", "field": "bip", "width": 150 },
139
- { "title": "H", "field": "hits", "width": 150 },
140
- { "title": "K", "field": "k", "width": 150 },
141
- { "title": "BB", "field": "bb", "width": 150 },
142
- { "title": "Max EV", "field": "max_launch_speed", "width": 150 },
143
- { "title": "90th% EV", "field": "launch_speed_90", "width": 150 },
144
- { "title": "EV", "field": "launch_speed", "width": 150 },
145
- { "title": "Pitches", "field": "pitches", "width": 150 },
146
- { "title": "AVG", "field": "avg", "width": 150 },
147
- { "title": "OBP", "field": "obp", "width": 150 },
148
- { "title": "SLG", "field": "slg", "width": 150 },
149
- { "title": "OPS", "field": "ops", "width": 150 },
150
- { "title": "K%", "field": "k_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
151
- { "title": "BB%", "field": "bb_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
152
- { "title": "K-BB%", "field": "k_minus_bb_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
153
- { "title": "SwSpot%", "field": "sweet_spot_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
154
- { "title": "wOBA", "field": "woba_percent", "width": 150 },
155
- { "title": "xwOBA", "field": "xwoba_percent", "width": 150 },
156
- { "title": "wOBACON", "field": "woba_percent_contact", "width": 150 },
157
- { "title": "xwOBACON", "field": "xwoba_percent_contact", "width": 150 },
158
- { "title": "HardHit%", "field": "hard_hit_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
159
- { "title": "Barrel%", "field": "barrel_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
160
- { "title": "Z-Contact%", "field": "zone_contact_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
161
- { "title": "Z-Swing%", "field": "zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
162
- { "title": "Zone%", "field": "zone_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
163
- { "title": "O-Swing%", "field": "chase_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
164
- { "title": "O-Contact%", "field": "chase_contact", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
165
- { "title": "Swing%", "field": "swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
166
- { "title": "Whiff%", "field": "whiff_rate", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
167
- { "title": "SwStr%", "field": "swstr_rate", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
168
- { "title": "GB%", "field": "ground_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
169
- { "title": "LD%", "field": "line_drive_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
170
- { "title": "FB%", "field": "fly_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
171
- { "title": "PU%", "field": "pop_up_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
172
-
173
- { "title": "Pull LD+FB%", "field": "pulled_fly_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
174
-
175
-
176
- { "title": "Heart Swing%", "field": "heart_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
177
- { "title": "Shadow Swing%", "field": "shadow_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
178
- { "title": "Chase Swing%", "field": "chase_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
179
- { "title": "Waste Swing%", "field": "waste_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
180
- { "title": "Heart Whiff%", "field": "heart_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
181
- { "title": "Shadow Whiff%", "field": "shadow_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
182
- { "title": "Chase Whiff%", "field": "chase_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
183
- { "title": "Waste Whiff%", "field": "waste_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
184
- { "title": "tjStuff+", "field": "tj_stuff_plus_avg", "width": 150 },
185
- { "title": "Velocity", "field": "start_speed_avg", "width": 150 },
186
- { "title": "Extension", "field": "extension_avg", "width": 150 },
187
- { "title": "VB", "field": "vb_avg", "width": 150 },
188
- { "title": "iVB", "field": "ivb_avg", "width": 150 },
189
- { "title": "HB", "field": "hb_avg", "width": 150 },
190
- { "title": "vRel", "field": "z0_avg", "width": 150 },
191
- { "title": "hRel", "field": "x0_avg", "width": 150 },
192
- { "title": "VAA", "field": "vaa_avg", "width": 150 },
193
- { "title": "HAA", "field": "haa_avg", "width": 150 },
194
- { "title": "Spin Rate", "field": "spin_rate_avg", "width": 150 },
195
- { "title": "Extension", "field": "extension_avg", "width": 150 },
196
-
197
- ]
198
-
199
- stat_titles = dict(zip([col["field"] for col in columns],[col["title"] for col in columns]))
200
-
201
- stat_selection = [key for key in stat_titles.keys()]
202
-
203
- agg_titles = {'batter_id':'Batter ID',
204
- 'batter_name':'Batter Name',
205
- 'batter_team':'Batter Team',
206
- 'batter_hand':'Batter Hand',
207
- 'pitcher_id':'Pitcher ID',
208
- 'pitcher_name':'Pitcher Name',
209
- 'pitcher_team':'Pitcher Team',
210
- 'pitcher_hand':'Pitcher Hand',
211
- 'pitch_type':'Pitch Type',
212
- 'pitch_group':'Pitch Group',
213
- 'home_away_batter':'Home/Away Batter',
214
- 'home_away_pitcher':'Home/Away Pitcher',
215
- 'is_swing':'Is Swing?',
216
- 'is_bip':'Is BIP?',
217
- 'in_zone_final':'In Zone?',
218
- 'attack_zone_final':'Attack Zone'}
219
-
220
-
221
- columns_group = [
222
- { "title": "Batter ID", "field": "batter_id", "width": 150, "headerFilter":"input","frozen":True,},
223
- { "title": "Batter Name", "field": "batter_name", "width": 200,"frozen":True, "headerFilter":"input" },
224
- { "title": "Batter Team", "field": "batter_team", "width": 150,"frozen":True, "headerFilter":"input" },
225
- { "title": "Batter Hand", "field": "batter_hand", "width": 150,"frozen":True, "headerFilter":"input" },
226
- { "title": "Pitcher ID", "field": "pitcher_id", "width": 150,"frozen":True, "headerFilter":"input" },
227
- { "title": "Pitcher Name", "field": "pitcher_name", "width": 200,"frozen":True, "headerFilter":"input" },
228
- { "title": "Pitcher Team", "field": "pitcher_team", "width": 150,"frozen":True, "headerFilter":"input" },
229
- { "title": "Pitcher Hand", "field": "pitcher_hand", "width": 150,"frozen":True, "headerFilter":"input" },
230
- { "title": "Pitch Type", "field": "pitch_type", "width": 150,"frozen":True, "headerFilter": "select", "headerFilterParams": {"multiselect": True}},
231
- { "title": "Pitch Group", "field": "pitch_group", "width": 150,"frozen":True, "headerFilter": "select", "headerFilterParams": {"multiselect": True}},
232
- { "title": "Home/Away Batter", "field": "home_away_batter", "width": 150,"frozen":True, "headerFilter":"input" },
233
- { "title": "Home/Away Pitcher", "field": "home_away_pitcher", "width": 150,"frozen":True, "headerFilter":"input" },
234
- { "title": "Is Swing?", "field": "is_swing", "width": 150,"frozen":True, "headerFilter":"input" },
235
- { "title": "Is BIP?", "field": "is_bip", "width": 150,"frozen":True, "headerFilter":"input" },
236
- { "title": "In Zone?", "field": "in_zone_final", "width": 150,"frozen":True, "headerFilter":"input" },
237
- { "title": "Attack Zone", "field": "attack_zone_final", "width": 150,"frozen":True, "headerFilter":"input" }
238
- ]
239
-
240
-
241
- app_ui = ui.page_sidebar(
242
- ui.sidebar(
243
- ui.input_selectize(
244
- "level_input",
245
- "Select Level:",
246
- choices=['MLB','AAA','A'],
247
- multiple=False,
248
- selected=['MLB']
249
- ),
250
- ui.input_selectize(
251
- "list_input",
252
- "Select Aggregation:",
253
- choices=agg_titles,
254
- multiple=True,
255
- selected=['batter_id', 'batter_name']
256
- ),
257
- ui.input_selectize(
258
- "list_stats",
259
- "Select Stats:",
260
- choices=stat_titles,
261
- multiple=True,
262
- selected=['pa']
263
- ),
264
- ui.input_date_range(
265
- "date_id",
266
- "Select Date Range",
267
- start=f'{season}-01-01',
268
- end=f'{season}-12-01',
269
- min=f'{season}-01-01',
270
- max=f'{season}-12-01',
271
- ),
272
- ui.hr(),
273
- ui.h4("Filters"),
274
- ui.div(
275
- {"id": "filter-container"},
276
- ui.div(
277
- {"class": "filter-row", "id": "filter_row_1"}, # Add id for deletion
278
- ui.row(
279
- ui.column(5, # Adjusted column widths to make room for delete button
280
- ui.input_select(
281
- "filter_column_1",
282
- "Metric",
283
- choices={}
284
- )
285
- ),
286
- ui.column(3,
287
- ui.input_select(
288
- "filter_operator_1",
289
- "Operator",
290
- choices=[">=", "<="]
291
- ),
292
- ),
293
- ui.column(3,
294
- ui.input_numeric(
295
- "filter_value_1",
296
- "Value",
297
- value=0
298
- )
299
- ),
300
- ui.column(1,
301
- ui.markdown("&nbsp;"),
302
-
303
-
304
- ui.input_action_button(
305
- f"delete_filter_1",
306
- "",
307
- class_="btn-danger btn-sm",
308
- style="padding: 3px 6px;",
309
- icon='✖'
310
-
311
- )
312
- )
313
- )
314
- )
315
- ),
316
- ui.input_action_button(
317
- "add_filter",
318
- "Add Filter",
319
- class_="btn-secondary"
320
- ),
321
- ui.br(),
322
- ui.br(),
323
- ui.input_action_button(
324
- "generate_table",
325
- "Generate Table",
326
- class_="btn-primary"
327
- ),
328
- width="400px"
329
- ),
330
- ui.navset_tab(
331
- ui.nav_panel("Leaderboard",
332
- ui.card(
333
- #ui.card_header("Leaderboard"),
334
- output_tabulator("tabulator")
335
- )
336
- ),
337
-
338
- )
339
- )
340
-
341
- def server(input, output, session):
342
- # Store the number of active filters
343
- filter_count = reactive.value(1)
344
- # Store active filter IDs
345
- active_filters = reactive.value([1])
346
-
347
- @reactive.effect
348
- @reactive.event(input.list_stats)
349
- def _():
350
- stat_choices = {k: k for k in input.list_stats()}
351
- filtered_stat_choices = {key: stat_titles[key] for key in stat_choices}
352
- ui.update_select("filter_column_1", choices=filtered_stat_choices)
353
-
354
- @reactive.effect
355
- @reactive.event(input.add_filter)
356
- def _():
357
- current_count = filter_count.get()
358
- new_count = current_count + 1
359
-
360
- stat_choices = {k: k for k in input.list_stats()}
361
- filtered_stat_choices = {key: stat_titles[key] for key in stat_choices}
362
-
363
- ui.insert_ui(
364
- selector="#filter-container",
365
- where="beforeEnd",
366
- ui=ui.div(
367
- {"class": "filter-row", "id": f"filter_row_{new_count}"},
368
- ui.row(
369
- ui.column(5,
370
- ui.input_select(
371
- f"filter_column_{new_count}",
372
- "Metric",
373
- choices=filtered_stat_choices
374
- ),
375
- ),
376
- ui.column(3,
377
- ui.input_select(
378
- f"filter_operator_{new_count}",
379
- "Operator",
380
- choices=[">=", "<="]
381
- ),
382
- ),
383
- ui.column(3,
384
- ui.input_numeric(
385
- f"filter_value_{new_count}",
386
- "Value",
387
- value=0
388
- )
389
- ),
390
- ui.column(1,
391
- ui.markdown("&nbsp;"),
392
-
393
-
394
- ui.input_action_button(
395
- f"delete_filter_{new_count}",
396
- "",
397
- class_="btn-danger btn-sm",
398
- style="padding: 3px 6px;",
399
- icon='✖'
400
-
401
- )
402
- )
403
- )
404
- )
405
- )
406
- filter_count.set(new_count)
407
- current_filters = active_filters.get()
408
- current_filters.append(new_count)
409
- active_filters.set(current_filters)
410
-
411
- @reactive.effect
412
- def _():
413
- # Monitor all possible delete buttons
414
- for i in range(1, filter_count.get() + 1):
415
- try:
416
- if getattr(input, f"delete_filter_{i}")() > 0:
417
- # Remove the filter row
418
- ui.remove_ui(f"#filter_row_{i}")
419
- # Update active filters
420
- current_filters = active_filters.get()
421
- if i in current_filters:
422
- current_filters.remove(i)
423
- active_filters.set(current_filters)
424
- except:
425
- continue
426
-
427
- @output
428
- @render_tabulator
429
- @reactive.event(input.generate_table, ignore_none=False)
430
- def tabulator():
431
- columns_c = columns.copy()
432
- selection_list = list(input.list_input())
433
- start_date = str(input.date_id()[0])
434
- end_date = str(input.date_id()[1])
435
-
436
-
437
- if input.level_input() == "MLB":
438
- df_agg = update.update_summary_select(df=df_mlb_total.filter((pl.col('game_date')>=start_date)&(pl.col('game_date')<=end_date)),
439
- selection=selection_list)
440
-
441
- elif input.level_input() == "AAA":
442
- df_agg = update.update_summary_select(df=df_aaa_total.filter((pl.col('game_date')>=start_date)&(pl.col('game_date')<=end_date)),
443
- selection=selection_list)
444
-
445
- elif input.level_input() == "A":
446
- df_agg = update.update_summary_select(df=df_a_total.filter((pl.col('game_date')>=start_date)&(pl.col('game_date')<=end_date)),
447
- selection=selection_list)
448
-
449
-
450
- df_agg = df_agg.select(selection_list + list(input.list_stats()))#.sort('pa', descending=True)
451
-
452
- # Apply filters - only for active filters
453
- for i in active_filters.get():
454
- try:
455
- col_name = getattr(input, f"filter_column_{i}")()
456
- if col_name: # Only apply filter if column is selected
457
- operator = getattr(input, f"filter_operator_{i}")()
458
- if col_name in [col["field"] for col in columns_c if col.get("formatter") == "money"]:
459
- value = getattr(input, f"filter_value_{i}")()/100
460
- else:
461
- value = getattr(input, f"filter_value_{i}")()
462
-
463
- if operator == ">=":
464
- df_agg = df_agg.filter(pl.col(col_name) >= value)
465
- elif operator == "<=":
466
- df_agg = df_agg.filter(pl.col(col_name) <= value)
467
- except:
468
- continue
469
-
470
- for col in df_agg.columns[len(selection_list):]:
471
- if col in rounding_dict:
472
- df_agg = df_agg.with_columns(pl.col(col).round(rounding_dict[col]))
473
-
474
- for column in columns_c:
475
- if column.get("formatter") == "money" and column.get("field") in df_agg.columns:
476
- df_agg = df_agg.with_columns(pl.col(column.get("field"))*100)
477
-
478
- col_group = []
479
- for column in columns_group:
480
- if column.get("field") in df_agg.columns:
481
- col_group.append(column)
482
-
483
- col_group_stats = []
484
- for column in columns_c:
485
- if column.get("field") in df_agg.columns:
486
- col_group_stats.append(column)
487
-
488
- columns_c = col_group + col_group_stats
489
-
490
- # Replace all boolean columns with 0 and 1
491
- df_agg = df_agg.with_columns(
492
- [df_agg[col].cast(pl.Int8) for col in df_agg.columns if df_agg[col].dtype == pl.Boolean]
493
- )
494
-
495
- return Tabulator(
496
- df_agg.to_pandas(),
497
-
498
- table_options=TableOptions(
499
- height=800,
500
-
501
- columns=columns_c,
502
- )
503
- )
504
-
505
- app = App(app_ui, server)
 
1
+ from shiny import App, ui, render, reactive
2
+ import polars as pl
3
+ import numpy as np
4
+ import pandas as pd
5
+ import api_scraper
6
+ scrape = api_scraper.MLB_Scrape()
7
+ from functions import df_update
8
+ from functions import pitch_summary_functions
9
+ update = df_update.df_update()
10
+ from stuff_model import feature_engineering as fe
11
+ from stuff_model import stuff_apply
12
+ import requests
13
+ import joblib
14
+ from matplotlib.gridspec import GridSpec
15
+ import math
16
+ from pytabulator import TableOptions, Tabulator, output_tabulator, render_tabulator, theme
17
+ theme.tabulator_site()
18
+
19
+ colour_palette = ['#FFB000','#648FFF','#785EF0',
20
+ '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
21
+
22
+ # df = pl.read_csv("data.csv")
23
+ # df = pl.read_parquet("data_small.parquet")[:]
24
+ # df = pl.read_parquet("data.parquet")[:]
25
+ # print('df')
26
+ season = 2024
27
+
28
+ df_mlb = pl.read_parquet("data/data_mlb_2024.parquet")[:]
29
+ df_aaa = pl.read_parquet("data/data_aaa_2024.parquet")[:]
30
+ df_a = pl.read_parquet("data/data_a_2024.parquet")[:]
31
+
32
+ # df_mlb = pl.read_parquet("data_small.parquet")[:]
33
+ # df_aaa = pl.read_parquet("data_small_aaa.parquet")[:]
34
+ # df_a = pl.read_parquet("data_small_a.parquet")[:]
35
+
36
+
37
+
38
+
39
+ def df_final(df:pl.dataframe,year_input:int,sport_id:int):
40
+
41
+ df_schedule = scrape.get_schedule(year_input=[year_input],sport_id=[sport_id])
42
+ df = df.join(df_schedule, on='game_id', how='left')
43
+
44
+ df = df.with_columns(
45
+ pl.when((pl.col('batter_team_id') == pl.col('away_id')))
46
+ .then(pl.lit('Away'))
47
+ .when((pl.col('batter_team_id') == pl.col('home_id')))
48
+ .then(pl.lit('Home'))
49
+ .otherwise(None)
50
+ .alias('home_away')
51
+ )
52
+
53
+ df = df.with_columns(
54
+ pl.when((pl.col('pitcher_team_id') == pl.col('away_id')))
55
+ .then(pl.lit('Away'))
56
+ .when((pl.col('pitcher_team_id') == pl.col('home_id')))
57
+ .then(pl.lit('Home'))
58
+ .otherwise(None)
59
+ .alias('home_away_pitcher')
60
+ )
61
+
62
+
63
+ print('schedule')
64
+
65
+ df_stuff = stuff_apply.stuff_apply(fe.feature_engineering(df))
66
+ print('stuff')
67
+ df_up = update.update(df)
68
+ print('update')
69
+ df_total = df_up.join(df_stuff[['play_id','tj_stuff_plus']], on='play_id', how='left')
70
+ print('total')
71
+ return df_total
72
+
73
+
74
+ df_mlb_total = df_final(df=df_mlb,year_input=season,sport_id=1)
75
+ df_aaa_total = df_final(df=df_aaa,year_input=season,sport_id=11)
76
+ df_a_total = df_final(df=df_a.drop_nulls(subset=['start_speed']),year_input=season,sport_id=14)
77
+
78
+ rounding_dict = {
79
+ 'pa': 0,
80
+ 'bip': 0,
81
+ 'hits': 0,
82
+ 'k': 0,
83
+ 'bb': 0,
84
+ 'max_launch_speed': 1,
85
+ 'launch_speed_90': 1,
86
+ 'launch_speed': 1,
87
+ 'pitches': 0,
88
+ 'tj_stuff_plus_avg': 0,
89
+ 'avg': 3,
90
+ 'obp': 3,
91
+ 'slg': 3,
92
+ 'ops': 3,
93
+ 'k_percent': 3,
94
+ 'bb_percent': 3,
95
+ 'k_minus_bb_percent': 3,
96
+ 'sweet_spot_percent': 3,
97
+ 'woba_percent': 3,
98
+ 'xwoba_percent': 3,
99
+ 'woba_percent_contact': 3,
100
+ 'xwoba_percent_contact': 3,
101
+ 'hard_hit_percent': 3,
102
+ 'barrel_percent': 3,
103
+ 'zone_contact_percent': 3,
104
+ 'zone_swing_percent': 3,
105
+ 'zone_percent': 3,
106
+ 'chase_percent': 3,
107
+ 'chase_contact': 3,
108
+ 'swing_percent': 3,
109
+ 'whiff_rate': 3,
110
+ 'swstr_rate': 3,
111
+ 'ground_ball_percent': 3,
112
+ 'line_drive_percent': 3,
113
+ 'fly_ball_percent': 3,
114
+ 'pop_up_percent': 3,
115
+ 'pulled_fly_ball_percent': 3,
116
+ 'heart_zone_swing_percent': 3,
117
+ 'shadow_zone_swing_percent': 3,
118
+ 'chase_zone_swing_percent': 3,
119
+ 'waste_zone_swing_percent': 3,
120
+ 'heart_zone_whiff_percent': 3,
121
+ 'shadow_zone_whiff_percent': 3,
122
+ 'chase_zone_whiff_percent': 3,
123
+ 'waste_zone_whiff_percent': 3,
124
+ 'start_speed_avg': 1,
125
+ 'vb_avg': 1,
126
+ 'ivb_avg': 1,
127
+ 'hb_avg': 1,
128
+ 'z0_avg': 1,
129
+ 'x0_avg': 1,
130
+ 'vaa_avg': 1,
131
+ 'haa_avg': 1,
132
+ 'spin_rate_avg': 0,
133
+ 'extension_avg': 1
134
+ }
135
+
136
+ columns = [
137
+ { "title": "PA", "field": "pa", "width": 150},
138
+ { "title": "BBE", "field": "bip", "width": 150 },
139
+ { "title": "H", "field": "hits", "width": 150 },
140
+ { "title": "K", "field": "k", "width": 150 },
141
+ { "title": "BB", "field": "bb", "width": 150 },
142
+ { "title": "Max EV", "field": "max_launch_speed", "width": 150 },
143
+ { "title": "90th% EV", "field": "launch_speed_90", "width": 150 },
144
+ { "title": "EV", "field": "launch_speed", "width": 150 },
145
+ { "title": "Pitches", "field": "pitches", "width": 150 },
146
+ { "title": "AVG", "field": "avg", "width": 150 },
147
+ { "title": "OBP", "field": "obp", "width": 150 },
148
+ { "title": "SLG", "field": "slg", "width": 150 },
149
+ { "title": "OPS", "field": "ops", "width": 150 },
150
+ { "title": "K%", "field": "k_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
151
+ { "title": "BB%", "field": "bb_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
152
+ { "title": "K-BB%", "field": "k_minus_bb_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
153
+ { "title": "SwSpot%", "field": "sweet_spot_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
154
+ { "title": "wOBA", "field": "woba_percent", "width": 150 },
155
+ { "title": "xwOBA", "field": "xwoba_percent", "width": 150 },
156
+ { "title": "wOBACON", "field": "woba_percent_contact", "width": 150 },
157
+ { "title": "xwOBACON", "field": "xwoba_percent_contact", "width": 150 },
158
+ { "title": "HardHit%", "field": "hard_hit_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
159
+ { "title": "Barrel%", "field": "barrel_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
160
+ { "title": "Z-Contact%", "field": "zone_contact_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
161
+ { "title": "Z-Swing%", "field": "zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
162
+ { "title": "Zone%", "field": "zone_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
163
+ { "title": "O-Swing%", "field": "chase_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
164
+ { "title": "O-Contact%", "field": "chase_contact", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
165
+ { "title": "Swing%", "field": "swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
166
+ { "title": "Whiff%", "field": "whiff_rate", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
167
+ { "title": "SwStr%", "field": "swstr_rate", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
168
+ { "title": "GB%", "field": "ground_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
169
+ { "title": "LD%", "field": "line_drive_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
170
+ { "title": "FB%", "field": "fly_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
171
+ { "title": "PU%", "field": "pop_up_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
172
+
173
+ { "title": "Pull LD+FB%", "field": "pulled_fly_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
174
+
175
+
176
+ { "title": "Heart Swing%", "field": "heart_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
177
+ { "title": "Shadow Swing%", "field": "shadow_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
178
+ { "title": "Chase Swing%", "field": "chase_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
179
+ { "title": "Waste Swing%", "field": "waste_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
180
+ { "title": "Heart Whiff%", "field": "heart_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
181
+ { "title": "Shadow Whiff%", "field": "shadow_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
182
+ { "title": "Chase Whiff%", "field": "chase_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
183
+ { "title": "Waste Whiff%", "field": "waste_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}},
184
+ { "title": "tjStuff+", "field": "tj_stuff_plus_avg", "width": 150 },
185
+ { "title": "Velocity", "field": "start_speed_avg", "width": 150 },
186
+ { "title": "Extension", "field": "extension_avg", "width": 150 },
187
+ { "title": "VB", "field": "vb_avg", "width": 150 },
188
+ { "title": "iVB", "field": "ivb_avg", "width": 150 },
189
+ { "title": "HB", "field": "hb_avg", "width": 150 },
190
+ { "title": "vRel", "field": "z0_avg", "width": 150 },
191
+ { "title": "hRel", "field": "x0_avg", "width": 150 },
192
+ { "title": "VAA", "field": "vaa_avg", "width": 150 },
193
+ { "title": "HAA", "field": "haa_avg", "width": 150 },
194
+ { "title": "Spin Rate", "field": "spin_rate_avg", "width": 150 },
195
+ { "title": "Extension", "field": "extension_avg", "width": 150 },
196
+
197
+ ]
198
+
199
+ stat_titles = dict(zip([col["field"] for col in columns],[col["title"] for col in columns]))
200
+
201
+ stat_selection = [key for key in stat_titles.keys()]
202
+
203
+ agg_titles = {'batter_id':'Batter ID',
204
+ 'batter_name':'Batter Name',
205
+ 'batter_team':'Batter Team',
206
+ 'batter_hand':'Batter Hand',
207
+ 'pitcher_id':'Pitcher ID',
208
+ 'pitcher_name':'Pitcher Name',
209
+ 'pitcher_team':'Pitcher Team',
210
+ 'pitcher_hand':'Pitcher Hand',
211
+ 'pitch_type':'Pitch Type',
212
+ 'pitch_group':'Pitch Group',
213
+ 'home_away_batter':'Home/Away Batter',
214
+ 'home_away_pitcher':'Home/Away Pitcher',
215
+ 'is_swing':'Is Swing?',
216
+ 'is_bip':'Is BIP?',
217
+ 'in_zone_final':'In Zone?',
218
+ 'attack_zone_final':'Attack Zone'}
219
+
220
+
221
+ columns_group = [
222
+ { "title": "Batter ID", "field": "batter_id", "width": 150, "headerFilter":"input","frozen":True,},
223
+ { "title": "Batter Name", "field": "batter_name", "width": 200,"frozen":True, "headerFilter":"input" },
224
+ { "title": "Batter Team", "field": "batter_team", "width": 150,"frozen":True, "headerFilter":"input" },
225
+ { "title": "Batter Hand", "field": "batter_hand", "width": 150,"frozen":True, "headerFilter":"input" },
226
+ { "title": "Pitcher ID", "field": "pitcher_id", "width": 150,"frozen":True, "headerFilter":"input" },
227
+ { "title": "Pitcher Name", "field": "pitcher_name", "width": 200,"frozen":True, "headerFilter":"input" },
228
+ { "title": "Pitcher Team", "field": "pitcher_team", "width": 150,"frozen":True, "headerFilter":"input" },
229
+ { "title": "Pitcher Hand", "field": "pitcher_hand", "width": 150,"frozen":True, "headerFilter":"input" },
230
+ { "title": "Pitch Type", "field": "pitch_type", "width": 150,"frozen":True, "headerFilter":"input" },
231
+ { "title": "Pitch Group", "field": "pitch_group", "width": 150,"frozen":True, "headerFilter":"input" },
232
+ { "title": "Home/Away Batter", "field": "home_away_batter", "width": 150,"frozen":True, "headerFilter":"input" },
233
+ { "title": "Home/Away Pitcher", "field": "home_away_pitcher", "width": 150,"frozen":True, "headerFilter":"input" },
234
+ { "title": "Is Swing?", "field": "is_swing", "width": 150,"frozen":True, "headerFilter":"input" },
235
+ { "title": "Is BIP?", "field": "is_bip", "width": 150,"frozen":True, "headerFilter":"input" },
236
+ { "title": "In Zone?", "field": "in_zone_final", "width": 150,"frozen":True, "headerFilter":"input" },
237
+ { "title": "Attack Zone", "field": "attack_zone_final", "width": 150,"frozen":True, "headerFilter":"input" }
238
+ ]
239
+
240
+
241
+ app_ui = ui.page_sidebar(
242
+ ui.sidebar(
243
+ ui.input_selectize(
244
+ "level_input",
245
+ "Select Level:",
246
+ choices=['MLB','AAA','A'],
247
+ multiple=False,
248
+ selected=['MLB']
249
+ ),
250
+ ui.input_selectize(
251
+ "list_input",
252
+ "Select Aggregation:",
253
+ choices=agg_titles,
254
+ multiple=True,
255
+ selected=['batter_id', 'batter_name']
256
+ ),
257
+ ui.input_selectize(
258
+ "list_stats",
259
+ "Select Stats:",
260
+ choices=stat_titles,
261
+ multiple=True,
262
+ selected=['pa']
263
+ ),
264
+ ui.input_date_range(
265
+ "date_id",
266
+ "Select Date Range",
267
+ start=f'{season}-01-01',
268
+ end=f'{season}-12-01',
269
+ min=f'{season}-01-01',
270
+ max=f'{season}-12-01',
271
+ ),
272
+ ui.hr(),
273
+ ui.h4("Filters"),
274
+ ui.div(
275
+ {"id": "filter-container"},
276
+ ui.div(
277
+ {"class": "filter-row", "id": "filter_row_1"}, # Add id for deletion
278
+ ui.row(
279
+ ui.column(5, # Adjusted column widths to make room for delete button
280
+ ui.input_select(
281
+ "filter_column_1",
282
+ "Metric",
283
+ choices={}
284
+ )
285
+ ),
286
+ ui.column(3,
287
+ ui.input_select(
288
+ "filter_operator_1",
289
+ "Operator",
290
+ choices=[">=", "<="]
291
+ ),
292
+ ),
293
+ ui.column(3,
294
+ ui.input_numeric(
295
+ "filter_value_1",
296
+ "Value",
297
+ value=0
298
+ )
299
+ ),
300
+ ui.column(1,
301
+ ui.markdown("&nbsp;"),
302
+
303
+
304
+ ui.input_action_button(
305
+ f"delete_filter_1",
306
+ "",
307
+ class_="btn-danger btn-sm",
308
+ style="padding: 3px 6px;",
309
+ icon='✖'
310
+
311
+ )
312
+ )
313
+ )
314
+ )
315
+ ),
316
+ ui.input_action_button(
317
+ "add_filter",
318
+ "Add Filter",
319
+ class_="btn-secondary"
320
+ ),
321
+ ui.br(),
322
+ ui.br(),
323
+ ui.input_action_button(
324
+ "generate_table",
325
+ "Generate Table",
326
+ class_="btn-primary"
327
+ ),
328
+ width="400px"
329
+ ),
330
+ ui.navset_tab(
331
+ ui.nav_panel("Leaderboard",
332
+ ui.card(
333
+ #ui.card_header("Leaderboard"),
334
+ output_tabulator("tabulator")
335
+ )
336
+ ),
337
+
338
+ )
339
+ )
340
+
341
+ def server(input, output, session):
342
+ # Store the number of active filters
343
+ filter_count = reactive.value(1)
344
+ # Store active filter IDs
345
+ active_filters = reactive.value([1])
346
+
347
+ @reactive.effect
348
+ @reactive.event(input.list_stats)
349
+ def _():
350
+ stat_choices = {k: k for k in input.list_stats()}
351
+ filtered_stat_choices = {key: stat_titles[key] for key in stat_choices}
352
+ ui.update_select("filter_column_1", choices=filtered_stat_choices)
353
+
354
+ @reactive.effect
355
+ @reactive.event(input.add_filter)
356
+ def _():
357
+ current_count = filter_count.get()
358
+ new_count = current_count + 1
359
+
360
+ stat_choices = {k: k for k in input.list_stats()}
361
+ filtered_stat_choices = {key: stat_titles[key] for key in stat_choices}
362
+
363
+ ui.insert_ui(
364
+ selector="#filter-container",
365
+ where="beforeEnd",
366
+ ui=ui.div(
367
+ {"class": "filter-row", "id": f"filter_row_{new_count}"},
368
+ ui.row(
369
+ ui.column(5,
370
+ ui.input_select(
371
+ f"filter_column_{new_count}",
372
+ "Metric",
373
+ choices=filtered_stat_choices
374
+ ),
375
+ ),
376
+ ui.column(3,
377
+ ui.input_select(
378
+ f"filter_operator_{new_count}",
379
+ "Operator",
380
+ choices=[">=", "<="]
381
+ ),
382
+ ),
383
+ ui.column(3,
384
+ ui.input_numeric(
385
+ f"filter_value_{new_count}",
386
+ "Value",
387
+ value=0
388
+ )
389
+ ),
390
+ ui.column(1,
391
+ ui.markdown("&nbsp;"),
392
+
393
+
394
+ ui.input_action_button(
395
+ f"delete_filter_{new_count}",
396
+ "",
397
+ class_="btn-danger btn-sm",
398
+ style="padding: 3px 6px;",
399
+ icon='✖'
400
+
401
+ )
402
+ )
403
+ )
404
+ )
405
+ )
406
+ filter_count.set(new_count)
407
+ current_filters = active_filters.get()
408
+ current_filters.append(new_count)
409
+ active_filters.set(current_filters)
410
+
411
+ @reactive.effect
412
+ def _():
413
+ # Monitor all possible delete buttons
414
+ for i in range(1, filter_count.get() + 1):
415
+ try:
416
+ if getattr(input, f"delete_filter_{i}")() > 0:
417
+ # Remove the filter row
418
+ ui.remove_ui(f"#filter_row_{i}")
419
+ # Update active filters
420
+ current_filters = active_filters.get()
421
+ if i in current_filters:
422
+ current_filters.remove(i)
423
+ active_filters.set(current_filters)
424
+ except:
425
+ continue
426
+
427
+ @output
428
+ @render_tabulator
429
+ @reactive.event(input.generate_table, ignore_none=False)
430
+ def tabulator():
431
+ columns_c = columns.copy()
432
+ selection_list = list(input.list_input())
433
+ start_date = str(input.date_id()[0])
434
+ end_date = str(input.date_id()[1])
435
+
436
+
437
+ if input.level_input() == "MLB":
438
+ df_agg = update.update_summary_select(df=df_mlb_total.filter((pl.col('game_date')>=start_date)&(pl.col('game_date')<=end_date)),
439
+ selection=selection_list)
440
+
441
+ elif input.level_input() == "AAA":
442
+ df_agg = update.update_summary_select(df=df_aaa_total.filter((pl.col('game_date')>=start_date)&(pl.col('game_date')<=end_date)),
443
+ selection=selection_list)
444
+
445
+ elif input.level_input() == "A":
446
+ df_agg = update.update_summary_select(df=df_a_total.filter((pl.col('game_date')>=start_date)&(pl.col('game_date')<=end_date)),
447
+ selection=selection_list)
448
+
449
+
450
+ df_agg = df_agg.select(selection_list + list(input.list_stats()))#.sort('pa', descending=True)
451
+
452
+ # Apply filters - only for active filters
453
+ for i in active_filters.get():
454
+ try:
455
+ col_name = getattr(input, f"filter_column_{i}")()
456
+ if col_name: # Only apply filter if column is selected
457
+ operator = getattr(input, f"filter_operator_{i}")()
458
+ if col_name in [col["field"] for col in columns_c if col.get("formatter") == "money"]:
459
+ value = getattr(input, f"filter_value_{i}")()/100
460
+ else:
461
+ value = getattr(input, f"filter_value_{i}")()
462
+
463
+ if operator == ">=":
464
+ df_agg = df_agg.filter(pl.col(col_name) >= value)
465
+ elif operator == "<=":
466
+ df_agg = df_agg.filter(pl.col(col_name) <= value)
467
+ except:
468
+ continue
469
+
470
+ for col in df_agg.columns[len(selection_list):]:
471
+ if col in rounding_dict:
472
+ df_agg = df_agg.with_columns(pl.col(col).round(rounding_dict[col]))
473
+
474
+ for column in columns_c:
475
+ if column.get("formatter") == "money" and column.get("field") in df_agg.columns:
476
+ df_agg = df_agg.with_columns(pl.col(column.get("field"))*100)
477
+
478
+ col_group = []
479
+ for column in columns_group:
480
+ if column.get("field") in df_agg.columns:
481
+ col_group.append(column)
482
+
483
+ col_group_stats = []
484
+ for column in columns_c:
485
+ if column.get("field") in df_agg.columns:
486
+ col_group_stats.append(column)
487
+
488
+ columns_c = col_group + col_group_stats
489
+
490
+ # Replace all boolean columns with 0 and 1
491
+ df_agg = df_agg.with_columns(
492
+ [df_agg[col].cast(pl.Int8) for col in df_agg.columns if df_agg[col].dtype == pl.Boolean]
493
+ )
494
+
495
+ return Tabulator(
496
+ df_agg.to_pandas(),
497
+
498
+ table_options=TableOptions(
499
+ height=800,
500
+
501
+ columns=columns_c,
502
+ )
503
+ )
504
+
505
+ app = App(app_ui, server)