npb_data_app / grade_utils.py
patrickramos's picture
Add temporary fix to missing grade constants
91014e8
import polars as pl
from datetime import date
def tmp_add_current_season(mapper):
current_season = date.today().year
seasons = list(mapper.keys())
if k_is_tuple := isinstance(seasons[0], tuple):
seasons = [season for _, season in seasons]
if current_season not in seasons:
latest_season = max(seasons)
print(f'Current season ({current_season}) does not exist. Temporary taking latest season ({latest_season}) constants for current season.')
new_mapper = {k: v for k, v in mapper.items()}
for k, v in mapper.items():
# if tuple, assume length is 2 and second item is season
season = k[1] if k_is_tuple else k
if season == latest_season:
new_k = (k[0], current_season) if k_is_tuple else current_season
new_mapper[new_k] = v
return new_mapper
season_to_kwera_constant = tmp_add_current_season({
2021: 4.71052375471603,
2022: 4.456282125871746,
2023: 4.380546406174146,
2024: 4.162264425433673,
2025: 4.284439127948289
})
pitch_season_to_avg_velo_mean = \
tmp_add_current_season({('Changeup', 2021): 79.2264563140855,
('Changeup', 2022): 80.22540759336417,
('Changeup', 2023): 80.05924071828115,
('Changeup', 2024): 79.50869133716515,
('Changeup', 2025): 80.46459156252178,
('Curve', 2021): 71.64615596456254,
('Curve', 2022): 72.76772512882965,
('Curve', 2023): 72.92047154415356,
('Curve', 2024): 71.57510150090833,
('Curve', 2025): 72.54297311971503,
('Cutter', 2021): 84.71017348983573,
('Cutter', 2022): 85.06437993344268,
('Cutter', 2023): 85.24453242561565,
('Cutter', 2024): 85.01479268663725,
('Cutter', 2025): 84.75907765011625,
('Fastball (4-seam)', 2021): 89.99996307509282,
('Fastball (4-seam)', 2022): 90.4383524800032,
('Fastball (4-seam)', 2023): 90.76556044285923,
('Fastball (4-seam)', 2024): 90.56578854872019,
('Fastball (4-seam)', 2025): 90.84227259807508,
('Sinker', 2021): 87.94587079133112,
('Sinker', 2022): 87.67678482660781,
('Sinker', 2023): 87.66616302326588,
('Sinker', 2024): 87.6962009827961,
('Sinker', 2025): 85.89648392765869,
('Slider', 2021): 79.99388790237634,
('Slider', 2022): 79.97710234253374,
('Slider', 2023): 79.822602274374,
('Slider', 2024): 80.28825962234853,
('Slider', 2025): 80.58239279631813,
('Splitter', 2021): 83.76784312842885,
('Splitter', 2022): 84.19481417068731,
('Splitter', 2023): 84.47671177217279,
('Splitter', 2024): 84.09874182518413,
('Splitter', 2025): 84.59070512574287})
pitch_season_to_avg_velo_std = \
tmp_add_current_season({('Changeup', 2021): 11.77807367165324,
('Changeup', 2022): 6.842380721578528,
('Changeup', 2023): 7.034741275891494,
('Changeup', 2024): 9.222281096549409,
('Changeup', 2025): 4.332096291355083,
('Curve', 2021): 8.896141160474572,
('Curve', 2022): 4.6045542352090685,
('Curve', 2023): 4.997999250858166,
('Curve', 2024): 10.119349771434923,
('Curve', 2025): 7.965097635262848,
('Cutter', 2021): 6.626344039538129,
('Cutter', 2022): 2.561660666079889,
('Cutter', 2023): 2.484307519636099,
('Cutter', 2024): 6.495453199542276,
('Cutter', 2025): 8.361297463158552,
('Fastball (4-seam)', 2021): 2.8854293308272596,
('Fastball (4-seam)', 2022): 2.59203258569976,
('Fastball (4-seam)', 2023): 2.583468821491537,
('Fastball (4-seam)', 2024): 5.578095403067859,
('Fastball (4-seam)', 2025): 5.5347981378812445,
('Sinker', 2021): 3.3650498082261513,
('Sinker', 2022): 7.033037320781944,
('Sinker', 2023): 9.618454968009972,
('Sinker', 2024): 7.242597468266641,
('Sinker', 2025): 15.607969181897545,
('Slider', 2021): 5.560160688395123,
('Slider', 2022): 7.217816556711487,
('Slider', 2023): 8.690264414411969,
('Slider', 2024): 5.5541978516315265,
('Slider', 2025): 3.2213471006750165,
('Splitter', 2021): 6.30744932804706,
('Splitter', 2022): 6.051070347374654,
('Splitter', 2023): 2.382725985165017,
('Splitter', 2024): 5.882295942651983,
('Splitter', 2025): 2.652376599285225})
pitch_season_to_lg_avg_velo = \
tmp_add_current_season({('Changeup', 2021): 80.81979495873371,
('Changeup', 2022): 80.1826668059388,
('Changeup', 2023): 80.25825972955694,
('Changeup', 2024): 80.6159499731265,
('Changeup', 2025): 80.73552461115297,
('Curve', 2021): 73.08359347209047,
('Curve', 2022): 72.663131555364,
('Curve', 2023): 73.00540595750557,
('Curve', 2024): 73.2837909372838,
('Curve', 2025): 73.95819962384012,
('Cutter', 2021): 85.32224632697931,
('Cutter', 2022): 85.07037777577476,
('Cutter', 2023): 85.21972033964573,
('Cutter', 2024): 85.38053259845024,
('Cutter', 2025): 85.34875249692138,
('Fastball (4-seam)', 2021): 90.40292491122308,
('Fastball (4-seam)', 2022): 90.7496914159221,
('Fastball (4-seam)', 2023): 91.01581969011761,
('Fastball (4-seam)', 2024): 91.17729966135065,
('Fastball (4-seam)', 2025): 91.40630027092632,
('Sinker', 2021): 88.54482201026023,
('Sinker', 2022): 88.51839694065075,
('Sinker', 2023): 88.61071203755115,
('Sinker', 2024): 88.17842039577434,
('Sinker', 2025): 88.45768097701763,
('Slider', 2021): 80.4470981766032,
('Slider', 2022): 80.47304311616857,
('Slider', 2023): 80.6871618777633,
('Slider', 2024): 80.72734236379378,
('Slider', 2025): 80.6491853445582,
('Splitter', 2021): 84.49963708579646,
('Splitter', 2022): 85.03980043832033,
('Splitter', 2023): 84.94997281080208,
('Splitter', 2024): 84.81718491347826,
('Splitter', 2025): 84.94535810660138})
pitch_season_to_lg_swstr = \
tmp_add_current_season({('Changeup', 2021): 0.16637748487752602,
('Changeup', 2022): 0.156681624014773,
('Changeup', 2023): 0.15953164169154327,
('Changeup', 2024): 0.16230286723790355,
('Changeup', 2025): 0.15754728955693326,
('Curve', 2021): 0.0841792761329794,
('Curve', 2022): 0.07568906344311736,
('Curve', 2023): 0.085216302940459,
('Curve', 2024): 0.07407738368406386,
('Curve', 2025): 0.08533175374286411,
('Cutter', 2021): 0.11237849666438401,
('Cutter', 2022): 0.11555261522029497,
('Cutter', 2023): 0.10969153499182595,
('Cutter', 2024): 0.10506994302411382,
('Cutter', 2025): 0.11436661688342542,
('Fastball (4-seam)', 2021): 0.07016573161563726,
('Fastball (4-seam)', 2022): 0.07271986844226992,
('Fastball (4-seam)', 2023): 0.069030396176365,
('Fastball (4-seam)', 2024): 0.06603969783651029,
('Fastball (4-seam)', 2025): 0.06946576686880798,
('Sinker', 2021): 0.07691654374071405,
('Sinker', 2022): 0.0719579736633534,
('Sinker', 2023): 0.0668242327881162,
('Sinker', 2024): 0.06939017968354451,
('Sinker', 2025): 0.07433348682831017,
('Slider', 2021): 0.12202042841666315,
('Slider', 2022): 0.11844836070468316,
('Slider', 2023): 0.12319532701955578,
('Slider', 2024): 0.12366405291824946,
('Slider', 2025): 0.13272742224411202,
('Splitter', 2021): 0.1871546131512068,
('Splitter', 2022): 0.20146180045915207,
('Splitter', 2023): 0.18781321255468442,
('Splitter', 2024): 0.17841031493864717,
('Splitter', 2025): 0.1845171659321444})
pitch_season_to_lg_ball = \
tmp_add_current_season({('Changeup', 2021): 0.37608331201571904,
('Changeup', 2022): 0.36806517672941913,
('Changeup', 2023): 0.3698294629281254,
('Changeup', 2024): 0.3573699048769258,
('Changeup', 2025): 0.3675993375542926,
('Curve', 2021): 0.4326866830222962,
('Curve', 2022): 0.4219689830035755,
('Curve', 2023): 0.4313316444928162,
('Curve', 2024): 0.4278066895540406,
('Curve', 2025): 0.4160657363371386,
('Cutter', 2021): 0.343726953038649,
('Cutter', 2022): 0.33070197652911604,
('Cutter', 2023): 0.33582160886879237,
('Cutter', 2024): 0.31892508314668944,
('Cutter', 2025): 0.31738003165424455,
('Fastball (4-seam)', 2021): 0.3480523717415547,
('Fastball (4-seam)', 2022): 0.33976145603254176,
('Fastball (4-seam)', 2023): 0.3324405514411442,
('Fastball (4-seam)', 2024): 0.32450414132816724,
('Fastball (4-seam)', 2025): 0.3264753426380296,
('Sinker', 2021): 0.3476336053550227,
('Sinker', 2022): 0.3334094880268241,
('Sinker', 2023): 0.3502397534473253,
('Sinker', 2024): 0.33140362156069103,
('Sinker', 2025): 0.3353790889841558,
('Slider', 2021): 0.36590655416706486,
('Slider', 2022): 0.3598460130140309,
('Slider', 2023): 0.3610944523615591,
('Slider', 2024): 0.35359114647382056,
('Slider', 2025): 0.3549468541998645,
('Splitter', 2021): 0.37864589728330705,
('Splitter', 2022): 0.35872441414644934,
('Splitter', 2023): 0.37472421442588716,
('Splitter', 2024): 0.38307578690597316,
('Splitter', 2025): 0.3657589773126842})
pitch_season_to_lg_gb = \
tmp_add_current_season({('Changeup', 2021): 0.5151924640356825,
('Changeup', 2022): 0.5082426703883928,
('Changeup', 2023): 0.499335650567513,
('Changeup', 2024): 0.5074289825830408,
('Changeup', 2025): 0.4952446276460013,
('Curve', 2021): 0.5001623391943489,
('Curve', 2022): 0.5147603046768613,
('Curve', 2023): 0.5129693236570477,
('Curve', 2024): 0.5045807180804599,
('Curve', 2025): 0.4978431974304818,
('Cutter', 2021): 0.4953464332712496,
('Cutter', 2022): 0.48439220324685733,
('Cutter', 2023): 0.47967216935564916,
('Cutter', 2024): 0.5011231357094771,
('Cutter', 2025): 0.4776510680721764,
('Fastball (4-seam)', 2021): 0.41939852917486875,
('Fastball (4-seam)', 2022): 0.4168521121334979,
('Fastball (4-seam)', 2023): 0.4182014227745429,
('Fastball (4-seam)', 2024): 0.42757037713131607,
('Fastball (4-seam)', 2025): 0.41769445516472087,
('Sinker', 2021): 0.5914879009825824,
('Sinker', 2022): 0.5953232174727541,
('Sinker', 2023): 0.5628058856054425,
('Sinker', 2024): 0.5799186224084921,
('Sinker', 2025): 0.566604996256388,
('Slider', 2021): 0.4688689903198683,
('Slider', 2022): 0.46847247869634867,
('Slider', 2023): 0.4751152878923993,
('Slider', 2024): 0.4573869469902662,
('Slider', 2025): 0.4546862785819797,
('Splitter', 2021): 0.6058706401023634,
('Splitter', 2022): 0.6130090508948522,
('Splitter', 2023): 0.6032889610134908,
('Splitter', 2024): 0.5805813808762167,
('Splitter', 2025): 0.5691024836489907})
pitch_season_to_lg_iffb = \
tmp_add_current_season({('Changeup', 2021): 0.10545553695231867,
('Changeup', 2022): 0.1164350215466145,
('Changeup', 2023): 0.09974654357094562,
('Changeup', 2024): 0.08888129846695998,
('Changeup', 2025): 0.09718559528302463,
('Curve', 2021): 0.08950467279626248,
('Curve', 2022): 0.10156228362443535,
('Curve', 2023): 0.07135954741673031,
('Curve', 2024): 0.0818545306671732,
('Curve', 2025): 0.07468644934088589,
('Cutter', 2021): 0.12498843244673186,
('Cutter', 2022): 0.13241429355147846,
('Cutter', 2023): 0.1159053378728157,
('Cutter', 2024): 0.1037388381114421,
('Cutter', 2025): 0.11752542587878748,
('Fastball (4-seam)', 2021): 0.12257287588467458,
('Fastball (4-seam)', 2022): 0.14306372762010805,
('Fastball (4-seam)', 2023): 0.12212900890111732,
('Fastball (4-seam)', 2024): 0.11133849847892519,
('Fastball (4-seam)', 2025): 0.11670604117917469,
('Sinker', 2021): 0.07568601180155406,
('Sinker', 2022): 0.0785953566879809,
('Sinker', 2023): 0.07929425996058133,
('Sinker', 2024): 0.06833488957718813,
('Sinker', 2025): 0.08078430721667568,
('Slider', 2021): 0.126607056202873,
('Slider', 2022): 0.13541538752029716,
('Slider', 2023): 0.12379920931398448,
('Slider', 2024): 0.11034889837827147,
('Slider', 2025): 0.12213796521283432,
('Splitter', 2021): 0.07239960591983487,
('Splitter', 2022): 0.0755513815183479,
('Splitter', 2023): 0.0658847619385671,
('Splitter', 2024): 0.07030224089625685,
('Splitter', 2025): 0.07327203895542692})
pitch_season_to_ypera_mean = \
tmp_add_current_season({('Changeup', 2021): 2.0212035191752107,
('Changeup', 2022): 2.242513737688488,
('Changeup', 2023): 2.2919935167474645,
('Changeup', 2024): 2.2703630590963284,
('Changeup', 2025): 2.259060204848251,
('Curve', 2021): 3.5255766815734306,
('Curve', 2022): 3.657318902073986,
('Curve', 2023): 3.768295399468496,
('Curve', 2024): 3.5934448033150446,
('Curve', 2025): 3.468577707148284,
('Cutter', 2021): 2.5672201215012698,
('Cutter', 2022): 2.417045615051075,
('Cutter', 2023): 2.674720295283657,
('Cutter', 2024): 2.5682547177544497,
('Cutter', 2025): 2.4566306965971996,
('Fastball (4-seam)', 2021): 3.3573253735150463,
('Fastball (4-seam)', 2022): 3.248391700489743,
('Fastball (4-seam)', 2023): 3.3242229018127394,
('Fastball (4-seam)', 2024): 3.33935166320063,
('Fastball (4-seam)', 2025): 3.247081079698762,
('Sinker', 2021): 2.986160856815144,
('Sinker', 2022): 2.925592745503959,
('Sinker', 2023): 3.261487691340379,
('Sinker', 2024): 2.8944678036568607,
('Sinker', 2025): 2.9574467806672304,
('Slider', 2021): 2.710310361455012,
('Slider', 2022): 2.5450710764584064,
('Slider', 2023): 2.642637248701539,
('Slider', 2024): 2.7622995709188234,
('Slider', 2025): 2.5420570710416794,
('Splitter', 2021): 1.9922330433345021,
('Splitter', 2022): 1.5293385819430683,
('Splitter', 2023): 1.88735112508446,
('Splitter', 2024): 2.075532084288298,
('Splitter', 2025): 1.866696950509079})
pitch_season_to_ypera_std = \
tmp_add_current_season({('Changeup', 2021): 0.8974489395499383,
('Changeup', 2022): 1.0125609520173373,
('Changeup', 2023): 0.8028957226402315,
('Changeup', 2024): 1.0120534169803679,
('Changeup', 2025): 0.8220994579436272,
('Curve', 2021): 0.9973030273710978,
('Curve', 2022): 0.8863579448027339,
('Curve', 2023): 0.9322313287499815,
('Curve', 2024): 0.8176962249094423,
('Curve', 2025): 0.861438318828155,
('Cutter', 2021): 0.7001010009651515,
('Cutter', 2022): 0.7893677679272747,
('Cutter', 2023): 0.7006412597050073,
('Cutter', 2024): 0.677980658372907,
('Cutter', 2025): 0.6721210156195512,
('Fastball (4-seam)', 2021): 0.7123541187668379,
('Fastball (4-seam)', 2022): 0.7747408365755386,
('Fastball (4-seam)', 2023): 0.6038027064664364,
('Fastball (4-seam)', 2024): 0.627299560515635,
('Fastball (4-seam)', 2025): 0.6946129421921423,
('Sinker', 2021): 0.7155444177358316,
('Sinker', 2022): 0.7084688427314332,
('Sinker', 2023): 0.8546656371594349,
('Sinker', 2024): 0.754954555008048,
('Sinker', 2025): 0.9595922203557051,
('Slider', 2021): 0.8076171273486882,
('Slider', 2022): 0.7935458482377613,
('Slider', 2023): 0.832090636265613,
('Slider', 2024): 0.8041770905692586,
('Slider', 2025): 0.8398824794513354,
('Splitter', 2021): 1.0767567786357717,
('Splitter', 2022): 1.0021069906789342,
('Splitter', 2023): 0.9287495187152668,
('Splitter', 2024): 0.9532164353990034,
('Splitter', 2025): 0.8604592214452735})
def create_map(expr, pitch_stats):
ball_kind_season_to_stat = {
(ball_kind, season): stat
for ball_kind, season, stat
in (
pitch_stats
# .fill_nan(0)
# .with_columns(pera_ball_kind_col)
.group_by('YpERA Pitch', 'season')
.agg(expr)
).rows()
}
return ball_kind_season_to_stat
def map_columns(mapper, cols=['YpERA Pitch', 'season'], return_dtype=pl.Float32()):
mapped = (
pl.struct(cols)
.map_elements(
lambda x: mapper[tuple(x.values())],
return_dtype=return_dtype
)
)
return mapped