CoolFace
Apppublic

TJStatsApps/2025_decision_value

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
statcast_compare.py701 linesDownload Raw Back to root
1from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui2import datasets3from datasets import load_dataset4import pandas as pd5import numpy as np6import matplotlib.pyplot as plt7import seaborn as sns8import numpy as np9from scipy.stats import gaussian_kde10import matplotlib11from matplotlib.ticker import MaxNLocator12from matplotlib.gridspec import GridSpec13from scipy.stats import zscore14import math15import matplotlib16from adjustText import adjust_text17import matplotlib.ticker as mtick18from shinywidgets import output_widget, render_widget19import pandas as pd20from configure import base_url21import shinyswatch22import inflect23from matplotlib.pyplot import text24 25def percentile(n):26    def percentile_(x):27        return np.nanpercentile(x, n)28    percentile_.__name__ = 'percentile_%s' % n29    return percentile_30 31from matplotlib.colors import Normalize32 33print('Running')34 35cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"])36df = pd.read_csv('statcast_20152023.csv')37df['last_name'] = df['last_name, first_name'].str.split(',').str[0]38df['first_name'] = df['last_name, first_name'].str.split(',').str[1].str.strip(' ')39df['name'] = df['first_name'] +' ' +df['last_name']40 41df[[x for x in df if x[-7:] == 'percent']] = df[[x for x in df if x[-7:] == 'percent']]/10042df['barrel_batted_rate'] = df['barrel_batted_rate']/10043 44player_dict = df[['player_id','name']].drop_duplicates().sort_values('name').set_index('player_id').to_dict()['name']45 46df_median = df[df.pa>=400]47 48format_dict = {49 'k_percent':{'format':':.1%','average':df.strikeout.sum()/df.pa.sum(),'a_d_good':False,'tab_name':'K%'},50 'bb_percent':{'format':':.1%','average':df.walk.sum()/df.pa.sum(),'a_d_good':True,'tab_name':'BB%'},51 'batting_avg':{'format':':.3f','average':df.hit.sum()/df.ab.sum(),'a_d_good':True,'tab_name':'AVG'},52 'on_base_plus_slg':{'format':':.3f','average':df.on_base_plus_slg.mean()/df.pa.mean(),'a_d_good':True,'tab_name':'OPS'},53 'isolated_power':{'format':':.3f','average':(df.single.sum() + df.double.sum()*2 + df.triple.sum()*3 + df.home_run.sum()*4)/df.ab.sum() - df.hit.sum()/df.ab.sum(),'a_d_good':True,'tab_name':'ISO'},54 'xba':{'format':':.3f','average':df_median.xba.median(),'a_d_good':True,'tab_name':'xBA'},55 'xslg':{'format':':.3f','average':df_median.xslg.median(),'a_d_good':True,'tab_name':'xSLG'},56 'woba':{'format':':.3f','average':df_median.woba.median(),'a_d_good':True,'tab_name':'wOBA'},57 'xwoba':{'format':':.3f','average':df_median.xwoba.median(),'a_d_good':True,'tab_name':'xwOBA'},58 'xobp':{'format':':.3f','average':df_median.xobp.median(),'a_d_good':True,'tab_name':'xOBP'},59 'xiso':{'format':':.3f','average':df_median.xiso.median(),'a_d_good':True,'tab_name':'xISO'},60 'wobacon':{'format':':.3f','average':df_median.wobacon.median(),'a_d_good':True,'tab_name':'wOBACON'},61 'xwobacon':{'format':':.3f','average':df_median.xwobacon.median(),'a_d_good':True,'tab_name':'xwOBACON'},62 'bacon':{'format':':.1f','average':df_median.bacon.median(),'a_d_good':True,'tab_name':'BACON'},63 'xbacon':{'format':':.1f','average':df_median.xbacon.median(),'a_d_good':True,'tab_name':'xBACON'},64 'xbadiff':{'format':':.3f','average':df_median.xbadiff.median(),'a_d_good':True,'tab_name':'BA-xBA'},65 'xslgdiff':{'format':':.3f','average':df_median.xslgdiff.median(),'a_d_good':True,'tab_name':'SLG-xSLG'},66 'wobadiff':{'format':':.3f','average':df_median.wobadiff.median(),'a_d_good':True,'tab_name':'wOBA-xwOBA'},67 'exit_velocity_avg':{'format':':.1f','average':(df.exit_velocity_avg * df.batted_ball).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'EV'},68 'launch_angle_avg':{'format':':.1f','average':(df.launch_angle_avg * df.batted_ball).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'LA'},69 'barrel':{'format':':.0f','average':df_median.barrel.median(),'a_d_good':True,'tab_name':'Barrel'},70 'barrel_batted_rate':{'format':':.1%','average':(df.barrel).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'Barrel%'},71 'avg_best_speed':{'format':':.1f','average':(df.avg_best_speed * df.batted_ball).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'Best Speed'},72 'avg_hyper_speed':{'format':':.1f','average':(df.avg_hyper_speed * df.batted_ball).sum() / (df.batted_ball).sum(),'a_d_good':True,'tab_name':'Hyper Speed'},73 'out_zone_swing_miss':{'format':':.0f','average':df_median.out_zone_swing_miss.mean(),'a_d_good':True,'tab_name':'O-Whiff%'},74 'out_zone_swing':{'format':':.0f','average':df_median.out_zone_swing.mean(),'a_d_good':True,'tab_name':'O-Swing'},75 'out_zone':{'format':':.0f','average':df_median.out_zone.mean(),'a_d_good':True,'tab_name':'O-Zone'},76 'pitch_count_offspeed':{'format':':.0f','average':df_median.pitch_count_offspeed.mean(),'a_d_good':True,'tab_name':'Pitch Off-Speed'},77 'pitch_count_fastball':{'format':':.0f','average':df_median.pitch_count_fastball.mean(),'a_d_good':True,'tab_name':'Pitch Fastball'},78 'pitch_count_breaking':{'format':':.0f','average':df_median.pitch_count_breaking.mean(),'a_d_good':True,'tab_name':'Pitch Breaking'},79 'pitch_count':{'format':':.0f','average':df_median.pitch_count.mean(),'a_d_good':True,'tab_name':'Pitches'},80 'in_zone_swing_miss':{'format':':.0f','average':df_median.in_zone_swing_miss.mean(),'a_d_good':False,'tab_name':'Z-Whiff'},81 'in_zone_swing':{'format':':.0f','average':df_median.in_zone_swing.mean(),'a_d_good':True,'tab_name':'Z-Swing'},82 'in_zone':{'format':':.0f','average':df_median.in_zone.mean(),'a_d_good':True,'tab_name':'Zone'},83 'edge':{'format':':.0f','average':df_median.edge.mean(),'a_d_good':True,'tab_name':'Edge'},84 'batted_ball':{'format':':.0f','average':df_median.batted_ball.mean(),'a_d_good':True,'tab_name':'Batted Balls'},85 'groundballs':{'format':':.0f','average':df_median.groundballs.mean(),'a_d_good':True,'tab_name':'Groundballs'},86 'flyballs':{'format':':.0f','average':df_median.flyballs.mean(),'a_d_good':True,'tab_name':'Flyballs'},87 'linedrives':{'format':':.0f','average':df_median.linedrives.mean(),'a_d_good':True,'tab_name':'Linedrives'},88 'popups':{'format':':.0f','average':df_median.popups.mean(),'a_d_good':True,'tab_name':'Popups'},89 'n_bolts':{'format':':.0f','average':df_median.n_bolts.mean(),'a_d_good':True,'tab_name':'Bolts'},90 'hp_to_1b':{'format':':.2f','average':df_median.hp_to_1b.mean(),'a_d_good':True,'tab_name':'Home Plate to 1st'},91 'sprint_speed':{'format':':.1f','average':df_median.sprint_speed.mean(),'a_d_good':True,'tab_name':'Sprint Speed'},92 93 'slg_percent':{'format':':.1%','average':(df.single.sum() + df.double.sum()*2 + df.triple.sum()*3 + df.home_run.sum()*4)/df.ab.sum(),'a_d_good':True,'tab_name':'SLG'},94 'on_base_percent':{'format':':.1%','average':df_median.on_base_percent.median(),'a_d_good':True,'tab_name':'OBP'},95 'sweet_spot_percent':{'format':':.1%','average':df_median.sweet_spot_percent.median(),'a_d_good':True,'tab_name':'SweetSpot%'},96 'solidcontact_percent':{'format':':.1%','average':df_median.solidcontact_percent.median(),'a_d_good':True,'tab_name':'Solid%'},97 'flareburner_percent':{'format':':.1%','average':df_median.flareburner_percent.median(),'a_d_good':False,'tab_name':'Flare/Burner%'},98 'poorlyunder_percent':{'format':':.1%','average':df_median.poorlyunder_percent.median(),'a_d_good':False,'tab_name':'Under%'},99 'poorlytopped_percent':{'format':':.1%','average':df_median.poorlytopped_percent.median(),'a_d_good':False,'tab_name':'Topped%'},100 'poorlyweak_percent':{'format':':.1%','average':df_median.poorlyweak_percent.median(),'a_d_good':False,'tab_name':'Weak%'},101 'hard_hit_percent':{'format':':.1%','average':df_median.hard_hit_percent.median(),'a_d_good':True,'tab_name':'HardHit%'},102 'z_swing_percent':{'format':':.1%','average':df.in_zone_swing.sum()/df.in_zone.sum(),'a_d_good':True,'tab_name':'Z-Swing%'},103 'z_swing_miss_percent':{'format':':.1%','average':df.in_zone_swing_miss.sum()/df.in_zone_swing.sum(),'a_d_good':False,'tab_name':'Z-Whiff%'},104 105 'out_zone_percent':{'format':':.1%','average':df.out_zone.sum()/df.pitch_count.sum(),'a_d_good':True,'tab_name':'O-Zone%'},106 'meatball_swing_percent':{'format':':.1%','average':df_median.meatball_swing_percent.median(),'a_d_good':True,'tab_name':'Meatball Swing%'},107 'meatball_percent':{'format':':.1%','average':df_median.meatball_percent.median(),'a_d_good':True,'tab_name':'Meatball%'},108 'iz_contact_percent':{'format':':.1%','average':1 - df.in_zone_swing_miss.sum()/df.in_zone_swing.sum(),'a_d_good':True,'tab_name':'Z-Contact%'},109 'in_zone_percent':{'format':':.1%','average':df.in_zone.mean()/df.pitch_count.sum(),'a_d_good':True,'tab_name':'Zone%'},110 'oz_swing_percent':{'format':':.1%','average':df.out_zone_swing.sum()/df.out_zone.sum(),'a_d_good':False,'tab_name':'O-Swing%'},111 'oz_swing_miss_percent':{'format':':.1%','average':df.out_zone_swing_miss.sum()/df.out_zone_swing.sum(),'a_d_good':False,'tab_name':'O-Whiff%'},112 'oz_contact_percent':{'format':':.1%','average':1 - df.out_zone_swing_miss.sum()/df.out_zone_swing.sum(),'a_d_good':True,'tab_name':'O-Contact%'},113 'edge_percent':{'format':':.1%','average':df_median.edge_percent.median(),'a_d_good':True,'tab_name':'Edge%'},114 'whiff_percent':{'format':':.1%','average':(df.in_zone_swing_miss.sum() + df.out_zone_swing_miss.sum()) / (df.in_zone_swing.sum() + df.out_zone_swing.sum()),'a_d_good':False,'tab_name':'Whiff%'},115 'swstr_percent':{'format':':.1%','average':(df.in_zone_swing_miss.sum() + df.out_zone_swing_miss.sum()) / (df.pitch_count.sum()),'a_d_good':False,'tab_name':'SwStr%'},116 'swing_percent':{'format':':.1%','average':(df.in_zone_swing.sum() + df.out_zone_swing.sum()) / (df.pitch_count.sum()),'a_d_good':True,'tab_name':'Swing%'},117 'pull_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Pull%'},118 'straightaway_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Straightaway%'},119 'opposite_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':True,'tab_name':'Opposite%'},120 'f_strike_percent':{'format':':.1%','average':df_median.hit.median(),'a_d_good':False,'tab_name':'1st Strike%'},121 'groundballs_percent':{'format':':.1%','average':df.groundballs.sum()/df.batted_ball.sum(),'a_d_good':False,'tab_name':'GB%'},122 'flyballs_percent':{'format':':.1%','average':df.flyballs.sum()/df.batted_ball.sum(),'a_d_good':True,'tab_name':'FB%'},123 'linedrives_percent':{'format':':.1%','average':df.linedrives.sum()/df.batted_ball.sum(),'a_d_good':True,'tab_name':'LD%'},124 'popups_percent':{'format':':.1%','average':df.popups.sum()/df.batted_ball.sum(),'a_d_good':False,'tab_name':'PU%'},}125 126column_dict = pd.DataFrame(format_dict.keys(),[format_dict[x]['tab_name'] for x in format_dict.keys()]).reset_index().set_index(0).to_dict()['index']127 128 129def server(input,output,session):130 131 132    @output133    @render.text134    @reactive.event(input.go, ignore_none=False)    135    def txt_title():136        if input.player_id() == '':137            return 'Select a Player'138 139        player_input = int(input.player_id())140        season_1 = max(2015,int(input.season_1()))141        season_2 = min(2023,int(input.season_2()))142 143        if season_1 < season_2:144            season_pick = [season_1,season_2]145 146        elif season_1 > season_2:147            season_pick = [season_2,season_1]148 149        if len(str(input.player_id())) == 0:150            return 'Select a Batter'151        if str(input.season_1()) == str(input.season_2()):152            return 'Select Different Seasons'153        if len(df[(df.player_id == player_input)&(df.year == season_pick[0])] )== 0 or len(df[(df.player_id == player_input)&(df.year == season_pick[1])] )== 0:154            return 'Select Different Seasons'155        return   f'{player_dict[int(input.player_id())]} Statcast Season Comparison'156 157    @output158    @render.text159    @reactive.event(input.go, ignore_none=False)    160    def txt_title_compare():161        if type(input.player_id()) is int or input.player_id()=='':162            return163 164        if type(input.player_id_2()) is int or input.player_id_2()=='':165            return    166 167        player_input_1 = int(input.player_id())168        player_input_2 = int(input.player_id_2())169 170 171        # season_pick = [input.season_1(),input.season_2()]172        columns_i_want = list(input.row_select())173        print(columns_i_want)174        season_1 = max(2015,int(input.season_1()))175        season_2 = min(2023,int(input.season_2()))176 177        player_list = [player_input_1,player_input_2]178        name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]179        season_pick_list = [season_1,season_2]180 181        if len(str(input.player_id())) == 0:182            return 'Select a Batter'183        if len(str(input.player_id_2())) == 0:184            return 'Select a Batter'185 186        if str(input.player_id()) == str(input.player_id_2()) and str(input.season_1()) == str(input.season_2()):187            return 'Select Different Seasons'188        if len(df[(df.player_id == player_list[0])&(df.year == season_pick_list[0])] )== 0 or len(df[(df.player_id == player_list[1])&(df.year == season_pick_list[1])])== 0:189            return 'No Data for Specified Batter in Given Season'190        191        return   f'Statcast Season Comparison'192 193 194 195 196    @output197    @render.text198    @reactive.event(input.go, ignore_none=False)199    def text_2022():200        if input.player_id() == '':201            return 'Select a Player'202        return   f'{int(input.season_1())} Season Results Compares to MLB Average'203 204 205    @output206    @render.text207    @reactive.event(input.go, ignore_none=False)208    def text_2022_1():209        if type(input.player_id()) is int or input.player_id()=='':210            return211 212        if type(input.player_id_2()) is int or input.player_id_2()=='':213            return     214        season_1 = max(2015,int(input.season_1()))215        season_2 = min(2023,int(input.season_2()))216        season_pick_list = [season_1,season_2]217        player_input_1 = int(input.player_id())218        player_input_2 = int(input.player_id_2())219        name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]220        return   f"{name_list[0]} '{str(season_pick_list[0])[2:]} Season Results Compares to MLB Average"221 222    @output223    @render.text224    @reactive.event(input.go, ignore_none=False)225    def text_2023():226        if input.player_id() == '':227            return 'Select a Player'228        return   f'{int(input.season_2())} Season Results Compares to MLB Average'229 230    @output231    @render.text232    @reactive.event(input.go, ignore_none=False)233    def text_2023_1():234        if type(input.player_id()) is int or input.player_id()=='':235            return236 237        if type(input.player_id_2()) is int or input.player_id_2()=='':238            return    239        season_1 = max(2015,int(input.season_1()))240        season_2 = min(2023,int(input.season_2()))241        season_pick_list = [season_1,season_2]242        player_input_1 = int(input.player_id())243        player_input_2 = int(input.player_id_2())244        name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]245        return   f"{name_list[1]} '{str(season_pick_list[1])[2:]} Season Results Compares to MLB Average"246 247    @output248    @render.text249    @reactive.event(input.go, ignore_none=False)250    def text_diff():251        if input.player_id() == '':252            return 'Select a Player'253        return   f'Difference Compares {int(input.season_2())} Results to {int(input.season_1())} Results'254 255    @output256    @render.text257    @reactive.event(input.go, ignore_none=False)258    def text_diff_compare():259        if type(input.player_id()) is int or input.player_id()=='':260            return261 262        if type(input.player_id_2()) is int or input.player_id_2()=='':263            return    264        player_input_1 = int(input.player_id())265        player_input_2 = int(input.player_id_2())266        name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]267        return   f'Difference Compares {name_list[0]} Results to {name_list[1]} Results'268 269 270 271    @output272    @render.table273    @reactive.event(input.go, ignore_none=False)274    def statcast_compare():275        if input.player_id() == '':276            return277 278        if len(str(input.player_id())) == 0:279            return280        if len(str(input.player_id_2())) == 0:281            return282 283 284        player_input = int(input.player_id())285 286 287        # season_pick = [input.season_1(),input.season_2()]288        columns_i_want = list(input.row_select())289        print(columns_i_want)290        season_1 = max(2015,int(input.season_1()))291        season_2 = min(2023,int(input.season_2()))292 293 294        if season_1 < season_2:295            season_pick = [season_1,season_2]296 297        elif season_1 > season_2:298            season_pick = [season_2,season_1]299 300        else:301            return302 303        print(df[(df.player_id == player_input)&(df.year == season_pick[0])])304    305 306        if len(df[(df.player_id == player_input)&(df.year == season_pick[0])] )== 0 or len(df[(df.player_id == player_input)&(df.year == season_pick[1])] )== 0:307            return308 309        df_compare = pd.concat([df[(df.player_id == player_input)&(df.year == season_pick[0])][[ 'player_age', 'pa']+columns_i_want],310                                df[(df.player_id == player_input)&(df.year == season_pick[1])][[ 'player_age', 'pa']+columns_i_want]]).reset_index(drop=True).T311 312 313        print('test')314        print(sum(df.player_id == input.player_id()))315        df_compare.columns = season_pick316 317        df_compare['Difference'] = df_compare.loc[columns_i_want][season_pick[1]] - df_compare.loc[columns_i_want][season_pick[0]]318 319        df_compare_style = df_compare.style.format(320                    "{:.0f}")321        df_compare_style = df_compare_style.set_properties(**{'background-color': 'white',322                                'color': 'white'},subset=(['player_age', 'pa'],df_compare_style.columns[2])).set_properties(323                    **{'min-width':'100px'},overwrite=False).set_table_styles(324            [{'selector': 'th:first-child', 'text-align': 'center','props': [('background-color', 'white')]}],overwrite=False).set_table_styles(325            [{'selector': 'tr:first-child','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(326            [{'selector': 'index','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(327            [{'selector': 'th', 'text-align': 'center','props': [('line-height', '40px'),('min-width', '30px')]}],overwrite=False).set_properties(328                329            **{'Height': '20px'},**{'text-align': 'center'},overwrite=False).set_table_styles([{330            'selector': 'caption',331            'props': [332                ('color', ''),333                ('fontname', 'Century Gothic'),334                ('font-size', '20px'),335                ('font-style', 'italic'),336                ('font-weight', ''),337                ('text-align', 'centre'),338                ]339 340                },{'selector' :'th', 'props':[('text-align', 'center'),('font-size', '20px'),('Height','20px'),('min-width','200px')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '20px'),('min-width','100px')]}],overwrite=False)341 342 343        for r in columns_i_want:344            if format_dict[r]['a_d_good']:345                cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"])346            else:347                cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#FBBC04","white","#4285F4"])348 349 350            colormap = plt.get_cmap(cmap)351            norm = Normalize(vmin=0.7, vmax=1.3)352 353            normalized_value = norm(df_compare[df_compare.columns[0]][r]/format_dict[r]['average'])354            df_compare_style.format(355                    f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[0])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),356                                'color': 'black'},subset=(r,df_compare_style.columns[0]))357 358            norm = Normalize(vmin=0.7, vmax=1.3)359            normalized_value = norm(df_compare[df_compare.columns[1]][r]/format_dict[r]['average'])360            df_compare_style.format(361                    f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[1])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),362                                'color': 'black'},subset=(r,df_compare_style.columns[1]))363 364            norm = Normalize(vmin=0.7, vmax=1.3)365            normalized_value = norm(df_compare[df_compare.columns[1]][r]/df_compare[df_compare.columns[0]][r])366            df_compare_style.format(367                    f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[2])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),368                                'color': 'black'},subset=(r,df_compare_style.columns[2]))369 370 371 372 373        df_compare_style.relabel_index(['Age', 'PA']+[format_dict[x]['tab_name'] for x in columns_i_want]).set_properties(374                    **{'border': '1px black solid !important'},overwrite=False).set_table_styles(375            [{"selector": "", "props": [("border", "1px solid")]},376            {"selector": "tbody td", "props": [("border", "1px solid")]},377            {"selector": "th", "props": [("border", "1px solid")]}],overwrite=False)378 379        #df_compare = df_compare.fillna(np.nan)     380 381        return df_compare_style382 383 384 385    @output386    @render.table387    @reactive.event(input.go, ignore_none=False)388    def statcast_compare_2():389 390        # if input.player_id() == 0:391        #     return392        if type(input.player_id()) is int or input.player_id()=='':393            return394 395        if type(input.player_id_2()) is int or input.player_id_2()=='':396            return      397        # if len(str(input.player_id())) == 0:398        #     return399 400        player_input_1 = int(input.player_id())401        player_input_2 = int(input.player_id_2())402 403 404        # season_pick = [input.season_1(),input.season_2()]405        columns_i_want = list(input.row_select())406        print(columns_i_want)407        season_1 = max(2015,int(input.season_1()))408        season_2 = min(2023,int(input.season_2()))409 410 411        # if season_1 < season_2:412        #     season_pick = [season_1,season_2]413 414        # elif season_1 > season_2:415        #     season_pick = [season_2,season_1]416 417        # else:418        #     return419 420        #print(df[(df.player_id == player_input)&(df.year == season_pick[0])])421        #player_list = ['Elly De La Cruz','Aaron Judge']422        player_list = [player_input_1,player_input_2]423        name_list = [player_dict[int(player_input_1)],player_dict[int(player_input_2)]]424        season_pick_list = [season_1,season_2]425 426        if len(df[(df.player_id == player_list[0])&(df.year == season_pick_list[0])] )== 0 or len(df[(df.player_id == player_list[1])&(df.year == season_pick_list[1])] )== 0:427            return428        if str(input.player_id()) == str(input.player_id_2()) and str(input.season_1()) == str(input.season_2()):429            return430 431 432        df_compare = pd.concat([df[(df.player_id == player_list[0])&(df.year == season_pick_list[0])][[ 'year','player_age', 'pa']+columns_i_want],433        df[(df.player_id == player_list[1])&(df.year == season_pick_list[1])][[ 'year','player_age', 'pa']+columns_i_want]]).reset_index(drop=True).T434 435        df_compare.columns = [f"{name_list[0]} '{str(season_pick_list[0])[2:]}",f"{name_list[1]} '{str(season_pick_list[1])[2:]}"]436        df_compare['Difference'] = df_compare.loc[columns_i_want][df_compare.columns [0]] - df_compare.loc[columns_i_want][df_compare.columns[1]]437        #df_compare = df_compare.fillna(np.nan)438 439        df_compare_style = df_compare.style.format(440                    "{:.0f}")441        df_compare_style = df_compare_style.set_properties(**{'background-color': 'white',442                                'color': 'white'},subset=(['year','player_age', 'pa'],df_compare_style.columns[2])).set_properties(443                    **{'min-width':'125px'},overwrite=False).set_table_styles(444            [{'selector': 'th:first-child', 'text-align': 'center','props': [('background-color', 'white')]}],overwrite=False).set_table_styles(445            [{'selector': 'tr:first-child','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(446            [{'selector': 'index','text-align': 'center', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(447            [{'selector': 'th', 'text-align': 'center','props': [('line-height', '40px'),('min-width', '30px')]}],overwrite=False).set_properties(448                449            **{'Height': '20px'},**{'text-align': 'center'},overwrite=False).set_table_styles([{450            'selector': 'caption',451            'props': [452                ('color', ''),453                ('fontname', 'Century Gothic'),454                ('font-size', '20px'),455                ('font-style', 'italic'),456                ('font-weight', ''),457                ('text-align', 'centre'),458                ]459 460                },{'selector' :'th', 'props':[('text-align', 'center'),('font-size', '20px'),('Height','20px'),('min-width','200px')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '20px'),('min-width','100px')]}],overwrite=False)461 462 463        for r in columns_i_want:464            if format_dict[r]['a_d_good']:465                cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"])466            else:467                cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#FBBC04","white","#4285F4"])468 469 470            colormap = plt.get_cmap(cmap)471            norm = Normalize(vmin=0.5, vmax=1.5)472            normalized_value = norm(df_compare[df_compare.columns[0]][r]/format_dict[r]['average'])473            df_compare_style.format(474                    f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[0])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),475                                'color': 'black'},subset=(r,df_compare_style.columns[0]))476 477            norm = Normalize(vmin=0.5, vmax=1.5)478            normalized_value = norm(df_compare[df_compare.columns[1]][r]/format_dict[r]['average'])479            df_compare_style.format(480                    f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[1])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),481                                'color': 'black'},subset=(r,df_compare_style.columns[1]))482 483            norm = Normalize(vmin=0.8, vmax=1.2)484            normalized_value = norm(df_compare[df_compare.columns[0]][r]/df_compare[df_compare.columns[1]][r])485            df_compare_style.format(486                    f"{{{format_dict[r]['format']}}}",subset=(r,df_compare_style.columns[2])).set_properties(**{'background-color': '#%02x%02x%02x' % (int(colormap(normalized_value)[0] *255), int(colormap(normalized_value)[1] *255), int(colormap(normalized_value)[2] *255)),487                                'color': 'black'},subset=(r,df_compare_style.columns[2]))488 489        df_compare_style = df_compare_style490 491 492        df_compare_style.relabel_index(['Year','Age', 'PA']+[format_dict[x]['tab_name'] for x in columns_i_want]).set_properties(493                    **{'border': '1px black solid !important'},overwrite=False).set_table_styles(494            [{"selector": "", "props": [("border", "1px solid")]},495            {"selector": "tbody td", "props": [("border", "1px solid")]},496            {"selector": "th", "props": [("border", "1px solid")]}],overwrite=False)497 498        #df_compare = df_compare.fillna(np.nan)     499 500        return df_compare_style501 502    @output503    @render.table504    @reactive.event(input.go, ignore_none=False)505    def colour_scale():506        off_b2b_df = pd.DataFrame(data={'one':-0.30,'two':0,'three':0.30},index=[0])507        off_b2b_df_style = off_b2b_df.style.set_properties(**{'border': '3 px'},overwrite=False).set_table_styles([{508            'selector': 'caption',509            'props': [510                ('color', ''),511                ('fontname', 'Century Gothic'),512                ('font-size', '20px'),513                ('font-style', 'italic'),514                ('font-weight', ''),515                ('text-align', 'centre'),516                ]517 518                },{'selector' :'th', 'props':[('text-align', 'center'),('Height','px'),('color','black'),(519                    'border', '1px black solid !important')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '18px'),('color','black')]}],overwrite=False).set_properties(520                    **{'background-color':'White','index':'White','min-width':'150px'},overwrite=False).set_table_styles(521            [{'selector': 'th:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(522            [{'selector': 'tr:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(523            [{'selector': 'tr', 'props': [('line-height', '20px')]}],overwrite=False).set_properties(524            **{'Height': '8px'},**{'text-align': 'center'},overwrite=False).set_properties(525                **{'background-color':'#4285F4'},subset=off_b2b_df.columns[0]).set_properties(526                **{'background-color':'white'},subset=off_b2b_df.columns[1]).set_properties(527                **{'background-color':'#FBBC04'},subset=off_b2b_df.columns[2]).set_properties(528                **{'color':'black'},subset=off_b2b_df.columns[:]).hide_index().set_table_styles([529            {'selector': 'thead', 'props': [('display', 'none')]}530            ]).set_properties(**{'border': '3 px','color':'black'},overwrite=False).set_properties(531                    **{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))).set_properties(532                    **{'min-width':'130'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:])),overwrite=False).set_properties(**{533                                'color': 'black'},overwrite=False).set_properties(534                    **{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))) .format(535                    "{:+.0%}")536        return off_b2b_df_style537 538    @output539    @render.table540    @reactive.event(input.go, ignore_none=False)541    def colour_scale_2():542        off_b2b_df = pd.DataFrame(data={'one':-0.30,'two':0,'three':0.30},index=[0])543        off_b2b_df_style = off_b2b_df.style.set_properties(**{'border': '3 px'},overwrite=False).set_table_styles([{544            'selector': 'caption',545            'props': [546                ('color', ''),547                ('fontname', 'Century Gothic'),548                ('font-size', '20px'),549                ('font-style', 'italic'),550                ('font-weight', ''),551                ('text-align', 'centre'),552                ]553 554                },{'selector' :'th', 'props':[('text-align', 'center'),('Height','px'),('color','black'),(555                    'border', '1px black solid !important')]},{'selector' :'td', 'props':[('text-align', 'center'),('font-size', '18px'),('color','black')]}],overwrite=False).set_properties(556                    **{'background-color':'White','index':'White','min-width':'150px'},overwrite=False).set_table_styles(557            [{'selector': 'th:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(558            [{'selector': 'tr:first-child', 'props': [('background-color', 'white')]}],overwrite=False).set_table_styles(559            [{'selector': 'tr', 'props': [('line-height', '20px')]}],overwrite=False).set_properties(560            **{'Height': '8px'},**{'text-align': 'center'},overwrite=False).set_properties(561                **{'background-color':'#4285F4'},subset=off_b2b_df.columns[0]).set_properties(562                **{'background-color':'white'},subset=off_b2b_df.columns[1]).set_properties(563                **{'background-color':'#FBBC04'},subset=off_b2b_df.columns[2]).set_properties(564                **{'color':'black'},subset=off_b2b_df.columns[:]).hide_index().set_table_styles([565            {'selector': 'thead', 'props': [('display', 'none')]}566            ]).set_properties(**{'border': '3 px','color':'black'},overwrite=False).set_properties(567                    **{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))).set_properties(568                    **{'min-width':'130'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:])),overwrite=False).set_properties(**{569                                'color': 'black'},overwrite=False).set_properties(570                    **{'border': '1px black solid !important'},subset = ((list(off_b2b_df.index[:]),off_b2b_df.columns[:]))) .format(571                    "{:+.0%}")572 573 574        return off_b2b_df_style575# test = test.fillna(0)576#test['PP TOI'] = ["%d:%02d" % (int(x),(x*60)%60) if x>0 else '0:00' for x in test['PP TOI']]577 578 579statcast_compare = App(ui.page_fluid(580    ui.tags.base(href=base_url), 581    ui.tags.div(582         {"style": "width:90%;margin: 0 auto;max-width: 1600px;"},583        ui.tags.style(584            """585            h4 {586                margin-top: 1em;font-size:35px;587            }588            h2{589                font-size:25px;590            }591            """592         ),593    shinyswatch.theme.simplex(),594    ui.tags.h4("TJStats"),595    ui.tags.i("Baseball Analytics and Visualizations"),596    ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),597    ui.navset_tab(598        ui.nav_control(599             ui.a(600                "Home",601                href="home/"602            ),603        ),604        ui.nav_menu(605            "Batter Charts",606            ui.nav_control(607            ui.a(608                "Batting Rolling",609                href="rolling_batter/"610            ),611            ui.a(612                "Spray & Damage",613                href="spray/"614            ),615            ui.a(616                "Decision Value",617                href="decision_value/"618            ),619            # ui.a(620            #     "Damage Model",621            #     href="damage_model/"622            # ),623            ui.a(624                "Batter Scatter",625                href="batter_scatter/"626            ),627            # ui.a(628            #     "EV vs LA Plot",629            #     href="ev_angle/"630            # ),631            ui.a(632                "Statcast Compare",633                href="statcast_compare/"634            )635        ),636        ),637        ui.nav_menu(638            "Pitcher Charts",639            ui.nav_control(640             ui.a(641                "Pitcher Rolling",642                href="rolling_pitcher/"643            ),644             ui.a(645                "Pitcher Summary",646                href="pitching_summary_graphic_new/"647            ),648             ui.a(649                "Pitcher Scatter",650                href="pitcher_scatter/"651            )652        ),653        )),ui.row(654    ui.layout_sidebar(655        656   657 658      ui.panel_sidebar(659        #ui.input_date_range("date_range_id", "Date range input",start = statcast_df.game_date.min(), end = statcast_df.game_date.max()),660        ui.input_select("player_id", "Select Player 1",player_dict,width=1,size=1,selectize=True,multiple=False,selected=592450),661        ui.input_select("player_id_2", "Select Player 2 (For Player Compare Tab)",player_dict,width=1,size=1,selectize=True,multiple=False,selected=592450),662        ui.input_numeric("season_1", "Season 1", value=2022,min=2015,max=2023),663        ui.input_numeric("season_2", "Season 2", value=2023,min=2015,max=2023),664        ui.input_select("row_select", "Select Stats",665                        column_dict,width=1,size=1,selectize=True,666                        multiple=True,667                        selected=['k_percent','bb_percent','woba','xwoba','iz_contact_percent','oz_swing_percent','whiff_percent']),668                ui.input_action_button("go", "Generate",class_="btn-primary",669                                       )),670 671        ui.panel_main(ui.tags.h3(""),672            ui.navset_tab(673                      ui.nav("Single Player",674                        ui.div({"style": "font-size:2.1em;"},ui.output_text("txt_title")),675                        #ui.tags.h2("Fantasy Hockey Schedule Summary"),676                        ui.tags.h5("Created By: @TJStats,     Data: MLB"),677                        #ui.div({"style": "font-size:1.6em;"},ui.output_text("txt")),678                        ui.output_table("statcast_compare"),679                        #ui.tags.h5('Legend'),680                        ui.tags.h3(""),681                        ui.tags.h5('Colour Scale:'),682                        ui.output_table("colour_scale"),683                        ui.div({"style": "font-size:1em;"},ui.output_text("text_2022")),684                        ui.div({"style": "font-size:1em;"},ui.output_text("text_2023")),685                        ui.div({"style": "font-size:1em;"},ui.output_text("text_diff"))),686 687                    ui.nav("Player Compare",688                        ui.div({"style": "font-size:2.1em;"},ui.output_text("txt_title_compare")),689                        #ui.tags.h2("Fantasy Hockey Schedule Summary"),690                        ui.tags.h5("Created By: @TJStats,     Data: MLB"),691                        #ui.div({"style": "font-size:1.6em;"},ui.output_text("txt")),692                        ui.output_table("statcast_compare_2"),693                        ui.tags.h3(""),694                        ui.tags.h5('Colour Scale:'),695                        ui.output_table("colour_scale_2"),696                        ui.div({"style": "font-size:1em;"},ui.output_text("text_2022_1")),697                        ui.div({"style": "font-size:1em;"},ui.output_text("text_2023_1")),698                        ui.div({"style": "font-size:1em;"},ui.output_text("text_diff_compare")))699        )700    ),701    )),)),server)