CoolFace
Apppublic

TJStatsApps/pitch_plot_select_mlb

sourceHugging Facemitupdated 1y agoView on Hugging Face
1likes
api_scraper.py892 linesDownload Raw Back to root
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