TJStatsApps/pitch_plot_select_mlb
1
1import requests
2import polars as pl
3import numpy as np
4from datetime import datetime
5from tqdm import tqdm
6from pytz import timezone
7import re
8from concurrent.futures import ThreadPoolExecutor, as_completed
9
10
11class MLB_Scrape:
12
13 def __init__(self):
14 # Initialize your class here if needed
15 pass
16
17 def get_sport_id(self):
18 """
19 Retrieves the list of sports from the MLB API and processes it into a Polars DataFrame.
20
21 Returns:
22 - df (pl.DataFrame): A DataFrame containing the sports information.
23 """
24 # Make API call to retrieve sports information
25 response = requests.get(url='https://statsapi.mlb.com/api/v1/sports').json()
26
27 # Convert the JSON response into a Polars DataFrame
28 df = pl.DataFrame(response['sports'])
29
30 return df
31
32 def get_sport_id_check(self, sport_id: int = 1):
33 """
34 Checks if the provided sport ID exists in the list of sports retrieved from the MLB API.
35
36 Parameters:
37 - sport_id (int): The sport ID to check. Default is 1.
38
39 Returns:
40 - bool: True if the sport ID exists, False otherwise. If False, prints the available sport IDs.
41 """
42 # Retrieve the list of sports from the MLB API
43 sport_id_df = self.get_sport_id()
44
45 # Check if the provided sport ID exists in the DataFrame
46 if sport_id not in sport_id_df['id']:
47 print('Please Select a New Sport ID from the following')
48 print(sport_id_df)
49 return False
50
51 return True
52
53
54 def get_game_types(self):
55 """
56 Retrieves the different types of MLB games from the MLB API and processes them into a Polars DataFrame.
57
58 Returns:
59 - df (pl.DataFrame): A DataFrame containing the game types information.
60 """
61 # Make API call to retrieve game types information
62 response = requests.get(url='https://statsapi.mlb.com/api/v1/gameTypes').json()
63
64 # Convert the JSON response into a Polars DataFrame
65 df = pl.DataFrame(response)
66
67 return df
68
69 def get_schedule(self,
70 year_input: list = [2024],
71 sport_id: list = [1],
72 game_type: list = ['R']):
73
74 """
75 Retrieves the schedule of baseball games based on the specified parameters.
76 Parameters:
77 - year_input (list): A list of years to filter the schedule. Default is [2024].
78 - sport_id (list): A list of sport IDs to filter the schedule. Default is [1].
79 - game_type (list): A list of game types to filter the schedule. Default is ['R'].
80 Returns:
81 - game_df (pandas.DataFrame): A DataFrame containing the game schedule information, including game ID, date, time, away team, home team, game state, venue ID, and venue name. If the schedule length is 0, it returns a message indicating that different parameters should be selected.
82 """
83
84 # Type checks
85 if not isinstance(year_input, list) or not all(isinstance(year, int) for year in year_input):
86 raise ValueError("year_input must be a list of integers.")
87 if not isinstance(sport_id, list) or not all(isinstance(sid, int) for sid in sport_id):
88 raise ValueError("sport_id must be a list of integers.")
89
90 if not isinstance(game_type, list) or not all(isinstance(gt, str) for gt in game_type):
91 raise ValueError("game_type must be a list of strings.")
92
93 eastern = timezone('US/Eastern')
94
95 # Convert input lists to comma-separated strings
96 year_input_str = ','.join([str(x) for x in year_input])
97 sport_id_str = ','.join([str(x) for x in sport_id])
98 game_type_str = ','.join([str(x) for x in game_type])
99
100 # Make API call to retrieve game schedule
101 game_call = requests.get(url=f'https://statsapi.mlb.com/api/v1/schedule/?sportId={sport_id_str}&gameTypes={game_type_str}&season={year_input_str}&hydrate=lineup,players').json()
102 try:
103 # Extract relevant data from the API response
104 game_list = [item for sublist in [[y['gamePk'] for y in x['games']] for x in game_call['dates']] for item in sublist]
105 time_list = [item for sublist in [[y['gameDate'] for y in x['games']] for x in game_call['dates']] for item in sublist]
106 date_list = [item for sublist in [[y['officialDate'] for y in x['games']] for x in game_call['dates']] for item in sublist]
107 away_team_list = [item for sublist in [[y['teams']['away']['team']['name'] for y in x['games']] for x in game_call['dates']] for item in sublist]
108 away_team_id_list = [item for sublist in [[y['teams']['away']['team']['id'] for y in x['games']] for x in game_call['dates']] for item in sublist]
109 home_team_list = [item for sublist in [[y['teams']['home']['team']['name'] for y in x['games']] for x in game_call['dates']] for item in sublist]
110 home_team_id_list = [item for sublist in [[y['teams']['home']['team']['id'] for y in x['games']] for x in game_call['dates']] for item in sublist]
111 state_list = [item for sublist in [[y['status']['codedGameState'] for y in x['games']] for x in game_call['dates']] for item in sublist]
112 venue_id = [item for sublist in [[y['venue']['id'] for y in x['games']] for x in game_call['dates']] for item in sublist]
113 venue_name = [item for sublist in [[y['venue']['name'] for y in x['games']] for x in game_call['dates']] for item in sublist]
114 gameday_type = [item for sublist in [[y['gamedayType'] for y in x['games']] for x in game_call['dates']] for item in sublist]
115 # Create a Polars DataFrame with the extracted data
116
117
118 # Create a Polars DataFrame with the extracted data
119 game_df = pl.DataFrame(data={'game_id': game_list,
120 'time': time_list,
121 'date': date_list,
122 'away': away_team_list,
123 'away_id': away_team_id_list,
124 'home': home_team_list,
125 'home_id': home_team_id_list,
126 'state': state_list,
127 'venue_id': venue_id,
128 'venue_name': venue_name,
129 'gameday_type':gameday_type})
130
131
132 # Check if the DataFrame is empty
133 if len(game_df) == 0:
134 print('Schedule Length of 0, please select different parameters.')
135 return None
136
137 # Convert date and time columns to appropriate formats
138 game_df = game_df.with_columns(
139 game_df['date'].str.to_date(),
140 game_df['time'].str.to_datetime().dt.convert_time_zone(eastern.zone).dt.strftime("%I:%M %p"))
141
142 # Remove duplicate games and sort by date
143 game_df = game_df.unique(subset='game_id').sort('date')
144
145 # Check again if the DataFrame is empty after processing
146 if len(game_df) == 0:
147 print('Schedule Length of 0, please select different parameters.')
148 return None
149 except KeyError:
150 print('No Data for Selected Parameters')
151 return None
152
153
154 return game_df
155
156
157 # def get_data(self, game_list_input: list):
158 # """
159 # Retrieves live game data for a list of game IDs in parallel.
160
161 # Parameters:
162 # - game_list_input (list): A list of game IDs for which to retrieve live data.
163
164 # Returns:
165 # - data_total (list): A list of JSON responses containing live game data for each game ID.
166 # """
167 # data_total = []
168 # print('This May Take a While. Progress Bar shows Completion of Data Retrieval.')
169
170 # def fetch_data(game_id):
171 # r = requests.get(f'https://statsapi.mlb.com/api/v1.1/game/{game_id}/feed/live')
172 # return r.json()
173
174 # with ThreadPoolExecutor() as executor:
175 # futures = {executor.submit(fetch_data, game_id): game_id for game_id in game_list_input}
176 # for future in tqdm(as_completed(futures), total=len(futures), desc="Processing", unit="iteration"):
177 # data_total.append(future.result())
178
179 # return data_total
180
181
182 def get_data(self,game_list_input = [748540]):
183 data_total = []
184 #n_count = 0
185 print('This May Take a While. Progress Bar shows Completion of Data Retrieval.')
186 for i in tqdm(range(len(game_list_input)), desc="Processing", unit="iteration"):
187 r = requests.get(f'https://statsapi.mlb.com/api/v1.1/game/{game_list_input[i]}/feed/live')
188 data_total.append(r.json())
189 return data_total
190
191
192 def get_data_df(self, data_list):
193 """
194 Converts a list of game data JSON objects into a Polars DataFrame.
195
196 Parameters:
197 - data_list (list): A list of JSON objects containing game data.
198
199 Returns:
200 - data_df (pl.DataFrame): A DataFrame containing the structured game data.
201 """
202 swing_list = ['X','F','S','D','E','T','W','L','M','Q','Z','R','O','J']
203 whiff_list = ['S','T','W','M','Q','O']
204 print('Converting Data to Dataframe.')
205 game_id = []
206 game_date = []
207 batter_id = []
208 batter_name = []
209 batter_hand = []
210 batter_team = []
211 batter_team_id = []
212 pitcher_id = []
213 pitcher_name = []
214 pitcher_hand = []
215 pitcher_team = []
216 pitcher_team_id = []
217
218 play_description = []
219 play_code = []
220 in_play = []
221 is_strike = []
222 is_swing = []
223 is_whiff = []
224 is_out = []
225 is_ball = []
226 is_review = []
227 pitch_type = []
228 pitch_description = []
229 strikes = []
230 balls = []
231 outs = []
232 strikes_after = []
233 balls_after = []
234 outs_after = []
235
236 start_speed = []
237 end_speed = []
238 sz_top = []
239 sz_bot = []
240 x = []
241 y = []
242 ax = []
243 ay = []
244 az = []
245 pfxx = []
246 pfxz = []
247 px = []
248 pz = []
249 vx0 = []
250 vy0 = []
251 vz0 = []
252 x0 = []
253 y0 = []
254 z0 = []
255 zone = []
256 type_confidence = []
257 plate_time = []
258 extension = []
259 spin_rate = []
260 spin_direction = []
261 vb = []
262 ivb = []
263 hb = []
264
265 launch_speed = []
266 launch_angle = []
267 launch_distance = []
268 launch_location = []
269 trajectory = []
270 hardness = []
271 hit_x = []
272 hit_y = []
273
274 index_play = []
275 play_id = []
276 start_time = []
277 end_time = []
278 is_pitch = []
279 type_type = []
280
281
282 type_ab = []
283 ab_number = []
284 event = []
285 event_type = []
286 rbi = []
287 away_score = []
288 home_score = []
289
290 for data in data_list:
291 try:
292 for ab_id in range(len(data['liveData']['plays']['allPlays'])):
293 ab_list = data['liveData']['plays']['allPlays'][ab_id]
294 for n in range(len(ab_list['playEvents'])):
295
296
297 if ab_list['playEvents'][n]['isPitch'] == True or 'call' in ab_list['playEvents'][n]['details']:
298 ab_number.append(ab_list['atBatIndex'] if 'atBatIndex' in ab_list else None)
299
300 game_id.append(data['gamePk'])
301 game_date.append(data['gameData']['datetime']['officialDate'])
302 if 'matchup' in ab_list:
303 batter_id.append(ab_list['matchup']['batter']['id'] if 'batter' in ab_list['matchup'] else None)
304 if 'batter' in ab_list['matchup']:
305 batter_name.append(ab_list['matchup']['batter']['fullName'] if 'fullName' in ab_list['matchup']['batter'] else None)
306 else:
307 batter_name.append(None)
308
309 batter_hand.append(ab_list['matchup']['batSide']['code'] if 'batSide' in ab_list['matchup'] else None)
310 pitcher_id.append(ab_list['matchup']['pitcher']['id'] if 'pitcher' in ab_list['matchup'] else None)
311 if 'pitcher' in ab_list['matchup']:
312 pitcher_name.append(ab_list['matchup']['pitcher']['fullName'] if 'fullName' in ab_list['matchup']['pitcher'] else None)
313 else:
314 pitcher_name.append(None)
315
316 pitcher_hand.append(ab_list['matchup']['pitchHand']['code'] if 'pitchHand' in ab_list['matchup'] else None)
317
318
319 if ab_list['about']['isTopInning']:
320 batter_team.append(data['gameData']['teams']['away']['abbreviation'] if 'away' in data['gameData']['teams'] else None)
321 batter_team_id.append(data['gameData']['teams']['away']['id'] if 'away' in data['gameData']['teams'] else None)
322 pitcher_team.append(data['gameData']['teams']['home']['abbreviation'] if 'home' in data['gameData']['teams'] else None)
323 pitcher_team_id.append(data['gameData']['teams']['home']['id'] if 'home' in data['gameData']['teams'] else None)
324
325 else:
326 batter_team.append(data['gameData']['teams']['home']['abbreviation'] if 'home' in data['gameData']['teams'] else None)
327 batter_team_id.append(data['gameData']['teams']['home']['id'] if 'home' in data['gameData']['teams'] else None)
328 pitcher_team.append(data['gameData']['teams']['away']['abbreviation'] if 'away' in data['gameData']['teams'] else None)
329 pitcher_team_id.append(data['gameData']['teams']['away']['id'] if 'away' in data['gameData']['teams'] else None)
330
331 play_description.append(ab_list['playEvents'][n]['details']['description'] if 'description' in ab_list['playEvents'][n]['details'] else None)
332 play_code.append(ab_list['playEvents'][n]['details']['code'] if 'code' in ab_list['playEvents'][n]['details'] else None)
333 in_play.append(ab_list['playEvents'][n]['details']['isInPlay'] if 'isInPlay' in ab_list['playEvents'][n]['details'] else None)
334 is_strike.append(ab_list['playEvents'][n]['details']['isStrike'] if 'isStrike' in ab_list['playEvents'][n]['details'] else None)
335
336 if 'details' in ab_list['playEvents'][n]:
337 is_swing.append(True if ab_list['playEvents'][n]['details']['code'] in swing_list else None)
338 is_whiff.append(True if ab_list['playEvents'][n]['details']['code'] in whiff_list else None)
339 else:
340 is_swing.append(None)
341 is_whiff.append(None)
342
343 is_ball.append(ab_list['playEvents'][n]['details']['isOut'] if 'isOut' in ab_list['playEvents'][n]['details'] else None)
344 is_review.append(ab_list['playEvents'][n]['details']['hasReview'] if 'hasReview' in ab_list['playEvents'][n]['details'] else None)
345 pitch_type.append(ab_list['playEvents'][n]['details']['type']['code'] if 'type' in ab_list['playEvents'][n]['details'] else None)
346 pitch_description.append(ab_list['playEvents'][n]['details']['type']['description'] if 'type' in ab_list['playEvents'][n]['details'] else None)
347
348 if ab_list['playEvents'][n]['pitchNumber'] == 1:
349 strikes.append(0)
350 balls.append(0)
351 strikes_after.append(ab_list['playEvents'][n]['count']['strikes'] if 'strikes' in ab_list['playEvents'][n]['count'] else None)
352 balls_after.append(ab_list['playEvents'][n]['count']['balls'] if 'balls' in ab_list['playEvents'][n]['count'] else None)
353 outs.append(ab_list['playEvents'][n]['count']['outs'] if 'outs' in ab_list['playEvents'][n]['count'] else None)
354 outs_after.append(ab_list['playEvents'][n]['count']['outs'] if 'outs' in ab_list['playEvents'][n]['count'] else None)
355
356 else:
357 strikes.append(ab_list['playEvents'][n-1]['count']['strikes'] if 'strikes' in ab_list['playEvents'][n-1]['count'] else None)
358 balls.append(ab_list['playEvents'][n-1]['count']['balls'] if 'balls' in ab_list['playEvents'][n-1]['count'] else None)
359 outs.append(ab_list['playEvents'][n-1]['count']['outs'] if 'outs' in ab_list['playEvents'][n-1]['count'] else None)
360
361 strikes_after.append(ab_list['playEvents'][n]['count']['strikes'] if 'strikes' in ab_list['playEvents'][n]['count'] else None)
362 balls_after.append(ab_list['playEvents'][n]['count']['balls'] if 'balls' in ab_list['playEvents'][n]['count'] else None)
363 outs_after.append(ab_list['playEvents'][n]['count']['outs'] if 'outs' in ab_list['playEvents'][n]['count'] else None)
364
365
366 if 'pitchData' in ab_list['playEvents'][n]:
367
368 start_speed.append(ab_list['playEvents'][n]['pitchData']['startSpeed'] if 'startSpeed' in ab_list['playEvents'][n]['pitchData'] else None)
369 end_speed.append(ab_list['playEvents'][n]['pitchData']['endSpeed'] if 'endSpeed' in ab_list['playEvents'][n]['pitchData'] else None)
370
371 sz_top.append(ab_list['playEvents'][n]['pitchData']['strikeZoneTop'] if 'strikeZoneTop' in ab_list['playEvents'][n]['pitchData'] else None)
372 sz_bot.append(ab_list['playEvents'][n]['pitchData']['strikeZoneBottom'] if 'strikeZoneBottom' in ab_list['playEvents'][n]['pitchData'] else None)
373 x.append(ab_list['playEvents'][n]['pitchData']['coordinates']['x'] if 'x' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
374 y.append(ab_list['playEvents'][n]['pitchData']['coordinates']['y'] if 'y' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
375
376 ax.append(ab_list['playEvents'][n]['pitchData']['coordinates']['aX'] if 'aX' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
377 ay.append(ab_list['playEvents'][n]['pitchData']['coordinates']['aY'] if 'aY' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
378 az.append(ab_list['playEvents'][n]['pitchData']['coordinates']['aZ'] if 'aZ' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
379 pfxx.append(ab_list['playEvents'][n]['pitchData']['coordinates']['pfxX'] if 'pfxX' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
380 pfxz.append(ab_list['playEvents'][n]['pitchData']['coordinates']['pfxZ'] if 'pfxZ' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
381 px.append(ab_list['playEvents'][n]['pitchData']['coordinates']['pX'] if 'pX' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
382 pz.append(ab_list['playEvents'][n]['pitchData']['coordinates']['pZ'] if 'pZ' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
383 vx0.append(ab_list['playEvents'][n]['pitchData']['coordinates']['vX0'] if 'vX0' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
384 vy0.append(ab_list['playEvents'][n]['pitchData']['coordinates']['vY0'] if 'vY0' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
385 vz0.append(ab_list['playEvents'][n]['pitchData']['coordinates']['vZ0'] if 'vZ0' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
386 x0.append(ab_list['playEvents'][n]['pitchData']['coordinates']['x0'] if 'x0' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
387 y0.append(ab_list['playEvents'][n]['pitchData']['coordinates']['y0'] if 'y0' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
388 z0.append(ab_list['playEvents'][n]['pitchData']['coordinates']['z0'] if 'z0' in ab_list['playEvents'][n]['pitchData']['coordinates'] else None)
389
390 zone.append(ab_list['playEvents'][n]['pitchData']['zone'] if 'zone' in ab_list['playEvents'][n]['pitchData'] else None)
391 type_confidence.append(ab_list['playEvents'][n]['pitchData']['typeConfidence'] if 'typeConfidence' in ab_list['playEvents'][n]['pitchData'] else None)
392 plate_time.append(ab_list['playEvents'][n]['pitchData']['plateTime'] if 'plateTime' in ab_list['playEvents'][n]['pitchData'] else None)
393 extension.append(ab_list['playEvents'][n]['pitchData']['extension'] if 'extension' in ab_list['playEvents'][n]['pitchData'] else None)
394
395 if 'breaks' in ab_list['playEvents'][n]['pitchData']:
396 spin_rate.append(ab_list['playEvents'][n]['pitchData']['breaks']['spinRate'] if 'spinRate' in ab_list['playEvents'][n]['pitchData']['breaks'] else None)
397 spin_direction.append(ab_list['playEvents'][n]['pitchData']['breaks']['spinDirection'] if 'spinDirection' in ab_list['playEvents'][n]['pitchData']['breaks'] else None)
398 vb.append(ab_list['playEvents'][n]['pitchData']['breaks']['breakVertical'] if 'breakVertical' in ab_list['playEvents'][n]['pitchData']['breaks'] else None)
399 ivb.append(ab_list['playEvents'][n]['pitchData']['breaks']['breakVerticalInduced'] if 'breakVerticalInduced' in ab_list['playEvents'][n]['pitchData']['breaks'] else None)
400 hb.append(ab_list['playEvents'][n]['pitchData']['breaks']['breakHorizontal'] if 'breakHorizontal' in ab_list['playEvents'][n]['pitchData']['breaks'] else None)
401
402 else:
403 start_speed.append(None)
404 end_speed.append(None)
405
406 sz_top.append(None)
407 sz_bot.append(None)
408 x.append(None)
409 y.append(None)
410
411 ax.append(None)
412 ay.append(None)
413 az.append(None)
414 pfxx.append(None)
415 pfxz.append(None)
416 px.append(None)
417 pz.append(None)
418 vx0.append(None)
419 vy0.append(None)
420 vz0.append(None)
421 x0.append(None)
422 y0.append(None)
423 z0.append(None)
424
425 zone.append(None)
426 type_confidence.append(None)
427 plate_time.append(None)
428 extension.append(None)
429 spin_rate.append(None)
430 spin_direction.append(None)
431 vb.append(None)
432 ivb.append(None)
433 hb.append(None)
434
435 if 'hitData' in ab_list['playEvents'][n]:
436 launch_speed.append(ab_list['playEvents'][n]['hitData']['launchSpeed'] if 'launchSpeed' in ab_list['playEvents'][n]['hitData'] else None)
437 launch_angle.append(ab_list['playEvents'][n]['hitData']['launchAngle'] if 'launchAngle' in ab_list['playEvents'][n]['hitData'] else None)
438 launch_distance.append(ab_list['playEvents'][n]['hitData']['totalDistance'] if 'totalDistance' in ab_list['playEvents'][n]['hitData'] else None)
439 launch_location.append(ab_list['playEvents'][n]['hitData']['location'] if 'location' in ab_list['playEvents'][n]['hitData'] else None)
440
441 trajectory.append(ab_list['playEvents'][n]['hitData']['trajectory'] if 'trajectory' in ab_list['playEvents'][n]['hitData'] else None)
442 hardness.append(ab_list['playEvents'][n]['hitData']['hardness'] if 'hardness' in ab_list['playEvents'][n]['hitData'] else None)
443 hit_x.append(ab_list['playEvents'][n]['hitData']['coordinates']['coordX'] if 'coordX' in ab_list['playEvents'][n]['hitData']['coordinates'] else None)
444 hit_y.append(ab_list['playEvents'][n]['hitData']['coordinates']['coordY'] if 'coordY' in ab_list['playEvents'][n]['hitData']['coordinates'] else None)
445 else:
446 launch_speed.append(None)
447 launch_angle.append(None)
448 launch_distance.append(None)
449 launch_location.append(None)
450 trajectory.append(None)
451 hardness.append(None)
452 hit_x.append(None)
453 hit_y.append(None)
454
455 index_play.append(ab_list['playEvents'][n]['index'] if 'index' in ab_list['playEvents'][n] else None)
456 play_id.append(ab_list['playEvents'][n]['playId'] if 'playId' in ab_list['playEvents'][n] else None)
457 start_time.append(ab_list['playEvents'][n]['startTime'] if 'startTime' in ab_list['playEvents'][n] else None)
458 end_time.append(ab_list['playEvents'][n]['endTime'] if 'endTime' in ab_list['playEvents'][n] else None)
459 is_pitch.append(ab_list['playEvents'][n]['isPitch'] if 'isPitch' in ab_list['playEvents'][n] else None)
460 type_type.append(ab_list['playEvents'][n]['type'] if 'type' in ab_list['playEvents'][n] else None)
461
462
463
464 if n == len(ab_list['playEvents']) - 1 :
465
466 type_ab.append(data['liveData']['plays']['allPlays'][ab_id]['result']['type'] if 'type' in data['liveData']['plays']['allPlays'][ab_id]['result'] else None)
467 event.append(data['liveData']['plays']['allPlays'][ab_id]['result']['event'] if 'event' in data['liveData']['plays']['allPlays'][ab_id]['result'] else None)
468 event_type.append(data['liveData']['plays']['allPlays'][ab_id]['result']['eventType'] if 'eventType' in data['liveData']['plays']['allPlays'][ab_id]['result'] else None)
469 rbi.append(data['liveData']['plays']['allPlays'][ab_id]['result']['rbi'] if 'rbi' in data['liveData']['plays']['allPlays'][ab_id]['result'] else None)
470 away_score.append(data['liveData']['plays']['allPlays'][ab_id]['result']['awayScore'] if 'awayScore' in data['liveData']['plays']['allPlays'][ab_id]['result'] else None)
471 home_score.append(data['liveData']['plays']['allPlays'][ab_id]['result']['homeScore'] if 'homeScore' in data['liveData']['plays']['allPlays'][ab_id]['result'] else None)
472 is_out.append(data['liveData']['plays']['allPlays'][ab_id]['result']['isOut'] if 'isOut' in data['liveData']['plays']['allPlays'][ab_id]['result'] else None)
473
474 else:
475
476 type_ab.append(None)
477 event.append(None)
478 event_type.append(None)
479 rbi.append(None)
480 away_score.append(None)
481 home_score.append(None)
482 is_out.append(None)
483
484 elif ab_list['playEvents'][n]['count']['balls'] == 4:
485
486 event.append(data['liveData']['plays']['allPlays'][ab_id]['result']['event'])
487 event_type.append(data['liveData']['plays']['allPlays'][ab_id]['result']['eventType'])
488
489
490 game_id.append(data['gamePk'])
491 game_date.append(data['gameData']['datetime']['officialDate'])
492 batter_id.append(ab_list['matchup']['batter']['id'] if 'batter' in ab_list['matchup'] else None)
493 batter_name.append(ab_list['matchup']['batter']['fullName'] if 'batter' in ab_list['matchup'] else None)
494 batter_hand.append(ab_list['matchup']['batSide']['code'] if 'batSide' in ab_list['matchup'] else None)
495 pitcher_id.append(ab_list['matchup']['pitcher']['id'] if 'pitcher' in ab_list['matchup'] else None)
496 pitcher_name.append(ab_list['matchup']['pitcher']['fullName'] if 'pitcher' in ab_list['matchup'] else None)
497 pitcher_hand.append(ab_list['matchup']['pitchHand']['code'] if 'pitchHand' in ab_list['matchup'] else None)
498 if ab_list['about']['isTopInning']:
499 batter_team.append(data['gameData']['teams']['away']['abbreviation'] if 'away' in data['gameData']['teams'] else None)
500 batter_team_id.append(data['gameData']['teams']['away']['id'] if 'away' in data['gameData']['teams'] else None)
501 pitcher_team.append(data['gameData']['teams']['home']['abbreviation'] if 'home' in data['gameData']['teams'] else None)
502 pitcher_team_id.append(data['gameData']['teams']['away']['id'] if 'away' in data['gameData']['teams'] else None)
503 else:
504 batter_team.append(data['gameData']['teams']['home']['abbreviation'] if 'home' in data['gameData']['teams'] else None)
505 batter_team_id.append(data['gameData']['teams']['home']['id'] if 'home' in data['gameData']['teams'] else None)
506 pitcher_team.append(data['gameData']['teams']['away']['abbreviation'] if 'away' in data['gameData']['teams'] else None)
507 pitcher_team_id.append(data['gameData']['teams']['home']['id'] if 'home' in data['gameData']['teams'] else None)
508
509 play_description.append(None)
510 play_code.append(None)
511 in_play.append(None)
512 is_strike.append(None)
513 is_ball.append(None)
514 is_review.append(None)
515 pitch_type.append(None)
516 pitch_description.append(None)
517 strikes.append(ab_list['playEvents'][n]['count']['balls'] if 'balls' in ab_list['playEvents'][n]['count'] else None)
518 balls.append(ab_list['playEvents'][n]['count']['strikes'] if 'strikes' in ab_list['playEvents'][n]['count'] else None)
519 outs.append(ab_list['playEvents'][n]['count']['outs'] if 'outs' in ab_list['playEvents'][n]['count'] else None)
520 strikes_after.append(ab_list['playEvents'][n]['count']['balls'] if 'balls' in ab_list['playEvents'][n]['count'] else None)
521 balls_after.append(ab_list['playEvents'][n]['count']['strikes'] if 'strikes' in ab_list['playEvents'][n]['count'] else None)
522 outs_after.append(ab_list['playEvents'][n]['count']['outs'] if 'outs' in ab_list['playEvents'][n]['count'] else None)
523 index_play.append(ab_list['playEvents'][n]['index'] if 'index' in ab_list['playEvents'][n] else None)
524 play_id.append(ab_list['playEvents'][n]['playId'] if 'playId' in ab_list['playEvents'][n] else None)
525 start_time.append(ab_list['playEvents'][n]['startTime'] if 'startTime' in ab_list['playEvents'][n] else None)
526 end_time.append(ab_list['playEvents'][n]['endTime'] if 'endTime' in ab_list['playEvents'][n] else None)
527 is_pitch.append(ab_list['playEvents'][n]['isPitch'] if 'isPitch' in ab_list['playEvents'][n] else None)
528 type_type.append(ab_list['playEvents'][n]['type'] if 'type' in ab_list['playEvents'][n] else None)
529
530
531
532 is_swing.append(None)
533 is_whiff.append(None)
534 start_speed.append(None)
535 end_speed.append(None)
536 sz_top.append(None)
537 sz_bot.append(None)
538 x.append(None)
539 y.append(None)
540 ax.append(None)
541 ay.append(None)
542 az.append(None)
543 pfxx.append(None)
544 pfxz.append(None)
545 px.append(None)
546 pz.append(None)
547 vx0.append(None)
548 vy0.append(None)
549 vz0.append(None)
550 x0.append(None)
551 y0.append(None)
552 z0.append(None)
553 zone.append(None)
554 type_confidence.append(None)
555 plate_time.append(None)
556 extension.append(None)
557 spin_rate.append(None)
558 spin_direction.append(None)
559 vb.append(None)
560 ivb.append(None)
561 hb.append(None)
562 launch_speed.append(None)
563 launch_angle.append(None)
564 launch_distance.append(None)
565 launch_location.append(None)
566 trajectory.append(None)
567 hardness.append(None)
568 hit_x.append(None)
569 hit_y.append(None)
570 type_ab.append(None)
571 ab_number.append(None)
572
573 rbi.append(None)
574 away_score.append(None)
575 home_score.append(None)
576 is_out.append(None)
577
578 except KeyError:
579 print(f"No Data for Game")
580
581 df = pl.DataFrame(data={
582 'game_id':game_id,
583 'game_date':game_date,
584 'batter_id':batter_id,
585 'batter_name':batter_name,
586 'batter_hand':batter_hand,
587 'batter_team':batter_team,
588 'batter_team_id':batter_team_id,
589 'pitcher_id':pitcher_id,
590 'pitcher_name':pitcher_name,
591 'pitcher_hand':pitcher_hand,
592 'pitcher_team':pitcher_team,
593 'pitcher_team_id':pitcher_team_id,
594 'ab_number':ab_number,
595 'play_description':play_description,
596 'play_code':play_code,
597 'in_play':in_play,
598 'is_strike':is_strike,
599 'is_swing':is_swing,
600 'is_whiff':is_whiff,
601 'is_out':is_out,
602 'is_ball':is_ball,
603 'is_review':is_review,
604 'pitch_type':pitch_type,
605 'pitch_description':pitch_description,
606 'strikes':strikes,
607 'balls':balls,
608 'outs':outs,
609 'strikes_after':strikes_after,
610 'balls_after':balls_after,
611 'outs_after':outs_after,
612 'start_speed':start_speed,
613 'end_speed':end_speed,
614 'sz_top':sz_top,
615 'sz_bot':sz_bot,
616 'x':x,
617 'y':y,
618 'ax':ax,
619 'ay':ay,
620 'az':az,
621 'pfxx':pfxx,
622 'pfxz':pfxz,
623 'px':px,
624 'pz':pz,
625 'vx0':vx0,
626 'vy0':vy0,
627 'vz0':vz0,
628 'x0':x0,
629 'y0':y0,
630 'z0':z0,
631 'zone':zone,
632 'type_confidence':type_confidence,
633 'plate_time':plate_time,
634 'extension':extension,
635 'spin_rate':spin_rate,
636 'spin_direction':spin_direction,
637 'vb':vb,
638 'ivb':ivb,
639 'hb':hb,
640 'launch_speed':launch_speed,
641 'launch_angle':launch_angle,
642 'launch_distance':launch_distance,
643 'launch_location':launch_location,
644 'trajectory':trajectory,
645 'hardness':hardness,
646 'hit_x':hit_x,
647 'hit_y':hit_y,
648 'index_play':index_play,
649 'play_id':play_id,
650 'start_time':start_time,
651 'end_time':end_time,
652 'is_pitch':is_pitch,
653 'type_type':type_type,
654 'type_ab':type_ab,
655 'event':event,
656 'event_type':event_type,
657 'rbi':rbi,
658 'away_score':away_score,
659 'home_score':home_score,
660
661 },strict=False
662 )
663
664 return df
665
666 def get_teams(self):
667 """
668 Retrieves information about MLB teams from the MLB API and processes it into a Polars DataFrame.
669
670 Returns:
671 - mlb_teams_df (pl.DataFrame): A DataFrame containing team information, including team ID, city, name, franchise, abbreviation, parent organization ID, parent organization name, league ID, and league name.
672 """
673 # Make API call to retrieve team information
674 teams = requests.get(url='https://statsapi.mlb.com/api/v1/teams/').json()
675
676 # Extract relevant data from the API response
677 mlb_teams_city = [x['franchiseName'] if 'franchiseName' in x else None for x in teams['teams']]
678 mlb_teams_name = [x['teamName'] if 'franchiseName' in x else None for x in teams['teams']]
679 mlb_teams_franchise = [x['name'] if 'franchiseName' in x else None for x in teams['teams']]
680 mlb_teams_id = [x['id'] if 'franchiseName' in x else None for x in teams['teams']]
681 mlb_teams_abb = [x['abbreviation'] if 'franchiseName' in x else None for x in teams['teams']]
682 mlb_teams_parent_id = [x['parentOrgId'] if 'parentOrgId' in x else None for x in teams['teams']]
683 mlb_teams_parent = [x['parentOrgName'] if 'parentOrgName' in x else None for x in teams['teams']]
684 mlb_teams_league_id = [x['league']['id'] if 'id' in x['league'] else None for x in teams['teams']]
685 mlb_teams_league_name = [x['league']['name'] if 'name' in x['league'] else None for x in teams['teams']]
686
687 # Create a Polars DataFrame with the extracted data
688 mlb_teams_df = pl.DataFrame(data={'team_id': mlb_teams_id,
689 'city': mlb_teams_franchise,
690 'name': mlb_teams_name,
691 'franchise': mlb_teams_franchise,
692 'abbreviation': mlb_teams_abb,
693 'parent_org_id': mlb_teams_parent_id,
694 'parent_org': mlb_teams_parent,
695 'league_id': mlb_teams_league_id,
696 'league_name': mlb_teams_league_name
697 }).unique().drop_nulls(subset=['team_id']).sort('team_id')
698
699 # Fill missing parent organization IDs with team IDs
700 mlb_teams_df = mlb_teams_df.with_columns(
701 pl.when(pl.col('parent_org_id').is_null())
702 .then(pl.col('team_id'))
703 .otherwise(pl.col('parent_org_id'))
704 .alias('parent_org_id')
705 )
706
707 # Fill missing parent organization names with franchise names
708 mlb_teams_df = mlb_teams_df.with_columns(
709 pl.when(pl.col('parent_org').is_null())
710 .then(pl.col('franchise'))
711 .otherwise(pl.col('parent_org'))
712 .alias('parent_org')
713 )
714
715 # Create a dictionary for mapping team IDs to abbreviations
716 abbreviation_dict = mlb_teams_df.select(['team_id', 'abbreviation']).to_dict(as_series=False)
717 abbreviation_map = {k: v for k, v in zip(abbreviation_dict['team_id'], abbreviation_dict['abbreviation'])}
718
719 # Create a DataFrame for parent organization abbreviations
720 abbreviation_df = mlb_teams_df.select(['team_id', 'abbreviation']).rename({'team_id': 'parent_org_id', 'abbreviation': 'parent_org_abbreviation'})
721
722 # Join the parent organization abbreviations with the main DataFrame
723 mlb_teams_df = mlb_teams_df.join(abbreviation_df, on='parent_org_id', how='left')
724
725 return mlb_teams_df
726
727 def get_leagues(self):
728 """
729 Retrieves information about MLB leagues from the MLB API and processes it into a Polars DataFrame.
730
731 Returns:
732 - leagues_df (pl.DataFrame): A DataFrame containing league information, including league ID, league name, league abbreviation, and sport ID.
733 """
734 # Make API call to retrieve league information
735 leagues = requests.get(url='https://statsapi.mlb.com/api/v1/leagues/').json()
736
737 # Extract relevant data from the API response
738 sport_id = [x['sport']['id'] if 'sport' in x else None for x in leagues['leagues']]
739 league_id = [x['id'] if 'id' in x else None for x in leagues['leagues']]
740 league_name = [x['name'] if 'name' in x else None for x in leagues['leagues']]
741 league_abbreviation = [x['abbreviation'] if 'abbreviation' in x else None for x in leagues['leagues']]
742
743 # Create a Polars DataFrame with the extracted data
744 leagues_df = pl.DataFrame(data={
745 'league_id': league_id,
746 'league_name': league_name,
747 'league_abbreviation': league_abbreviation,
748 'sport_id': sport_id,
749 })
750
751 return leagues_df
752
753 def get_player_games_list(self, player_id: int,
754 season: int,
755 start_date: str = None,
756 end_date: str = None,
757 sport_id: int = 1,
758 game_type: list = ['R'],
759 pitching: bool = True):
760 """
761 Retrieves a list of game IDs for a specific player in a given season.
762
763 Parameters:
764 - player_id (int): The ID of the player.
765 - season (int): The season year for which to retrieve the game list.
766 - start_date (str): The start date (YYYY-MM-DD) of the range (default is January 1st of the specified season).
767 - end_date (str): The end date (YYYY-MM-DD) of the range (default is December 31st of the specified season).
768 - sport_id (int): The ID of the sport for which to retrieve player data.
769 - game_type (list): A list of game types to filter the schedule. Default is ['R'].
770 - pitching (bool): Return pitching games.
771
772 Returns:
773 - player_game_list (list): A list of game IDs in which the player participated during the specified season.
774 """
775 # Set default start and end dates if not provided
776 if not start_date:
777 start_date = f'{season}-01-01'
778 if not end_date:
779 end_date = f'{season}-12-31'
780
781 # Determine the group based on the pitching flag
782 group = 'pitching' if pitching else 'hitting'
783
784 # Validate date format
785 date_pattern = re.compile(r'^\d{4}-\d{2}-\d{2}$')
786 if not date_pattern.match(start_date):
787 raise ValueError(f"start_date {start_date} is not in YYYY-MM-DD format")
788 if not date_pattern.match(end_date):
789 raise ValueError(f"end_date {end_date} is not in YYYY-MM-DD format")
790
791 # Convert game type list to a comma-separated string
792 game_type_str = ','.join([str(x) for x in game_type])
793
794 # Make API call to retrieve player game logs
795 response = requests.get(url=f'http://statsapi.mlb.com/api/v1/people/{player_id}?hydrate=stats(group={group},type=gameLog,season={season},startDate={start_date},endDate={end_date},sportId={sport_id},gameType=[{game_type_str}]),hydrations').json()
796
797 # Check if stats are available in the response
798 if 'stats' not in response['people'][0]:
799 print(f'No {group} games found for player {player_id} in season {season}')
800 return []
801
802 # Extract game IDs from the API response
803 player_game_list = [x['game']['gamePk'] for x in response['people'][0]['stats'][0]['splits']]
804
805 return player_game_list
806
807 def get_players(self, sport_id: int, season: int, game_type: list = ['R']):
808 """
809 Retrieves data frame of players in a given league
810
811 Parameters:
812 - sport_id (int): The ID of the sport for which to retrieve player data.
813 - season (int): The season year for which to retrieve player data.
814 - game_type (list): A list of game types to filter the players. Default is ['R'].
815
816 Returns:
817 - player_df (pl.DataFrame): A DataFrame containing player information, including player ID, name, position, team, and age.
818 """
819 game_type_str = ','.join([str(x) for x in game_type])
820
821 # If game type is 'S', fetch data from a different endpoint
822 if game_type_str == 'S':
823 # Fetch pitcher data
824 pitcher_data = requests.get(f'https://bdfed.stitch.mlbinfra.com/bdfed/stats/player?&env=prod&season={season}&sportId=1&stats=season&group=pitching&gameType=S&limit=1000000&offset=0&sortStat=inningsPitched&order=asc').json()
825 fullName_list = [x['playerFullName'] for x in pitcher_data['stats']]
826 firstName_list = [x['playerFirstName'] for x in pitcher_data['stats']]
827 lastName_list = [x['playerLastName'] for x in pitcher_data['stats']]
828 id_list = [x['playerId'] for x in pitcher_data['stats']]
829 position_list = [x['primaryPositionAbbrev'] for x in pitcher_data['stats']]
830 team_list = [x['teamId'] for x in pitcher_data['stats']]
831
832 df_pitcher = pl.DataFrame(data={
833 'player_id': id_list,
834 'first_name': firstName_list,
835 'last_name': lastName_list,
836 'name': fullName_list,
837 'position': position_list,
838 'team': team_list
839 })
840
841 # Fetch batter data
842 batter_data = requests.get(f'https://bdfed.stitch.mlbinfra.com/bdfed/stats/player?&env=prod&season={season}&sportId=1&stats=season&group=hitting&gameType=S&limit=1000000&offset=0').json()
843 fullName_list = [x['playerFullName'] for x in batter_data['stats']]
844 firstName_list = [x['playerFirstName'] for x in batter_data['stats']]
845 lastName_list = [x['playerLastName'] for x in batter_data['stats']]
846 id_list = [x['playerId'] for x in batter_data['stats']]
847 position_list = [x['primaryPositionAbbrev'] for x in batter_data['stats']]
848 team_list = [x['teamId'] for x in batter_data['stats']]
849
850 df_batter = pl.DataFrame(data={
851 'player_id': id_list,
852 'first_name': firstName_list,
853 'last_name': lastName_list,
854 'name': fullName_list,
855 'position': position_list,
856 'team': team_list
857 })
858
859 # Combine pitcher and batter data
860 df = pl.concat([df_pitcher, df_batter]).unique().drop_nulls(subset=['player_id']).sort('player_id')
861
862 else:
863 # Fetch player data for other game types
864 player_data = requests.get(url=f'https://statsapi.mlb.com/api/v1/sports/{sport_id}/players?season={season}&gameType=[{game_type_str}]').json()['people']
865
866 # Extract relevant data
867 fullName_list = [x['fullName'] for x in player_data]
868 firstName_list = [x['firstName'] for x in player_data]
869 lastName_list = [x['lastName'] for x in player_data]
870 id_list = [x['id'] for x in player_data]
871 position_list = [x['primaryPosition']['abbreviation'] if 'primaryPosition' in x else None for x in player_data]
872 team_list = [x['currentTeam']['id'] if 'currentTeam' in x else None for x in player_data]
873 weight_list = [x['weight'] if 'weight' in x else None for x in player_data]
874 height_list = [x['height'] if 'height' in x else None for x in player_data]
875 age_list = [x['currentAge'] if 'currentAge' in x else None for x in player_data]
876 birthDate_list = [x['birthDate'] if 'birthDate' in x else None for x in player_data]
877
878 df = pl.DataFrame(data={
879 'player_id': id_list,
880 'first_name': firstName_list,
881 'last_name': lastName_list,
882 'name': fullName_list,
883 'position': position_list,
884 'team': team_list,
885 'weight': weight_list,
886 'height': height_list,
887 'age': age_list,
888 'birthDate': birthDate_list
889 })
890
891 return df
892 