Upload 34 files
Browse files- app.py +16 -20
- batter_scatter.py +21 -31
- batter_scatter_2.py +502 -0
- chadwick_df.csv +0 -0
- damage.py +21 -32
- decision_value.py +24 -32
- ev_angle.py +23 -33
- home.py +68 -32
- pitcher_scatter.py +508 -0
- pitcher_summary_graphic.ipynb +0 -0
- pitching_summary_graphic.py +0 -0
- pitching_summary_graphic_new.py +0 -0
- pitching_summary_graphic_new_fg_api.py +0 -0
- rolling_batter.py +732 -0
- rolling_pitcher.py +719 -0
- spray.py +23 -34
- statcast_20152023.csv +0 -0
- statcast_compare.py +701 -0
- statcast_pitch_summary.csv +19 -0
- summary_pitcher.csv +0 -0
- summary_pitcher_level.csv +0 -0
- test_data_mlb.ipynb +238 -0
app.py
CHANGED
@@ -9,40 +9,36 @@ import shinyswatch
|
|
9 |
|
10 |
#Import pages
|
11 |
from home import home
|
12 |
-
|
13 |
-
# from gsax_timeline import gsax_timeline
|
14 |
-
# from on_ice_xg_rates import on_ice_xg
|
15 |
-
# from gsax_leaderboard import gsax_leaderboard
|
16 |
-
# from on_ice_xgfp import on_ice_xgfp
|
17 |
-
# from team_xg_rates import team_xg_rates
|
18 |
-
# from gsax_comparison import gsax_comparison
|
19 |
-
# from game import game
|
20 |
from spray import spray
|
21 |
from decision_value import decision_value
|
22 |
from damage import damage
|
23 |
from batter_scatter import batter_scatter
|
24 |
from ev_angle import ev_angle
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
# Create app
|
29 |
routes = [
|
30 |
Mount('/home', app=home),
|
31 |
-
|
32 |
-
# Mount('/gsax-timeline', app=gsax_timeline),
|
33 |
-
# Mount('/skater-xg-rates', app=on_ice_xg),
|
34 |
-
# Mount('/gsax-leaderboard', app=gsax_leaderboard),
|
35 |
-
# Mount('/skater-xg-percentages', app=on_ice_xgfp),
|
36 |
-
# Mount('/team-xg-rates', app=team_xg_rates),
|
37 |
-
# Mount('/gsax-comparison',app=gsax_comparison),
|
38 |
Mount('/spray',app=spray),
|
39 |
Mount('/decision_value',app=decision_value),
|
40 |
Mount('/damage_model',app=damage),
|
41 |
Mount('/batter_scatter',app=batter_scatter),
|
42 |
Mount('/ev_angle',app=ev_angle),
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
46 |
]
|
47 |
|
48 |
#Run App
|
|
|
9 |
|
10 |
#Import pages
|
11 |
from home import home
|
12 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
from spray import spray
|
14 |
from decision_value import decision_value
|
15 |
from damage import damage
|
16 |
from batter_scatter import batter_scatter
|
17 |
from ev_angle import ev_angle
|
18 |
+
from rolling_batter import rolling_batter
|
19 |
+
from statcast_compare import statcast_compare
|
20 |
+
|
21 |
+
from rolling_pitcher import rolling_pitcher
|
22 |
+
from pitching_summary_graphic_new_fg_api import pitching_summary_graphic_new
|
23 |
+
from pitcher_scatter import pitcher_scatter
|
24 |
+
|
25 |
+
|
26 |
|
27 |
# Create app
|
28 |
routes = [
|
29 |
Mount('/home', app=home),
|
30 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
Mount('/spray',app=spray),
|
32 |
Mount('/decision_value',app=decision_value),
|
33 |
Mount('/damage_model',app=damage),
|
34 |
Mount('/batter_scatter',app=batter_scatter),
|
35 |
Mount('/ev_angle',app=ev_angle),
|
36 |
+
Mount('/rolling_batter',app=rolling_batter),
|
37 |
+
Mount('/statcast_compare',app=statcast_compare),
|
38 |
+
|
39 |
+
Mount('/rolling_pitcher',app=rolling_pitcher),
|
40 |
+
Mount('/pitching_summary_graphic_new',app=pitching_summary_graphic_new),
|
41 |
+
Mount('/pitcher_scatter',app=pitcher_scatter),
|
42 |
]
|
43 |
|
44 |
#Run App
|
batter_scatter.py
CHANGED
@@ -19,6 +19,8 @@ from shinywidgets import output_widget, render_widget
|
|
19 |
import pandas as pd
|
20 |
from configure import base_url
|
21 |
import shinyswatch
|
|
|
|
|
22 |
|
23 |
|
24 |
exit_velo_df_codes_summ_batter = pd.read_csv('summary_batter.csv',index_col=[0])
|
@@ -126,6 +128,7 @@ def server(input,output,session):
|
|
126 |
|
127 |
@output
|
128 |
@render.plot(alt="A histogram")
|
|
|
129 |
def plot():
|
130 |
sns.set_theme(style="whitegrid", palette="pastel")
|
131 |
print(input.level_id())
|
@@ -387,6 +390,7 @@ batter_scatter = App(ui.page_fluid(
|
|
387 |
shinyswatch.theme.simplex(),
|
388 |
ui.tags.h4("TJStats"),
|
389 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
|
|
390 |
ui.navset_tab(
|
391 |
ui.nav_control(
|
392 |
ui.a(
|
@@ -397,7 +401,11 @@ batter_scatter = App(ui.page_fluid(
|
|
397 |
ui.nav_menu(
|
398 |
"Batter Charts",
|
399 |
ui.nav_control(
|
400 |
-
|
|
|
|
|
|
|
|
|
401 |
"Spray",
|
402 |
href="spray/"
|
403 |
),
|
@@ -416,48 +424,29 @@ batter_scatter = App(ui.page_fluid(
|
|
416 |
ui.a(
|
417 |
"EV vs LA Plot",
|
418 |
href="ev_angle/"
|
|
|
|
|
|
|
|
|
419 |
)
|
420 |
),
|
421 |
),
|
422 |
ui.nav_menu(
|
423 |
-
"
|
424 |
ui.nav_control(
|
425 |
ui.a(
|
426 |
-
"
|
427 |
-
href="
|
428 |
),
|
429 |
ui.a(
|
430 |
-
"
|
431 |
-
href="
|
432 |
),
|
433 |
ui.a(
|
434 |
-
"
|
435 |
-
href="
|
436 |
)
|
437 |
),
|
438 |
-
),ui.nav_menu(
|
439 |
-
"Team Charts",
|
440 |
-
ui.nav_control(
|
441 |
-
ui.a(
|
442 |
-
"Team xG Rates",
|
443 |
-
href="team-xg-rates/"
|
444 |
-
),
|
445 |
-
),
|
446 |
-
),ui.nav_control(
|
447 |
-
ui.a(
|
448 |
-
"Games",
|
449 |
-
href="games/"
|
450 |
-
),
|
451 |
-
),ui.nav_control(
|
452 |
-
ui.a(
|
453 |
-
"About",
|
454 |
-
href="about/"
|
455 |
-
),
|
456 |
-
),ui.nav_control(
|
457 |
-
ui.a(
|
458 |
-
"Articles",
|
459 |
-
href="articles/"
|
460 |
-
),
|
461 |
)),ui.row(
|
462 |
ui.layout_sidebar(
|
463 |
|
@@ -491,6 +480,7 @@ batter_scatter = App(ui.page_fluid(
|
|
491 |
ui.row(
|
492 |
ui.input_switch("names", "Toggle Names"),
|
493 |
ui.input_switch("group_level", "Group Levels")),
|
|
|
494 |
),
|
495 |
|
496 |
ui.panel_main(
|
|
|
19 |
import pandas as pd
|
20 |
from configure import base_url
|
21 |
import shinyswatch
|
22 |
+
import inflect
|
23 |
+
from matplotlib.pyplot import text
|
24 |
|
25 |
|
26 |
exit_velo_df_codes_summ_batter = pd.read_csv('summary_batter.csv',index_col=[0])
|
|
|
128 |
|
129 |
@output
|
130 |
@render.plot(alt="A histogram")
|
131 |
+
@reactive.event(input.go, ignore_none=False)
|
132 |
def plot():
|
133 |
sns.set_theme(style="whitegrid", palette="pastel")
|
134 |
print(input.level_id())
|
|
|
390 |
shinyswatch.theme.simplex(),
|
391 |
ui.tags.h4("TJStats"),
|
392 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
393 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
394 |
ui.navset_tab(
|
395 |
ui.nav_control(
|
396 |
ui.a(
|
|
|
401 |
ui.nav_menu(
|
402 |
"Batter Charts",
|
403 |
ui.nav_control(
|
404 |
+
ui.a(
|
405 |
+
"Batting Rolling",
|
406 |
+
href="rolling_batter/"
|
407 |
+
),
|
408 |
+
ui.a(
|
409 |
"Spray",
|
410 |
href="spray/"
|
411 |
),
|
|
|
424 |
ui.a(
|
425 |
"EV vs LA Plot",
|
426 |
href="ev_angle/"
|
427 |
+
),
|
428 |
+
ui.a(
|
429 |
+
"Statcast Compare",
|
430 |
+
href="statcast_compare/"
|
431 |
)
|
432 |
),
|
433 |
),
|
434 |
ui.nav_menu(
|
435 |
+
"Pitcher Charts",
|
436 |
ui.nav_control(
|
437 |
ui.a(
|
438 |
+
"Pitcher Rolling",
|
439 |
+
href="rolling_pitcher/"
|
440 |
),
|
441 |
ui.a(
|
442 |
+
"Pitcher Summary",
|
443 |
+
href="pitching_summary_graphic_new/"
|
444 |
),
|
445 |
ui.a(
|
446 |
+
"Pitcher Scatter",
|
447 |
+
href="pitcher_scatter/"
|
448 |
)
|
449 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
450 |
)),ui.row(
|
451 |
ui.layout_sidebar(
|
452 |
|
|
|
480 |
ui.row(
|
481 |
ui.input_switch("names", "Toggle Names"),
|
482 |
ui.input_switch("group_level", "Group Levels")),
|
483 |
+
ui.input_action_button("go", "Generate",class_="btn-primary"),
|
484 |
),
|
485 |
|
486 |
ui.panel_main(
|
batter_scatter_2.py
ADDED
@@ -0,0 +1,502 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
|
2 |
+
import datasets
|
3 |
+
from datasets import load_dataset
|
4 |
+
import pandas as pd
|
5 |
+
import numpy as np
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
import seaborn as sns
|
8 |
+
import numpy as np
|
9 |
+
from scipy.stats import gaussian_kde
|
10 |
+
import matplotlib
|
11 |
+
from matplotlib.ticker import MaxNLocator
|
12 |
+
from matplotlib.gridspec import GridSpec
|
13 |
+
from scipy.stats import zscore
|
14 |
+
import math
|
15 |
+
import matplotlib
|
16 |
+
from adjustText import adjust_text
|
17 |
+
import matplotlib.ticker as mtick
|
18 |
+
from shinywidgets import output_widget, render_widget
|
19 |
+
import pandas as pd
|
20 |
+
from configure import base_url
|
21 |
+
import shinyswatch
|
22 |
+
import inflect
|
23 |
+
from matplotlib.pyplot import text
|
24 |
+
|
25 |
+
|
26 |
+
exit_velo_df_codes_summ_batter = pd.read_csv('summary_batter.csv',index_col=[0])
|
27 |
+
#exit_velo_df_codes_summ = pd.read_csv('summary_pitcher.csv',index_col=[0])
|
28 |
+
|
29 |
+
exit_velo_df_codes_summ_non_level = pd.read_csv('summary_batter_level.csv',index_col=[0]).reset_index(drop=True)
|
30 |
+
|
31 |
+
exit_velo_df_codes_summ_non_level['levels'] = exit_velo_df_codes_summ_non_level.levels.str.split(', ')
|
32 |
+
|
33 |
+
exit_velo_df_codes_summ_non_level = exit_velo_df_codes_summ_non_level.rename(columns={'levels':'level'})
|
34 |
+
|
35 |
+
|
36 |
+
|
37 |
+
print(exit_velo_df_codes_summ_batter.bb_minus_k_percent)
|
38 |
+
|
39 |
+
batter_dict_stat = { 'sweet_spot_percent':{'x_axis':'SweetSpot%','title':'SweetSpot%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
40 |
+
'max_launch_speed':{'x_axis':'Max Exit Velocity','title':'Max Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
|
41 |
+
'launch_speed_90':{'x_axis':'90th Percentile EV','title':'90th Percentile EV','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
|
42 |
+
'launch_speed':{'x_axis':'Exit Velocity','title':'Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
|
43 |
+
'launch_angle':{'x_axis':'Launch Angle','title':'Launch Angle','flip_p':False,'decimal_format':'string_0','percent_adjust':100},
|
44 |
+
'avg':{'x_axis':'AVG','title':'AVG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
45 |
+
'obp':{'x_axis':'OBP','title':'OBP','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
46 |
+
'slg':{'x_axis':'SLG','title':'SLG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
47 |
+
'ops':{'x_axis':'OPS','title':'OPS','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
48 |
+
'k_percent':{'x_axis':'K%','title':'K%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
49 |
+
'bb_percent':{'x_axis':'BB%','title':'BB%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
50 |
+
'bb_over_k_percent':{'x_axis':'BB/K','title':'BB/K','flip_p':False,'decimal_format':'string_1','percent_adjust':100},
|
51 |
+
'bb_minus_k_percent':{'x_axis':'BB%-K%','title':'BB%-K%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
52 |
+
'csw_percent':{'x_axis':'CSW%','title':'CSW%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
53 |
+
'woba_percent':{'x_axis':'wOBA','title':'wOBA','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
54 |
+
'hard_hit_percent':{'x_axis':'HardHit%','title':'HardHit%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
55 |
+
'barrel_percent':{'x_axis':'Barrel%','title':'Barrel%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
56 |
+
'zone_contact_percent':{'x_axis':'Z-Contact%','title':'Z-Contact%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
57 |
+
'zone_swing_percent':{'x_axis':'Z-Swing%','title':'Z-Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
58 |
+
'zone_percent':{'x_axis':'Zone%','title':'Zone%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
59 |
+
'chase_percent':{'x_axis':'O-Swing%','title':'O-Swing%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
60 |
+
'chase_contact':{'x_axis':'O-Contact%','title':'O-Contact%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
61 |
+
'swing_percent':{'x_axis':'Swing%','title':'Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
62 |
+
'whiff_rate':{'x_axis':'Whiff%','title':'Whiff%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
63 |
+
'swstr_rate':{'x_axis':'SwStr%','title':'SwStr%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
64 |
+
}
|
65 |
+
|
66 |
+
batter_dict_stat_small = { 'sweet_spot_percent':'SweetSpot%',
|
67 |
+
'max_launch_speed':'Max Exit Velocity',
|
68 |
+
'launch_speed_90':'90th Percentile EV',
|
69 |
+
'launch_speed':'Exit Velocity',
|
70 |
+
'launch_angle':'Launch Angle',
|
71 |
+
'avg':'AVG',
|
72 |
+
'obp':'OBP',
|
73 |
+
'slg':'SLG',
|
74 |
+
'ops':'OPS',
|
75 |
+
'k_percent':'K%',
|
76 |
+
'bb_percent':'BB%',
|
77 |
+
'bb_over_k_percent':'BB/K',
|
78 |
+
'bb_minus_k_percent':'BB%-K%',
|
79 |
+
'csw_percent':'CSW%',
|
80 |
+
'woba_percent':'wOBA',
|
81 |
+
'hard_hit_percent':'HardHit%',
|
82 |
+
'barrel_percent':'Barrel%',
|
83 |
+
'zone_contact_percent':'Z-Contact%',
|
84 |
+
'zone_swing_percent':'Z-Swing%',
|
85 |
+
'zone_percent':'Zone%',
|
86 |
+
'chase_percent':'O-Swing%',
|
87 |
+
'chase_contact':'O-Contact%',
|
88 |
+
'swing_percent':'Swing%',
|
89 |
+
'whiff_rate':'Whiff%',
|
90 |
+
'swstr_rate':'SwStr%',
|
91 |
+
}
|
92 |
+
|
93 |
+
|
94 |
+
colour_palette = ['#FFB000','#648FFF','#785EF0',
|
95 |
+
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
|
96 |
+
|
97 |
+
level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A','ROK':'ROK'}
|
98 |
+
|
99 |
+
batter_test_df = exit_velo_df_codes_summ_batter.sort_values(by='batter').drop_duplicates(subset='batter_id').reset_index(drop=True)[['batter_id','batter']]#['pitcher'].to_dict()
|
100 |
+
batter_test_df = batter_test_df.set_index('batter_id')
|
101 |
+
|
102 |
+
|
103 |
+
def decimal_format_assign(x):
|
104 |
+
if x['decimal_format'] == 'percent_1':
|
105 |
+
return mtick.PercentFormatter(1,decimals=1)
|
106 |
+
if x['decimal_format'] == 'string_3':
|
107 |
+
return mtick.FormatStrFormatter('%.3f')
|
108 |
+
if x['decimal_format'] == 'string_0':
|
109 |
+
return mtick.FormatStrFormatter('%.0f')
|
110 |
+
if x['decimal_format'] == 'string_1':
|
111 |
+
return mtick.FormatStrFormatter('%.1f')
|
112 |
+
|
113 |
+
|
114 |
+
#test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])
|
115 |
+
|
116 |
+
batter_dict = batter_test_df['batter'].to_dict()
|
117 |
+
|
118 |
+
exit_velo_df_codes_summ_batter.position = exit_velo_df_codes_summ_batter.position.replace(['LF','RF','CF','TWP'],['OF','OF','OF','DH'])
|
119 |
+
exit_velo_df_codes_summ_non_level.position = exit_velo_df_codes_summ_non_level.position.replace(['LF','RF','CF','TWP'],['OF','OF','OF','DH'])
|
120 |
+
|
121 |
+
position_list = ['All'] + list(exit_velo_df_codes_summ_batter.position.unique())
|
122 |
+
team_list = ['All'] + sorted(list(exit_velo_df_codes_summ_batter.parent_org_abb.unique()))
|
123 |
+
|
124 |
+
|
125 |
+
|
126 |
+
def server(input,output,session):
|
127 |
+
|
128 |
+
|
129 |
+
@output
|
130 |
+
@render.plot(alt="A histogram")
|
131 |
+
def plot():
|
132 |
+
sns.set_theme(style="whitegrid", palette="pastel")
|
133 |
+
print(input.level_id())
|
134 |
+
print(input.n())
|
135 |
+
print('we made it here',input.team_id(),input.position_id())
|
136 |
+
if input.group_level():
|
137 |
+
data_df = exit_velo_df_codes_summ_non_level.copy()
|
138 |
+
|
139 |
+
turth_list = []
|
140 |
+
#turth_list_2 = []
|
141 |
+
for x in range(0,len(data_df.level)):
|
142 |
+
turth_list_2 = []
|
143 |
+
for y in range(0,len(data_df.level[x])):
|
144 |
+
#print(level_list[x][y])
|
145 |
+
turth_list_2.append(data_df.level[x][y] in input.level_id())
|
146 |
+
turth_list.append(turth_list_2)
|
147 |
+
|
148 |
+
final_check_list = [True if True in x else False for x in turth_list]
|
149 |
+
|
150 |
+
|
151 |
+
data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(final_check_list)]
|
152 |
+
|
153 |
+
|
154 |
+
else:
|
155 |
+
|
156 |
+
|
157 |
+
data_df = exit_velo_df_codes_summ_batter.copy()
|
158 |
+
data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(data_df.level.isin(input.level_id()))]
|
159 |
+
print(data_df)
|
160 |
+
if 'All' in input.team_id():
|
161 |
+
print('nice')#data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)
|
162 |
+
|
163 |
+
else:
|
164 |
+
data_df = data_df[(data_df.parent_org_abb.isin(input.team_id()))].reset_index(drop=True)
|
165 |
+
|
166 |
+
if 'All' in input.position_id():
|
167 |
+
print('nice')#data_df = data_df[(data_df.level.isin(input.level_id()))&(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)
|
168 |
+
|
169 |
+
else:
|
170 |
+
data_df = data_df[(data_df.position.isin(input.position_id()))].reset_index(drop=True)
|
171 |
+
|
172 |
+
|
173 |
+
#print('we made it here')
|
174 |
+
print(data_df)
|
175 |
+
data_df = data_df.sort_values(by='level').reset_index(drop=True)
|
176 |
+
print(batter_dict_stat[input.stat_x()]['flip_p'])
|
177 |
+
|
178 |
+
|
179 |
+
|
180 |
+
x_flip = batter_dict_stat[input.stat_x()]['flip_p']
|
181 |
+
y_flip = batter_dict_stat[input.stat_y()]['flip_p']
|
182 |
+
cbr_flip = batter_dict_stat[input.stat_z()]['flip_p']
|
183 |
+
|
184 |
+
|
185 |
+
|
186 |
+
data_df[input.stat_x()+'_percent'] = data_df[input.stat_x()].rank(pct=True,ascending=abs(x_flip-1))
|
187 |
+
|
188 |
+
data_df[input.stat_y()+'_percent'] = data_df[input.stat_y()].rank(pct=True,ascending=abs(y_flip-1))
|
189 |
+
|
190 |
+
data_df[input.stat_z()+'_percent'] = data_df[input.stat_z()].rank(pct=True,ascending=abs(cbr_flip-1))
|
191 |
+
|
192 |
+
|
193 |
+
|
194 |
+
fig, ax = plt.subplots(1, 1, figsize=(9, 9))
|
195 |
+
|
196 |
+
#data_df['bb_over_obp'] = data_df['bb']/data_df['k']
|
197 |
+
|
198 |
+
#data_df[input.stat_z()]= data_df[input.stat_z()].fillna(-100000)
|
199 |
+
|
200 |
+
|
201 |
+
if cbr_flip:
|
202 |
+
cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[0],colour_palette[3],colour_palette[1]])
|
203 |
+
norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())
|
204 |
+
|
205 |
+
else:
|
206 |
+
cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],colour_palette[3],colour_palette[0]])
|
207 |
+
norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())
|
208 |
+
|
209 |
+
sm = plt.cm.ScalarMappable(cmap=cmap_hue, norm=norm)
|
210 |
+
print('we made it here')
|
211 |
+
|
212 |
+
# sns.regplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[6],ax=ax,scatter=False,
|
213 |
+
# line_kws=dict(alpha=0.3,linewidth=2,zorder=1))
|
214 |
+
# scatter_plot = sns.scatterplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[0],ax=ax,hue=stat_z,palette=cmap_hue)
|
215 |
+
|
216 |
+
|
217 |
+
|
218 |
+
# r, p = sp.stats.pearsonr(data_df[input.stat_x()], data_df[input.stat_y()])
|
219 |
+
# ax = plt.gca()
|
220 |
+
# # ax.text(.25, 0.3, 'r={:.2f}, p={:.2g}'.format(r, p),
|
221 |
+
# # transform=ax.transAxes, fontsize=12)
|
222 |
+
|
223 |
+
# ax.annotate('R²={:.2f}'.format(r, p), ( math.ceil(data_df[input.stat_x()].max()*batter_dict_stat[input.stat_x()]['percent_adjust']/5)*5/batter_dict_stat[input.stat_x()]['percent_adjust']*(1-batter_dict_stat[input.stat_x()]['flip_p']),
|
224 |
+
# math.floor(data_df[input.stat_y()].min()*batter_dict_stat[input.stat_y()]['percent_adjust']/5)*5/batter_dict_stat[input.stat_y()]['percent_adjust']*(1-batter_dict_stat[input.stat_y()]['flip_p'])),
|
225 |
+
# fontsize=18,fontname='Century Gothic',ha='right')
|
226 |
+
|
227 |
+
if input.group_level():
|
228 |
+
scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3')
|
229 |
+
#ax.get_legend().remove()
|
230 |
+
scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue)
|
231 |
+
else:
|
232 |
+
scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3',style='level')
|
233 |
+
#ax.get_legend().remove()
|
234 |
+
scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue,style='level')
|
235 |
+
sns.set_theme(style="whitegrid", palette="pastel")
|
236 |
+
|
237 |
+
fig.set_facecolor('#F0F0F0')
|
238 |
+
ax.set_facecolor('white')
|
239 |
+
|
240 |
+
print('we made it here')
|
241 |
+
# for i in range(0,len(pitch_group_unique)):
|
242 |
+
# data_df = elly_zone_df[elly_zone_df.pitch_group==pitch_group_unique[i]]
|
243 |
+
# len_df.append(len(data_df))
|
244 |
+
# sns.lineplot(x=range(1,len(data_df)+1),y=data_df.swings.rolling(window=rolling_window_input).sum()/data_df.pitches.rolling(window=rolling_window_input).sum(),color=colour_palette[i],linewidth=3,ax=ax,
|
245 |
+
# label=f'{pitch_group_unique[i]} (Season Average {float(data_df.swings.sum()/data_df.pitches.sum()):.1%})',zorder=i+10)
|
246 |
+
# ax.hlines(xmin=0,xmax=len(elly_zone_df),y=data_df.swings.sum()/data_df.pitches.sum(),color=colour_palette[i],linewidth=3,linestyle='-.',alpha=0.4,zorder=i)
|
247 |
+
|
248 |
+
ts=[]
|
249 |
+
print(input.player_id())
|
250 |
+
|
251 |
+
print(len(data_df))
|
252 |
+
if input.names():
|
253 |
+
for i in range(len(data_df)):
|
254 |
+
if (data_df[input.stat_x()+'_percent'].values[i] < input.n_percent_bot_x() or data_df[input.stat_x()+'_percent'].values[i] > 1 - input.n_percent_top_x() ) \
|
255 |
+
or (data_df[input.stat_y()+'_percent'].values[i] < input.n_percent_bot_y() or data_df[input.stat_y()+'_percent'].values[i] > 1 -input.n_percent_top_y()) \
|
256 |
+
or (data_df[input.stat_z()+'_percent'].values[i] < input.n_percent_bot_z() or data_df[input.stat_z()+'_percent'].values[i] > 1 -input.n_percent_top_z() )\
|
257 |
+
or (str(data_df.batter_id[i]) in (input.player_id())):
|
258 |
+
# print(data_df.batter[i])
|
259 |
+
# ax.annotate(data_df.batter[i], xy=((data_df[input.stat_x()][i])+0.025/batter_dict_stat[input.stat_x()]['percent_adjust'], data_df[input.stat_y()][i]+0.01/batter_dict_stat[input.stat_x()]['percent_adjust']), xytext=(-20,20),
|
260 |
+
# textcoords='offset points', ha='center', va='bottom',fontsize=7,
|
261 |
+
# bbox=dict(boxstyle='round,pad=0', fc=colour_palette[6], alpha=0.0),
|
262 |
+
# arrowprops=dict(arrowstyle='->', connectionstyle="angle,angleA=-90,angleB=-10,rad=2",
|
263 |
+
# color=colour_palette[8]))
|
264 |
+
|
265 |
+
#if data_df['batter'][i] != 'Jo Adell':
|
266 |
+
# ax.annotate(data_df.batter[i], (data_df[input.stat_x()][i]-len(data_df.batter[i])*0.00025, data_df[input.stat_y()][i]+0.001),fontsize=8)
|
267 |
+
ts.append(ax.text(data_df[input.stat_x()][i], data_df[input.stat_y()][i], data_df.batter[i],fontsize=8))
|
268 |
+
|
269 |
+
|
270 |
+
|
271 |
+
ax.hlines(xmin=(math.floor((data_df[input.stat_x()].min()*batter_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'],
|
272 |
+
xmax= (math.ceil((data_df[input.stat_x()].max()*batter_dict_stat[input.stat_x()]['percent_adjust']+0.01)/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'],
|
273 |
+
y=data_df[input.stat_y()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
|
274 |
+
|
275 |
+
print('we made it here')
|
276 |
+
|
277 |
+
ax.vlines(ymin=(math.floor((data_df[input.stat_y()].min()*batter_dict_stat[input.stat_y()]['percent_adjust']-0.01)/5))*5/batter_dict_stat[input.stat_y()]['percent_adjust'],
|
278 |
+
ymax= (math.ceil((data_df[input.stat_y()].max()*batter_dict_stat[input.stat_y()]['percent_adjust']+0.01)/5))*5/batter_dict_stat[input.stat_y()]['percent_adjust'],
|
279 |
+
x=data_df[input.stat_x()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
|
280 |
+
|
281 |
+
print(data_df[input.stat_x()].min())
|
282 |
+
print(batter_dict_stat[input.stat_x()]['percent_adjust'])
|
283 |
+
print((math.floor((data_df[input.stat_x()].min()*batter_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'])
|
284 |
+
|
285 |
+
|
286 |
+
ax.set_xlim((math.floor((data_df[input.stat_x()].min()*batter_dict_stat[input.stat_x()]['percent_adjust'])/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'],
|
287 |
+
(math.ceil((data_df[input.stat_x()].max()*batter_dict_stat[input.stat_x()]['percent_adjust'])/5))*5/batter_dict_stat[input.stat_x()]['percent_adjust'])
|
288 |
+
|
289 |
+
|
290 |
+
ax.set_ylim((math.floor((data_df[input.stat_y()].min()*batter_dict_stat[input.stat_y()]['percent_adjust'])/5))*5/batter_dict_stat[input.stat_y()]['percent_adjust'],
|
291 |
+
(math.ceil((data_df[input.stat_y()].max()*batter_dict_stat[input.stat_y()]['percent_adjust'])/5))*5/batter_dict_stat[input.stat_y()]['percent_adjust'])
|
292 |
+
|
293 |
+
|
294 |
+
|
295 |
+
title_level = str([x .strip("\'")for x in input.level_id()]).strip('[').strip(']').replace("'",'')
|
296 |
+
|
297 |
+
if title_level == 'AAA, AA, A+, A':
|
298 |
+
title_level='MiLB'
|
299 |
+
#title_level = input.level_id()[0]
|
300 |
+
if input.n_age() >= 50:
|
301 |
+
title_spot = f'{title_level} Batter {batter_dict_stat[input.stat_y()]["title"]} vs {batter_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA)'
|
302 |
+
|
303 |
+
else:
|
304 |
+
title_spot = f'{title_level} Batter {batter_dict_stat[input.stat_y()]["title"]} vs {batter_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA, Max Age {input.n_age()})'
|
305 |
+
|
306 |
+
ax.set_title(title_spot, fontsize=24/(len(title_spot)*0.03),fontname='Century Gothic')
|
307 |
+
# #vals = ax.get_yticks()
|
308 |
+
ax.set_xlabel(batter_dict_stat[input.stat_x()]['x_axis'], fontsize=16,fontname='Century Gothic')
|
309 |
+
ax.set_ylabel(batter_dict_stat[input.stat_y()]['x_axis'], fontsize=16,fontname='Century Gothic')
|
310 |
+
|
311 |
+
|
312 |
+
if input.group_level():
|
313 |
+
ax.get_legend().remove()
|
314 |
+
|
315 |
+
if not input.group_level():
|
316 |
+
if len(input.level_id()) > 1:
|
317 |
+
h,l = scatter.get_legend_handles_labels()
|
318 |
+
l[-(len(input.level_id())+1)] = 'Level'
|
319 |
+
ax.legend(h[-(len(input.level_id())+1):],l[-(len(input.level_id())+1):], borderaxespad=0.1,loc=0)
|
320 |
+
|
321 |
+
else:
|
322 |
+
ax.get_legend().remove()
|
323 |
+
|
324 |
+
#plt.show(g)
|
325 |
+
# ax.figure.colorbar(sm, ax=ax)
|
326 |
+
|
327 |
+
cbar = ax.figure.colorbar(sm, ax=ax,format=decimal_format_assign(x=batter_dict_stat[input.stat_z()]),orientation='vertical',aspect=30)
|
328 |
+
cbar.set_label(batter_dict_stat[input.stat_z()]['x_axis'])
|
329 |
+
#fig.axes[0].invert_yaxis()
|
330 |
+
print('we made it here5')
|
331 |
+
fig.subplots_adjust(wspace=.02, hspace=.02)
|
332 |
+
# ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
|
333 |
+
#ax.set_yticks([0,0.1,0.2,0.3,0.4,0.5])
|
334 |
+
# fig.colorbar(plot_dist, ax=ax)
|
335 |
+
# fig.colorbar(plot_dist)
|
336 |
+
|
337 |
+
if batter_dict_stat[input.stat_x()]['flip_p']:
|
338 |
+
fig.axes[0].invert_xaxis()
|
339 |
+
|
340 |
+
if batter_dict_stat[input.stat_y()]['flip_p']:
|
341 |
+
fig.axes[0].invert_yaxis()
|
342 |
+
|
343 |
+
|
344 |
+
# ax.xaxis.set_major_formatter(mtick.PercentFormatter(1,decimals=0))
|
345 |
+
# ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
|
346 |
+
|
347 |
+
|
348 |
+
|
349 |
+
|
350 |
+
|
351 |
+
print('we made it here6')
|
352 |
+
|
353 |
+
ax.xaxis.set_major_formatter(decimal_format_assign(x=batter_dict_stat[input.stat_x()]))
|
354 |
+
ax.yaxis.set_major_formatter(decimal_format_assign(x=batter_dict_stat[input.stat_y()]))
|
355 |
+
|
356 |
+
|
357 |
+
print('we made it here7')
|
358 |
+
# ax.text(0.5, 0.5, '/u/tomstoms', transform=ax.transAxes,
|
359 |
+
# fontsize=60, color='gray', alpha=0.075,
|
360 |
+
# ha='center', va='center', rotation=45)
|
361 |
+
|
362 |
+
print(ts)
|
363 |
+
if len(ts) > 0:
|
364 |
+
adjust_text(ts,
|
365 |
+
arrowprops=dict(arrowstyle="-", color=colour_palette[4], lw=1),ax=ax)
|
366 |
+
|
367 |
+
#ax.legend(fontsize='16')
|
368 |
+
fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
|
369 |
+
fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
|
370 |
+
fig.tight_layout()
|
371 |
+
#matplotlib.rcParams["figure.dpi"] = 600
|
372 |
+
#plt.show()
|
373 |
+
|
374 |
+
|
375 |
+
rolling_pitcher = App(ui.page_fluid(
|
376 |
+
ui.tags.base(href=base_url),
|
377 |
+
ui.tags.div(
|
378 |
+
{"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
|
379 |
+
ui.tags.style(
|
380 |
+
"""
|
381 |
+
h4 {
|
382 |
+
margin-top: 1em;font-size:35px;
|
383 |
+
}
|
384 |
+
h2{
|
385 |
+
font-size:25px;
|
386 |
+
}
|
387 |
+
"""
|
388 |
+
),
|
389 |
+
shinyswatch.theme.simplex(),
|
390 |
+
ui.tags.h4("TJStats"),
|
391 |
+
ui.tags.i("Baseball Analytics and Visualizations"),
|
392 |
+
ui.navset_tab(
|
393 |
+
ui.nav_control(
|
394 |
+
ui.a(
|
395 |
+
"Home",
|
396 |
+
href="home/"
|
397 |
+
),
|
398 |
+
),
|
399 |
+
ui.nav_menu(
|
400 |
+
"Batter Charts",
|
401 |
+
ui.nav_control(
|
402 |
+
ui.a(
|
403 |
+
"Spray",
|
404 |
+
href="spray/"
|
405 |
+
),
|
406 |
+
ui.a(
|
407 |
+
"Decision Value",
|
408 |
+
href="decision_value/"
|
409 |
+
),
|
410 |
+
ui.a(
|
411 |
+
"Damage Model",
|
412 |
+
href="damage_model/"
|
413 |
+
),
|
414 |
+
ui.a(
|
415 |
+
"Batter Scatter",
|
416 |
+
href="batter_scatter/"
|
417 |
+
),
|
418 |
+
ui.a(
|
419 |
+
"EV vs LA Plot",
|
420 |
+
href="ev_angle/"
|
421 |
+
)
|
422 |
+
),
|
423 |
+
),
|
424 |
+
ui.nav_menu(
|
425 |
+
"Goalie Charts",
|
426 |
+
ui.nav_control(
|
427 |
+
ui.a(
|
428 |
+
"GSAx Timeline",
|
429 |
+
href="gsax-timeline/"
|
430 |
+
),
|
431 |
+
ui.a(
|
432 |
+
"GSAx Leaderboard",
|
433 |
+
href="gsax-leaderboard/"
|
434 |
+
),
|
435 |
+
ui.a(
|
436 |
+
"GSAx Comparison",
|
437 |
+
href="gsax-comparison/"
|
438 |
+
)
|
439 |
+
),
|
440 |
+
),ui.nav_menu(
|
441 |
+
"Team Charts",
|
442 |
+
ui.nav_control(
|
443 |
+
ui.a(
|
444 |
+
"Team xG Rates",
|
445 |
+
href="team-xg-rates/"
|
446 |
+
),
|
447 |
+
),
|
448 |
+
),ui.nav_control(
|
449 |
+
ui.a(
|
450 |
+
"Games",
|
451 |
+
href="games/"
|
452 |
+
),
|
453 |
+
),ui.nav_control(
|
454 |
+
ui.a(
|
455 |
+
"About",
|
456 |
+
href="about/"
|
457 |
+
),
|
458 |
+
),ui.nav_control(
|
459 |
+
ui.a(
|
460 |
+
"Articles",
|
461 |
+
href="articles/"
|
462 |
+
),
|
463 |
+
)),ui.row(
|
464 |
+
ui.layout_sidebar(
|
465 |
+
|
466 |
+
|
467 |
+
|
468 |
+
ui.panel_sidebar(
|
469 |
+
#ui.input_select("id", "Select Batter",batter_dict,selected=675911,width=1,size=1),
|
470 |
+
ui.row(
|
471 |
+
ui.column(4,ui.input_select("level_id", "Select Level",level_dict,width=1,size=1,multiple=True,selected='MLB',selectize=True),),
|
472 |
+
ui.column(4,ui.input_select("team_id", "Select Team",team_list,width=1,size=1,multiple=True,selected='All',selectize=True),),
|
473 |
+
ui.column(4,ui.input_select("position_id", "Select Position",position_list,width=1,size=1,selected='All',multiple=True,selectize=True))),
|
474 |
+
ui.row(
|
475 |
+
ui.column(6,ui.input_numeric("n", "Minimum PA", value=100)),
|
476 |
+
ui.column(6,ui.input_numeric("n_age", "Maximum Age", value=50))),
|
477 |
+
ui.row(
|
478 |
+
ui.column(4,ui.input_select("stat_x", "X-Axis",batter_dict_stat_small,selected='k_percent',width=1,size=1)),
|
479 |
+
ui.column(4,ui.input_select("stat_y", "Y-Axis",batter_dict_stat_small,selected='bb_percent',width=1,size=1)),
|
480 |
+
ui.column(4,ui.input_select("stat_z", "Colour-Bar Axis",batter_dict_stat_small,selected='bb_over_k_percent',width=1,size=1))),
|
481 |
+
|
482 |
+
ui.row(
|
483 |
+
ui.column(6,ui.input_numeric("n_percent_top_x", "Top 'n' Percentile X-Labels", value=0.01)),
|
484 |
+
ui.column(6,ui.input_numeric("n_percent_bot_x", "Bottom 'n' Percentile X-Labels", value=0.01))),
|
485 |
+
ui.row(
|
486 |
+
ui.column(6,ui.input_numeric("n_percent_top_y", "Top 'n' Percentile Y-Labels", value=0.01)),
|
487 |
+
ui.column(6,ui.input_numeric("n_percent_bot_y", "Bottom 'n' Percentile Y-Labels", value=0.01))),
|
488 |
+
ui.row(
|
489 |
+
ui.column(6,ui.input_numeric("n_percent_top_z", "Top 'n' Percentile Z-Labels", value=0.01)),
|
490 |
+
ui.column(6,ui.input_numeric("n_percent_bot_z", "Bottom 'n' Percentile Z-Labels", value=0.01))),
|
491 |
+
|
492 |
+
ui.input_select("player_id", "Label Player",batter_dict,width=1,size=1,multiple=True,selectize=True),
|
493 |
+
ui.row(
|
494 |
+
ui.input_switch("names", "Toggle Names"),
|
495 |
+
ui.input_switch("group_level", "Group Levels")),
|
496 |
+
),
|
497 |
+
|
498 |
+
ui.panel_main(
|
499 |
+
ui.output_plot("plot",height = "1000px",width="1000px")
|
500 |
+
,
|
501 |
+
),
|
502 |
+
)),)),server)
|
chadwick_df.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
damage.py
CHANGED
@@ -165,6 +165,7 @@ def server(input,output,session):
|
|
165 |
|
166 |
@output
|
167 |
@render.plot(alt="hex_plot")
|
|
|
168 |
def hex_plot():
|
169 |
|
170 |
if input.batter_id() is "":
|
@@ -391,6 +392,7 @@ def server(input,output,session):
|
|
391 |
|
392 |
@output
|
393 |
@render.plot(alt="roll_plot")
|
|
|
394 |
def roll_plot():
|
395 |
# player_select = 'Nolan Gorman'
|
396 |
# player_select_full =player_select
|
@@ -505,6 +507,7 @@ damage = App(ui.page_fluid(
|
|
505 |
shinyswatch.theme.simplex(),
|
506 |
ui.tags.h4("TJStats"),
|
507 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
|
|
508 |
ui.navset_tab(
|
509 |
ui.nav_control(
|
510 |
ui.a(
|
@@ -515,7 +518,11 @@ damage = App(ui.page_fluid(
|
|
515 |
ui.nav_menu(
|
516 |
"Batter Charts",
|
517 |
ui.nav_control(
|
518 |
-
|
|
|
|
|
|
|
|
|
519 |
"Spray",
|
520 |
href="spray/"
|
521 |
),
|
@@ -534,48 +541,29 @@ damage = App(ui.page_fluid(
|
|
534 |
ui.a(
|
535 |
"EV vs LA Plot",
|
536 |
href="ev_angle/"
|
|
|
|
|
|
|
|
|
537 |
)
|
538 |
),
|
539 |
),
|
540 |
ui.nav_menu(
|
541 |
-
"
|
542 |
ui.nav_control(
|
543 |
ui.a(
|
544 |
-
"
|
545 |
-
href="
|
546 |
),
|
547 |
ui.a(
|
548 |
-
"
|
549 |
-
href="
|
550 |
),
|
551 |
ui.a(
|
552 |
-
"
|
553 |
-
href="
|
554 |
)
|
555 |
),
|
556 |
-
),ui.nav_menu(
|
557 |
-
"Team Charts",
|
558 |
-
ui.nav_control(
|
559 |
-
ui.a(
|
560 |
-
"Team xG Rates",
|
561 |
-
href="team-xg-rates/"
|
562 |
-
),
|
563 |
-
),
|
564 |
-
),ui.nav_control(
|
565 |
-
ui.a(
|
566 |
-
"Games",
|
567 |
-
href="games/"
|
568 |
-
),
|
569 |
-
),ui.nav_control(
|
570 |
-
ui.a(
|
571 |
-
"About",
|
572 |
-
href="about/"
|
573 |
-
),
|
574 |
-
),ui.nav_control(
|
575 |
-
ui.a(
|
576 |
-
"Articles",
|
577 |
-
href="articles/"
|
578 |
-
),
|
579 |
)),ui.row(
|
580 |
ui.layout_sidebar(
|
581 |
|
@@ -593,7 +581,8 @@ damage = App(ui.page_fluid(
|
|
593 |
ui.input_numeric("rolling_window",
|
594 |
"Select Rolling Window",
|
595 |
value=50,
|
596 |
-
min=1)
|
|
|
597 |
|
598 |
ui.panel_main(
|
599 |
ui.navset_tab(
|
|
|
165 |
|
166 |
@output
|
167 |
@render.plot(alt="hex_plot")
|
168 |
+
@reactive.event(input.go, ignore_none=False)
|
169 |
def hex_plot():
|
170 |
|
171 |
if input.batter_id() is "":
|
|
|
392 |
|
393 |
@output
|
394 |
@render.plot(alt="roll_plot")
|
395 |
+
@reactive.event(input.go, ignore_none=False)
|
396 |
def roll_plot():
|
397 |
# player_select = 'Nolan Gorman'
|
398 |
# player_select_full =player_select
|
|
|
507 |
shinyswatch.theme.simplex(),
|
508 |
ui.tags.h4("TJStats"),
|
509 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
510 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
511 |
ui.navset_tab(
|
512 |
ui.nav_control(
|
513 |
ui.a(
|
|
|
518 |
ui.nav_menu(
|
519 |
"Batter Charts",
|
520 |
ui.nav_control(
|
521 |
+
ui.a(
|
522 |
+
"Batting Rolling",
|
523 |
+
href="rolling_batter/"
|
524 |
+
),
|
525 |
+
ui.a(
|
526 |
"Spray",
|
527 |
href="spray/"
|
528 |
),
|
|
|
541 |
ui.a(
|
542 |
"EV vs LA Plot",
|
543 |
href="ev_angle/"
|
544 |
+
),
|
545 |
+
ui.a(
|
546 |
+
"Statcast Compare",
|
547 |
+
href="statcast_compare/"
|
548 |
)
|
549 |
),
|
550 |
),
|
551 |
ui.nav_menu(
|
552 |
+
"Pitcher Charts",
|
553 |
ui.nav_control(
|
554 |
ui.a(
|
555 |
+
"Pitcher Rolling",
|
556 |
+
href="rolling_pitcher/"
|
557 |
),
|
558 |
ui.a(
|
559 |
+
"Pitcher Summary",
|
560 |
+
href="pitching_summary_graphic_new/"
|
561 |
),
|
562 |
ui.a(
|
563 |
+
"Pitcher Scatter",
|
564 |
+
href="pitcher_scatter/"
|
565 |
)
|
566 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
567 |
)),ui.row(
|
568 |
ui.layout_sidebar(
|
569 |
|
|
|
581 |
ui.input_numeric("rolling_window",
|
582 |
"Select Rolling Window",
|
583 |
value=50,
|
584 |
+
min=1),
|
585 |
+
ui.input_action_button("go", "Generate",class_="btn-primary")),
|
586 |
|
587 |
ui.panel_main(
|
588 |
ui.navset_tab(
|
decision_value.py
CHANGED
@@ -146,6 +146,7 @@ def server(input,output,session):
|
|
146 |
|
147 |
@output
|
148 |
@render.plot(alt="hex_plot")
|
|
|
149 |
def scatter_plot():
|
150 |
|
151 |
if input.batter_id() is "":
|
@@ -264,6 +265,7 @@ def server(input,output,session):
|
|
264 |
|
265 |
@output
|
266 |
@render.plot(alt="hex_plot")
|
|
|
267 |
def dv_plot():
|
268 |
|
269 |
if input.batter_id() is "":
|
@@ -357,6 +359,7 @@ def server(input,output,session):
|
|
357 |
|
358 |
@output
|
359 |
@render.plot(alt="hex_plot")
|
|
|
360 |
def iz_plot():
|
361 |
|
362 |
if input.batter_id() is "":
|
@@ -450,6 +453,7 @@ def server(input,output,session):
|
|
450 |
|
451 |
@output
|
452 |
@render.plot(alt="hex_plot")
|
|
|
453 |
def oz_plot():
|
454 |
if input.batter_id() is "":
|
455 |
fig = plt.figure(figsize=(12, 12))
|
@@ -557,6 +561,7 @@ decision_value = App(ui.page_fluid(
|
|
557 |
shinyswatch.theme.simplex(),
|
558 |
ui.tags.h4("TJStats"),
|
559 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
|
|
560 |
ui.navset_tab(
|
561 |
ui.nav_control(
|
562 |
ui.a(
|
@@ -567,7 +572,11 @@ decision_value = App(ui.page_fluid(
|
|
567 |
ui.nav_menu(
|
568 |
"Batter Charts",
|
569 |
ui.nav_control(
|
570 |
-
|
|
|
|
|
|
|
|
|
571 |
"Spray",
|
572 |
href="spray/"
|
573 |
),
|
@@ -586,48 +595,29 @@ decision_value = App(ui.page_fluid(
|
|
586 |
ui.a(
|
587 |
"EV vs LA Plot",
|
588 |
href="ev_angle/"
|
|
|
|
|
|
|
|
|
589 |
)
|
590 |
),
|
591 |
),
|
592 |
ui.nav_menu(
|
593 |
-
"
|
594 |
ui.nav_control(
|
595 |
ui.a(
|
596 |
-
"
|
597 |
-
href="
|
598 |
),
|
599 |
ui.a(
|
600 |
-
"
|
601 |
-
href="
|
602 |
),
|
603 |
ui.a(
|
604 |
-
"
|
605 |
-
href="
|
606 |
)
|
607 |
),
|
608 |
-
),ui.nav_menu(
|
609 |
-
"Team Charts",
|
610 |
-
ui.nav_control(
|
611 |
-
ui.a(
|
612 |
-
"Team xG Rates",
|
613 |
-
href="team-xg-rates/"
|
614 |
-
),
|
615 |
-
),
|
616 |
-
),ui.nav_control(
|
617 |
-
ui.a(
|
618 |
-
"Games",
|
619 |
-
href="games/"
|
620 |
-
),
|
621 |
-
),ui.nav_control(
|
622 |
-
ui.a(
|
623 |
-
"About",
|
624 |
-
href="about/"
|
625 |
-
),
|
626 |
-
),ui.nav_control(
|
627 |
-
ui.a(
|
628 |
-
"Articles",
|
629 |
-
href="articles/"
|
630 |
-
),
|
631 |
)),ui.row(
|
632 |
ui.layout_sidebar(
|
633 |
|
@@ -658,7 +648,9 @@ decision_value = App(ui.page_fluid(
|
|
658 |
ui.input_select("level_list",
|
659 |
"Select Level",
|
660 |
['MLB','AAA'],
|
661 |
-
selected='MLB')
|
|
|
|
|
662 |
|
663 |
ui.panel_main(
|
664 |
ui.navset_tab(
|
|
|
146 |
|
147 |
@output
|
148 |
@render.plot(alt="hex_plot")
|
149 |
+
@reactive.event(input.go, ignore_none=False)
|
150 |
def scatter_plot():
|
151 |
|
152 |
if input.batter_id() is "":
|
|
|
265 |
|
266 |
@output
|
267 |
@render.plot(alt="hex_plot")
|
268 |
+
@reactive.event(input.go, ignore_none=False)
|
269 |
def dv_plot():
|
270 |
|
271 |
if input.batter_id() is "":
|
|
|
359 |
|
360 |
@output
|
361 |
@render.plot(alt="hex_plot")
|
362 |
+
@reactive.event(input.go, ignore_none=False)
|
363 |
def iz_plot():
|
364 |
|
365 |
if input.batter_id() is "":
|
|
|
453 |
|
454 |
@output
|
455 |
@render.plot(alt="hex_plot")
|
456 |
+
@reactive.event(input.go, ignore_none=False)
|
457 |
def oz_plot():
|
458 |
if input.batter_id() is "":
|
459 |
fig = plt.figure(figsize=(12, 12))
|
|
|
561 |
shinyswatch.theme.simplex(),
|
562 |
ui.tags.h4("TJStats"),
|
563 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
564 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
565 |
ui.navset_tab(
|
566 |
ui.nav_control(
|
567 |
ui.a(
|
|
|
572 |
ui.nav_menu(
|
573 |
"Batter Charts",
|
574 |
ui.nav_control(
|
575 |
+
ui.a(
|
576 |
+
"Batting Rolling",
|
577 |
+
href="rolling_batter/"
|
578 |
+
),
|
579 |
+
ui.a(
|
580 |
"Spray",
|
581 |
href="spray/"
|
582 |
),
|
|
|
595 |
ui.a(
|
596 |
"EV vs LA Plot",
|
597 |
href="ev_angle/"
|
598 |
+
),
|
599 |
+
ui.a(
|
600 |
+
"Statcast Compare",
|
601 |
+
href="statcast_compare/"
|
602 |
)
|
603 |
),
|
604 |
),
|
605 |
ui.nav_menu(
|
606 |
+
"Pitcher Charts",
|
607 |
ui.nav_control(
|
608 |
ui.a(
|
609 |
+
"Pitcher Rolling",
|
610 |
+
href="rolling_pitcher/"
|
611 |
),
|
612 |
ui.a(
|
613 |
+
"Pitcher Summary",
|
614 |
+
href="pitching_summary_graphic_new/"
|
615 |
),
|
616 |
ui.a(
|
617 |
+
"Pitcher Scatter",
|
618 |
+
href="pitcher_scatter/"
|
619 |
)
|
620 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
621 |
)),ui.row(
|
622 |
ui.layout_sidebar(
|
623 |
|
|
|
648 |
ui.input_select("level_list",
|
649 |
"Select Level",
|
650 |
['MLB','AAA'],
|
651 |
+
selected='MLB'),
|
652 |
+
ui.input_action_button("go", "Generate",class_="btn-primary"),
|
653 |
+
),
|
654 |
|
655 |
ui.panel_main(
|
656 |
ui.navset_tab(
|
ev_angle.py
CHANGED
@@ -60,6 +60,7 @@ angle_ranges = list(range(8,51))
|
|
60 |
def server(input,output,session):
|
61 |
@output
|
62 |
@render.plot(alt="A histogram")
|
|
|
63 |
def plot():
|
64 |
data_df = exit_velo_df[exit_velo_df.batter_id==int(input.id())]
|
65 |
#pitch_list = exit_velo_df_small.pitch_type.unique()
|
@@ -182,6 +183,7 @@ ev_angle = App(ui.page_fluid(
|
|
182 |
shinyswatch.theme.simplex(),
|
183 |
ui.tags.h4("TJStats"),
|
184 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
|
|
185 |
ui.navset_tab(
|
186 |
ui.nav_control(
|
187 |
ui.a(
|
@@ -192,7 +194,11 @@ ev_angle = App(ui.page_fluid(
|
|
192 |
ui.nav_menu(
|
193 |
"Batter Charts",
|
194 |
ui.nav_control(
|
195 |
-
|
|
|
|
|
|
|
|
|
196 |
"Spray",
|
197 |
href="spray/"
|
198 |
),
|
@@ -211,48 +217,29 @@ ev_angle = App(ui.page_fluid(
|
|
211 |
ui.a(
|
212 |
"EV vs LA Plot",
|
213 |
href="ev_angle/"
|
|
|
|
|
|
|
|
|
214 |
)
|
215 |
),
|
216 |
),
|
217 |
ui.nav_menu(
|
218 |
-
"
|
219 |
ui.nav_control(
|
220 |
ui.a(
|
221 |
-
"
|
222 |
-
href="
|
223 |
),
|
224 |
ui.a(
|
225 |
-
"
|
226 |
-
href="
|
227 |
),
|
228 |
ui.a(
|
229 |
-
"
|
230 |
-
href="
|
231 |
)
|
232 |
),
|
233 |
-
),ui.nav_menu(
|
234 |
-
"Team Charts",
|
235 |
-
ui.nav_control(
|
236 |
-
ui.a(
|
237 |
-
"Team xG Rates",
|
238 |
-
href="team-xg-rates/"
|
239 |
-
),
|
240 |
-
),
|
241 |
-
),ui.nav_control(
|
242 |
-
ui.a(
|
243 |
-
"Games",
|
244 |
-
href="games/"
|
245 |
-
),
|
246 |
-
),ui.nav_control(
|
247 |
-
ui.a(
|
248 |
-
"About",
|
249 |
-
href="about/"
|
250 |
-
),
|
251 |
-
),ui.nav_control(
|
252 |
-
ui.a(
|
253 |
-
"Articles",
|
254 |
-
href="articles/"
|
255 |
-
),
|
256 |
)),ui.row(
|
257 |
ui.layout_sidebar(
|
258 |
|
@@ -261,8 +248,11 @@ ev_angle = App(ui.page_fluid(
|
|
261 |
|
262 |
ui.panel_sidebar(
|
263 |
ui.input_select("id", "Select Batter",batter_dict,width=1),
|
264 |
-
ui.input_select("plot_id", "Select Plot",{'scatter':'Scatter Plot','dist':'Distribution Plot'},width=1)
|
265 |
-
|
|
|
|
|
|
|
266 |
|
267 |
ui.panel_main(
|
268 |
ui.output_plot("plot",height = "1000px",width="1000px")
|
|
|
60 |
def server(input,output,session):
|
61 |
@output
|
62 |
@render.plot(alt="A histogram")
|
63 |
+
@reactive.event(input.go, ignore_none=False)
|
64 |
def plot():
|
65 |
data_df = exit_velo_df[exit_velo_df.batter_id==int(input.id())]
|
66 |
#pitch_list = exit_velo_df_small.pitch_type.unique()
|
|
|
183 |
shinyswatch.theme.simplex(),
|
184 |
ui.tags.h4("TJStats"),
|
185 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
186 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
187 |
ui.navset_tab(
|
188 |
ui.nav_control(
|
189 |
ui.a(
|
|
|
194 |
ui.nav_menu(
|
195 |
"Batter Charts",
|
196 |
ui.nav_control(
|
197 |
+
ui.a(
|
198 |
+
"Batting Rolling",
|
199 |
+
href="rolling_batter/"
|
200 |
+
),
|
201 |
+
ui.a(
|
202 |
"Spray",
|
203 |
href="spray/"
|
204 |
),
|
|
|
217 |
ui.a(
|
218 |
"EV vs LA Plot",
|
219 |
href="ev_angle/"
|
220 |
+
),
|
221 |
+
ui.a(
|
222 |
+
"Statcast Compare",
|
223 |
+
href="statcast_compare/"
|
224 |
)
|
225 |
),
|
226 |
),
|
227 |
ui.nav_menu(
|
228 |
+
"Pitcher Charts",
|
229 |
ui.nav_control(
|
230 |
ui.a(
|
231 |
+
"Pitcher Rolling",
|
232 |
+
href="rolling_pitcher/"
|
233 |
),
|
234 |
ui.a(
|
235 |
+
"Pitcher Summary",
|
236 |
+
href="pitching_summary_graphic_new/"
|
237 |
),
|
238 |
ui.a(
|
239 |
+
"Pitcher Scatter",
|
240 |
+
href="pitcher_scatter/"
|
241 |
)
|
242 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
243 |
)),ui.row(
|
244 |
ui.layout_sidebar(
|
245 |
|
|
|
248 |
|
249 |
ui.panel_sidebar(
|
250 |
ui.input_select("id", "Select Batter",batter_dict,width=1),
|
251 |
+
ui.input_select("plot_id", "Select Plot",{'scatter':'Scatter Plot','dist':'Distribution Plot'},width=1),
|
252 |
+
|
253 |
+
ui.input_action_button("go", "Generate",class_="btn-primary",
|
254 |
+
)),
|
255 |
+
|
256 |
|
257 |
ui.panel_main(
|
258 |
ui.output_plot("plot",height = "1000px",width="1000px")
|
home.py
CHANGED
@@ -28,6 +28,7 @@ home = App(ui.page_fluid(
|
|
28 |
shinyswatch.theme.simplex(),
|
29 |
ui.tags.h4("TJStats"),
|
30 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
|
|
31 |
ui.navset_tab(
|
32 |
ui.nav_control(
|
33 |
ui.a(
|
@@ -38,7 +39,11 @@ home = App(ui.page_fluid(
|
|
38 |
ui.nav_menu(
|
39 |
"Batter Charts",
|
40 |
ui.nav_control(
|
41 |
-
|
|
|
|
|
|
|
|
|
42 |
"Spray",
|
43 |
href="spray/"
|
44 |
),
|
@@ -57,46 +62,77 @@ home = App(ui.page_fluid(
|
|
57 |
ui.a(
|
58 |
"EV vs LA Plot",
|
59 |
href="ev_angle/"
|
|
|
|
|
|
|
|
|
60 |
)
|
61 |
),
|
62 |
),
|
63 |
ui.nav_menu(
|
64 |
-
"
|
65 |
ui.nav_control(
|
66 |
ui.a(
|
67 |
-
"
|
68 |
-
href="
|
69 |
),
|
70 |
ui.a(
|
71 |
-
"
|
72 |
-
href="
|
73 |
),
|
74 |
ui.a(
|
75 |
-
"
|
76 |
-
href="
|
77 |
)
|
78 |
),
|
79 |
-
),ui.
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
shinyswatch.theme.simplex(),
|
29 |
ui.tags.h4("TJStats"),
|
30 |
ui.tags.i("Baseball Analytics and Visualizations"),
|
31 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
32 |
ui.navset_tab(
|
33 |
ui.nav_control(
|
34 |
ui.a(
|
|
|
39 |
ui.nav_menu(
|
40 |
"Batter Charts",
|
41 |
ui.nav_control(
|
42 |
+
ui.a(
|
43 |
+
"Batting Rolling",
|
44 |
+
href="rolling_batter/"
|
45 |
+
),
|
46 |
+
ui.a(
|
47 |
"Spray",
|
48 |
href="spray/"
|
49 |
),
|
|
|
62 |
ui.a(
|
63 |
"EV vs LA Plot",
|
64 |
href="ev_angle/"
|
65 |
+
),
|
66 |
+
ui.a(
|
67 |
+
"Statcast Compare",
|
68 |
+
href="statcast_compare/"
|
69 |
)
|
70 |
),
|
71 |
),
|
72 |
ui.nav_menu(
|
73 |
+
"Pitcher Charts",
|
74 |
ui.nav_control(
|
75 |
ui.a(
|
76 |
+
"Pitcher Rolling",
|
77 |
+
href="rolling_pitcher/"
|
78 |
),
|
79 |
ui.a(
|
80 |
+
"Pitcher Summary",
|
81 |
+
href="pitching_summary_graphic_new/"
|
82 |
),
|
83 |
ui.a(
|
84 |
+
"Pitcher Scatter",
|
85 |
+
href="pitcher_scatter/"
|
86 |
)
|
87 |
),
|
88 |
+
)),ui.tags.h6(""),
|
89 |
+
ui.markdown(
|
90 |
+
"""
|
91 |
+
# Welcome to TJStats!
|
92 |
+
---
|
93 |
+
## Intro
|
94 |
+
Hello, my name is Thomas Nestico, and welcome to my Baseball Analytics and Visualizations Website, TJStats!
|
95 |
+
I am a Civil Engineering by trade, but I have a passion for data analytics and machine learning, specifically
|
96 |
+
with respect to sports. This site and all of my visualizations were created using Python and its vast array of packages.
|
97 |
+
The framework for this site and all of the apps is <a href='https://shiny.posit.co/py/'>Shiny Python</a><sup>1</sup>.
|
98 |
+
All sources of data are referenced on each visualization.
|
99 |
+
|
100 |
+
## Apps
|
101 |
+
This site hosts my Baseball Data Viz Apps from the 2023 season. Here is a summary of what is accessible:
|
102 |
+
|
103 |
+
* Batter Charts
|
104 |
+
- **Batter Rolling**: Rolling Stats Charts for MLB and MiLB batters
|
105 |
+
- **Spray**: Batted Ball Trajectory Distributions of MLB batters vs rest of league
|
106 |
+
- **Decision Value**: Scatter plot and Rolling Stats Chart for my <a href='https://medium.com/@thomasjamesnestico/modelling-batter-decision-value-dac74c55e20a'>Decision Value Model</a><sup>1</sup>
|
107 |
+
- **Damage Model**: Hexbin and Rolling Stat Chart for my Damage Model (Article TBD)
|
108 |
+
- **Batter Scatter**: Scatter Plots for MLB and MiLB batters
|
109 |
+
- **EV vs LA Plot**: Scatter plot visualizing a MLB Batter's Exit Velocities and Launch Angles
|
110 |
+
- **Statcast Compare**: Table which compares MLB Batter's Statcast metrics
|
111 |
+
<br>
|
112 |
+
* Pitcher Charts
|
113 |
+
- **Pitcher Rolling**: Rolling Stats Charts for MLB and MiLB pitchers
|
114 |
+
- **Pitcher Summary**: Pitching Summary for MLB pitchers for any date range during 2023
|
115 |
+
- **Pitcher Scatter**: Scatter Plots for MLB and MiLB pitchers
|
116 |
+
|
117 |
+
|
118 |
+
## Articles
|
119 |
+
I have published several articles on Medium covering different Baseball Analytics topics. In these articles I go through my methodology
|
120 |
+
when applying machine learning algorithms to create machine learning models to help better understand baseball concepts. Here is a list of
|
121 |
+
articles which I have published:
|
122 |
+
* <a href='https://medium.com/@thomasjamesnestico/modelling-tjstuff-d9a451765484'>Modelling tjStuff+</a><sup>1</sup>
|
123 |
+
* <a href='https://medium.com/@thomasjamesnestico/modelling-batter-decision-value-dac74c55e20a'>Modelling Batter Decision Value</a><sup>1</sup>
|
124 |
+
* <a href='https://medium.com/@thomasjamesnestico/classifying-mlb-pitch-zones-and-predicting-milb-zones-7e95cf308254'>Classifying MLB Pitch Zones and Predicting MiLB Zones</a><sup>1</sup>
|
125 |
+
|
126 |
+
|
127 |
+
## 2024 Apps
|
128 |
+
Thank you for checking out the site. If you would like to further support my endeavours in the Baseball Analytics sphere,
|
129 |
+
here are few places where you can support me and get up to date baseball coverage and Apps for the 2024 season.
|
130 |
+
|
131 |
+
* Patreon: https://www.patreon.com/tj_stats
|
132 |
+
* Twitter: https://twitter.com/TJStats
|
133 |
+
|
134 |
+
## About Me
|
135 |
+
Here are a few other sites to learn more about me:
|
136 |
+
* LinkedIn: https://www.linkedin.com/in/thomas-nestico-b66013173/
|
137 |
+
* GitHub: https://github.com/tnestico
|
138 |
+
"""))), None)
|
pitcher_scatter.py
ADDED
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
|
2 |
+
import datasets
|
3 |
+
from datasets import load_dataset
|
4 |
+
import pandas as pd
|
5 |
+
import numpy as np
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
import seaborn as sns
|
8 |
+
import numpy as np
|
9 |
+
from scipy.stats import gaussian_kde
|
10 |
+
import matplotlib
|
11 |
+
from matplotlib.ticker import MaxNLocator
|
12 |
+
from matplotlib.gridspec import GridSpec
|
13 |
+
from scipy.stats import zscore
|
14 |
+
import math
|
15 |
+
import matplotlib
|
16 |
+
from adjustText import adjust_text
|
17 |
+
import matplotlib.ticker as mtick
|
18 |
+
from shinywidgets import output_widget, render_widget
|
19 |
+
import pandas as pd
|
20 |
+
from configure import base_url
|
21 |
+
import shinyswatch
|
22 |
+
import inflect
|
23 |
+
from matplotlib.pyplot import text
|
24 |
+
|
25 |
+
|
26 |
+
exit_velo_df_codes_summ = pd.read_csv('summary_pitcher.csv',index_col=[0]).reset_index(drop=True)
|
27 |
+
exit_velo_df_codes_summ_non_level = pd.read_csv('summary_pitcher_level.csv',index_col=[0]).reset_index(drop=True)
|
28 |
+
|
29 |
+
exit_velo_df_codes_summ_non_level['levels'] = exit_velo_df_codes_summ_non_level.levels.str.split(', ')
|
30 |
+
|
31 |
+
exit_velo_df_codes_summ_non_level = exit_velo_df_codes_summ_non_level.rename(columns={'levels':'level'})
|
32 |
+
|
33 |
+
print(exit_velo_df_codes_summ.bb_minus_k_percent)
|
34 |
+
exit_velo_df_codes_summ.bb_minus_k_percent = -1*exit_velo_df_codes_summ.bb_minus_k_percent
|
35 |
+
exit_velo_df_codes_summ.bb_over_k_percent = exit_velo_df_codes_summ.k_percent/exit_velo_df_codes_summ.bb_percent
|
36 |
+
|
37 |
+
exit_velo_df_codes_summ_non_level.bb_minus_k_percent = -1*exit_velo_df_codes_summ_non_level.bb_minus_k_percent
|
38 |
+
exit_velo_df_codes_summ_non_level.bb_over_k_percent = exit_velo_df_codes_summ_non_level.k_percent/exit_velo_df_codes_summ_non_level.bb_percent
|
39 |
+
|
40 |
+
pitcher_dict_stat = { 'sweet_spot_percent':{'x_axis':'SweetSpot%','title':'SweetSpot%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
41 |
+
'max_launch_speed':{'x_axis':'Max Exit Velocity','title':'Max Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
|
42 |
+
'launch_speed_90':{'x_axis':'90th Percentile EV','title':'90th Percentile EV','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
|
43 |
+
'launch_speed':{'x_axis':'Exit Velocity','title':'Exit Velocity','flip_p':False,'decimal_format':'string_0','percent_adjust':1},
|
44 |
+
'launch_angle':{'x_axis':'Launch Angle','title':'Launch Angle','flip_p':False,'decimal_format':'string_0','percent_adjust':100},
|
45 |
+
'avg':{'x_axis':'AVG','title':'AVG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
46 |
+
'obp':{'x_axis':'OBP','title':'OBP','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
47 |
+
'slg':{'x_axis':'SLG','title':'SLG','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
48 |
+
'ops':{'x_axis':'OPS','title':'OPS','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
49 |
+
'k_percent':{'x_axis':'K%','title':'K%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
50 |
+
'bb_percent':{'x_axis':'BB%','title':'BB%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
51 |
+
'bb_over_k_percent':{'x_axis':'K/BB','title':'K/BB','flip_p':True,'decimal_format':'string_1','percent_adjust':100},
|
52 |
+
'bb_minus_k_percent':{'x_axis':'K%-BB%','title':'K%-BB%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
53 |
+
'csw_percent':{'x_axis':'CSW%','title':'CSW%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
54 |
+
'woba_percent':{'x_axis':'wOBA','title':'wOBA','flip_p':False,'decimal_format':'string_3','percent_adjust':100},
|
55 |
+
'hard_hit_percent':{'x_axis':'HardHit%','title':'HardHit%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
56 |
+
'barrel_percent':{'x_axis':'Barrel%','title':'Barrel%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
57 |
+
'zone_contact_percent':{'x_axis':'Z-Contact%','title':'Z-Contact%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
58 |
+
'zone_swing_percent':{'x_axis':'Z-Swing%','title':'Z-Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
59 |
+
'zone_percent':{'x_axis':'Zone%','title':'Zone%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
60 |
+
'chase_percent':{'x_axis':'O-Swing%','title':'O-Swing%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
61 |
+
'chase_contact':{'x_axis':'O-Contact%','title':'O-Contact%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
62 |
+
'swing_percent':{'x_axis':'Swing%','title':'Swing%','flip_p':False,'decimal_format':'percent_1','percent_adjust':100},
|
63 |
+
'whiff_rate':{'x_axis':'Whiff%','title':'Whiff%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
64 |
+
'swstr_rate':{'x_axis':'SwStr%','title':'SwStr%','flip_p':True,'decimal_format':'percent_1','percent_adjust':100},
|
65 |
+
}
|
66 |
+
|
67 |
+
pitcher_dict_stat_small = { 'sweet_spot_percent':'SweetSpot%',
|
68 |
+
'max_launch_speed':'Max Exit Velocity',
|
69 |
+
'launch_speed_90':'90th Percentile EV',
|
70 |
+
'launch_speed':'Exit Velocity',
|
71 |
+
'launch_angle':'Launch Angle',
|
72 |
+
'avg':'AVG',
|
73 |
+
'obp':'OBP',
|
74 |
+
'slg':'SLG',
|
75 |
+
'ops':'OPS',
|
76 |
+
'k_percent':'K%',
|
77 |
+
'bb_percent':'BB%',
|
78 |
+
'bb_over_k_percent':'K/BB',
|
79 |
+
'bb_minus_k_percent':'K%-BB%',
|
80 |
+
'csw_percent':'CSW%',
|
81 |
+
'woba_percent':'wOBA',
|
82 |
+
'hard_hit_percent':'HardHit%',
|
83 |
+
'barrel_percent':'Barrel%',
|
84 |
+
'zone_contact_percent':'Z-Contact%',
|
85 |
+
'zone_swing_percent':'Z-Swing%',
|
86 |
+
'zone_percent':'Zone%',
|
87 |
+
'chase_percent':'O-Swing%',
|
88 |
+
'chase_contact':'O-Contact%',
|
89 |
+
'swing_percent':'Swing%',
|
90 |
+
'whiff_rate':'Whiff%',
|
91 |
+
'swstr_rate':'SwStr%',
|
92 |
+
}
|
93 |
+
|
94 |
+
|
95 |
+
colour_palette = ['#FFB000','#648FFF','#785EF0',
|
96 |
+
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
|
97 |
+
|
98 |
+
level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A','ROK':'ROK'}
|
99 |
+
|
100 |
+
batter_test_df = exit_velo_df_codes_summ.sort_values(by='pitcher').drop_duplicates(subset='pitcher_id').reset_index(drop=True)[['pitcher_id','pitcher']]#['pitcher'].to_dict()
|
101 |
+
batter_test_df = batter_test_df.set_index('pitcher_id')
|
102 |
+
|
103 |
+
|
104 |
+
def decimal_format_assign(x):
|
105 |
+
if x['decimal_format'] == 'percent_1':
|
106 |
+
return mtick.PercentFormatter(1,decimals=1)
|
107 |
+
if x['decimal_format'] == 'string_3':
|
108 |
+
return mtick.FormatStrFormatter('%.3f')
|
109 |
+
if x['decimal_format'] == 'string_0':
|
110 |
+
return mtick.FormatStrFormatter('%.0f')
|
111 |
+
if x['decimal_format'] == 'string_1':
|
112 |
+
return mtick.FormatStrFormatter('%.1f')
|
113 |
+
|
114 |
+
|
115 |
+
#test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])
|
116 |
+
|
117 |
+
pitcher_dict = batter_test_df['pitcher'].to_dict()
|
118 |
+
|
119 |
+
exit_velo_df_codes_summ.position = exit_velo_df_codes_summ.position.replace(['TWP'],['P'])
|
120 |
+
|
121 |
+
exit_velo_df_codes_summ_non_level.position = exit_velo_df_codes_summ_non_level.position.replace(['TWP'],['P'])
|
122 |
+
|
123 |
+
|
124 |
+
position_list = ['All'] + list(exit_velo_df_codes_summ.position.unique())
|
125 |
+
team_list = ['All'] + sorted(list(exit_velo_df_codes_summ.parent_org_abb.unique()))
|
126 |
+
|
127 |
+
|
128 |
+
|
129 |
+
def server(input,output,session):
|
130 |
+
|
131 |
+
|
132 |
+
@output
|
133 |
+
@render.plot(alt="A histogram")
|
134 |
+
@reactive.event(input.go, ignore_none=False)
|
135 |
+
def plot():
|
136 |
+
sns.set_theme(style="whitegrid", palette="pastel")
|
137 |
+
|
138 |
+
|
139 |
+
print(input.level_id())
|
140 |
+
print(input.n())
|
141 |
+
print('we made it here')
|
142 |
+
|
143 |
+
#data_df = exit_velo_df_codes_summ.copy()
|
144 |
+
|
145 |
+
|
146 |
+
if input.group_level():
|
147 |
+
data_df = exit_velo_df_codes_summ_non_level.copy()
|
148 |
+
|
149 |
+
turth_list = []
|
150 |
+
#turth_list_2 = []
|
151 |
+
for x in range(0,len(data_df.level)):
|
152 |
+
turth_list_2 = []
|
153 |
+
for y in range(0,len(data_df.level[x])):
|
154 |
+
#print(level_list[x][y])
|
155 |
+
turth_list_2.append(data_df.level[x][y] in input.level_id())
|
156 |
+
turth_list.append(turth_list_2)
|
157 |
+
|
158 |
+
final_check_list = [True if True in x else False for x in turth_list]
|
159 |
+
|
160 |
+
|
161 |
+
data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(final_check_list)]
|
162 |
+
|
163 |
+
else:
|
164 |
+
data_df = exit_velo_df_codes_summ.copy()
|
165 |
+
|
166 |
+
data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())&(data_df.level.isin(input.level_id()))]
|
167 |
+
print(data_df)
|
168 |
+
if 'All' in input.team_id():
|
169 |
+
print('nice')#data_df = data_df[(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)
|
170 |
+
|
171 |
+
else:
|
172 |
+
data_df = data_df[(data_df.parent_org_abb.isin(input.team_id()))].reset_index(drop=True)
|
173 |
+
|
174 |
+
if 'All' in input.position_id():
|
175 |
+
print('nice')#data_df = data_df[(data_df.level.isin(input.level_id()))&(data_df.pa >= input.n())&(data_df.age <= input.n_age())].reset_index(drop=True)
|
176 |
+
|
177 |
+
else:
|
178 |
+
data_df = data_df[(data_df.position.isin(input.position_id()))].reset_index(drop=True)
|
179 |
+
|
180 |
+
|
181 |
+
|
182 |
+
|
183 |
+
|
184 |
+
|
185 |
+
data_df = data_df.sort_values(by='level').reset_index(drop=True)
|
186 |
+
print(pitcher_dict_stat[input.stat_x()]['flip_p'])
|
187 |
+
|
188 |
+
|
189 |
+
|
190 |
+
x_flip = pitcher_dict_stat[input.stat_x()]['flip_p']
|
191 |
+
y_flip = pitcher_dict_stat[input.stat_y()]['flip_p']
|
192 |
+
cbr_flip = pitcher_dict_stat[input.stat_z()]['flip_p']
|
193 |
+
|
194 |
+
|
195 |
+
|
196 |
+
data_df[input.stat_x()+'_percent'] = data_df[input.stat_x()].rank(pct=True,ascending=abs(x_flip-1))
|
197 |
+
|
198 |
+
data_df[input.stat_y()+'_percent'] = data_df[input.stat_y()].rank(pct=True,ascending=abs(y_flip-1))
|
199 |
+
|
200 |
+
data_df[input.stat_z()+'_percent'] = data_df[input.stat_z()].rank(pct=True,ascending=abs(cbr_flip-1))
|
201 |
+
|
202 |
+
|
203 |
+
|
204 |
+
fig, ax = plt.subplots(1, 1, figsize=(9, 9))
|
205 |
+
|
206 |
+
#data_df['bb_over_obp'] = data_df['bb']/data_df['k']
|
207 |
+
|
208 |
+
#data_df[input.stat_z()]= data_df[input.stat_z()].fillna(-100000)
|
209 |
+
|
210 |
+
|
211 |
+
if not cbr_flip:
|
212 |
+
cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[0],colour_palette[3],colour_palette[1]])
|
213 |
+
norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())
|
214 |
+
|
215 |
+
else:
|
216 |
+
cmap_hue = matplotlib.colors.LinearSegmentedColormap.from_list("", [colour_palette[1],colour_palette[3],colour_palette[0]])
|
217 |
+
norm = plt.Normalize(data_df[input.stat_z()].min(), data_df[input.stat_z()].max())
|
218 |
+
|
219 |
+
sm = plt.cm.ScalarMappable(cmap=cmap_hue, norm=norm)
|
220 |
+
print('we made it here')
|
221 |
+
|
222 |
+
# sns.regplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[6],ax=ax,scatter=False,
|
223 |
+
# line_kws=dict(alpha=0.3,linewidth=2,zorder=1))
|
224 |
+
# scatter_plot = sns.scatterplot(x = stat_x, y = stat_y, data=data_df, color = colour_palette[0],ax=ax,hue=stat_z,palette=cmap_hue)
|
225 |
+
|
226 |
+
|
227 |
+
|
228 |
+
# r, p = sp.stats.pearsonr(data_df[input.stat_x()], data_df[input.stat_y()])
|
229 |
+
# ax = plt.gca()
|
230 |
+
# # ax.text(.25, 0.3, 'r={:.2f}, p={:.2g}'.format(r, p),
|
231 |
+
# # transform=ax.transAxes, fontsize=12)
|
232 |
+
|
233 |
+
# ax.annotate('R²={:.2f}'.format(r, p), ( math.ceil(data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust']*(1-pitcher_dict_stat[input.stat_x()]['flip_p']),
|
234 |
+
# math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust']*(1-pitcher_dict_stat[input.stat_y()]['flip_p'])),
|
235 |
+
# fontsize=18,fontname='Century Gothic',ha='right')
|
236 |
+
|
237 |
+
if input.group_level():
|
238 |
+
scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3')
|
239 |
+
#ax.get_legend().remove()
|
240 |
+
scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue)
|
241 |
+
else:
|
242 |
+
scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = '#b3b3b3',style='level')
|
243 |
+
#ax.get_legend().remove()
|
244 |
+
scatter = sns.scatterplot(x = input.stat_x(), y = input.stat_y(), data=data_df, color = colour_palette[0],ax=ax,hue=input.stat_z(),palette=cmap_hue,style='level')
|
245 |
+
|
246 |
+
|
247 |
+
sns.set_theme(style="whitegrid", palette="pastel")
|
248 |
+
|
249 |
+
fig.set_facecolor('#F0F0F0')
|
250 |
+
ax.set_facecolor('white')
|
251 |
+
|
252 |
+
print('we made it here!')
|
253 |
+
# for i in range(0,len(pitch_group_unique)):
|
254 |
+
# data_df = elly_zone_df[elly_zone_df.pitch_group==pitch_group_unique[i]]
|
255 |
+
# len_df.append(len(data_df))
|
256 |
+
# sns.lineplot(x=range(1,len(data_df)+1),y=data_df.swings.rolling(window=rolling_window_input).sum()/data_df.pitches.rolling(window=rolling_window_input).sum(),color=colour_palette[i],linewidth=3,ax=ax,
|
257 |
+
# label=f'{pitch_group_unique[i]} (Season Average {float(data_df.swings.sum()/data_df.pitches.sum()):.1%})',zorder=i+10)
|
258 |
+
# ax.hlines(xmin=0,xmax=len(elly_zone_df),y=data_df.swings.sum()/data_df.pitches.sum(),color=colour_palette[i],linewidth=3,linestyle='-.',alpha=0.4,zorder=i)
|
259 |
+
|
260 |
+
ts=[]
|
261 |
+
xs=[]
|
262 |
+
ys=[]
|
263 |
+
labels=[]
|
264 |
+
|
265 |
+
print(input.player_id())
|
266 |
+
if input.names():
|
267 |
+
for i in range(len(data_df)):
|
268 |
+
if (data_df[input.stat_x()+'_percent'][i] < input.n_percent_bot_x() or data_df[input.stat_x()+'_percent'][i] > 1 - input.n_percent_top_x() ) \
|
269 |
+
or (data_df[input.stat_y()+'_percent'][i] < input.n_percent_bot_y() or data_df[input.stat_y()+'_percent'][i] > 1 -input.n_percent_top_y()) \
|
270 |
+
or (data_df[input.stat_z()+'_percent'][i] < input.n_percent_bot_z() or data_df[input.stat_z()+'_percent'][i] > 1 -input.n_percent_top_z() )\
|
271 |
+
or (str(data_df.pitcher_id[i]) in (input.player_id())):
|
272 |
+
ts.append(ax.text(data_df[input.stat_x()][i], data_df[input.stat_y()][i], data_df.pitcher[i],fontsize=8))
|
273 |
+
|
274 |
+
|
275 |
+
# ax.annotate(data_df.pitcher[i], xy=((data_df[input.stat_x()][i])+0.025/pitcher_dict_stat[input.stat_x()]['percent_adjust'], data_df[input.stat_y()][i]+0.01/pitcher_dict_stat[input.stat_x()]['percent_adjust']), xytext=(-20,20),
|
276 |
+
# textcoords='offset points', ha='center', va='bottom',fontsize=7,
|
277 |
+
# bbox=dict(boxstyle='round,pad=0', fc=colour_palette[6], alpha=0.0),
|
278 |
+
# arrowprops=dict(arrowstyle='->', connectionstyle="angle,angleA=-90,angleB=-10,rad=5",
|
279 |
+
# color=colour_palette[4]))
|
280 |
+
|
281 |
+
# print('CONDITION CHEK')
|
282 |
+
# print(data_df[input.stat_x()][i])
|
283 |
+
# #if data_df['pitcher'][i] != 'Jo Adell':
|
284 |
+
# #ax.annotate(data_df.pitcher[i], ((data_df[input.stat_x()][i])+0.025/pitcher_dict_stat[input.stat_x()]['percent_adjust'], data_df[input.stat_y()][i]+0.01/pitcher_dict_stat[input.stat_x()]['percent_adjust']),fontsize=8)
|
285 |
+
# #ts.append(data_df[input.stat_x()][i], data_df[input.stat_y()][i], data_df.pitcher[i],fontsize=8))
|
286 |
+
# xs.append(data_df[input.stat_x()][i])
|
287 |
+
# ys.append(data_df[input.stat_y()][i])
|
288 |
+
# labels.append(data_df.pitcher[i])
|
289 |
+
|
290 |
+
# # adjust_text(ts, force_points=0.2, force_text=0.2,
|
291 |
+
# # expand_points=(1, 1), expand_text=(1, 1),
|
292 |
+
# # arrowprops=dict(arrowstyle="-", color='black', lw=0))
|
293 |
+
# #print(xs)
|
294 |
+
# if len(xs)>0:
|
295 |
+
# repel_labels(ax=ax, x = xs,y = ys, labels = labels, k=0.0025)
|
296 |
+
|
297 |
+
ax.hlines(xmin=math.floor((data_df[input.stat_x()].min()*pitcher_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],
|
298 |
+
xmax= math.ceil((data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],
|
299 |
+
y=data_df[input.stat_y()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
|
300 |
+
|
301 |
+
print('we made it here')
|
302 |
+
ax.vlines(ymin=(math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],
|
303 |
+
ymax= (math.ceil(data_df[input.stat_y()].max()*pitcher_dict_stat[input.stat_y()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],
|
304 |
+
x=data_df[input.stat_x()].mean(),color='gray',linewidth=3,linestyle='dotted',alpha=0.4)
|
305 |
+
|
306 |
+
ax.set_xlim((math.floor(data_df[input.stat_x()].min()*pitcher_dict_stat[input.stat_x()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'],
|
307 |
+
(math.ceil(data_df[input.stat_x()].max()*pitcher_dict_stat[input.stat_x()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_x()]['percent_adjust'])
|
308 |
+
|
309 |
+
ax.set_ylim((math.floor(data_df[input.stat_y()].min()*pitcher_dict_stat[input.stat_y()]['percent_adjust']-0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'],
|
310 |
+
(math.ceil(data_df[input.stat_y()].max()*pitcher_dict_stat[input.stat_y()]['percent_adjust']+0.01)/5)*5/pitcher_dict_stat[input.stat_y()]['percent_adjust'])
|
311 |
+
|
312 |
+
title_level = str([x .strip("\'")for x in input.level_id()]).strip('[').strip(']').replace("'",'')
|
313 |
+
|
314 |
+
if title_level == 'AAA, AA, A+, A':
|
315 |
+
title_level='MiLB'
|
316 |
+
|
317 |
+
|
318 |
+
if input.n_age() >= 50:
|
319 |
+
title_spot = f'{title_level} Pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA)'
|
320 |
+
|
321 |
+
else:
|
322 |
+
title_spot = f'{title_level} Pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA, Max Age {input.n_age()})'
|
323 |
+
|
324 |
+
#title_level = input.level_id()[0]
|
325 |
+
#title_spot = f'{title_level} pitcher {pitcher_dict_stat[input.stat_y()]["title"]} vs {pitcher_dict_stat[input.stat_x()]["title"]} (min. {input.n()} PA)'
|
326 |
+
|
327 |
+
ax.set_title(title_spot, fontsize=24/(len(title_spot)*0.03),fontname='Century Gothic')
|
328 |
+
# #vals = ax.get_yticks()
|
329 |
+
ax.set_xlabel(pitcher_dict_stat[input.stat_x()]['x_axis'], fontsize=16,fontname='Century Gothic')
|
330 |
+
ax.set_ylabel(pitcher_dict_stat[input.stat_y()]['x_axis'], fontsize=16,fontname='Century Gothic')
|
331 |
+
|
332 |
+
|
333 |
+
if input.group_level():
|
334 |
+
ax.get_legend().remove()
|
335 |
+
|
336 |
+
if not input.group_level():
|
337 |
+
if len(input.level_id()) > 1:
|
338 |
+
h,l = scatter.get_legend_handles_labels()
|
339 |
+
l[-(len(input.level_id())+1)] = 'Level'
|
340 |
+
ax.legend(h[-(len(input.level_id())+1):],l[-(len(input.level_id())+1):], borderaxespad=0.1,loc=0)
|
341 |
+
|
342 |
+
else:
|
343 |
+
ax.get_legend().remove()
|
344 |
+
|
345 |
+
|
346 |
+
|
347 |
+
#plt.show(g)
|
348 |
+
# ax.figure.colorbar(sm, ax=ax)
|
349 |
+
|
350 |
+
cbar = ax.figure.colorbar(sm, ax=ax,format=decimal_format_assign(x=pitcher_dict_stat[input.stat_z()]),orientation='vertical',aspect=30)
|
351 |
+
cbar.set_label(pitcher_dict_stat[input.stat_z()]['x_axis'])
|
352 |
+
#fig.axes[0].invert_yaxis()
|
353 |
+
print('we made it here5')
|
354 |
+
fig.subplots_adjust(wspace=.02, hspace=.02)
|
355 |
+
# ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
|
356 |
+
#ax.set_yticks([0,0.1,0.2,0.3,0.4,0.5])
|
357 |
+
# fig.colorbar(plot_dist, ax=ax)
|
358 |
+
# fig.colorbar(plot_dist)
|
359 |
+
|
360 |
+
if not pitcher_dict_stat[input.stat_x()]['flip_p']:
|
361 |
+
fig.axes[0].invert_xaxis()
|
362 |
+
|
363 |
+
if not pitcher_dict_stat[input.stat_y()]['flip_p']:
|
364 |
+
fig.axes[0].invert_yaxis()
|
365 |
+
|
366 |
+
|
367 |
+
# ax.xaxis.set_major_formatter(mtick.PercentFormatter(1,decimals=0))
|
368 |
+
# ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
|
369 |
+
|
370 |
+
|
371 |
+
|
372 |
+
|
373 |
+
|
374 |
+
print('we made it here6')
|
375 |
+
|
376 |
+
ax.xaxis.set_major_formatter(decimal_format_assign(x=pitcher_dict_stat[input.stat_x()]))
|
377 |
+
ax.yaxis.set_major_formatter(decimal_format_assign(x=pitcher_dict_stat[input.stat_y()]))
|
378 |
+
|
379 |
+
|
380 |
+
print('we made it here7')
|
381 |
+
# ax.text(0.5, 0.5, '/u/tomstoms', transform=ax.transAxes,
|
382 |
+
# fontsize=60, color='gray', alpha=0.075,
|
383 |
+
# ha='center', va='center', rotation=45)
|
384 |
+
adjust_text(ts,
|
385 |
+
arrowprops=dict(arrowstyle="-", color=colour_palette[4], lw=1),ax=ax)
|
386 |
+
|
387 |
+
#ax.legend(fontsize='16')
|
388 |
+
fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
|
389 |
+
fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
|
390 |
+
fig.tight_layout()
|
391 |
+
|
392 |
+
|
393 |
+
pitcher_scatter = App(ui.page_fluid(
|
394 |
+
ui.tags.base(href=base_url),
|
395 |
+
ui.tags.div(
|
396 |
+
{"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
|
397 |
+
ui.tags.style(
|
398 |
+
"""
|
399 |
+
h4 {
|
400 |
+
margin-top: 1em;font-size:35px;
|
401 |
+
}
|
402 |
+
h2{
|
403 |
+
font-size:25px;
|
404 |
+
}
|
405 |
+
"""
|
406 |
+
),
|
407 |
+
shinyswatch.theme.simplex(),
|
408 |
+
ui.tags.h4("TJStats"),
|
409 |
+
ui.tags.i("Baseball Analytics and Visualizations"),
|
410 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
411 |
+
ui.navset_tab(
|
412 |
+
ui.nav_control(
|
413 |
+
ui.a(
|
414 |
+
"Home",
|
415 |
+
href="home/"
|
416 |
+
),
|
417 |
+
),
|
418 |
+
ui.nav_menu(
|
419 |
+
"Batter Charts",
|
420 |
+
ui.nav_control(
|
421 |
+
ui.a(
|
422 |
+
"Batting Rolling",
|
423 |
+
href="rolling_batter/"
|
424 |
+
),
|
425 |
+
ui.a(
|
426 |
+
"Spray",
|
427 |
+
href="spray/"
|
428 |
+
),
|
429 |
+
ui.a(
|
430 |
+
"Decision Value",
|
431 |
+
href="decision_value/"
|
432 |
+
),
|
433 |
+
ui.a(
|
434 |
+
"Damage Model",
|
435 |
+
href="damage_model/"
|
436 |
+
),
|
437 |
+
ui.a(
|
438 |
+
"Batter Scatter",
|
439 |
+
href="batter_scatter/"
|
440 |
+
),
|
441 |
+
ui.a(
|
442 |
+
"EV vs LA Plot",
|
443 |
+
href="ev_angle/"
|
444 |
+
),
|
445 |
+
ui.a(
|
446 |
+
"Statcast Compare",
|
447 |
+
href="statcast_compare/"
|
448 |
+
)
|
449 |
+
),
|
450 |
+
),
|
451 |
+
ui.nav_menu(
|
452 |
+
"Pitcher Charts",
|
453 |
+
ui.nav_control(
|
454 |
+
ui.a(
|
455 |
+
"Pitcher Rolling",
|
456 |
+
href="rolling_pitcher/"
|
457 |
+
),
|
458 |
+
ui.a(
|
459 |
+
"Pitcher Summary",
|
460 |
+
href="pitching_summary_graphic_new/"
|
461 |
+
),
|
462 |
+
ui.a(
|
463 |
+
"Pitcher Scatter",
|
464 |
+
href="pitcher_scatter/"
|
465 |
+
)
|
466 |
+
),
|
467 |
+
)),ui.row(
|
468 |
+
ui.layout_sidebar(
|
469 |
+
|
470 |
+
|
471 |
+
|
472 |
+
ui.panel_sidebar(
|
473 |
+
#ui.input_select("id", "Select Batter",batter_dict,selected=675911,width=1,size=1),
|
474 |
+
ui.row(
|
475 |
+
ui.column(4,ui.input_select("level_id", "Select Level",level_dict,width=1,size=1,multiple=True,selected='MLB',selectize=True),),
|
476 |
+
ui.column(4,ui.input_select("team_id", "Select Team",team_list,width=1,size=1,multiple=True,selected='All',selectize=True),),
|
477 |
+
ui.column(4,ui.input_select("position_id", "Select Position",position_list,width=1,size=1,selected='All',multiple=True,selectize=True))),
|
478 |
+
ui.row(
|
479 |
+
ui.column(6,ui.input_numeric("n", "Minimum PA", value=100)),
|
480 |
+
ui.column(6,ui.input_numeric("n_age", "Maximum Age", value=50))),
|
481 |
+
ui.row(
|
482 |
+
ui.column(4,ui.input_select("stat_x", "X-Axis",pitcher_dict_stat_small,selected='k_percent',width=1,size=1)),
|
483 |
+
ui.column(4,ui.input_select("stat_y", "Y-Axis",pitcher_dict_stat_small,selected='bb_percent',width=1,size=1)),
|
484 |
+
ui.column(4,ui.input_select("stat_z", "Colour-Bar Axis",pitcher_dict_stat_small,selected='bb_minus_k_percent',width=1,size=1))),
|
485 |
+
|
486 |
+
ui.row(
|
487 |
+
ui.column(6,ui.input_numeric("n_percent_top_x", "Top 'n' Percentile X-Labels", value=0.01)),
|
488 |
+
ui.column(6,ui.input_numeric("n_percent_bot_x", "Bottom 'n' Percentile X-Labels", value=0.01))),
|
489 |
+
ui.row(
|
490 |
+
ui.column(6,ui.input_numeric("n_percent_top_y", "Top 'n' Percentile Y-Labels", value=0.01)),
|
491 |
+
ui.column(6,ui.input_numeric("n_percent_bot_y", "Bottom 'n' Percentile Y-Labels", value=0.01))),
|
492 |
+
ui.row(
|
493 |
+
ui.column(6,ui.input_numeric("n_percent_top_z", "Top 'n' Percentile Z-Labels", value=0.01)),
|
494 |
+
ui.column(6,ui.input_numeric("n_percent_bot_z", "Bottom 'n' Percentile Z-Labels", value=0.01))),
|
495 |
+
|
496 |
+
ui.input_select("player_id", "Label Player",pitcher_dict,width=1,size=1,multiple=True,selectize=True),
|
497 |
+
ui.row(
|
498 |
+
ui.input_switch("names", "Toggle Names"),
|
499 |
+
ui.input_switch("group_level", "Group Levels")),
|
500 |
+
ui.input_action_button("go", "Generate",class_="btn-primary"),
|
501 |
+
),
|
502 |
+
|
503 |
+
|
504 |
+
ui.panel_main(
|
505 |
+
ui.output_plot("plot",height = "1000px",width="1000px")
|
506 |
+
,
|
507 |
+
),
|
508 |
+
)),)),server)
|
pitcher_summary_graphic.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
pitching_summary_graphic.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
pitching_summary_graphic_new.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
pitching_summary_graphic_new_fg_api.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
rolling_batter.py
ADDED
@@ -0,0 +1,732 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
|
2 |
+
import datasets
|
3 |
+
from datasets import load_dataset
|
4 |
+
import pandas as pd
|
5 |
+
import numpy as np
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
import seaborn as sns
|
8 |
+
import numpy as np
|
9 |
+
from scipy.stats import gaussian_kde
|
10 |
+
import matplotlib
|
11 |
+
from matplotlib.ticker import MaxNLocator
|
12 |
+
from matplotlib.gridspec import GridSpec
|
13 |
+
from scipy.stats import zscore
|
14 |
+
import math
|
15 |
+
import matplotlib
|
16 |
+
from adjustText import adjust_text
|
17 |
+
import matplotlib.ticker as mtick
|
18 |
+
from shinywidgets import output_widget, render_widget
|
19 |
+
import pandas as pd
|
20 |
+
from configure import base_url
|
21 |
+
import shinyswatch
|
22 |
+
import inflect
|
23 |
+
from matplotlib.pyplot import text
|
24 |
+
|
25 |
+
def percentile(n):
|
26 |
+
def percentile_(x):
|
27 |
+
return np.nanpercentile(x, n)
|
28 |
+
percentile_.__name__ = 'percentile_%s' % n
|
29 |
+
return percentile_
|
30 |
+
|
31 |
+
colour_palette = ['#FFB000','#648FFF','#785EF0',
|
32 |
+
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
|
33 |
+
|
34 |
+
|
35 |
+
|
36 |
+
print('Starting Everything:')
|
37 |
+
# exit_velo_df = milb_a_ev_df.append([triple_a_ev_df,double_a_ev_df,a_high_a_ev_df,single_a_ev_df]).reset_index(drop=True)
|
38 |
+
# player_df_all = mlb_a_player_df.append([triple_a_player_df,double_a_player_df,a_high_a_player_df,single_a_player_df]).reset_index(drop=True)
|
39 |
+
# exit_velo_df = pd.read_csv('exit_velo_df_all.csv',index_col=[0])
|
40 |
+
# player_df_all = pd.read_csv('player_df_all.csv',index_col=[0])
|
41 |
+
|
42 |
+
# pa_df = pd.read_csv('pa_df_all.csv',index_col=[0])
|
43 |
+
# pa_df_full_na = pa_df.dropna()
|
44 |
+
|
45 |
+
### Import Datasets
|
46 |
+
dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2023.csv',
|
47 |
+
])
|
48 |
+
dataset_train = dataset['train']
|
49 |
+
exit_velo_df_mlb = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
50 |
+
#print(df_2023)
|
51 |
+
exit_velo_df_mlb['level'] = 'MLB'
|
52 |
+
|
53 |
+
# ### Import Datasets
|
54 |
+
# dataset = load_dataset('nesticot/mlb_data', data_files=['aaa_pitch_data_2023.csv',
|
55 |
+
# ])
|
56 |
+
# dataset_train = dataset['train']
|
57 |
+
# exit_velo_df_aaa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
58 |
+
# #print(df_2023)
|
59 |
+
# exit_velo_df_aaa['level'] = 'AAA'
|
60 |
+
|
61 |
+
# ### Import Datasets
|
62 |
+
# dataset = load_dataset('nesticot/mlb_data', data_files=['aa_pitch_data_2023.csv',
|
63 |
+
# ])
|
64 |
+
# dataset_train = dataset['train']
|
65 |
+
# exit_velo_df_aa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
66 |
+
# #print(df_2023)
|
67 |
+
# exit_velo_df_aa['level'] = 'AA'
|
68 |
+
|
69 |
+
# ### Import Datasets
|
70 |
+
# dataset = load_dataset('nesticot/mlb_data', data_files=['high_a_pitch_data_2023.csv',
|
71 |
+
# ])
|
72 |
+
# dataset_train = dataset['train']
|
73 |
+
# exit_velo_df_ha = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
74 |
+
# #print(df_2023)
|
75 |
+
# exit_velo_df_ha['level'] = 'A+'
|
76 |
+
|
77 |
+
# ### Import Datasets
|
78 |
+
# dataset = load_dataset('nesticot/mlb_data', data_files=['a_pitch_data_2023.csv',
|
79 |
+
# ])
|
80 |
+
# dataset_train = dataset['train']
|
81 |
+
# exit_velo_df_a = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
82 |
+
# #print(df_2023)
|
83 |
+
# exit_velo_df_a['level'] = 'A'
|
84 |
+
|
85 |
+
# exit_velo_df = pd.concat([exit_velo_df_mlb,exit_velo_df_aaa,exit_velo_df_aa,exit_velo_df_ha,exit_velo_df_a])
|
86 |
+
exit_velo_df = pd.concat([exit_velo_df_mlb])
|
87 |
+
# exit_velo_df_copy = exit_velo_df.copy()
|
88 |
+
|
89 |
+
# exit_velo_df = exit_velo_df_copy.copy()
|
90 |
+
|
91 |
+
end_codes = ['strikeout', 'field_out', 'single', 'walk', 'hit_by_pitch',
|
92 |
+
'double', 'sac_fly', 'force_out', 'home_run',
|
93 |
+
'grounded_into_double_play', 'fielders_choice', 'field_error',
|
94 |
+
'triple', 'sac_bunt', 'double_play', 'intent_walk',
|
95 |
+
'fielders_choice_out', 'strikeout_double_play',
|
96 |
+
'sac_fly_double_play', 'catcher_interf', 'other_out']
|
97 |
+
|
98 |
+
|
99 |
+
|
100 |
+
exit_velo_df['pa'] = exit_velo_df.event_type.isin(end_codes)
|
101 |
+
#exit_velo_df['pa'] = 1
|
102 |
+
exit_velo_df['k'] = exit_velo_df.event_type.isin(list(filter(None, [x if 'strikeout' in x else '' for x in exit_velo_df.event_type.fillna('None').unique()])))
|
103 |
+
exit_velo_df['bb'] = exit_velo_df.event_type.isin(list(filter(None, [x if 'walk' in x else '' for x in exit_velo_df.event_type.fillna('None').unique()])))
|
104 |
+
|
105 |
+
#exit_velo_df['k_minus_bb'] = exit_velo_df['k'].astype(np.float32)-exit_velo_df['bb'].astype(np.float32)
|
106 |
+
exit_velo_df['bb_minus_k'] = exit_velo_df['bb'].astype(np.float32)-exit_velo_df['k'].astype(np.float32)
|
107 |
+
|
108 |
+
|
109 |
+
|
110 |
+
exit_velo_df = exit_velo_df.drop_duplicates(subset=['play_id'])
|
111 |
+
|
112 |
+
|
113 |
+
|
114 |
+
swing_codes = ['Swinging Strike', 'In play, no out',
|
115 |
+
'Foul', 'In play, out(s)',
|
116 |
+
'In play, run(s)', 'Swinging Strike (Blocked)',
|
117 |
+
'Foul Bunt','Foul Tip', 'Missed Bunt','Foul Pitchout','Swinging Pitchout']
|
118 |
+
|
119 |
+
swings_in = ['Swinging Strike', 'In play, no out',
|
120 |
+
'Foul', 'In play, out(s)',
|
121 |
+
'In play, run(s)', 'Swinging Strike (Blocked)',
|
122 |
+
'Foul Bunt','Foul Tip', 'Missed Bunt','Foul Pitchout','Swinging Pitchout']
|
123 |
+
|
124 |
+
swing_strike_codes = ['Swinging Strike',
|
125 |
+
'Swinging Strike (Blocked)','Missed Bunt','Foul Tip','Swinging Pitchout']
|
126 |
+
|
127 |
+
|
128 |
+
contact_codes = ['In play, no out',
|
129 |
+
'Foul', 'In play, out(s)',
|
130 |
+
'In play, run(s)',
|
131 |
+
'Foul Bunt']
|
132 |
+
|
133 |
+
codes_in = ['In play, out(s)',
|
134 |
+
'Swinging Strike',
|
135 |
+
'Ball',
|
136 |
+
'Foul',
|
137 |
+
'In play, no out',
|
138 |
+
'Called Strike',
|
139 |
+
'Foul Tip',
|
140 |
+
'In play, run(s)',
|
141 |
+
'Hit By Pitch',
|
142 |
+
'Ball In Dirt',
|
143 |
+
'Pitchout',
|
144 |
+
'Swinging Strike (Blocked)',
|
145 |
+
'Foul Bunt',
|
146 |
+
'Missed Bunt',
|
147 |
+
'Foul Pitchout',
|
148 |
+
'Intent Ball',
|
149 |
+
'Swinging Pitchout']
|
150 |
+
|
151 |
+
exit_velo_df['in_zone'] = exit_velo_df['zone'] < 10
|
152 |
+
|
153 |
+
|
154 |
+
exit_velo_df = exit_velo_df.drop_duplicates(subset=['play_id'])
|
155 |
+
|
156 |
+
exit_velo_df_codes = exit_velo_df[exit_velo_df.play_description.isin(codes_in)].dropna(subset=['in_zone'])
|
157 |
+
|
158 |
+
exit_velo_df_codes['bip'] = ~exit_velo_df_codes.launch_speed.isna()
|
159 |
+
conditions = [
|
160 |
+
(exit_velo_df_codes['launch_speed'].isna()),
|
161 |
+
(exit_velo_df_codes['launch_speed']*1.5 - exit_velo_df_codes['launch_angle'] >= 117 ) & (exit_velo_df_codes['launch_speed'] + exit_velo_df_codes['launch_angle'] >= 124) & (exit_velo_df_codes['launch_speed'] > 98) & (exit_velo_df_codes['launch_angle'] >= 8) & (exit_velo_df_codes['launch_angle'] <= 50)
|
162 |
+
]
|
163 |
+
|
164 |
+
choices = [False,True]
|
165 |
+
exit_velo_df_codes['barrel'] = np.select(conditions, choices, default=np.nan)
|
166 |
+
|
167 |
+
conditions_ss = [
|
168 |
+
(exit_velo_df_codes['launch_angle'].isna()),
|
169 |
+
(exit_velo_df_codes['launch_angle'] >= 8 ) * (exit_velo_df_codes['launch_angle'] <= 32 )
|
170 |
+
]
|
171 |
+
|
172 |
+
choices_ss = [False,True]
|
173 |
+
exit_velo_df_codes['sweet_spot'] = np.select(conditions_ss, choices_ss, default=np.nan)
|
174 |
+
|
175 |
+
|
176 |
+
conditions_hh = [
|
177 |
+
(exit_velo_df_codes['launch_speed'].isna()),
|
178 |
+
(exit_velo_df_codes['launch_speed'] >= 94.5 )
|
179 |
+
]
|
180 |
+
|
181 |
+
choices_hh = [False,True]
|
182 |
+
exit_velo_df_codes['hard_hit'] = np.select(conditions_hh, choices_hh, default=np.nan)
|
183 |
+
|
184 |
+
|
185 |
+
conditions_tb = [
|
186 |
+
(exit_velo_df_codes['event_type']=='single'),
|
187 |
+
(exit_velo_df_codes['event_type']=='double'),
|
188 |
+
(exit_velo_df_codes['event_type']=='triple'),
|
189 |
+
(exit_velo_df_codes['event_type']=='home_run'),
|
190 |
+
]
|
191 |
+
|
192 |
+
choices_tb = [1,2,3,4]
|
193 |
+
|
194 |
+
exit_velo_df_codes['tb'] = np.select(conditions_tb, choices_tb, default=np.nan)
|
195 |
+
|
196 |
+
conditions_woba = [
|
197 |
+
(exit_velo_df_codes['event_type']=='walk'),
|
198 |
+
(exit_velo_df_codes['event_type']=='hit_by_pitch'),
|
199 |
+
(exit_velo_df_codes['event_type']=='single'),
|
200 |
+
(exit_velo_df_codes['event_type']=='double'),
|
201 |
+
(exit_velo_df_codes['event_type']=='triple'),
|
202 |
+
(exit_velo_df_codes['event_type']=='home_run'),
|
203 |
+
]
|
204 |
+
|
205 |
+
choices_woba = [0.705,
|
206 |
+
0.688,
|
207 |
+
0.897,
|
208 |
+
1.233,
|
209 |
+
1.612,
|
210 |
+
2.013]
|
211 |
+
|
212 |
+
exit_velo_df_codes['woba'] = np.select(conditions_woba, choices_woba, default=np.nan)
|
213 |
+
|
214 |
+
|
215 |
+
woba_codes = ['strikeout', 'field_out', 'single', 'walk', 'hit_by_pitch',
|
216 |
+
'double', 'sac_fly', 'force_out', 'home_run',
|
217 |
+
'grounded_into_double_play', 'fielders_choice', 'field_error',
|
218 |
+
'triple', 'sac_bunt', 'double_play',
|
219 |
+
'fielders_choice_out', 'strikeout_double_play',
|
220 |
+
'sac_fly_double_play', 'other_out']
|
221 |
+
|
222 |
+
|
223 |
+
|
224 |
+
|
225 |
+
|
226 |
+
conditions_woba_code = [
|
227 |
+
(exit_velo_df_codes['event_type'].isin(woba_codes))
|
228 |
+
]
|
229 |
+
|
230 |
+
choices_woba_code = [1]
|
231 |
+
|
232 |
+
exit_velo_df_codes['woba_codes'] = np.select(conditions_woba_code, choices_woba_code, default=np.nan)
|
233 |
+
|
234 |
+
|
235 |
+
#exit_velo_df_codes['barrel'] = (exit_velo_df_codes.launch_speed >= 98) & (exit_velo_df_codes.launch_angle >= (26 - (-98 + exit_velo_df_codes.launch_speed))) & (exit_velo_df_codes.launch_angle <= 30 + (-98 + exit_velo_df_codes.launch_speed)) & (exit_velo_df_codes.launch_angle >= 8) & (exit_velo_df_codes.launch_angle <= 50)
|
236 |
+
|
237 |
+
|
238 |
+
|
239 |
+
|
240 |
+
|
241 |
+
#exit_velo_df_codes['barrel'] = (exit_velo_df_codes.launch_speed >= 98) & (exit_velo_df_codes.launch_angle >= (26 - (-98 + exit_velo_df_codes.launch_speed))) & (exit_velo_df_codes.launch_angle <= 30 + (-98 + exit_velo_df_codes.launch_speed)) & (exit_velo_df_codes.launch_angle >= 8) & (exit_velo_df_codes.launch_angle <= 50)
|
242 |
+
exit_velo_df_codes['pitches'] = 1
|
243 |
+
exit_velo_df_codes['whiffs'] = [1 if ((x == 'S')|(x == 'W')|(x =='T')) else 0 for x in exit_velo_df_codes.play_code]
|
244 |
+
exit_velo_df_codes['csw'] = [1 if ((x == 'S')|(x == 'W')|(x =='T')|(x == 'C')) else 0 for x in exit_velo_df_codes.play_code]
|
245 |
+
exit_velo_df_codes['swings'] = [1 if x in swings_in else 0 for x in exit_velo_df_codes.play_description]
|
246 |
+
|
247 |
+
exit_velo_df_codes['out_zone'] = exit_velo_df_codes.in_zone == False
|
248 |
+
exit_velo_df_codes['zone_swing'] = (exit_velo_df_codes.in_zone == True)&(exit_velo_df_codes.swings == 1)
|
249 |
+
exit_velo_df_codes['zone_contact'] = (exit_velo_df_codes.in_zone == True)&(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.whiffs == 0)
|
250 |
+
exit_velo_df_codes['ozone_swing'] = (exit_velo_df_codes.in_zone==False)&(exit_velo_df_codes.swings == 1)
|
251 |
+
exit_velo_df_codes['ozone_contact'] = (exit_velo_df_codes.in_zone==False)&(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.whiffs == 0)
|
252 |
+
|
253 |
+
|
254 |
+
|
255 |
+
exit_velo_df_codes_summ = exit_velo_df_codes.groupby(['batter_id','batter_name','level']).agg(
|
256 |
+
pa = ('pa','sum'),
|
257 |
+
k = ('k','sum'),
|
258 |
+
bb = ('bb','sum'),
|
259 |
+
bb_minus_k = ('bb_minus_k','sum'),
|
260 |
+
csw = ('csw','sum'),
|
261 |
+
bip = ('bip','sum'),
|
262 |
+
tb = ('tb','sum'),
|
263 |
+
woba = ('woba','sum'),
|
264 |
+
woba_codes = ('woba_codes','sum'),
|
265 |
+
hard_hit = ('hard_hit','sum'),
|
266 |
+
barrel = ('barrel','sum'),
|
267 |
+
sweet_spot = ('sweet_spot','sum'),
|
268 |
+
max_launch_speed = ('launch_speed','max'),
|
269 |
+
launch_speed_90 = ('launch_speed',percentile(90)),
|
270 |
+
launch_speed = ('launch_speed','mean'),
|
271 |
+
launch_angle = ('launch_angle','mean'),
|
272 |
+
pitches = ('pitches','sum'),
|
273 |
+
swings = ('swings','sum'),
|
274 |
+
in_zone = ('in_zone','sum'),
|
275 |
+
out_zone = ('out_zone','sum'),
|
276 |
+
whiffs = ('whiffs','sum'),
|
277 |
+
zone_swing = ('zone_swing','sum'),
|
278 |
+
zone_contact = ('zone_contact','sum'),
|
279 |
+
ozone_swing = ('ozone_swing','sum'),
|
280 |
+
ozone_contact = ('ozone_contact','sum'),
|
281 |
+
).reset_index()
|
282 |
+
|
283 |
+
#exit_velo_df_codes_summ['out_zone'] = ~exit_velo_df_codes_summ.in_zone
|
284 |
+
#bip_min_input = int(input())
|
285 |
+
#bip_min = min(bip_min_input,50)
|
286 |
+
#exit_velo_df_codes_summ = exit_velo_df_codes_summ[exit_velo_df_codes_summ.balls_in_play>=bip_min]
|
287 |
+
|
288 |
+
|
289 |
+
exit_velo_df_codes_summ['k_percent'] = [exit_velo_df_codes_summ.k[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
290 |
+
exit_velo_df_codes_summ['bb_percent'] =[exit_velo_df_codes_summ.bb[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
291 |
+
exit_velo_df_codes_summ['bb_minus_k_percent'] =[exit_velo_df_codes_summ.bb_minus_k[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
292 |
+
|
293 |
+
exit_velo_df_codes_summ['csw_percent'] =[exit_velo_df_codes_summ.csw[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
294 |
+
|
295 |
+
|
296 |
+
exit_velo_df_codes_summ['sweet_spot_percent'] = [exit_velo_df_codes_summ.sweet_spot[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
297 |
+
|
298 |
+
exit_velo_df_codes_summ['woba_percent'] = [exit_velo_df_codes_summ.woba[x]/exit_velo_df_codes_summ.woba_codes[x] if exit_velo_df_codes_summ.woba_codes[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
299 |
+
#exit_velo_df_codes_summ['hard_hit_percent'] = [exit_velo_df_codes_summ.sweet_spot[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
300 |
+
exit_velo_df_codes_summ['hard_hit_percent'] = [exit_velo_df_codes_summ.hard_hit[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
301 |
+
|
302 |
+
|
303 |
+
exit_velo_df_codes_summ['barrel_percent'] = [exit_velo_df_codes_summ.barrel[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
304 |
+
|
305 |
+
exit_velo_df_codes_summ['zone_contact_percent'] = [exit_velo_df_codes_summ.zone_contact[x]/exit_velo_df_codes_summ.zone_swing[x] if exit_velo_df_codes_summ.zone_swing[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
306 |
+
|
307 |
+
exit_velo_df_codes_summ['zone_swing_percent'] = [exit_velo_df_codes_summ.zone_swing[x]/exit_velo_df_codes_summ.in_zone[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
308 |
+
|
309 |
+
exit_velo_df_codes_summ['zone_percent'] = [exit_velo_df_codes_summ.in_zone[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
310 |
+
|
311 |
+
exit_velo_df_codes_summ['chase_percent'] = [exit_velo_df_codes_summ.ozone_swing[x]/(exit_velo_df_codes_summ.pitches[x] - exit_velo_df_codes_summ.in_zone[x]) if (exit_velo_df_codes_summ.pitches[x]- exit_velo_df_codes_summ.in_zone[x]) != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
312 |
+
|
313 |
+
exit_velo_df_codes_summ['chase_contact'] = [exit_velo_df_codes_summ.ozone_contact[x]/exit_velo_df_codes_summ.ozone_swing[x] if exit_velo_df_codes_summ.ozone_swing[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
314 |
+
|
315 |
+
exit_velo_df_codes_summ['swing_percent'] = [exit_velo_df_codes_summ.swings[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
316 |
+
|
317 |
+
exit_velo_df_codes_summ['whiff_rate'] = [exit_velo_df_codes_summ.whiffs[x]/exit_velo_df_codes_summ.swings[x] if exit_velo_df_codes_summ.swings[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
318 |
+
|
319 |
+
exit_velo_df_codes_summ['swstr_rate'] = [exit_velo_df_codes_summ.whiffs[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
320 |
+
|
321 |
+
exit_velo_df_codes_summ = exit_velo_df_codes_summ.dropna(subset=['bip'])
|
322 |
+
|
323 |
+
woba_list = ['woba']
|
324 |
+
pa_list = ['k','bb','bb_minus_k']
|
325 |
+
balls_in_play_list = ['hard_hit','launch_speed','launch_speed_90','launch_angle','barrel','sweet_spot']
|
326 |
+
pitches_list = ['zone_percent','swing_percent','sw_str','csw']
|
327 |
+
swings_list = ['whiff_percent']
|
328 |
+
in_zone_pitches_list = ['zone_swing']
|
329 |
+
in_zone_swings_list = ['zone_contact']
|
330 |
+
out_zone_pitches_list = ['chase_percent']
|
331 |
+
out_zone_swings_list = ['chase_contact']
|
332 |
+
|
333 |
+
plot_dict = {
|
334 |
+
'k':{'x_axis':'Plate Appearances','y_axis':'K%','title':'K%','x_value':'k','x_range':[0.0,0.1,0.2,0.3,0.4],'percent':True,'percentile_label':'k_percent','flip_p':True,'percentile':False,'avg_adjust':False},
|
335 |
+
'bb':{'x_axis':'Plate Appearances','y_axis':'BB%','title':'BB%','x_value':'bb','x_range':[0.0,0.1,0.2,0.3],'percent':True,'percentile_label':'bb_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
336 |
+
'bb_minus_k':{'x_axis':'Plate Appearances','y_axis':'BB-K%','title':'BB-K%','x_value':'bb_minus_k','x_range':[-0.3,-0.2,-0.1,0,0.1,0.2],'percent':True,'percentile_label':'bb_minus_k_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
337 |
+
'csw':{'x_axis':'Pitches','y_axis':'CSW%','title':'CSW%','x_value':'csw','x_range':[.2,.25,.3,.35,.4],'percent':True,'percentile_label':'csw_percent','flip_p':True,'percentile':False,'avg_adjust':False},
|
338 |
+
'woba':{'x_axis':'wOBA PA','y_axis':'wOBA','title':'wOBA','x_value':'woba','x_range':[.20,.30,.40,.50],'percent':False,'percentile_label':'woba_percent','flip_p':False,'percentile':False,'avg_adjust':True},
|
339 |
+
'launch_speed':{'x_axis':'Balls In Play','y_axis':'Exit Velocity','title':'Exit Velocity','x_value':'launch_speed','x_range':[85,90,95,100],'percent':False,'percentile_label':'launch_speed','flip_p':False,'percentile':False,'avg_adjust':False},
|
340 |
+
'launch_speed_90':{'x_axis':'Balls In Play','y_axis':'90th Percentile Exit Velocity','title':'90th Percentile Exit Velocity','x_value':'launch_speed','x_range':[95,100,105,110,115],'percent':False,'percentile_label':'launch_speed_90','flip_p':False,'percentile':True,'avg_adjust':False},
|
341 |
+
'hard_hit':{'x_axis':'Balls In Play','y_axis':'HardHit%','title':'HardHit%','x_value':'hard_hit','x_range':[0.2,0.3,0.4,0.5,0.6,0.7],'percent':True,'percentile_label':'hard_hit_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
342 |
+
'sweet_spot':{'x_axis':'Balls In Play','y_axis':'SweetSpot%','title':'SweetSpot%','x_value':'sweet_spot','x_range':[0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'sweet_spot_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
343 |
+
'launch_angle':{'x_axis':'Balls In Play','y_axis':'Launch Angle','title':'Launch Angle','x_value':'launch_angle','x_range':[-20,-10,0,10,20],'percent':False,'percentile_label':'launch_angle','flip_p':False,'percentile':False,'avg_adjust':False},
|
344 |
+
'barrel':{'x_axis':'Balls In Play','y_axis':'Barrel%','title':'Barrel%','x_value':'barrel','x_range':[0,0.05,0.10,.15,.20,.25,.30],'percent':True,'percentile_label':'barrel_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
345 |
+
'zone_percent':{'x_axis':'Pitches','y_axis':'Zone%','title':'Zone%','x_value':'in_zone','x_range':[0.3,0.4,0.5,0.6,0.7],'percent':True,'percentile_label':'zone_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
346 |
+
'swing_percent':{'x_axis':'Pitches','y_axis':'Swing%','title':'Swing%','x_value':'swings','x_range':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],'percent':True,'percentile_label':'swing_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
347 |
+
'whiff_percent':{'x_axis':'Swings','y_axis':'Whiff%','title':'Whiff%','x_value':'whiffs','x_range':[0.0,0.1,0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'whiff_rate','flip_p':True,'percentile':False,'avg_adjust':False},
|
348 |
+
'sw_str':{'x_axis':'Pitches','y_axis':'SwStr%','title':'SwStr%','x_value':'whiffs','x_range':[0.0,0.05,0.1,0.15,0.2,0.25],'percent':True,'percentile_label':'swstr_rate','flip_p':True,'percentile':False,'avg_adjust':False},
|
349 |
+
'zone_swing':{'x_axis':'In-Zone Pitches','y_axis':'Z-Swing%','title':'Z-Swing%','x_value':'zone_swing','x_range':[0.3,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1],'percent':True,'percentile_label':'zone_swing_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
350 |
+
'zone_contact':{'x_axis':'In-Zone Swings','y_axis':'Z-Contact%','title':'Z-Contact%','x_value':'zone_contact','x_range':[0.5,0.6,0.7,0.8,0.9,1],'percent':True,'percentile_label':'zone_contact_percent','flip_p':False,'percentile':False,'avg_adjust':False},
|
351 |
+
'chase_percent':{'x_axis':'Out-of-Zone Pitches','y_axis':'O-Swing%','title':'O-Swing%','x_value':'ozone_swing','x_range':[0.0,0.1,0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'chase_percent','flip_p':True,'percentile':False,'avg_adjust':False},
|
352 |
+
'chase_contact':{'x_axis':'Out-of-Zone Swings','y_axis':'O-Contact%','title':'O-Contact%','x_value':'ozone_contact','x_range':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],'percent':True,'percentile_label':'chase_contact','flip_p':False,'percentile':False,'avg_adjust':False},}
|
353 |
+
|
354 |
+
|
355 |
+
|
356 |
+
|
357 |
+
test_df = exit_velo_df.sort_values(by='batter_name').drop_duplicates(subset='batter_id').reset_index(drop=True)[['batter_id','batter_name']]#['pitcher'].to_dict()
|
358 |
+
test_df = test_df.dropna()
|
359 |
+
test_df['batter_id'] = test_df['batter_id'].astype(int)
|
360 |
+
test_df = test_df.set_index('batter_id')
|
361 |
+
#test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])
|
362 |
+
|
363 |
+
batter_dict = test_df['batter_name'].to_dict()
|
364 |
+
|
365 |
+
level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A'}
|
366 |
+
|
367 |
+
plot_dict_small = {
|
368 |
+
'k':'K%',
|
369 |
+
'bb':'BB%',
|
370 |
+
'csw':'CSW%',
|
371 |
+
'launch_speed':'Exit Velocity',
|
372 |
+
'launch_speed_90':'90th Percentile Exit Velocity',
|
373 |
+
'sweet_spot':'SweetSpot%',
|
374 |
+
'launch_angle':'Launch Angle',
|
375 |
+
'zone_percent':'Zone%',
|
376 |
+
'barrel':'Barrel%',
|
377 |
+
'swing_percent':'Swing%',
|
378 |
+
'whiff_percent':'Whiff%',
|
379 |
+
'sw_str':'SwStr%',
|
380 |
+
'zone_swing':'Z-Swing%',
|
381 |
+
'zone_contact':'Z-Contact%',
|
382 |
+
'chase_percent':'O-Swing%',
|
383 |
+
'chase_contact':'O-Contact%',}
|
384 |
+
|
385 |
+
|
386 |
+
def server(input,output,session):
|
387 |
+
|
388 |
+
@output
|
389 |
+
@render.plot(alt="A histogram")
|
390 |
+
@reactive.event(input.go, ignore_none=False)
|
391 |
+
def plot():
|
392 |
+
# np.random.seed(19680801)
|
393 |
+
# x = 100 + 15 * np.random.randn(437)
|
394 |
+
# fig, ax = plt.subplots()
|
395 |
+
# ax.hist(x, input.n(), density=True)
|
396 |
+
# return fig
|
397 |
+
sns.set_theme(style="whitegrid", palette="pastel")
|
398 |
+
if input.id() is "":
|
399 |
+
fig = plt.figure(figsize=(12, 12))
|
400 |
+
fig.text(s='Please Select a Pitcher',x=0.5,y=0.5)
|
401 |
+
return
|
402 |
+
|
403 |
+
swing_min = int(input.n())
|
404 |
+
fig, ax = plt.subplots(1, 1, figsize=(10, 10))
|
405 |
+
|
406 |
+
fig.set_facecolor('white')
|
407 |
+
#ax.set_facecolor('white')
|
408 |
+
#fig.patch.set_facecolor('lightblue')
|
409 |
+
|
410 |
+
print(input.stat_id())
|
411 |
+
|
412 |
+
if input.stat_id() in pa_list:
|
413 |
+
print('we hAVE MADE IT TO THIS PART OF THE CODE')
|
414 |
+
|
415 |
+
|
416 |
+
if input.stat_id() in pa_list:
|
417 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.pa==1)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
418 |
+
divisor_x = 'pa'
|
419 |
+
print('this is short')
|
420 |
+
print(elly_zone_df)
|
421 |
+
|
422 |
+
|
423 |
+
if input.stat_id() in balls_in_play_list:
|
424 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.bip)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
425 |
+
divisor_x = 'bip'
|
426 |
+
#print('this is short')
|
427 |
+
|
428 |
+
if input.stat_id() in balls_in_play_list:
|
429 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.bip)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
430 |
+
divisor_x = 'bip'
|
431 |
+
print('this is short')
|
432 |
+
|
433 |
+
if input.stat_id() in pitches_list:
|
434 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.pitches == 1)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
435 |
+
divisor_x = 'pitches'
|
436 |
+
|
437 |
+
if input.stat_id() in swings_list:
|
438 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
439 |
+
divisor_x = 'swings'
|
440 |
+
|
441 |
+
|
442 |
+
if input.stat_id() in in_zone_pitches_list:
|
443 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.in_zone)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
444 |
+
divisor_x = 'in_zone'
|
445 |
+
|
446 |
+
|
447 |
+
if input.stat_id() in in_zone_swings_list:
|
448 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.zone_swing)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
449 |
+
divisor_x = 'zone_swing'
|
450 |
+
|
451 |
+
|
452 |
+
if input.stat_id() in out_zone_pitches_list:
|
453 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.in_zone == False)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
454 |
+
divisor_x = 'out_zone'
|
455 |
+
|
456 |
+
|
457 |
+
if input.stat_id() in out_zone_swings_list:
|
458 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.ozone_swing)&(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
459 |
+
divisor_x = 'ozone_swing'
|
460 |
+
|
461 |
+
# penguins = sns.load_dataset("penguins")
|
462 |
+
# sns.histplot(data=penguins, x="flipper_length_mm")
|
463 |
+
# print('we made it here:')
|
464 |
+
# print(int(input.id()))
|
465 |
+
# print(input.stat_id())
|
466 |
+
# print(input.level_id())
|
467 |
+
# print(exit_velo_df_codes[(exit_velo_df_codes.batter_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())])
|
468 |
+
# print(exit_velo_df_codes.columns)
|
469 |
+
# print(elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum())
|
470 |
+
|
471 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ.copy()
|
472 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.set_index('batter_id','batter_name','level')
|
473 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new[divisor_x] >= int(input.n())]
|
474 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.level==input.level_id()]
|
475 |
+
|
476 |
+
exit_velo_df_codes_summ_rank = exit_velo_df_codes_summ_new.rank(method='max',ascending=False)
|
477 |
+
exit_velo_df_codes_summ_rank.columns = exit_velo_df_codes_summ_rank.columns+['_rank']
|
478 |
+
|
479 |
+
exit_velo_df_codes_summ_rank_percent = exit_velo_df_codes_summ_new.rank(pct=True)
|
480 |
+
exit_velo_df_codes_summ_rank_percent.columns = exit_velo_df_codes_summ_rank_percent.columns+['_percent']
|
481 |
+
|
482 |
+
|
483 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.reset_index()
|
484 |
+
exit_velo_df_codes_summ_rank = exit_velo_df_codes_summ_rank.reset_index()
|
485 |
+
exit_velo_df_codes_summ_rank_percent = exit_velo_df_codes_summ_rank_percent.reset_index()
|
486 |
+
print('Table columns:')
|
487 |
+
|
488 |
+
exit_velo_df_codes_summ_new.batter_id = exit_velo_df_codes_summ_new.batter_id.astype(int)
|
489 |
+
exit_velo_df_codes_summ_rank.batter_id = exit_velo_df_codes_summ_rank.batter_id.astype(int)
|
490 |
+
exit_velo_df_codes_summ_rank_percent.batter_id = exit_velo_df_codes_summ_rank_percent.batter_id.astype(int)
|
491 |
+
|
492 |
+
print('Table columns2:')
|
493 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.merge(exit_velo_df_codes_summ_rank,left_on=['batter_id'],right_on=['batter_id'],how='left',suffixes=['','_rank'])
|
494 |
+
|
495 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.merge(exit_velo_df_codes_summ_rank_percent,left_on=['batter_id'],right_on=['batter_id'],how='left',suffixes=['','_percent'])
|
496 |
+
|
497 |
+
|
498 |
+
print(exit_velo_df_codes_summ_new)
|
499 |
+
print(exit_velo_df_codes_summ_rank)
|
500 |
+
print(exit_velo_df_codes_summ_rank_percent)
|
501 |
+
|
502 |
+
|
503 |
+
|
504 |
+
|
505 |
+
#sns.scatterplot(x=data_df.launch_speed_90,y=data_df.zone_contact,color=colour_palette[0],s=75,label=int(input.id()))
|
506 |
+
|
507 |
+
exit_velo_df_codes_summ_new_select = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.batter_id == int(input.id())].reset_index(drop=True)
|
508 |
+
print('whiffing')
|
509 |
+
print(exit_velo_df_codes)
|
510 |
+
print('Player _df:')
|
511 |
+
print(exit_velo_df_codes_summ_new_select)
|
512 |
+
|
513 |
+
if len(exit_velo_df_codes_summ_new_select) < 1:
|
514 |
+
ax.text(x=0.5,y=0.5,s='Please Select Different Parameters to Produce a plot',fontsize=18,ha='center')
|
515 |
+
return
|
516 |
+
|
517 |
+
p = inflect.engine()
|
518 |
+
|
519 |
+
exit_velo_df_codes_summ_new_select = exit_velo_df_codes_summ_new_select.loc[:,~exit_velo_df_codes_summ_new_select.columns.duplicated(keep='last')].copy()
|
520 |
+
print('Table for the player:')
|
521 |
+
print(list(exit_velo_df_codes_summ_new_select.columns))
|
522 |
+
print(plot_dict[input.stat_id()]["percentile_label"])
|
523 |
+
print(plot_dict[input.stat_id()]["percentile_label"]+'_percent')
|
524 |
+
print(exit_velo_df_codes_summ_new_select)
|
525 |
+
print(1*plot_dict[input.stat_id()]["flip_p"])
|
526 |
+
print(round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))
|
527 |
+
print((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)
|
528 |
+
|
529 |
+
# print(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+'_percent'])
|
530 |
+
|
531 |
+
if plot_dict[input.stat_id()]['percent']:
|
532 |
+
label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum():.1%}'
|
533 |
+
label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
|
534 |
+
#label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%}'
|
535 |
+
ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
|
536 |
+
|
537 |
+
else:
|
538 |
+
label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum():.1f}'
|
539 |
+
label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1f} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
|
540 |
+
#label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1f}'
|
541 |
+
#ax.yaxis.set_major_formatter(mtick.int)
|
542 |
+
|
543 |
+
|
544 |
+
if plot_dict[input.stat_id()]['percentile']:
|
545 |
+
label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].quantile(0.9):.1f}'
|
546 |
+
label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].quantile(0.9):.1f} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
|
547 |
+
#label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%}'
|
548 |
+
#ax.yaxis.set_major_formatter(mtick.int)
|
549 |
+
|
550 |
+
|
551 |
+
print(plot_dict[input.stat_id()]["x_value"])
|
552 |
+
print(divisor_x)
|
553 |
+
|
554 |
+
# exit_velo_df_codes_summ_new = exit_velo_df_codes_summ.copy()
|
555 |
+
# exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.balls_in_play >= int(input.n())]
|
556 |
+
# exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.level==input.level_id()]
|
557 |
+
|
558 |
+
|
559 |
+
print('this is here:')
|
560 |
+
print(exit_velo_df_codes_summ_new.head())
|
561 |
+
print(exit_velo_df_codes_summ_new.columns)
|
562 |
+
|
563 |
+
|
564 |
+
if plot_dict[input.stat_id()]["flip_p"] == False:
|
565 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[2],linestyle='dotted',alpha=0.5)
|
566 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[3],linestyle='dotted',alpha=0.5)
|
567 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[4],linestyle='dotted',alpha=0.5)
|
568 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[5],linestyle='dotted',alpha=0.5)
|
569 |
+
|
570 |
+
|
571 |
+
hard_hit_dates = [(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),
|
572 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),
|
573 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),
|
574 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1)]
|
575 |
+
hard_hit_text = ['90th %','75th %','25th %','10th %']
|
576 |
+
for i, x in enumerate(hard_hit_dates):
|
577 |
+
text(min(input.n()+input.n()/100,+input.n()+1), x ,hard_hit_text[i], rotation=0, ha='left',
|
578 |
+
bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[2+i], pad=2))
|
579 |
+
|
580 |
+
|
581 |
+
|
582 |
+
if plot_dict[input.stat_id()]["flip_p"] == True:
|
583 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[2],linestyle='dotted',alpha=0.5)
|
584 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[3],linestyle='dotted',alpha=0.5)
|
585 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[4],linestyle='dotted',alpha=0.5)
|
586 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[5],linestyle='dotted',alpha=0.5)
|
587 |
+
|
588 |
+
hard_hit_dates = [(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),
|
589 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),
|
590 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),
|
591 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1)]
|
592 |
+
hard_hit_text = ['10th %','25th %','75th %','90th %']
|
593 |
+
for i, x in enumerate(hard_hit_dates):
|
594 |
+
text(min(input.n()+input.n()/100,input.n()+input.n()+3), x ,hard_hit_text[i], rotation=0, ha='left',
|
595 |
+
bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[2+i], pad=2))
|
596 |
+
|
597 |
+
|
598 |
+
|
599 |
+
|
600 |
+
|
601 |
+
|
602 |
+
if plot_dict[input.stat_id()]["percentile"] == False:
|
603 |
+
ax.hlines(y=exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum(),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[1],linestyle='-.',label=label_1)
|
604 |
+
|
605 |
+
ax.hlines(y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum(),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[0],linestyle='--',label=label_2)
|
606 |
+
|
607 |
+
sns.lineplot(x=range(1,len(elly_zone_df)+1),y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).rolling(window=swing_min).sum()/swing_min,color=colour_palette[0],linewidth=3,ax=ax)
|
608 |
+
|
609 |
+
|
610 |
+
|
611 |
+
if plot_dict[input.stat_id()]["percentile"] == True:
|
612 |
+
|
613 |
+
ax.hlines(y=exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[1],linestyle='-.',label=label_1)
|
614 |
+
|
615 |
+
ax.hlines(y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[0],linestyle='--',label=label_2)
|
616 |
+
|
617 |
+
sns.lineplot(x=range(1,len(elly_zone_df)+1),y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).rolling(window=swing_min).quantile(0.9),color=colour_palette[0],linewidth=3,ax=ax)
|
618 |
+
|
619 |
+
|
620 |
+
#ax.set_xlim(input.n(),exit_velo_df_small.pitch.max())
|
621 |
+
#plt.yticks([0,0.2,0.4,0.6,0.8,1])
|
622 |
+
#ax.set_ylim(math.floor((min(exit_velo_df_codes_summ.zone_contact)/5)*100)*5/100,1)
|
623 |
+
ax.set_xlim(math.floor(swing_min),len(elly_zone_df))
|
624 |
+
ax.set_title(f'{batter_dict[int(input.id())]} - {input.level_id()} - {swing_min} {plot_dict[input.stat_id()]["x_axis"]} Rolling {plot_dict[input.stat_id()]["title"]}', fontsize=16,fontname='Century Gothic',)
|
625 |
+
#vals = ax.get_yticks()
|
626 |
+
ax.set_xlabel(plot_dict[input.stat_id()]['x_axis'], fontsize=16,fontname='Century Gothic')
|
627 |
+
ax.set_ylabel(plot_dict[input.stat_id()]['y_axis'], fontsize=16,fontname='Century Gothic')
|
628 |
+
|
629 |
+
#fig.axes[0].invert_yaxis()
|
630 |
+
|
631 |
+
#fig.subplots_adjust(wspace=.02, hspace=.02)
|
632 |
+
#ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
|
633 |
+
ax.set_yticks(plot_dict[input.stat_id()]["x_range"])
|
634 |
+
#fig.colorbar(plot_dist, ax=ax)
|
635 |
+
#fig.colorbar(plot_dist)
|
636 |
+
#fig.axes[0].invert_yaxis()
|
637 |
+
ax.legend(fontsize='16')
|
638 |
+
fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
|
639 |
+
fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
|
640 |
+
fig.tight_layout()
|
641 |
+
|
642 |
+
|
643 |
+
rolling_batter = App(ui.page_fluid(
|
644 |
+
ui.tags.base(href=base_url),
|
645 |
+
ui.tags.div(
|
646 |
+
{"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
|
647 |
+
ui.tags.style(
|
648 |
+
"""
|
649 |
+
h4 {
|
650 |
+
margin-top: 1em;font-size:35px;
|
651 |
+
}
|
652 |
+
h2{
|
653 |
+
font-size:25px;
|
654 |
+
}
|
655 |
+
"""
|
656 |
+
),
|
657 |
+
shinyswatch.theme.simplex(),
|
658 |
+
ui.tags.h4("TJStats"),
|
659 |
+
ui.tags.i("Baseball Analytics and Visualizations"),
|
660 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
661 |
+
ui.navset_tab(
|
662 |
+
ui.nav_control(
|
663 |
+
ui.a(
|
664 |
+
"Home",
|
665 |
+
href="home/"
|
666 |
+
),
|
667 |
+
),
|
668 |
+
ui.nav_menu(
|
669 |
+
"Batter Charts",
|
670 |
+
ui.nav_control(
|
671 |
+
ui.a(
|
672 |
+
"Batting Rolling",
|
673 |
+
href="rolling_batter/"
|
674 |
+
),
|
675 |
+
ui.a(
|
676 |
+
"Spray",
|
677 |
+
href="spray/"
|
678 |
+
),
|
679 |
+
ui.a(
|
680 |
+
"Decision Value",
|
681 |
+
href="decision_value/"
|
682 |
+
),
|
683 |
+
ui.a(
|
684 |
+
"Damage Model",
|
685 |
+
href="damage_model/"
|
686 |
+
),
|
687 |
+
ui.a(
|
688 |
+
"Batter Scatter",
|
689 |
+
href="batter_scatter/"
|
690 |
+
),
|
691 |
+
ui.a(
|
692 |
+
"EV vs LA Plot",
|
693 |
+
href="ev_angle/"
|
694 |
+
),
|
695 |
+
ui.a(
|
696 |
+
"Statcast Compare",
|
697 |
+
href="statcast_compare/"
|
698 |
+
)
|
699 |
+
),
|
700 |
+
),
|
701 |
+
ui.nav_menu(
|
702 |
+
"Pitcher Charts",
|
703 |
+
ui.nav_control(
|
704 |
+
ui.a(
|
705 |
+
"Pitcher Rolling",
|
706 |
+
href="rolling_pitcher/"
|
707 |
+
),
|
708 |
+
ui.a(
|
709 |
+
"Pitcher Summary",
|
710 |
+
href="pitching_summary_graphic_new/"
|
711 |
+
),
|
712 |
+
ui.a(
|
713 |
+
"Pitcher Scatter",
|
714 |
+
href="pitcher_scatter/"
|
715 |
+
)
|
716 |
+
),
|
717 |
+
)),ui.row(
|
718 |
+
ui.layout_sidebar(
|
719 |
+
|
720 |
+
ui.panel_sidebar(
|
721 |
+
ui.input_select("id", "Select Pitcher",batter_dict,selected=675911,width=1,size=1,selectize=True),
|
722 |
+
ui.input_select("level_id", "Select Level",level_dict,width=1,size=1),
|
723 |
+
ui.input_select("stat_id", "Select Stat",plot_dict_small,width=1,size=1),
|
724 |
+
ui.input_numeric("n", "Rolling Window Size", value=50),
|
725 |
+
ui.input_action_button("go", "Generate",class_="btn-primary"),
|
726 |
+
ui.output_table("result")
|
727 |
+
),
|
728 |
+
|
729 |
+
ui.panel_main(
|
730 |
+
ui.output_plot("plot",height = "1000px",width="1000px")
|
731 |
+
),
|
732 |
+
)),)),server)
|
rolling_pitcher.py
ADDED
@@ -0,0 +1,719 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
|
2 |
+
import datasets
|
3 |
+
from datasets import load_dataset
|
4 |
+
import pandas as pd
|
5 |
+
import numpy as np
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
import seaborn as sns
|
8 |
+
import numpy as np
|
9 |
+
from scipy.stats import gaussian_kde
|
10 |
+
import matplotlib
|
11 |
+
from matplotlib.ticker import MaxNLocator
|
12 |
+
from matplotlib.gridspec import GridSpec
|
13 |
+
from scipy.stats import zscore
|
14 |
+
import math
|
15 |
+
import matplotlib
|
16 |
+
from adjustText import adjust_text
|
17 |
+
import matplotlib.ticker as mtick
|
18 |
+
from shinywidgets import output_widget, render_widget
|
19 |
+
import pandas as pd
|
20 |
+
from configure import base_url
|
21 |
+
import shinyswatch
|
22 |
+
import inflect
|
23 |
+
from matplotlib.pyplot import text
|
24 |
+
|
25 |
+
def percentile(n):
|
26 |
+
def percentile_(x):
|
27 |
+
return np.nanpercentile(x, n)
|
28 |
+
percentile_.__name__ = 'percentile_%s' % n
|
29 |
+
return percentile_
|
30 |
+
|
31 |
+
colour_palette = ['#FFB000','#648FFF','#785EF0',
|
32 |
+
'#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']
|
33 |
+
|
34 |
+
|
35 |
+
|
36 |
+
print('Starting Everything:')
|
37 |
+
# exit_velo_df = milb_a_ev_df.append([triple_a_ev_df,double_a_ev_df,a_high_a_ev_df,single_a_ev_df]).reset_index(drop=True)
|
38 |
+
# player_df_all = mlb_a_player_df.append([triple_a_player_df,double_a_player_df,a_high_a_player_df,single_a_player_df]).reset_index(drop=True)
|
39 |
+
# exit_velo_df = pd.read_csv('exit_velo_df_all.csv',index_col=[0])
|
40 |
+
# player_df_all = pd.read_csv('player_df_all.csv',index_col=[0])
|
41 |
+
|
42 |
+
# pa_df = pd.read_csv('pa_df_all.csv',index_col=[0])
|
43 |
+
# pa_df_full_na = pa_df.dropna()
|
44 |
+
|
45 |
+
### Import Datasets
|
46 |
+
dataset = load_dataset('nesticot/mlb_data', data_files=['mlb_pitch_data_2023.csv',
|
47 |
+
])
|
48 |
+
dataset_train = dataset['train']
|
49 |
+
exit_velo_df_mlb = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
50 |
+
#print(df_2023)
|
51 |
+
exit_velo_df_mlb['level'] = 'MLB'
|
52 |
+
|
53 |
+
### Import Datasets
|
54 |
+
dataset = load_dataset('nesticot/mlb_data', data_files=['aaa_pitch_data_2023.csv',
|
55 |
+
])
|
56 |
+
dataset_train = dataset['train']
|
57 |
+
exit_velo_df_aaa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
58 |
+
#print(df_2023)
|
59 |
+
exit_velo_df_aaa['level'] = 'AAA'
|
60 |
+
|
61 |
+
### Import Datasets
|
62 |
+
dataset = load_dataset('nesticot/mlb_data', data_files=['aa_pitch_data_2023.csv',
|
63 |
+
])
|
64 |
+
dataset_train = dataset['train']
|
65 |
+
exit_velo_df_aa = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
66 |
+
#print(df_2023)
|
67 |
+
exit_velo_df_aa['level'] = 'AA'
|
68 |
+
|
69 |
+
### Import Datasets
|
70 |
+
dataset = load_dataset('nesticot/mlb_data', data_files=['high_a_pitch_data_2023.csv',
|
71 |
+
])
|
72 |
+
dataset_train = dataset['train']
|
73 |
+
exit_velo_df_ha = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
74 |
+
#print(df_2023)
|
75 |
+
exit_velo_df_ha['level'] = 'A+'
|
76 |
+
|
77 |
+
### Import Datasets
|
78 |
+
dataset = load_dataset('nesticot/mlb_data', data_files=['a_pitch_data_2023.csv',
|
79 |
+
])
|
80 |
+
dataset_train = dataset['train']
|
81 |
+
exit_velo_df_a = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)
|
82 |
+
#print(df_2023)
|
83 |
+
exit_velo_df_a['level'] = 'A'
|
84 |
+
|
85 |
+
exit_velo_df = pd.concat([exit_velo_df_mlb,exit_velo_df_aaa,exit_velo_df_aa,exit_velo_df_ha,exit_velo_df_a])
|
86 |
+
# exit_velo_df_copy = exit_velo_df.copy()
|
87 |
+
|
88 |
+
# exit_velo_df = exit_velo_df_copy.copy()
|
89 |
+
|
90 |
+
end_codes = ['strikeout', 'field_out', 'single', 'walk', 'hit_by_pitch',
|
91 |
+
'double', 'sac_fly', 'force_out', 'home_run',
|
92 |
+
'grounded_into_double_play', 'fielders_choice', 'field_error',
|
93 |
+
'triple', 'sac_bunt', 'double_play', 'intent_walk',
|
94 |
+
'fielders_choice_out', 'strikeout_double_play',
|
95 |
+
'sac_fly_double_play', 'catcher_interf', 'other_out']
|
96 |
+
|
97 |
+
|
98 |
+
|
99 |
+
exit_velo_df['pa'] = exit_velo_df.event_type.isin(end_codes)
|
100 |
+
#exit_velo_df['pa'] = 1
|
101 |
+
exit_velo_df['k'] = exit_velo_df.event_type.isin(list(filter(None, [x if 'strikeout' in x else '' for x in exit_velo_df.event_type.fillna('None').unique()])))
|
102 |
+
exit_velo_df['bb'] = exit_velo_df.event_type.isin(list(filter(None, [x if 'walk' in x else '' for x in exit_velo_df.event_type.fillna('None').unique()])))
|
103 |
+
exit_velo_df['k_minus_bb'] = exit_velo_df['k'].astype(np.float32)-exit_velo_df['bb'].astype(np.float32)
|
104 |
+
|
105 |
+
exit_velo_df = exit_velo_df.drop_duplicates(subset=['play_id'])
|
106 |
+
|
107 |
+
|
108 |
+
|
109 |
+
swing_codes = ['Swinging Strike', 'In play, no out',
|
110 |
+
'Foul', 'In play, out(s)',
|
111 |
+
'In play, run(s)', 'Swinging Strike (Blocked)',
|
112 |
+
'Foul Bunt','Foul Tip', 'Missed Bunt','Foul Pitchout','Swinging Pitchout']
|
113 |
+
|
114 |
+
swings_in = ['Swinging Strike', 'In play, no out',
|
115 |
+
'Foul', 'In play, out(s)',
|
116 |
+
'In play, run(s)', 'Swinging Strike (Blocked)',
|
117 |
+
'Foul Bunt','Foul Tip', 'Missed Bunt','Foul Pitchout','Swinging Pitchout']
|
118 |
+
|
119 |
+
swing_strike_codes = ['Swinging Strike',
|
120 |
+
'Swinging Strike (Blocked)','Missed Bunt','Foul Tip','Swinging Pitchout']
|
121 |
+
|
122 |
+
|
123 |
+
contact_codes = ['In play, no out',
|
124 |
+
'Foul', 'In play, out(s)',
|
125 |
+
'In play, run(s)',
|
126 |
+
'Foul Bunt']
|
127 |
+
|
128 |
+
codes_in = ['In play, out(s)',
|
129 |
+
'Swinging Strike',
|
130 |
+
'Ball',
|
131 |
+
'Foul',
|
132 |
+
'In play, no out',
|
133 |
+
'Called Strike',
|
134 |
+
'Foul Tip',
|
135 |
+
'In play, run(s)',
|
136 |
+
'Hit By Pitch',
|
137 |
+
'Ball In Dirt',
|
138 |
+
'Pitchout',
|
139 |
+
'Swinging Strike (Blocked)',
|
140 |
+
'Foul Bunt',
|
141 |
+
'Missed Bunt',
|
142 |
+
'Foul Pitchout',
|
143 |
+
'Intent Ball',
|
144 |
+
'Swinging Pitchout']
|
145 |
+
|
146 |
+
exit_velo_df['in_zone'] = exit_velo_df['zone'] < 10
|
147 |
+
|
148 |
+
|
149 |
+
exit_velo_df = exit_velo_df.drop_duplicates(subset=['play_id'])
|
150 |
+
|
151 |
+
exit_velo_df_codes = exit_velo_df[exit_velo_df.play_description.isin(codes_in)].dropna(subset=['in_zone'])
|
152 |
+
|
153 |
+
exit_velo_df_codes['bip'] = ~exit_velo_df_codes.launch_speed.isna()
|
154 |
+
conditions = [
|
155 |
+
(exit_velo_df_codes['launch_speed'].isna()),
|
156 |
+
(exit_velo_df_codes['launch_speed']*1.5 - exit_velo_df_codes['launch_angle'] >= 117 ) & (exit_velo_df_codes['launch_speed'] + exit_velo_df_codes['launch_angle'] >= 124) & (exit_velo_df_codes['launch_speed'] > 98) & (exit_velo_df_codes['launch_angle'] >= 8) & (exit_velo_df_codes['launch_angle'] <= 50)
|
157 |
+
]
|
158 |
+
|
159 |
+
choices = [False,True]
|
160 |
+
exit_velo_df_codes['barrel'] = np.select(conditions, choices, default=np.nan)
|
161 |
+
|
162 |
+
conditions_ss = [
|
163 |
+
(exit_velo_df_codes['launch_angle'].isna()),
|
164 |
+
(exit_velo_df_codes['launch_angle'] >= 8 ) * (exit_velo_df_codes['launch_angle'] <= 32 )
|
165 |
+
]
|
166 |
+
|
167 |
+
choices_ss = [False,True]
|
168 |
+
exit_velo_df_codes['sweet_spot'] = np.select(conditions_ss, choices_ss, default=np.nan)
|
169 |
+
conditions_hh = [
|
170 |
+
(exit_velo_df_codes['launch_speed'].isna()),
|
171 |
+
(exit_velo_df_codes['launch_speed'] >= 94.5 )
|
172 |
+
]
|
173 |
+
|
174 |
+
choices_hh = [False,True]
|
175 |
+
exit_velo_df_codes['hard_hit'] = np.select(conditions_hh, choices_hh, default=np.nan)
|
176 |
+
|
177 |
+
|
178 |
+
conditions_tb = [
|
179 |
+
(exit_velo_df_codes['event_type']=='single'),
|
180 |
+
(exit_velo_df_codes['event_type']=='double'),
|
181 |
+
(exit_velo_df_codes['event_type']=='triple'),
|
182 |
+
(exit_velo_df_codes['event_type']=='home_run'),
|
183 |
+
]
|
184 |
+
|
185 |
+
choices_tb = [1,2,3,4]
|
186 |
+
|
187 |
+
exit_velo_df_codes['tb'] = np.select(conditions_tb, choices_tb, default=np.nan)
|
188 |
+
|
189 |
+
conditions_woba = [
|
190 |
+
(exit_velo_df_codes['event_type']=='walk'),
|
191 |
+
(exit_velo_df_codes['event_type']=='hit_by_pitch'),
|
192 |
+
(exit_velo_df_codes['event_type']=='single'),
|
193 |
+
(exit_velo_df_codes['event_type']=='double'),
|
194 |
+
(exit_velo_df_codes['event_type']=='triple'),
|
195 |
+
(exit_velo_df_codes['event_type']=='home_run'),
|
196 |
+
]
|
197 |
+
|
198 |
+
choices_woba = [0.705,
|
199 |
+
0.688,
|
200 |
+
0.897,
|
201 |
+
1.233,
|
202 |
+
1.612,
|
203 |
+
2.013]
|
204 |
+
|
205 |
+
exit_velo_df_codes['woba'] = np.select(conditions_woba, choices_woba, default=np.nan)
|
206 |
+
|
207 |
+
|
208 |
+
woba_codes = ['strikeout', 'field_out', 'single', 'walk', 'hit_by_pitch',
|
209 |
+
'double', 'sac_fly', 'force_out', 'home_run',
|
210 |
+
'grounded_into_double_play', 'fielders_choice', 'field_error',
|
211 |
+
'triple', 'sac_bunt', 'double_play',
|
212 |
+
'fielders_choice_out', 'strikeout_double_play',
|
213 |
+
'sac_fly_double_play', 'other_out']
|
214 |
+
|
215 |
+
|
216 |
+
|
217 |
+
|
218 |
+
|
219 |
+
conditions_woba_code = [
|
220 |
+
(exit_velo_df_codes['event_type'].isin(woba_codes))
|
221 |
+
]
|
222 |
+
|
223 |
+
choices_woba_code = [1]
|
224 |
+
|
225 |
+
exit_velo_df_codes['woba_codes'] = np.select(conditions_woba_code, choices_woba_code, default=np.nan)
|
226 |
+
|
227 |
+
|
228 |
+
#exit_velo_df_codes['barrel'] = (exit_velo_df_codes.launch_speed >= 98) & (exit_velo_df_codes.launch_angle >= (26 - (-98 + exit_velo_df_codes.launch_speed))) & (exit_velo_df_codes.launch_angle <= 30 + (-98 + exit_velo_df_codes.launch_speed)) & (exit_velo_df_codes.launch_angle >= 8) & (exit_velo_df_codes.launch_angle <= 50)
|
229 |
+
|
230 |
+
|
231 |
+
|
232 |
+
#exit_velo_df_codes['barrel'] = (exit_velo_df_codes.launch_speed >= 98) & (exit_velo_df_codes.launch_angle >= (26 - (-98 + exit_velo_df_codes.launch_speed))) & (exit_velo_df_codes.launch_angle <= 30 + (-98 + exit_velo_df_codes.launch_speed)) & (exit_velo_df_codes.launch_angle >= 8) & (exit_velo_df_codes.launch_angle <= 50)
|
233 |
+
exit_velo_df_codes['pitches'] = 1
|
234 |
+
exit_velo_df_codes['whiffs'] = [1 if ((x == 'S')|(x == 'W')|(x =='T')) else 0 for x in exit_velo_df_codes.play_code]
|
235 |
+
exit_velo_df_codes['csw'] = [1 if ((x == 'S')|(x == 'W')|(x =='T')|(x == 'C')) else 0 for x in exit_velo_df_codes.play_code]
|
236 |
+
exit_velo_df_codes['swings'] = [1 if x in swings_in else 0 for x in exit_velo_df_codes.play_description]
|
237 |
+
|
238 |
+
exit_velo_df_codes['out_zone'] = exit_velo_df_codes.in_zone == False
|
239 |
+
exit_velo_df_codes['zone_swing'] = (exit_velo_df_codes.in_zone == True)&(exit_velo_df_codes.swings == 1)
|
240 |
+
exit_velo_df_codes['zone_contact'] = (exit_velo_df_codes.in_zone == True)&(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.whiffs == 0)
|
241 |
+
exit_velo_df_codes['ozone_swing'] = (exit_velo_df_codes.in_zone==False)&(exit_velo_df_codes.swings == 1)
|
242 |
+
exit_velo_df_codes['ozone_contact'] = (exit_velo_df_codes.in_zone==False)&(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.whiffs == 0)
|
243 |
+
|
244 |
+
exit_velo_df_codes_summ = exit_velo_df_codes.groupby(['pitcher_id','pitcher_name','level']).agg(
|
245 |
+
pa = ('pa','sum'),
|
246 |
+
k = ('k','sum'),
|
247 |
+
bb = ('bb','sum'),
|
248 |
+
k_minus_bb = ('k_minus_bb','sum'),
|
249 |
+
csw = ('csw','sum'),
|
250 |
+
bip = ('bip','sum'),
|
251 |
+
tb = ('tb','sum'),
|
252 |
+
woba = ('woba','sum'),
|
253 |
+
woba_codes = ('woba_codes','sum'),
|
254 |
+
hard_hit = ('hard_hit','sum'),
|
255 |
+
barrel = ('barrel','sum'),
|
256 |
+
sweet_spot = ('sweet_spot','sum'),
|
257 |
+
max_launch_speed = ('launch_speed','max'),
|
258 |
+
launch_speed_90 = ('launch_speed',percentile(90)),
|
259 |
+
launch_speed = ('launch_speed','mean'),
|
260 |
+
launch_angle = ('launch_angle','mean'),
|
261 |
+
pitches = ('pitches','sum'),
|
262 |
+
swings = ('swings','sum'),
|
263 |
+
in_zone = ('in_zone','sum'),
|
264 |
+
out_zone = ('out_zone','sum'),
|
265 |
+
whiffs = ('whiffs','sum'),
|
266 |
+
zone_swing = ('zone_swing','sum'),
|
267 |
+
zone_contact = ('zone_contact','sum'),
|
268 |
+
ozone_swing = ('ozone_swing','sum'),
|
269 |
+
ozone_contact = ('ozone_contact','sum'),
|
270 |
+
).reset_index()
|
271 |
+
|
272 |
+
|
273 |
+
|
274 |
+
exit_velo_df_codes_summ['k_percent'] = [exit_velo_df_codes_summ.k[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
275 |
+
exit_velo_df_codes_summ['bb_percent'] =[exit_velo_df_codes_summ.bb[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
276 |
+
exit_velo_df_codes_summ['k_minus_bb_percent'] =[exit_velo_df_codes_summ.k_minus_bb[x]/exit_velo_df_codes_summ.pa[x] if exit_velo_df_codes_summ.pa[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
277 |
+
|
278 |
+
|
279 |
+
exit_velo_df_codes_summ['sweet_spot_percent'] = [exit_velo_df_codes_summ.sweet_spot[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
280 |
+
|
281 |
+
exit_velo_df_codes_summ['woba_percent'] = [exit_velo_df_codes_summ.woba[x]/exit_velo_df_codes_summ.woba_codes[x] if exit_velo_df_codes_summ.woba_codes[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
282 |
+
|
283 |
+
|
284 |
+
exit_velo_df_codes_summ['hard_hit_percent'] = [exit_velo_df_codes_summ.hard_hit[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
285 |
+
|
286 |
+
|
287 |
+
|
288 |
+
exit_velo_df_codes_summ['csw_percent'] = [exit_velo_df_codes_summ.csw[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
289 |
+
|
290 |
+
|
291 |
+
exit_velo_df_codes_summ['barrel_percent'] = exit_velo_df_codes_summ.barrel = [exit_velo_df_codes_summ.barrel[x]/exit_velo_df_codes_summ.bip[x] if exit_velo_df_codes_summ.bip[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
292 |
+
|
293 |
+
exit_velo_df_codes_summ['zone_contact_percent'] = [exit_velo_df_codes_summ.zone_contact[x]/exit_velo_df_codes_summ.zone_swing[x] if exit_velo_df_codes_summ.zone_swing[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
294 |
+
|
295 |
+
exit_velo_df_codes_summ['zone_swing_percent'] = [exit_velo_df_codes_summ.zone_swing[x]/exit_velo_df_codes_summ.in_zone[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
296 |
+
|
297 |
+
exit_velo_df_codes_summ['zone_percent'] = [exit_velo_df_codes_summ.in_zone[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
298 |
+
|
299 |
+
exit_velo_df_codes_summ['chase_percent'] = [exit_velo_df_codes_summ.ozone_swing[x]/(exit_velo_df_codes_summ.pitches[x] - exit_velo_df_codes_summ.in_zone[x]) if (exit_velo_df_codes_summ.pitches[x]- exit_velo_df_codes_summ.in_zone[x]) != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
300 |
+
|
301 |
+
exit_velo_df_codes_summ['chase_contact'] = [exit_velo_df_codes_summ.ozone_contact[x]/exit_velo_df_codes_summ.ozone_swing[x] if exit_velo_df_codes_summ.ozone_swing[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
302 |
+
|
303 |
+
exit_velo_df_codes_summ['swing_percent'] = [exit_velo_df_codes_summ.swings[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
304 |
+
|
305 |
+
exit_velo_df_codes_summ['whiff_rate'] = [exit_velo_df_codes_summ.whiffs[x]/exit_velo_df_codes_summ.swings[x] if exit_velo_df_codes_summ.swings[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
306 |
+
|
307 |
+
exit_velo_df_codes_summ['swstr_rate'] = [exit_velo_df_codes_summ.whiffs[x]/exit_velo_df_codes_summ.pitches[x] if exit_velo_df_codes_summ.pitches[x] != 0 else np.nan for x in range(len(exit_velo_df_codes_summ))]
|
308 |
+
|
309 |
+
exit_velo_df_codes_summ = exit_velo_df_codes_summ.dropna(subset=['bip'])
|
310 |
+
|
311 |
+
print('whiffing')
|
312 |
+
print(exit_velo_df_codes_summ['whiff_rate'])
|
313 |
+
|
314 |
+
exit_velo_df_codes_summ.head()
|
315 |
+
|
316 |
+
woba_list = ['woba']
|
317 |
+
pa_list = ['k','bb','k_minus_bb','bb_minus_k']
|
318 |
+
balls_in_play_list = ['hard_hit','launch_speed','launch_speed_90','launch_angle','barrel','sweet_spot']
|
319 |
+
pitches_list = ['zone_percent','swing_percent','sw_str','csw']
|
320 |
+
swings_list = ['whiff_percent']
|
321 |
+
in_zone_pitches_list = ['zone_swing']
|
322 |
+
in_zone_swings_list = ['zone_contact']
|
323 |
+
out_zone_pitches_list = ['chase_percent']
|
324 |
+
out_zone_swings_list = ['chase_contact']
|
325 |
+
|
326 |
+
plot_dict = {
|
327 |
+
'k':{'x_axis':'Plate Appearances','y_axis':'K%','title':'K%','x_value':'k','x_range':[0.0,0.1,0.2,0.3,0.4],'percent':True,'percentile_label':'k_percent','flip_p':False,'percentile':False},
|
328 |
+
'bb':{'x_axis':'Plate Appearances','y_axis':'BB%','title':'BB%','x_value':'bb','x_range':[0.0,0.1,0.2,0.3],'percent':True,'percentile_label':'bb_percent','flip_p':True,'percentile':False},
|
329 |
+
'csw':{'x_axis':'Pitches','y_axis':'CSW%','title':'CSW%','x_value':'csw','x_range':[.2,.25,.3,.35,.4],'percent':True,'percentile_label':'csw_percent','flip_p':False,'percentile':False},
|
330 |
+
'launch_speed':{'x_axis':'Balls In Play','y_axis':'Exit Velocity','title':'Exit Velocity','x_value':'launch_speed','x_range':[85,90,95,100,105],'percent':True,'percentile_label':'launch_speed','flip_p':True,'percentile':False},
|
331 |
+
'launch_speed_90':{'x_axis':'Balls In Play','y_axis':'90th Percentile Exit Velocity','title':'90th Percentile Exit Velocity','x_value':'launch_speed','x_range':[90,95,100,105,110,115,120],'percent':False,'percentile_label':'launch_speed_90','flip_p':True,'percentile':True},
|
332 |
+
'sweet_spot':{'x_axis':'Balls In Play','y_axis':'SweetSpot%','title':'SweetSpot%','x_value':'sweet_spot','x_range':[0.2,0.3,0.4,0.5,0.6],'percent':True,'percentile_label':'sweet_spot_percent','flip_p':True,'percentile':False},
|
333 |
+
'launch_angle':{'x_axis':'Balls In Play','y_axis':'Launch Angle','title':'Launch Angle','x_value':'launch_angle','x_range':[-20,-10,0,10,20],'percent':False,'percentile_label':'launch_angle','flip_p':True,'percentile':False},
|
334 |
+
'barrel':{'x_axis':'Balls In Play','y_axis':'Barrel%','title':'Barrel%','x_value':'barrel','x_range':[0,0.05,0.10,.15,.20,.25,.30],'percent':True,'percentile_label':'barrel_percent','flip_p':False,'percentile':False},
|
335 |
+
'zone_percent':{'x_axis':'Pitches','y_axis':'Zone%','title':'Zone%','x_value':'in_zone','x_range':[0.3,0.4,0.5,0.6,0.7],'percent':True,'percentile_label':'zone_percent','flip_p':True,'percentile':False},
|
336 |
+
'swing_percent':{'x_axis':'Pitches','y_axis':'Swing%','title':'Swing%','x_value':'swings','x_range':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],'percent':True,'percentile_label':'swing_percent','flip_p':False,'percentile':False},
|
337 |
+
'whiff_percent':{'x_axis':'Swings','y_axis':'Whiff%','title':'Whiff%','x_value':'whiffs','x_range':[0.0,0.1,0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'whiff_rate','flip_p':False,'percentile':False},
|
338 |
+
'sw_str':{'x_axis':'Pitches','y_axis':'SwStr%','title':'SwStr%','x_value':'whiffs','x_range':[0.0,0.05,0.1,0.15,0.2,0.25],'percent':True,'percentile_label':'swstr_rate','flip_p':False,'percentile':False},
|
339 |
+
'zone_swing':{'x_axis':'In-Zone Pitches','y_axis':'Z-Swing%','title':'Z-Swing%','x_value':'zone_swing','x_range':[0.3,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1],'percent':True,'percentile_label':'zone_swing_percent','flip_p':True,'percentile':False},
|
340 |
+
'zone_contact':{'x_axis':'In-Zone Swings','y_axis':'Z-Contact%','title':'Z-Contact%','x_value':'zone_contact','x_range':[0.5,0.6,0.7,0.8,0.9,1],'percent':True,'percentile_label':'zone_contact_percent','flip_p':True,'percentile':False},
|
341 |
+
'chase_percent':{'x_axis':'Out-of-Zone Pitches','y_axis':'O-Swing%','title':'O-Swing%','x_value':'ozone_swing','x_range':[0.0,0.1,0.2,0.3,0.4,0.5],'percent':True,'percentile_label':'chase_percent','flip_p':False,'percentile':False},
|
342 |
+
'chase_contact':{'x_axis':'Out-of-Zone Swings','y_axis':'O-Contact%','title':'O-Contact%','x_value':'ozone_contact','x_range':[0.2,0.3,0.4,0.5,0.6,0.7,0.8],'percent':True,'percentile_label':'chase_contact','flip_p':True,'percentile':False},}
|
343 |
+
|
344 |
+
test_df = exit_velo_df.sort_values(by='pitcher_name').drop_duplicates(subset='pitcher_id').reset_index(drop=True)[['pitcher_id','pitcher_name']]#['pitcher'].to_dict()
|
345 |
+
test_df = test_df.set_index('pitcher_id')
|
346 |
+
|
347 |
+
#test_df = test_df[test_df.pitcher == 'Chris Bassitt'].append(test_df[test_df.pitcher != 'Chris Bassitt'])
|
348 |
+
|
349 |
+
batter_dict = test_df['pitcher_name'].to_dict()
|
350 |
+
|
351 |
+
level_dict = {'MLB':'MLB','AAA':'AAA','AA':'AA','A+':'A+','A':'A'}
|
352 |
+
|
353 |
+
plot_dict_small = {
|
354 |
+
'k':'K%',
|
355 |
+
'bb':'BB%',
|
356 |
+
'csw':'CSW%',
|
357 |
+
'launch_speed':'Exit Velocity',
|
358 |
+
'launch_speed_90':'90th Percentile Exit Velocity',
|
359 |
+
'sweet_spot':'SweetSpot%',
|
360 |
+
'launch_angle':'Launch Angle',
|
361 |
+
'zone_percent':'Zone%',
|
362 |
+
'barrel':'Barrel%',
|
363 |
+
'swing_percent':'Swing%',
|
364 |
+
'whiff_percent':'Whiff%',
|
365 |
+
'sw_str':'SwStr%',
|
366 |
+
'zone_swing':'Z-Swing%',
|
367 |
+
'zone_contact':'Z-Contact%',
|
368 |
+
'chase_percent':'O-Swing%',
|
369 |
+
'chase_contact':'O-Contact%',}
|
370 |
+
|
371 |
+
|
372 |
+
def server(input,output,session):
|
373 |
+
|
374 |
+
@output
|
375 |
+
@render.plot(alt="A histogram")
|
376 |
+
@reactive.event(input.go, ignore_none=False)
|
377 |
+
def plot():
|
378 |
+
# np.random.seed(19680801)
|
379 |
+
# x = 100 + 15 * np.random.randn(437)
|
380 |
+
# fig, ax = plt.subplots()
|
381 |
+
# ax.hist(x, input.n(), density=True)
|
382 |
+
# return fig
|
383 |
+
sns.set_theme(style="whitegrid", palette="pastel")
|
384 |
+
if input.id() is "":
|
385 |
+
fig = plt.figure(figsize=(12, 12))
|
386 |
+
fig.text(s='Please Select a Pitcher',x=0.5,y=0.5)
|
387 |
+
return
|
388 |
+
|
389 |
+
swing_min = int(input.n())
|
390 |
+
fig, ax = plt.subplots(1, 1, figsize=(10, 10))
|
391 |
+
|
392 |
+
fig.set_facecolor('white')
|
393 |
+
#ax.set_facecolor('white')
|
394 |
+
#fig.patch.set_facecolor('lightblue')
|
395 |
+
|
396 |
+
print(input.stat_id())
|
397 |
+
|
398 |
+
if input.stat_id() in pa_list:
|
399 |
+
print('we hAVE MADE IT TO THIS PART OF THE CODE')
|
400 |
+
|
401 |
+
|
402 |
+
if input.stat_id() in pa_list:
|
403 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.pa==1)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
404 |
+
divisor_x = 'pa'
|
405 |
+
print('this is short')
|
406 |
+
print(elly_zone_df)
|
407 |
+
|
408 |
+
|
409 |
+
if input.stat_id() in balls_in_play_list:
|
410 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.bip)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
411 |
+
divisor_x = 'bip'
|
412 |
+
#print('this is short')
|
413 |
+
|
414 |
+
if input.stat_id() in balls_in_play_list:
|
415 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.bip)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
416 |
+
divisor_x = 'bip'
|
417 |
+
print('this is short')
|
418 |
+
|
419 |
+
if input.stat_id() in pitches_list:
|
420 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.pitches == 1)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
421 |
+
divisor_x = 'pitches'
|
422 |
+
|
423 |
+
if input.stat_id() in swings_list:
|
424 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.swings == 1)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
425 |
+
divisor_x = 'swings'
|
426 |
+
|
427 |
+
|
428 |
+
if input.stat_id() in in_zone_pitches_list:
|
429 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.in_zone)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
430 |
+
divisor_x = 'in_zone'
|
431 |
+
|
432 |
+
|
433 |
+
if input.stat_id() in in_zone_swings_list:
|
434 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.zone_swing)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
435 |
+
divisor_x = 'zone_swing'
|
436 |
+
|
437 |
+
|
438 |
+
if input.stat_id() in out_zone_pitches_list:
|
439 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.in_zone == False)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
440 |
+
divisor_x = 'out_zone'
|
441 |
+
|
442 |
+
|
443 |
+
if input.stat_id() in out_zone_swings_list:
|
444 |
+
elly_zone_df = exit_velo_df_codes[(exit_velo_df_codes.ozone_swing)&(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())]
|
445 |
+
divisor_x = 'ozone_swing'
|
446 |
+
|
447 |
+
# penguins = sns.load_dataset("penguins")
|
448 |
+
# sns.histplot(data=penguins, x="flipper_length_mm")
|
449 |
+
# print('we made it here:')
|
450 |
+
# print(int(input.id()))
|
451 |
+
# print(input.stat_id())
|
452 |
+
# print(input.level_id())
|
453 |
+
# print(exit_velo_df_codes[(exit_velo_df_codes.pitcher_id == int(input.id()))&(exit_velo_df_codes.level==input.level_id())])
|
454 |
+
# print(exit_velo_df_codes.columns)
|
455 |
+
# print(elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum())
|
456 |
+
|
457 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ.copy()
|
458 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.set_index('pitcher_id','pitcher','level')
|
459 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new[divisor_x] >= int(input.n())]
|
460 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.level==input.level_id()]
|
461 |
+
|
462 |
+
exit_velo_df_codes_summ_rank = exit_velo_df_codes_summ_new.rank(method='max',ascending=False)
|
463 |
+
exit_velo_df_codes_summ_rank.columns = exit_velo_df_codes_summ_rank.columns+['_rank']
|
464 |
+
|
465 |
+
exit_velo_df_codes_summ_rank_percent = exit_velo_df_codes_summ_new.rank(pct=True)
|
466 |
+
exit_velo_df_codes_summ_rank_percent.columns = exit_velo_df_codes_summ_rank_percent.columns+['_percent']
|
467 |
+
|
468 |
+
|
469 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.reset_index()
|
470 |
+
exit_velo_df_codes_summ_rank = exit_velo_df_codes_summ_rank.reset_index()
|
471 |
+
exit_velo_df_codes_summ_rank_percent = exit_velo_df_codes_summ_rank_percent.reset_index()
|
472 |
+
print('Table columns:')
|
473 |
+
|
474 |
+
exit_velo_df_codes_summ_new.pitcher_id = exit_velo_df_codes_summ_new.pitcher_id.astype(int)
|
475 |
+
exit_velo_df_codes_summ_rank.pitcher_id = exit_velo_df_codes_summ_rank.pitcher_id.astype(int)
|
476 |
+
exit_velo_df_codes_summ_rank_percent.pitcher_id = exit_velo_df_codes_summ_rank_percent.pitcher_id.astype(int)
|
477 |
+
|
478 |
+
print('Table columns2:')
|
479 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.merge(exit_velo_df_codes_summ_rank,left_on=['pitcher_id'],right_on=['pitcher_id'],how='left',suffixes=['','_rank'])
|
480 |
+
|
481 |
+
exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new.merge(exit_velo_df_codes_summ_rank_percent,left_on=['pitcher_id'],right_on=['pitcher_id'],how='left',suffixes=['','_percent'])
|
482 |
+
|
483 |
+
|
484 |
+
print(exit_velo_df_codes_summ_new)
|
485 |
+
print(exit_velo_df_codes_summ_rank)
|
486 |
+
print(exit_velo_df_codes_summ_rank_percent)
|
487 |
+
|
488 |
+
|
489 |
+
|
490 |
+
|
491 |
+
#sns.scatterplot(x=data_df.launch_speed_90,y=data_df.zone_contact,color=colour_palette[0],s=75,label=int(input.id()))
|
492 |
+
|
493 |
+
exit_velo_df_codes_summ_new_select = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.pitcher_id == int(input.id())].reset_index(drop=True)
|
494 |
+
print('whiffing')
|
495 |
+
print(exit_velo_df_codes)
|
496 |
+
print('Player _df:')
|
497 |
+
print(exit_velo_df_codes_summ_new_select)
|
498 |
+
|
499 |
+
if len(exit_velo_df_codes_summ_new_select) < 1:
|
500 |
+
ax.text(x=0.5,y=0.5,s='Please Select Different Parameters to Produce a plot',fontsize=18,ha='center')
|
501 |
+
return
|
502 |
+
|
503 |
+
p = inflect.engine()
|
504 |
+
|
505 |
+
exit_velo_df_codes_summ_new_select = exit_velo_df_codes_summ_new_select.loc[:,~exit_velo_df_codes_summ_new_select.columns.duplicated(keep='last')].copy()
|
506 |
+
print('Table for the player:')
|
507 |
+
print(list(exit_velo_df_codes_summ_new_select.columns))
|
508 |
+
print(plot_dict[input.stat_id()]["percentile_label"])
|
509 |
+
print(plot_dict[input.stat_id()]["percentile_label"]+'_percent')
|
510 |
+
print(exit_velo_df_codes_summ_new_select)
|
511 |
+
print(1*plot_dict[input.stat_id()]["flip_p"])
|
512 |
+
print(round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))
|
513 |
+
print((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)
|
514 |
+
|
515 |
+
# print(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+'_percent'])
|
516 |
+
|
517 |
+
if plot_dict[input.stat_id()]['percent']:
|
518 |
+
label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum():.1%}'
|
519 |
+
label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
|
520 |
+
#label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%}'
|
521 |
+
ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
|
522 |
+
|
523 |
+
else:
|
524 |
+
label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum():.1f}'
|
525 |
+
label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1f} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
|
526 |
+
#label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1f}'
|
527 |
+
#ax.yaxis.set_major_formatter(mtick.int)
|
528 |
+
|
529 |
+
|
530 |
+
if plot_dict[input.stat_id()]['percentile']:
|
531 |
+
label_1=f'{input.level_id()} Average {exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].quantile(0.9):.1f}'
|
532 |
+
label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].quantile(0.9):.1f} ({p.ordinal(abs(int((1*plot_dict[input.stat_id()]["flip_p"]-round(exit_velo_df_codes_summ_new_select[plot_dict[input.stat_id()]["percentile_label"]+"_percent"][0],2))*100)))} Percentile)'
|
533 |
+
#label_2=f'{batter_dict[int(input.id())]} Average {elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum():.1%}'
|
534 |
+
#ax.yaxis.set_major_formatter(mtick.int)
|
535 |
+
|
536 |
+
|
537 |
+
print(plot_dict[input.stat_id()]["x_value"])
|
538 |
+
print(divisor_x)
|
539 |
+
|
540 |
+
# exit_velo_df_codes_summ_new = exit_velo_df_codes_summ.copy()
|
541 |
+
# exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.balls_in_play >= int(input.n())]
|
542 |
+
# exit_velo_df_codes_summ_new = exit_velo_df_codes_summ_new[exit_velo_df_codes_summ_new.level==input.level_id()]
|
543 |
+
|
544 |
+
|
545 |
+
print('this is here:')
|
546 |
+
print(exit_velo_df_codes_summ_new.head())
|
547 |
+
print(exit_velo_df_codes_summ_new.columns)
|
548 |
+
|
549 |
+
|
550 |
+
if plot_dict[input.stat_id()]["flip_p"] == False:
|
551 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[2],linestyle='dotted',alpha=0.5)
|
552 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[3],linestyle='dotted',alpha=0.5)
|
553 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[4],linestyle='dotted',alpha=0.5)
|
554 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[5],linestyle='dotted',alpha=0.5)
|
555 |
+
|
556 |
+
|
557 |
+
hard_hit_dates = [(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),
|
558 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),
|
559 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),
|
560 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1)]
|
561 |
+
hard_hit_text = ['90th %','75th %','25th %','10th %']
|
562 |
+
for i, x in enumerate(hard_hit_dates):
|
563 |
+
text(min(input.n()+input.n()/100,+input.n()+1), x ,hard_hit_text[i], rotation=0, ha='left',
|
564 |
+
bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[2+i], pad=2))
|
565 |
+
|
566 |
+
|
567 |
+
|
568 |
+
if plot_dict[input.stat_id()]["flip_p"] == True:
|
569 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[2],linestyle='dotted',alpha=0.5)
|
570 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[3],linestyle='dotted',alpha=0.5)
|
571 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[4],linestyle='dotted',alpha=0.5)
|
572 |
+
ax.hlines(y=(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[5],linestyle='dotted',alpha=0.5)
|
573 |
+
|
574 |
+
hard_hit_dates = [(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.9),
|
575 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.75),
|
576 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.25),
|
577 |
+
(exit_velo_df_codes_summ_new[plot_dict[input.stat_id()]["percentile_label"]]).quantile(0.1)]
|
578 |
+
hard_hit_text = ['10th %','25th %','75th %','90th %']
|
579 |
+
for i, x in enumerate(hard_hit_dates):
|
580 |
+
text(min(input.n()+input.n()/100,input.n()+input.n()+3), x ,hard_hit_text[i], rotation=0, ha='left',
|
581 |
+
bbox=dict(facecolor='white',alpha=0.5, edgecolor=colour_palette[2+i], pad=2))
|
582 |
+
|
583 |
+
|
584 |
+
|
585 |
+
|
586 |
+
|
587 |
+
|
588 |
+
if plot_dict[input.stat_id()]["percentile"] == False:
|
589 |
+
ax.hlines(y=exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].sum()/exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][divisor_x].sum(),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[1],linestyle='-.',label=label_1)
|
590 |
+
|
591 |
+
ax.hlines(y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].sum()/elly_zone_df[divisor_x].sum(),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[0],linestyle='--',label=label_2)
|
592 |
+
|
593 |
+
sns.lineplot(x=range(1,len(elly_zone_df)+1),y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).rolling(window=swing_min).sum()/swing_min,color=colour_palette[0],linewidth=3,ax=ax)
|
594 |
+
|
595 |
+
|
596 |
+
|
597 |
+
if plot_dict[input.stat_id()]["percentile"] == True:
|
598 |
+
|
599 |
+
ax.hlines(y=exit_velo_df_codes[exit_velo_df_codes.level == input.level_id()][plot_dict[input.stat_id()]["x_value"]].quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[1],linestyle='-.',label=label_1)
|
600 |
+
|
601 |
+
ax.hlines(y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).quantile(0.9),xmin=swing_min,xmax=len(elly_zone_df),color=colour_palette[0],linestyle='--',label=label_2)
|
602 |
+
|
603 |
+
sns.lineplot(x=range(1,len(elly_zone_df)+1),y=elly_zone_df[plot_dict[input.stat_id()]["x_value"]].fillna(0).rolling(window=swing_min).quantile(0.9),color=colour_palette[0],linewidth=3,ax=ax)
|
604 |
+
|
605 |
+
|
606 |
+
#ax.set_xlim(input.n(),exit_velo_df_small.pitch.max())
|
607 |
+
#plt.yticks([0,0.2,0.4,0.6,0.8,1])
|
608 |
+
#ax.set_ylim(math.floor((min(exit_velo_df_codes_summ.zone_contact)/5)*100)*5/100,1)
|
609 |
+
ax.set_xlim(math.floor(swing_min),len(elly_zone_df))
|
610 |
+
ax.set_title(f'{batter_dict[int(input.id())]} - {input.level_id()} - {swing_min} {plot_dict[input.stat_id()]["x_axis"]} Rolling {plot_dict[input.stat_id()]["title"]}', fontsize=16,fontname='Century Gothic',)
|
611 |
+
#vals = ax.get_yticks()
|
612 |
+
ax.set_xlabel(plot_dict[input.stat_id()]['x_axis'], fontsize=16,fontname='Century Gothic')
|
613 |
+
ax.set_ylabel(plot_dict[input.stat_id()]['y_axis'], fontsize=16,fontname='Century Gothic')
|
614 |
+
|
615 |
+
#fig.axes[0].invert_yaxis()
|
616 |
+
|
617 |
+
#fig.subplots_adjust(wspace=.02, hspace=.02)
|
618 |
+
#ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: int(x)))
|
619 |
+
ax.set_yticks(plot_dict[input.stat_id()]["x_range"])
|
620 |
+
#fig.colorbar(plot_dist, ax=ax)
|
621 |
+
#fig.colorbar(plot_dist)
|
622 |
+
#fig.axes[0].invert_yaxis()
|
623 |
+
ax.legend(fontsize='16')
|
624 |
+
fig.text(x=0.03,y=0.02,s='By: @TJStats',fontname='Century Gothic')
|
625 |
+
fig.text(x=1-0.03,y=0.02,s='Data: MLB',ha='right',fontname='Century Gothic')
|
626 |
+
fig.tight_layout()
|
627 |
+
|
628 |
+
|
629 |
+
rolling_pitcher = App(ui.page_fluid(
|
630 |
+
ui.tags.base(href=base_url),
|
631 |
+
ui.tags.div(
|
632 |
+
{"style": "width:90%;margin: 0 auto;max-width: 1600px;"},
|
633 |
+
ui.tags.style(
|
634 |
+
"""
|
635 |
+
h4 {
|
636 |
+
margin-top: 1em;font-size:35px;
|
637 |
+
}
|
638 |
+
h2{
|
639 |
+
font-size:25px;
|
640 |
+
}
|
641 |
+
"""
|
642 |
+
),
|
643 |
+
shinyswatch.theme.simplex(),
|
644 |
+
ui.tags.h4("TJStats"),
|
645 |
+
ui.tags.i("Baseball Analytics and Visualizations"),
|
646 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
647 |
+
ui.navset_tab(
|
648 |
+
ui.nav_control(
|
649 |
+
ui.a(
|
650 |
+
"Home",
|
651 |
+
href="home/"
|
652 |
+
),
|
653 |
+
),
|
654 |
+
ui.nav_menu(
|
655 |
+
"Batter Charts",
|
656 |
+
ui.nav_control(
|
657 |
+
ui.a(
|
658 |
+
"Batting Rolling",
|
659 |
+
href="rolling_batter/"
|
660 |
+
),
|
661 |
+
ui.a(
|
662 |
+
"Spray",
|
663 |
+
href="spray/"
|
664 |
+
),
|
665 |
+
ui.a(
|
666 |
+
"Decision Value",
|
667 |
+
href="decision_value/"
|
668 |
+
),
|
669 |
+
ui.a(
|
670 |
+
"Damage Model",
|
671 |
+
href="damage_model/"
|
672 |
+
),
|
673 |
+
ui.a(
|
674 |
+
"Batter Scatter",
|
675 |
+
href="batter_scatter/"
|
676 |
+
),
|
677 |
+
ui.a(
|
678 |
+
"EV vs LA Plot",
|
679 |
+
href="ev_angle/"
|
680 |
+
),
|
681 |
+
ui.a(
|
682 |
+
"Statcast Compare",
|
683 |
+
href="statcast_compare/"
|
684 |
+
)
|
685 |
+
),
|
686 |
+
),
|
687 |
+
ui.nav_menu(
|
688 |
+
"Pitcher Charts",
|
689 |
+
ui.nav_control(
|
690 |
+
ui.a(
|
691 |
+
"Pitcher Rolling",
|
692 |
+
href="rolling_pitcher/"
|
693 |
+
),
|
694 |
+
ui.a(
|
695 |
+
"Pitcher Summary",
|
696 |
+
href="pitching_summary_graphic_new/"
|
697 |
+
),
|
698 |
+
ui.a(
|
699 |
+
"Pitcher Scatter",
|
700 |
+
href="pitcher_scatter/"
|
701 |
+
)
|
702 |
+
),
|
703 |
+
)),ui.row(
|
704 |
+
ui.layout_sidebar(
|
705 |
+
|
706 |
+
ui.panel_sidebar(
|
707 |
+
ui.input_select("id", "Select Pitcher",batter_dict,selected=675911,width=1,size=1,selectize=True),
|
708 |
+
ui.input_select("level_id", "Select Level",level_dict,width=1,size=1),
|
709 |
+
ui.input_select("stat_id", "Select Stat",plot_dict_small,width=1,size=1),
|
710 |
+
ui.input_numeric("n", "Rolling Window Size", value=50),
|
711 |
+
ui.input_action_button("go", "Generate",class_="btn-primary"),
|
712 |
+
|
713 |
+
ui.output_table("result")
|
714 |
+
),
|
715 |
+
|
716 |
+
ui.panel_main(
|
717 |
+
ui.output_plot("plot",height = "1000px",width="1000px")
|
718 |
+
),
|
719 |
+
)),)),server)
|
spray.py
CHANGED
@@ -81,6 +81,7 @@ batter_dict = df_2023_bip.sort_values('batter_name').set_index('batter_id')['bat
|
|
81 |
def server(input,output,session):
|
82 |
@output
|
83 |
@render.plot(alt="plot")
|
|
|
84 |
def plot():
|
85 |
|
86 |
batter_id_select = int(input.batter_id())
|
@@ -342,8 +343,9 @@ spray = App(ui.page_fluid(
|
|
342 |
"""
|
343 |
),
|
344 |
shinyswatch.theme.simplex(),
|
345 |
-
ui.tags.h4("
|
346 |
-
ui.tags.i("
|
|
|
347 |
ui.navset_tab(
|
348 |
ui.nav_control(
|
349 |
ui.a(
|
@@ -354,7 +356,11 @@ spray = App(ui.page_fluid(
|
|
354 |
ui.nav_menu(
|
355 |
"Batter Charts",
|
356 |
ui.nav_control(
|
357 |
-
|
|
|
|
|
|
|
|
|
358 |
"Spray",
|
359 |
href="spray/"
|
360 |
),
|
@@ -373,48 +379,29 @@ spray = App(ui.page_fluid(
|
|
373 |
ui.a(
|
374 |
"EV vs LA Plot",
|
375 |
href="ev_angle/"
|
|
|
|
|
|
|
|
|
376 |
)
|
377 |
),
|
378 |
),
|
379 |
ui.nav_menu(
|
380 |
-
"
|
381 |
ui.nav_control(
|
382 |
ui.a(
|
383 |
-
"
|
384 |
-
href="
|
385 |
),
|
386 |
ui.a(
|
387 |
-
"
|
388 |
-
href="
|
389 |
),
|
390 |
ui.a(
|
391 |
-
"
|
392 |
-
href="
|
393 |
)
|
394 |
),
|
395 |
-
),ui.nav_menu(
|
396 |
-
"Team Charts",
|
397 |
-
ui.nav_control(
|
398 |
-
ui.a(
|
399 |
-
"Team xG Rates",
|
400 |
-
href="team-xg-rates/"
|
401 |
-
),
|
402 |
-
),
|
403 |
-
),ui.nav_control(
|
404 |
-
ui.a(
|
405 |
-
"Games",
|
406 |
-
href="games/"
|
407 |
-
),
|
408 |
-
),ui.nav_control(
|
409 |
-
ui.a(
|
410 |
-
"About",
|
411 |
-
href="about/"
|
412 |
-
),
|
413 |
-
),ui.nav_control(
|
414 |
-
ui.a(
|
415 |
-
"Articles",
|
416 |
-
href="articles/"
|
417 |
-
),
|
418 |
)),ui.row(
|
419 |
ui.layout_sidebar(
|
420 |
|
@@ -424,7 +411,9 @@ spray = App(ui.page_fluid(
|
|
424 |
batter_dict,
|
425 |
width=1,
|
426 |
size=1,
|
427 |
-
selectize=True)
|
|
|
|
|
428 |
|
429 |
ui.panel_main(
|
430 |
ui.navset_tab(
|
|
|
81 |
def server(input,output,session):
|
82 |
@output
|
83 |
@render.plot(alt="plot")
|
84 |
+
@reactive.event(input.go, ignore_none=False)
|
85 |
def plot():
|
86 |
|
87 |
batter_id_select = int(input.batter_id())
|
|
|
343 |
"""
|
344 |
),
|
345 |
shinyswatch.theme.simplex(),
|
346 |
+
ui.tags.h4("TJStats"),
|
347 |
+
ui.tags.i("Baseball Analytics and Visualizations"),
|
348 |
+
ui.markdown("""<a href='https://www.patreon.com/tj_stats'>Support me on Patreon for Access to 2024 Apps</a><sup>1</sup>"""),
|
349 |
ui.navset_tab(
|
350 |
ui.nav_control(
|
351 |
ui.a(
|
|
|
356 |
ui.nav_menu(
|
357 |
"Batter Charts",
|
358 |
ui.nav_control(
|
359 |
+
ui.a(
|
360 |
+
"Batting Rolling",
|
361 |
+
href="rolling_batter/"
|
362 |
+
),
|
363 |
+
ui.a(
|
364 |
"Spray",
|
365 |
href="spray/"
|
366 |
),
|
|
|
379 |
ui.a(
|
380 |
"EV vs LA Plot",
|
381 |
href="ev_angle/"
|
382 |
+
),
|
383 |
+
ui.a(
|
384 |
+
"Statcast Compare",
|
385 |
+
href="statcast_compare/"
|
386 |
)
|
387 |
),
|
388 |
),
|
389 |
ui.nav_menu(
|
390 |
+
"Pitcher Charts",
|
391 |
ui.nav_control(
|
392 |
ui.a(
|
393 |
+
"Pitcher Rolling",
|
394 |
+
href="rolling_pitcher/"
|
395 |
),
|
396 |
ui.a(
|
397 |
+
"Pitcher Summary",
|
398 |
+
href="pitching_summary_graphic_new/"
|
399 |
),
|
400 |
ui.a(
|
401 |
+
"Pitcher Scatter",
|
402 |
+
href="pitcher_scatter/"
|
403 |
)
|
404 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
405 |
)),ui.row(
|
406 |
ui.layout_sidebar(
|
407 |
|
|
|
411 |
batter_dict,
|
412 |
width=1,
|
413 |
size=1,
|
414 |
+
selectize=True),
|
415 |
+
ui.input_action_button("go", "Generate",class_="btn-primary",
|
416 |
+
)),
|
417 |
|
418 |
ui.panel_main(
|
419 |
ui.navset_tab(
|
statcast_20152023.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
statcast_compare.py
ADDED
@@ -0,0 +1,701 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
|
2 |
+
import datasets
|
3 |
+
from datasets import load_dataset
|
4 |
+
import pandas as pd
|
5 |
+
import numpy as np
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
import seaborn as sns
|
8 |
+
import numpy as np
|
9 |
+
from scipy.stats import gaussian_kde
|
10 |
+
import matplotlib
|
11 |
+
from matplotlib.ticker import MaxNLocator
|
12 |
+
from matplotlib.gridspec import GridSpec
|
13 |
+
from scipy.stats import zscore
|
14 |
+
import math
|
15 |
+
import matplotlib
|
16 |
+
from adjustText import adjust_text
|
17 |
+
import matplotlib.ticker as mtick
|
18 |
+
from shinywidgets import output_widget, render_widget
|
19 |
+
import pandas as pd
|
20 |
+
from configure import base_url
|
21 |
+
import shinyswatch
|
22 |
+
import inflect
|
23 |
+
from matplotlib.pyplot import text
|
24 |
+
|
25 |
+
def percentile(n):
|
26 |
+
def percentile_(x):
|
27 |
+
return np.nanpercentile(x, n)
|
28 |
+
percentile_.__name__ = 'percentile_%s' % n
|
29 |
+
return percentile_
|
30 |
+
|
31 |
+
from matplotlib.colors import Normalize
|
32 |
+
|
33 |
+
print('Running')
|
34 |
+
|
35 |
+
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["#4285F4","white","#FBBC04"])
|
36 |
+
df = pd.read_csv('statcast_20152023.csv')
|
37 |
+
df['last_name'] = df['last_name, first_name'].str.split(',').str[0]
|
38 |
+
df['first_name'] = df['last_name, first_name'].str.split(',').str[1].str.strip(' ')
|
39 |
+
df['name'] = df['first_name'] +' ' +df['last_name']
|
40 |
+
|
41 |
+
df[[x for x in df if x[-7:] == 'percent']] = df[[x for x in df if x[-7:] == 'percent']]/100
|
42 |
+
df['barrel_batted_rate'] = df['barrel_batted_rate']/100
|
43 |
+
|
44 |
+
player_dict = df[['player_id','name']].drop_duplicates().sort_values('name').set_index('player_id').to_dict()['name']
|
45 |
+
|
46 |
+
df_median = df[df.pa>=400]
|
47 |
+
|
48 |
+
format_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 |
+
|
126 |
+
column_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 |
+
|
129 |
+
def server(input,output,session):
|
130 |
+
|
131 |
+
|
132 |
+
@output
|
133 |
+
@render.text
|
134 |
+
@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 |
+
@output
|
158 |
+
@render.text
|
159 |
+
@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 |
+
return
|
163 |
+
|
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 |
+
@output
|
197 |
+
@render.text
|
198 |
+
@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 |
+
@output
|
206 |
+
@render.text
|
207 |
+
@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 |
+
return
|
211 |
+
|
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 |
+
@output
|
223 |
+
@render.text
|
224 |
+
@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 |
+
@output
|
231 |
+
@render.text
|
232 |
+
@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 |
+
return
|
236 |
+
|
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 |
+
@output
|
248 |
+
@render.text
|
249 |
+
@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 |
+
@output
|
256 |
+
@render.text
|
257 |
+
@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 |
+
return
|
261 |
+
|
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 |
+
@output
|
272 |
+
@render.table
|
273 |
+
@reactive.event(input.go, ignore_none=False)
|
274 |
+
def statcast_compare():
|
275 |
+
if input.player_id() == '':
|
276 |
+
return
|
277 |
+
|
278 |
+
if len(str(input.player_id())) == 0:
|
279 |
+
return
|
280 |
+
if len(str(input.player_id_2())) == 0:
|
281 |
+
return
|
282 |
+
|
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 |
+
return
|
302 |
+
|
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 |
+
return
|
308 |
+
|
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).T
|
311 |
+
|
312 |
+
|
313 |
+
print('test')
|
314 |
+
print(sum(df.player_id == input.player_id()))
|
315 |
+
df_compare.columns = season_pick
|
316 |
+
|
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_style
|
382 |
+
|
383 |
+
|
384 |
+
|
385 |
+
@output
|
386 |
+
@render.table
|
387 |
+
@reactive.event(input.go, ignore_none=False)
|
388 |
+
def statcast_compare_2():
|
389 |
+
|
390 |
+
# if input.player_id() == 0:
|
391 |
+
# return
|
392 |
+
if type(input.player_id()) is int or input.player_id()=='':
|
393 |
+
return
|
394 |
+
|
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 |
+
# return
|
399 |
+
|
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 |
+
# return
|
419 |
+
|
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 |
+
return
|
428 |
+
if str(input.player_id()) == str(input.player_id_2()) and str(input.season_1()) == str(input.season_2()):
|
429 |
+
return
|
430 |
+
|
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).T
|
434 |
+
|
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_style
|
490 |
+
|
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_style
|
501 |
+
|
502 |
+
@output
|
503 |
+
@render.table
|
504 |
+
@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_style
|
537 |
+
|
538 |
+
@output
|
539 |
+
@render.table
|
540 |
+
@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_style
|
575 |
+
# 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 |
+
|
579 |
+
statcast_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",
|
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)
|
statcast_pitch_summary.csv
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
,pitch_name,pitches,swings,whiff,csw,out_zone,chase,pitch_velocity,spin_rate,exit_velocity,pitch_velocity_std,spin_rate_std,exit_velocity_std,pfx_x,pfx_z,extension,release_x,release_z,zone_percent,xwOBA,pitch,whiff_rate,csw_rate,chase_percent,pitch_percent
|
2 |
+
0,4-Seam Fastball,230929.0,111299.0,24750.0,64996.0,105087,25567.0,94.19093625756828,2282.8438012609377,83.74365680792623,2.4786532395740055,166.7404996241093,13.467960960835905,-0.2653508555392431,1.3087501028552622,6.512675018103453,-0.8060702360690668,5.833463472307033,0.5448427871770111,0.40198316316909566,,0.2223739656241296,0.28145447301984594,0.243293651926499,0.16085685746486877
|
3 |
+
1,Slider,126071.0,60687.0,20453.0,39120.0,69402,21991.0,85.11885798600645,2408.7220697139937,81.64673187847214,3.3273820646525754,254.3806272913541,15.94645745407159,0.25503050110660713,0.1340754872641023,6.415392251066094,-0.9940703310302156,5.742570977542618,0.4494213578063155,0.36039362273548514,,0.3370244039085801,0.3103013381348605,0.31686406731794475,0.08781653615376793
|
4 |
+
2,Sinker,110881.0,50193.0,7235.0,31514.0,49458,12493.0,93.31904199285688,2150.2386523758873,83.87984289276808,2.892717921060944,168.33013944189094,15.447823735442787,-0.5781807359307359,0.6536634199134199,6.419496957697072,-0.8578973665223666,5.636833874458874,0.5539452205517627,0.3630746667835847,,0.14414360568206722,0.28421460845410845,0.25259816409883135,0.07723572705273965
|
5 |
+
3,Changeup,78512.0,39518.0,12295.0,19910.0,47870,16185.0,85.45176986078029,1789.468468007219,80.71900963512873,3.4111014745537154,305.36591130209456,16.47019099576866,-0.396463507833397,0.474831486434849,6.458975699314073,-0.568952999617883,5.749977200356643,0.3902588139392704,0.34566906762705085,,0.3111240447391062,0.2535918076217648,0.3381031961562565,0.054688642800522144
|
6 |
+
4,Cutter,55805.0,27665.0,6714.0,15683.0,27167,7768.0,89.22766029952471,2387.958273978037,82.31847715218278,3.2905648267822385,206.66655976707708,15.415902082385763,0.12278660592581965,0.639279718774661,6.36341704390314,-0.9624070952005166,5.842086232871798,0.5123017650748141,0.36965886615098403,,0.24268931863365262,0.28103216557656124,0.2859351419000994,0.03887176115094684
|
7 |
+
5,Curveball,51679.0,21553.0,6701.0,16148.0,29175,8309.0,79.2795228051164,2529.729456384324,81.09545750923714,3.845559953202193,303.1060225022905,16.897464856909227,0.3128750556318814,-0.7793070686352289,6.357315891472868,-0.6464800015480177,5.931275179473287,0.4354573424408367,0.36297365675534693,,0.3109079942467406,0.31246734650438285,0.2847986289631534,0.03599773755971296
|
8 |
+
6,Sweeper,30983.0,14387.0,4714.0,9472.0,17497,5501.0,81.87104541200013,2573.167409915042,79.5424205782126,2.9169456469962296,242.3612934233338,16.05341300254306,0.626698834844915,0.10297743924087403,6.440382997994436,-1.0498637962753767,5.449356421263274,0.4352709550398606,0.34346382040489076,,0.32765691249044276,0.3057160378271956,0.3143967537292107,0.021581646371109865
|
9 |
+
7,Split-Finger,15570.0,8130.0,2828.0,3995.0,9884,3589.0,86.61461143224149,1356.4218921536972,81.58194705023973,3.210384887902199,330.0858364738183,17.118239986777326,-0.8700796403339756,0.2713461785484907,6.4252829218107,-1.7122678227360306,5.850094412331407,0.36518946692357096,0.3443354243542435,,0.34784747847478475,0.25658317276814385,0.36311210036422503,0.01084550346958592
|
10 |
+
8,Knuckle Curve,12154.0,5439.0,1805.0,3752.0,7059,2292.0,81.43667105479678,2469.610794751176,81.99410356180783,3.3353055020455646,284.5354624498529,16.265551322730666,0.4980812901102518,-0.7952756294224125,6.3597479614529275,-1.0805693598815205,5.938246667763699,0.41920355438538753,0.37180873734683006,,0.33186247471961755,0.30870495310185947,0.3246918827029324,0.00846604040907818
|
11 |
+
9,Slurve,2330.0,984.0,273.0,767.0,1262,365.0,82.12420600858368,2524.4065886432595,80.52035398230089,2.558741806283268,306.1556785170377,16.356798085102586,0.3707424892703863,-0.25004291845493565,6.026536796536797,-0.7101459227467811,5.605351931330472,0.45836909871244635,0.3978255208333333,,0.2774390243902439,0.32918454935622316,0.28922345483359746,0.00162299441773508
|
12 |
+
10,Other,1099.0,553.0,39.0,192.0,576,161.0,66.9722474977252,1636.2980856882407,84.21832669322708,9.112324944344907,265.8499548102452,16.429985208785887,-0.4521565059144677,1.3244676979071883,4.820714940421631,-1.647452229299363,6.3448225659690625,0.47588717015468607,0.44125391849529777,,0.0705244122965642,0.17470427661510465,0.2795138888888889,0.0007655239764338425
|
13 |
+
11,Forkball,778.0,358.0,199.0,247.0,590,214.0,82.96773778920308,1079.639686684073,77.80469798657718,1.5813830914720153,180.5812639240975,16.635375590080205,-0.6015809768637532,0.10609254498714654,6.465681233933163,-1.8326992287917736,5.97146529562982,0.2416452442159383,0.31482432432432433,,0.5558659217877095,0.31748071979434445,0.36271186440677966,0.0005419268914154044
|
14 |
+
12,Eephus,522.0,226.0,7.0,79.0,339,92.0,47.99444444444444,1168.9176245210729,85.22054794520548,6.869976110853827,235.4947805488423,16.73137435394275,0.008371647509578542,1.393352490421456,4.532567049808429,-1.4747318007662835,6.651915708812261,0.3505747126436782,0.3907325581395349,,0.030973451327433628,0.15134099616858238,0.2713864306784661,0.00036360647470288056
|
15 |
+
13,Knuckleball,190.0,81.0,20.0,44.0,97,18.0,75.68631578947368,337.4578947368421,79.05087719298245,4.960630411514611,393.10082691661756,16.785560576270584,-0.2866315789473684,-0.16442105263157897,6.280526315789474,-1.598,5.561368421052632,0.48947368421052634,0.3248333333333333,,0.24691358024691357,0.23157894736842105,0.18556701030927836,0.00013234718427882627
|
16 |
+
14,Screwball,74.0,34.0,5.0,10.0,41,10.0,80.18648648648649,2094.9594594594596,83.2423076923077,1.450679984566931,133.61049905317344,13.026010074521608,-0.7537837837837837,-0.37148648648648647,6.20945945945946,-1.2633783783783783,6.0683783783783785,0.44594594594594594,0.40330000000000005,,0.14705882352941177,0.13513513513513514,0.24390243902439024,5.1545745455963914e-05
|
17 |
+
15,Slow Curve,51.0,21.0,1.0,2.0,36,8.0,59.17450980392157,2058.7551020408164,77.07000000000001,11.825867293983224,429.4794101880903,17.29673227428872,0.06490196078431372,-0.7588235294117648,5.652941176470589,-0.72,6.276274509803921,0.29411764705882354,0.21123076923076922,,0.047619047619047616,0.0392156862745098,0.2222222222222222,3.5524770516948104e-05
|
18 |
+
16,Pitch Out,46.0,0.0,0.0,0.0,46,0.0,90.01086956521739,2212.1304347826085,,2.9954950072006916,143.40565751983308,,-0.6036956521739131,1.2219565217391304,6.254347826086956,-1.803478260869565,5.74195652173913,0.0,,,,0.0,0.0,3.204194987803162e-05
|
19 |
+
17,All,717944.0,341263.0,88076.0,206012.0,365586,104563.0,88.99871914935127,2249.1345844165853,82.5959800585916,6.109454214467076,339.2167730889254,15.224075051082256,-0.12493170194679414,0.5903365570869159,6.43911718239724,-0.8595453392605805,5.7720100196491035,0.4902945076496217,0.37195099345233773,1.0,0.25808833656153757,0.2869471713671261,0.28601478174765993,0.5000940361572508
|
summary_pitcher.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
summary_pitcher_level.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
test_data_mlb.ipynb
ADDED
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 1,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [],
|
8 |
+
"source": [
|
9 |
+
"import time\n",
|
10 |
+
"import requests\n",
|
11 |
+
"import pandas as pd\n",
|
12 |
+
"import seaborn as sns\n",
|
13 |
+
"import matplotlib.pyplot as plt\n",
|
14 |
+
"from matplotlib.pyplot import figure\n",
|
15 |
+
"from matplotlib.offsetbox import OffsetImage, AnnotationBbox\n",
|
16 |
+
"from scipy import stats\n",
|
17 |
+
"import matplotlib.lines as mlines\n",
|
18 |
+
"import matplotlib.transforms as mtransforms\n",
|
19 |
+
"import numpy as np\n",
|
20 |
+
"import time\n",
|
21 |
+
"#import plotly.express as px\n",
|
22 |
+
"#!pip install chart_studio\n",
|
23 |
+
"#import chart_studio.tools as tls\n",
|
24 |
+
"from bs4 import BeautifulSoup\n",
|
25 |
+
"import matplotlib.pyplot as plt\n",
|
26 |
+
"import numpy as np\n",
|
27 |
+
"import matplotlib.font_manager as font_manager\n",
|
28 |
+
"from datetime import datetime\n",
|
29 |
+
"import pytz\n",
|
30 |
+
"from matplotlib.ticker import MaxNLocator\n",
|
31 |
+
"from matplotlib.patches import Ellipse\n",
|
32 |
+
"import matplotlib.transforms as transforms\n",
|
33 |
+
"from matplotlib.gridspec import GridSpec\n",
|
34 |
+
"from datasets import load_dataset"
|
35 |
+
]
|
36 |
+
},
|
37 |
+
{
|
38 |
+
"cell_type": "code",
|
39 |
+
"execution_count": 2,
|
40 |
+
"metadata": {},
|
41 |
+
"outputs": [
|
42 |
+
{
|
43 |
+
"name": "stdout",
|
44 |
+
"output_type": "stream",
|
45 |
+
"text": [
|
46 |
+
"Starting Everything:\n"
|
47 |
+
]
|
48 |
+
},
|
49 |
+
{
|
50 |
+
"name": "stderr",
|
51 |
+
"output_type": "stream",
|
52 |
+
"text": [
|
53 |
+
"Found cached dataset csv (C:/Users/thoma/.cache/huggingface/datasets/nesticot___csv/nesticot--mlb_data-a391519415fcbccf/0.0.0/6954658bab30a358235fa864b05cf819af0e179325c740e4bc853bcc7ec513e1)\n",
|
54 |
+
"100%|██████████| 1/1 [00:00<00:00, 2.02it/s]\n"
|
55 |
+
]
|
56 |
+
}
|
57 |
+
],
|
58 |
+
"source": [
|
59 |
+
"\n",
|
60 |
+
"colour_palette = ['#FFB000','#648FFF','#785EF0',\n",
|
61 |
+
" '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']\n",
|
62 |
+
"\n",
|
63 |
+
"print('Starting Everything:')\n",
|
64 |
+
"# exit_velo_df = milb_a_ev_df.append([triple_a_ev_df,double_a_ev_df,a_high_a_ev_df,single_a_ev_df]).reset_index(drop=True)\n",
|
65 |
+
"# player_df_all = mlb_a_player_df.append([triple_a_player_df,double_a_player_df,a_high_a_player_df,single_a_player_df]).reset_index(drop=True)\n",
|
66 |
+
"# exit_velo_df = pd.read_csv('exit_velo_df_all.csv',index_col=[0])\n",
|
67 |
+
"# player_df_all = pd.read_csv('player_df_all.csv',index_col=[0])\n",
|
68 |
+
"\n",
|
69 |
+
"# pa_df = pd.read_csv('pa_df_all.csv',index_col=[0])\n",
|
70 |
+
"# pa_df_full_na = pa_df.dropna()\n",
|
71 |
+
"\n",
|
72 |
+
"### Import Datasets\n",
|
73 |
+
"dataset = load_dataset('nesticot/mlb_data', data_files=['a_pitch_data_2023.csv',\n",
|
74 |
+
" ])\n",
|
75 |
+
"dataset_train = dataset['train']\n",
|
76 |
+
"exit_velo_df = dataset_train.to_pandas().set_index(list(dataset_train.features.keys())[0]).reset_index(drop=True)\n",
|
77 |
+
"colour_palette = ['#FFB000','#648FFF','#785EF0',\n",
|
78 |
+
" '#DC267F','#FE6100','#3D1EB2','#894D80','#16AA02','#B5592B','#A3C1ED']\n",
|
79 |
+
"\n",
|
80 |
+
"\n"
|
81 |
+
]
|
82 |
+
},
|
83 |
+
{
|
84 |
+
"cell_type": "code",
|
85 |
+
"execution_count": 9,
|
86 |
+
"metadata": {},
|
87 |
+
"outputs": [
|
88 |
+
{
|
89 |
+
"data": {
|
90 |
+
"text/plain": [
|
91 |
+
"0 True\n",
|
92 |
+
"1 True\n",
|
93 |
+
"2 True\n",
|
94 |
+
"3 True\n",
|
95 |
+
"4 True\n",
|
96 |
+
" ... \n",
|
97 |
+
"575260 True\n",
|
98 |
+
"575261 True\n",
|
99 |
+
"575262 True\n",
|
100 |
+
"575263 True\n",
|
101 |
+
"575264 True\n",
|
102 |
+
"Name: is_pitch, Length: 575265, dtype: bool"
|
103 |
+
]
|
104 |
+
},
|
105 |
+
"execution_count": 9,
|
106 |
+
"metadata": {},
|
107 |
+
"output_type": "execute_result"
|
108 |
+
}
|
109 |
+
],
|
110 |
+
"source": [
|
111 |
+
"exit_velo_df['is_pitch']"
|
112 |
+
]
|
113 |
+
},
|
114 |
+
{
|
115 |
+
"cell_type": "code",
|
116 |
+
"execution_count": 18,
|
117 |
+
"metadata": {},
|
118 |
+
"outputs": [],
|
119 |
+
"source": [
|
120 |
+
"tl_df = exit_velo_df[exit_velo_df['batter_id'] == 699073].groupby(['batter_id','batter_name','batter_hand']).agg(\n",
|
121 |
+
" pitches = ('is_pitch','sum'),\n",
|
122 |
+
" swings = ('is_swing','sum'),\n",
|
123 |
+
" whiffs = ('is_whiff','sum')\n",
|
124 |
+
")"
|
125 |
+
]
|
126 |
+
},
|
127 |
+
{
|
128 |
+
"cell_type": "code",
|
129 |
+
"execution_count": 19,
|
130 |
+
"metadata": {},
|
131 |
+
"outputs": [],
|
132 |
+
"source": [
|
133 |
+
"tl_df['whiff_rate'] = tl_df['whiffs'] / tl_df['swings']"
|
134 |
+
]
|
135 |
+
},
|
136 |
+
{
|
137 |
+
"cell_type": "code",
|
138 |
+
"execution_count": 20,
|
139 |
+
"metadata": {},
|
140 |
+
"outputs": [
|
141 |
+
{
|
142 |
+
"data": {
|
143 |
+
"text/html": [
|
144 |
+
"<div>\n",
|
145 |
+
"<style scoped>\n",
|
146 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
147 |
+
" vertical-align: middle;\n",
|
148 |
+
" }\n",
|
149 |
+
"\n",
|
150 |
+
" .dataframe tbody tr th {\n",
|
151 |
+
" vertical-align: top;\n",
|
152 |
+
" }\n",
|
153 |
+
"\n",
|
154 |
+
" .dataframe thead th {\n",
|
155 |
+
" text-align: right;\n",
|
156 |
+
" }\n",
|
157 |
+
"</style>\n",
|
158 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
159 |
+
" <thead>\n",
|
160 |
+
" <tr style=\"text-align: right;\">\n",
|
161 |
+
" <th></th>\n",
|
162 |
+
" <th></th>\n",
|
163 |
+
" <th></th>\n",
|
164 |
+
" <th>pitches</th>\n",
|
165 |
+
" <th>swings</th>\n",
|
166 |
+
" <th>whiffs</th>\n",
|
167 |
+
" <th>whiff_rate</th>\n",
|
168 |
+
" </tr>\n",
|
169 |
+
" <tr>\n",
|
170 |
+
" <th>batter_id</th>\n",
|
171 |
+
" <th>batter_name</th>\n",
|
172 |
+
" <th>batter_hand</th>\n",
|
173 |
+
" <th></th>\n",
|
174 |
+
" <th></th>\n",
|
175 |
+
" <th></th>\n",
|
176 |
+
" <th></th>\n",
|
177 |
+
" </tr>\n",
|
178 |
+
" </thead>\n",
|
179 |
+
" <tbody>\n",
|
180 |
+
" <tr>\n",
|
181 |
+
" <th rowspan=\"2\" valign=\"top\">699073</th>\n",
|
182 |
+
" <th rowspan=\"2\" valign=\"top\">Thayron Liranzo</th>\n",
|
183 |
+
" <th>L</th>\n",
|
184 |
+
" <td>1344</td>\n",
|
185 |
+
" <td>554</td>\n",
|
186 |
+
" <td>189</td>\n",
|
187 |
+
" <td>0.341155</td>\n",
|
188 |
+
" </tr>\n",
|
189 |
+
" <tr>\n",
|
190 |
+
" <th>R</th>\n",
|
191 |
+
" <td>343</td>\n",
|
192 |
+
" <td>160</td>\n",
|
193 |
+
" <td>60</td>\n",
|
194 |
+
" <td>0.375</td>\n",
|
195 |
+
" </tr>\n",
|
196 |
+
" </tbody>\n",
|
197 |
+
"</table>\n",
|
198 |
+
"</div>"
|
199 |
+
],
|
200 |
+
"text/plain": [
|
201 |
+
" pitches swings whiffs whiff_rate\n",
|
202 |
+
"batter_id batter_name batter_hand \n",
|
203 |
+
"699073 Thayron Liranzo L 1344 554 189 0.341155\n",
|
204 |
+
" R 343 160 60 0.375"
|
205 |
+
]
|
206 |
+
},
|
207 |
+
"execution_count": 20,
|
208 |
+
"metadata": {},
|
209 |
+
"output_type": "execute_result"
|
210 |
+
}
|
211 |
+
],
|
212 |
+
"source": [
|
213 |
+
"tl_df"
|
214 |
+
]
|
215 |
+
}
|
216 |
+
],
|
217 |
+
"metadata": {
|
218 |
+
"kernelspec": {
|
219 |
+
"display_name": "Python 3",
|
220 |
+
"language": "python",
|
221 |
+
"name": "python3"
|
222 |
+
},
|
223 |
+
"language_info": {
|
224 |
+
"codemirror_mode": {
|
225 |
+
"name": "ipython",
|
226 |
+
"version": 3
|
227 |
+
},
|
228 |
+
"file_extension": ".py",
|
229 |
+
"mimetype": "text/x-python",
|
230 |
+
"name": "python",
|
231 |
+
"nbconvert_exporter": "python",
|
232 |
+
"pygments_lexer": "ipython3",
|
233 |
+
"version": "3.9.13"
|
234 |
+
}
|
235 |
+
},
|
236 |
+
"nbformat": 4,
|
237 |
+
"nbformat_minor": 2
|
238 |
+
}
|