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