| | from shiny import App, ui, render, reactive |
| | import polars as pl |
| | import numpy as np |
| | import pandas as pd |
| | import api_scraper |
| | scrape = api_scraper.MLB_Scrape() |
| | from functions import df_update |
| | from functions import pitch_summary_functions |
| | update = df_update.df_update() |
| | from stuff_model import feature_engineering as fe |
| | from stuff_model import stuff_apply |
| | import requests |
| | import joblib |
| | from matplotlib.gridspec import GridSpec |
| | import math |
| | from pytabulator import TableOptions, Tabulator, output_tabulator, render_tabulator, theme |
| | theme.tabulator_site() |
| |
|
| | colour_palette = ['#FFB000','#648FFF','#785EF0', |
| | '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED'] |
| |
|
| | |
| | |
| | |
| | |
| | season = 2025 |
| |
|
| |
|
| | |
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | df_mlb = pl.read_parquet(f"hf://datasets/TJStatsApps/mlb_data/data/mlb_pitch_data_2025_spring.parquet") |
| | |
| |
|
| | def df_final(df:pl.dataframe,year_input:int,sport_id:int): |
| |
|
| | |
| | |
| |
|
| |
|
| | print('schedule') |
| | |
| | df = df.with_columns([ |
| | (60.5 - df["extension"]).alias("release_pos_y") |
| | ]) |
| | |
| | |
| | delta_t = (df["release_pos_y"] - df["y0"]) / df["vy0"] |
| |
|
| | |
| | df = df.with_columns([ |
| | (df["x0"] + df["vx0"] * delta_t + 0.5 * df["ax"] * delta_t ** 2).alias("release_pos_x"), |
| | (df["z0"] + df["vz0"] * delta_t + 0.5 * df["az"] * delta_t ** 2).alias("release_pos_z") |
| | ]) |
| | |
| | df_stuff = stuff_apply.stuff_apply(fe.feature_engineering(df)) |
| | print('stuff') |
| | df_up = update.update(df) |
| | print('update') |
| | df_total = df_up.join(df_stuff[['play_id','tj_stuff_plus']], on='play_id', how='left') |
| | print('total') |
| |
|
| |
|
| |
|
| | |
| | return df_total |
| | |
| | df_mlb_total = df_final(df=df_mlb,year_input=season,sport_id=1) |
| |
|
| | df_max_ev = pl.read_csv("max_ev.csv") |
| |
|
| | df_max_ev = df_max_ev.filter(pl.col('max_launch_speed') >= 100) |
| | |
| | |
| |
|
| | rounding_dict = { |
| | 'pa': 0, |
| | 'bip': 0, |
| | 'hits': 0, |
| | 'k': 0, |
| | 'bb': 0, |
| | 'max_launch_speed': 1, |
| | 'launch_speed_90': 1, |
| | 'launch_speed': 1, |
| | 'pitches': 0, |
| | 'tj_stuff_plus_avg': 0, |
| | 'avg': 3, |
| | 'obp': 3, |
| | 'slg': 3, |
| | 'ops': 3, |
| | 'k_percent': 3, |
| | 'bb_percent': 3, |
| | 'k_minus_bb_percent': 3, |
| | 'sweet_spot_percent': 3, |
| | 'woba_percent': 3, |
| | 'xwoba_percent': 3, |
| | 'woba_percent_contact': 3, |
| | 'xwoba_percent_contact': 3, |
| | 'hard_hit_percent': 3, |
| | 'barrel_percent': 3, |
| | 'zone_contact_percent': 3, |
| | 'zone_swing_percent': 3, |
| | 'zone_percent': 3, |
| | 'chase_percent': 3, |
| | 'chase_contact': 3, |
| | 'swing_percent': 3, |
| | 'whiff_rate': 3, |
| | 'swstr_rate': 3, |
| | 'ground_ball_percent': 3, |
| | 'line_drive_percent': 3, |
| | 'fly_ball_percent': 3, |
| | 'pop_up_percent': 3, |
| | 'pulled_fly_ball_percent': 3, |
| | 'heart_zone_swing_percent': 3, |
| | 'shadow_zone_swing_percent': 3, |
| | 'chase_zone_swing_percent': 3, |
| | 'waste_zone_swing_percent': 3, |
| | 'heart_zone_whiff_percent': 3, |
| | 'shadow_zone_whiff_percent': 3, |
| | 'chase_zone_whiff_percent': 3, |
| | 'waste_zone_whiff_percent': 3, |
| | 'start_speed_avg': 1, |
| | 'vb_avg': 1, |
| | 'ivb_avg': 1, |
| | 'hb_avg': 1, |
| | 'z0_avg': 1, |
| | 'x0_avg': 1, |
| | 'vaa_avg': 1, |
| | 'haa_avg': 1, |
| | 'spin_rate_avg': 0, |
| | 'extension_avg': 1 |
| | } |
| |
|
| | columns = [ |
| | { "title": "PA", "field": "pa", "width": 150}, |
| | { "title": "BBE", "field": "bip", "width": 150 }, |
| | { "title": "H", "field": "hits", "width": 150 }, |
| | { "title": "K", "field": "k", "width": 150 }, |
| | { "title": "BB", "field": "bb", "width": 150 }, |
| | { "title": "Max EV", "field": "max_launch_speed", "width": 150 }, |
| | { "title": "90th% EV", "field": "launch_speed_90", "width": 150 }, |
| | { "title": "EV", "field": "launch_speed", "width": 150 }, |
| | { "title": "Pitches", "field": "pitches", "width": 150 }, |
| | { "title": "AVG", "field": "avg", "width": 150 }, |
| | { "title": "OBP", "field": "obp", "width": 150 }, |
| | { "title": "SLG", "field": "slg", "width": 150 }, |
| | { "title": "OPS", "field": "ops", "width": 150 }, |
| | { "title": "K%", "field": "k_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "BB%", "field": "bb_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "K-BB%", "field": "k_minus_bb_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "SwSpot%", "field": "sweet_spot_percent", "width": 150,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "wOBA", "field": "woba_percent", "width": 150 }, |
| | { "title": "xwOBA", "field": "xwoba_percent", "width": 150 }, |
| | { "title": "wOBACON", "field": "woba_percent_contact", "width": 150 }, |
| | { "title": "xwOBACON", "field": "xwoba_percent_contact", "width": 150 }, |
| | { "title": "HardHit%", "field": "hard_hit_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Barrel%", "field": "barrel_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Z-Contact%", "field": "zone_contact_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Z-Swing%", "field": "zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Zone%", "field": "zone_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "O-Swing%", "field": "chase_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "O-Contact%", "field": "chase_contact", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Swing%", "field": "swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Whiff%", "field": "whiff_rate", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "SwStr%", "field": "swstr_rate", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "GB%", "field": "ground_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "LD%", "field": "line_drive_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "FB%", "field": "fly_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "PU%", "field": "pop_up_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| |
|
| | { "title": "Pull LD+FB%", "field": "pulled_fly_ball_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| |
|
| |
|
| | { "title": "Heart Swing%", "field": "heart_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Shadow Swing%", "field": "shadow_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Chase Swing%", "field": "chase_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Waste Swing%", "field": "waste_zone_swing_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Heart Whiff%", "field": "heart_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Shadow Whiff%", "field": "shadow_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Chase Whiff%", "field": "chase_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "Waste Whiff%", "field": "waste_zone_whiff_percent", "width": 150 ,"formatter": "money", "formatterParams":{"decimal":".","thousand":".","symbol":"%","symbolAfter":"%","negativeSign":True,"precision":1}}, |
| | { "title": "tjStuff+", "field": "tj_stuff_plus_avg", "width": 150 }, |
| | { "title": "Velocity", "field": "start_speed_avg", "width": 150 }, |
| | { "title": "Extension", "field": "extension_avg", "width": 150 }, |
| | { "title": "VB", "field": "vb_avg", "width": 150 }, |
| | { "title": "iVB", "field": "ivb_avg", "width": 150 }, |
| | { "title": "HB", "field": "hb_avg", "width": 150 }, |
| | { "title": "vRel", "field": "z0_avg", "width": 150 }, |
| | { "title": "hRel", "field": "x0_avg", "width": 150 }, |
| | { "title": "VAA", "field": "vaa_avg", "width": 150 }, |
| | { "title": "HAA", "field": "haa_avg", "width": 150 }, |
| | { "title": "Spin Rate", "field": "spin_rate_avg", "width": 150 }, |
| | { "title": "Extension", "field": "extension_avg", "width": 150 }, |
| |
|
| | ] |
| |
|
| | stat_titles = dict(zip([col["field"] for col in columns],[col["title"] for col in columns])) |
| |
|
| | stat_selection = [key for key in stat_titles.keys()] |
| |
|
| | agg_titles = {'batter_id':'Batter ID', |
| | 'batter_name':'Batter Name', |
| | 'batter_team':'Batter Team', |
| | 'batter_hand':'Batter Hand', |
| | 'pitcher_id':'Pitcher ID', |
| | 'pitcher_name':'Pitcher Name', |
| | 'pitcher_team':'Pitcher Team', |
| | 'pitcher_hand':'Pitcher Hand', |
| | 'pitch_type':'Pitch Type', |
| | 'pitch_group':'Pitch Group', |
| | |
| | |
| | 'is_swing':'Is Swing?', |
| | 'is_bip':'Is BIP?', |
| | 'in_zone_final':'In Zone?', |
| | 'attack_zone_final':'Attack Zone'} |
| |
|
| |
|
| | columns_group = [ |
| | { "title": "Batter ID", "field": "batter_id", "width": 150, "headerFilter":"input","frozen":True,}, |
| | { "title": "Batter Name", "field": "batter_name", "width": 200,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Batter Team", "field": "batter_team", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Batter Hand", "field": "batter_hand", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Pitcher ID", "field": "pitcher_id", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Pitcher Name", "field": "pitcher_name", "width": 200,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Pitcher Team", "field": "pitcher_team", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Pitcher Hand", "field": "pitcher_hand", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Pitch Type", "field": "pitch_type", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Pitch Group", "field": "pitch_group", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | |
| | |
| | { "title": "Is Swing?", "field": "is_swing", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Is BIP?", "field": "is_bip", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "In Zone?", "field": "in_zone_final", "width": 150,"frozen":True, "headerFilter":"input" }, |
| | { "title": "Attack Zone", "field": "attack_zone_final", "width": 150,"frozen":True, "headerFilter":"input" } |
| | ] |
| |
|
| |
|
| | agg_titles_mev = [{ "title": "Batter ID", "field": "batter_id", "width": 150, "headerFilter":"input","frozen":True,}, |
| | { "title": "Batter Name", "field": "batter_name", "width": 150, "headerFilter":"input","frozen":True,}, |
| | { "title": "BBE", "field": "bip", "width": 150, "headerFilter":"input","frozen":True,}, |
| | { "title": "Max EV", "field": "max_launch_speed", "width": 150, "headerFilter":"input","frozen":False,}, |
| | { "title": "Previous Max EV", "field": "max_launch_speed_old", "width": 200, "headerFilter":"input","frozen":False,}, |
| | { "title": "Difference", "field": "diff", "width": 150, "headerFilter":"input","frozen":False,}, |
| | ] |
| |
|
| | app_ui = ui.page_sidebar( |
| | ui.sidebar( |
| |
|
| | ui.row( |
| | ui.markdown("## Spring Training Statcast Leaderboard"), |
| | ui.markdown("This app generates a table with Spring Training Statcast metrics."), |
| | ui.column(4,ui.div( |
| | "By: ", |
| | ui.tags.a( |
| | "@TJStats", |
| | href="https://x.com/TJStats", |
| | target="_blank" |
| | ) |
| | ), |
| | ui.tags.p("Data: MLB")), |
| | ui.column(8, |
| | ui.tags.p( |
| | ui.tags.a( |
| | "Support me on Patreon for more apps", |
| | href="https://www.patreon.com/TJ_Stats", |
| | target="_blank" |
| | )))), |
| | |
| | ui.input_selectize( |
| | "level_input", |
| | "Select Level:", |
| | choices=['MLB'], |
| | multiple=False, |
| | selected=['MLB'] |
| | ), |
| | ui.input_selectize( |
| | "list_input", |
| | "Select Aggregation:", |
| | choices=agg_titles, |
| | multiple=True, |
| | selected=['batter_id', 'batter_name'] |
| | ), |
| | ui.input_selectize( |
| | "list_stats", |
| | "Select Stats:", |
| | choices=stat_titles, |
| | multiple=True, |
| | selected=['pa'] |
| | ), |
| | ui.input_date_range( |
| | "date_id", |
| | "Select Date Range", |
| | start=f'{season}-01-01', |
| | end=f'{season}-12-01', |
| | min=f'{season}-01-01', |
| | max=f'{season}-12-01', |
| | ), |
| | ui.hr(), |
| | ui.h4("Filters"), |
| | ui.div( |
| | {"id": "filter-container"}, |
| | ui.div( |
| | {"class": "filter-row", "id": "filter_row_1"}, |
| | ui.row( |
| | ui.column(5, |
| | ui.input_select( |
| | "filter_column_1", |
| | "Metric", |
| | choices={} |
| | ) |
| | ), |
| | ui.column(3, |
| | ui.input_select( |
| | "filter_operator_1", |
| | "Operator", |
| | choices=[">=", "<="] |
| | ), |
| | ), |
| | ui.column(3, |
| | ui.input_numeric( |
| | "filter_value_1", |
| | "Value", |
| | value=0 |
| | ) |
| | ), |
| | ui.column(1, |
| | ui.markdown(" "), |
| | |
| |
|
| | ui.input_action_button( |
| | f"delete_filter_1", |
| | "", |
| | class_="btn-danger btn-sm", |
| | style="padding: 3px 6px;", |
| | icon='✖' |
| | |
| | ) |
| | ) |
| | ) |
| | ) |
| | ), |
| | ui.input_action_button( |
| | "add_filter", |
| | "Add Filter", |
| | class_="btn-secondary" |
| | ), |
| | ui.br(), |
| | ui.br(), |
| | ui.input_action_button( |
| | "generate_table", |
| | "Generate Table", |
| | class_="btn-primary" |
| | ), |
| | width="400px" |
| | ), |
| | ui.navset_tab( |
| | ui.nav_panel("Leaderboard", |
| | ui.card( |
| | |
| | output_tabulator("tabulator") |
| | ) |
| | ), |
| | ui.nav_panel("Max EV", |
| | ui.card( |
| | |
| | output_tabulator("tabulator_max_ev") |
| | ) |
| | ), |
| | id="tabset" |
| | ) |
| | ) |
| |
|
| | def server(input, output, session): |
| | |
| | filter_count = reactive.value(1) |
| | |
| | active_filters = reactive.value([1]) |
| |
|
| | @reactive.effect |
| | @reactive.event(input.list_stats) |
| | def _(): |
| | stat_choices = {k: k for k in input.list_stats()} |
| | filtered_stat_choices = {key: stat_titles[key] for key in stat_choices} |
| | ui.update_select("filter_column_1", choices=filtered_stat_choices) |
| |
|
| | @reactive.effect |
| | @reactive.event(input.add_filter) |
| | def _(): |
| | current_count = filter_count.get() |
| | new_count = current_count + 1 |
| | |
| | stat_choices = {k: k for k in input.list_stats()} |
| | filtered_stat_choices = {key: stat_titles[key] for key in stat_choices} |
| | |
| | ui.insert_ui( |
| | selector="#filter-container", |
| | where="beforeEnd", |
| | ui=ui.div( |
| | {"class": "filter-row", "id": f"filter_row_{new_count}"}, |
| | ui.row( |
| | ui.column(5, |
| | ui.input_select( |
| | f"filter_column_{new_count}", |
| | "Metric", |
| | choices=filtered_stat_choices |
| | ), |
| | ), |
| | ui.column(3, |
| | ui.input_select( |
| | f"filter_operator_{new_count}", |
| | "Operator", |
| | choices=[">=", "<="] |
| | ), |
| | ), |
| | ui.column(3, |
| | ui.input_numeric( |
| | f"filter_value_{new_count}", |
| | "Value", |
| | value=0 |
| | ) |
| | ), |
| | ui.column(1, |
| | ui.markdown(" "), |
| | |
| |
|
| | ui.input_action_button( |
| | f"delete_filter_{new_count}", |
| | "", |
| | class_="btn-danger btn-sm", |
| | style="padding: 3px 6px;", |
| | icon='✖' |
| | |
| | ) |
| | ) |
| | ) |
| | ) |
| | ) |
| | filter_count.set(new_count) |
| | current_filters = active_filters.get() |
| | current_filters.append(new_count) |
| | active_filters.set(current_filters) |
| |
|
| | @reactive.effect |
| | def _(): |
| | |
| | for i in range(1, filter_count.get() + 1): |
| | try: |
| | if getattr(input, f"delete_filter_{i}")() > 0: |
| | |
| | ui.remove_ui(f"#filter_row_{i}") |
| | |
| | current_filters = active_filters.get() |
| | if i in current_filters: |
| | current_filters.remove(i) |
| | active_filters.set(current_filters) |
| | except: |
| | continue |
| |
|
| | @output |
| | @render_tabulator |
| | @reactive.event(input.generate_table, ignore_none=False) |
| | def tabulator(): |
| | columns_c = columns.copy() |
| | selection_list = list(input.list_input()) |
| | start_date = str(input.date_id()[0]) |
| | end_date = str(input.date_id()[1]) |
| |
|
| |
|
| |
|
| | |
| |
|
| | |
| | |
| |
|
| | |
| | |
| |
|
| |
|
| | |
| | |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| |
|
| |
|
| | if input.level_input() == "MLB": |
| | df_agg = update.update_summary_select(df=df_mlb_total.filter((pl.col('game_date')>=start_date)&(pl.col('game_date')<=end_date)&(pl.col('start_speed')>0)), |
| | selection=selection_list) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | df_agg = df_agg.select(selection_list + list(input.list_stats())) |
| | |
| | |
| | for i in active_filters.get(): |
| | try: |
| | col_name = getattr(input, f"filter_column_{i}")() |
| | if col_name: |
| | operator = getattr(input, f"filter_operator_{i}")() |
| | if col_name in [col["field"] for col in columns_c if col.get("formatter") == "money"]: |
| | value = getattr(input, f"filter_value_{i}")()/100 |
| | else: |
| | value = getattr(input, f"filter_value_{i}")() |
| | |
| | if operator == ">=": |
| | df_agg = df_agg.filter(pl.col(col_name) >= value) |
| | elif operator == "<=": |
| | df_agg = df_agg.filter(pl.col(col_name) <= value) |
| | except: |
| | continue |
| | |
| | for col in df_agg.columns[len(selection_list):]: |
| | if col in rounding_dict: |
| | df_agg = df_agg.with_columns(pl.col(col).round(rounding_dict[col])) |
| |
|
| | for column in columns_c: |
| | if column.get("formatter") == "money" and column.get("field") in df_agg.columns: |
| | df_agg = df_agg.with_columns(pl.col(column.get("field"))*100) |
| |
|
| | col_group = [] |
| | for column in columns_group: |
| | if column.get("field") in df_agg.columns: |
| | col_group.append(column) |
| |
|
| | col_group_stats = [] |
| | for column in columns_c: |
| | if column.get("field") in df_agg.columns: |
| | col_group_stats.append(column) |
| |
|
| | columns_c = col_group + col_group_stats |
| |
|
| | |
| | df_agg = df_agg.with_columns( |
| | [df_agg[col].cast(pl.Int8) for col in df_agg.columns if df_agg[col].dtype == pl.Boolean] |
| | ) |
| |
|
| | return Tabulator( |
| | df_agg.to_pandas(), |
| | |
| | table_options=TableOptions( |
| | height=800, |
| | |
| | columns=columns_c, |
| | ) |
| | ) |
| |
|
| |
|
| |
|
| |
|
| | @output |
| | @render_tabulator |
| | def tabulator_max_ev(): |
| | if input.tabset() == "Max EV": |
| | |
| | start_date = str(input.date_id()[0]) |
| | end_date = str(input.date_id()[1]) |
| | |
| | |
| | df_agg = update.update_summary_select(df=df_mlb_total.filter((pl.col('game_date')>=start_date)&(pl.col('game_date')<=end_date)), |
| | selection=['batter_id', 'batter_name']) |
| | |
| | |
| | df_agg = df_agg.join(df_max_ev, on='batter_id', how='left',suffix='_old') |
| | |
| | df_agg = df_agg.with_columns([ |
| | (pl.col('max_launch_speed')-pl.col('max_launch_speed_old')).alias('diff') |
| | ]) |
| | |
| | df_agg = df_agg.select(['batter_id', 'batter_name','bip','max_launch_speed','max_launch_speed_old','diff']) |
| | df_agg = df_agg.drop_nulls(subset=['max_launch_speed']) |
| | |
| | df_agg = df_agg.filter(pl.col("diff").is_not_null()).with_columns( |
| | pl.col("diff").round(1).map_elements(lambda x: f"{x:+.1f}") |
| | ).sort("max_launch_speed", descending=True) |
| | |
| | |
| | |
| | |
| | return Tabulator( |
| | df_agg.to_pandas(), |
| | |
| | table_options=TableOptions( |
| | height=800, |
| | |
| | columns=agg_titles_mev, |
| | ) |
| | ) |
| |
|
| | |
| |
|
| |
|
| | app = App(app_ui, server) |
| |
|