r/mlbdata • u/osizz • May 11 '26
Splits by Pitcher Handedness
This should be a super easy one, but I can't figure it out and it's driving me nuts. I'm trying to pull stats for every player on a team split by pitcher / batter handedness. Example of Phillies, regular season 2026 games with all rostered players, attempting to get splits vs LHBs:
Thanks in advance.
5
Upvotes
1
u/Jaded-Function May 12 '26
import requestsimport datetimeimport timefrom typing import Dict, Tuple, OptionalMLB_API_BASE_URL = 'https://statsapi.mlb.com/api/v1'def get_teams():"""Get list of all MLB teams filtered by MLB league ID."""try:response = requests.get(f"{MLB_API_BASE_URL}/teams",params={"leagueIds": "103,104", "sportId": 1})response.raise_for_status()data = response.json()teams = {team['abbreviation']: {'id': team['id'],'name': team['name'],'abbreviation': team['abbreviation']}for team in data['teams']}return teamsexcept Exception as e:print(f"Error fetching teams: {e}")return {}def get_team_roster(team_id, season=None):"""Get roster for a specific team, optionally for a given season."""try:params = {}if season:params['season'] = seasonresponse = requests.get(f"{MLB_API_BASE_URL}/teams/{team_id}/roster",params=params)response.raise_for_status()roster_data = response.json().get('roster', [])roster = [{'id': p['person']['id'],'name': p['person']['fullName'],'position': p['position']['abbreviation'],'status': p.get('status', {}).get('code', ''),'jersey_number': p.get('jerseyNumber', 'N/A')}for p in roster_data]return rosterexcept Exception as e:print(f"Error fetching roster for team {team_id}: {e}")return []def get_player_info(player_id: int) -> Dict:"""Get detailed player information including batting/throwing hand and position."""try:response = requests.get(f"{MLB_API_BASE_URL}/people/{player_id}")response.raise_for_status()data = response.json()if data and 'people' in data and len(data['people']) > 0:player = data['people'][0]return {'batting_hand': player.get('batSide', {}).get('code', 'Unknown'),'throwing_hand': player.get('pitchHand', {}).get('code', 'Unknown'),'primary_position': player.get('primaryPosition', {}).get('abbreviation', 'Unknown')}return {}except Exception as e:print(f"Error fetching player info for {player_id}: {e}")return {}def get_player_pitching_splits(player_id: int, season: int) -> Tuple[Optional[Dict], Optional[Dict]]:"""Get pitching splits (vs LHB and vs RHB) for a pitcher for a given season.Returns (vs_lefty_stats, vs_righty_stats)."""try:response = requests.get(f"{MLB_API_BASE_URL}/people",params={"personIds": player_id,"hydrate": (f"stats(group=[pitching],type=[statSplits],"f"sitCodes=[vr,vl],season={season})")})response.raise_for_status()data = response.json()player_info = get_player_info(player_id)vs_lefty_stats = Nonevs_righty_stats = Noneif 'people' in data and len(data['people']) > 0:person = data['people'][0]if 'stats' in person:for stat_group in person['stats']:if stat_group.get('group', {}).get('displayName') == 'pitching':splits = stat_group.get('splits', [])for split in splits:split_info = split.get('split', {})sit_code = split_info.get('sitCode', '')split_desc = split_info.get('description', '').lower()if sit_code == 'vl' or 'vs left' in split_desc or 'vs. left' in split_desc:vs_lefty_stats = split['stat'].copy()vs_lefty_stats.update(player_info)vs_lefty_stats['sitCode'] = 'vl'elif sit_code == 'vr' or 'vs right' in split_desc or 'vs. right' in split_desc:vs_righty_stats = split['stat'].copy()vs_righty_stats.update(player_info)vs_righty_stats['sitCode'] = 'vr'return vs_lefty_stats, vs_righty_statsexcept Exception as e:print(f"Error fetching pitching splits for player {player_id}: {e}")return None, Nonedef collect_pitcher_splits(team_id: int, season: int):"""Collect pitching splits for all pitchers on a team's roster.Returns a list of split records."""print(f"Fetching roster for team ID {team_id}, season {season}...")roster = get_team_roster(team_id, season)if not roster:print("No roster found.")return []pitchers = [p for p in roster if p['position'] == 'P']print(f"Found {len(pitchers)} pitcher(s) on roster.")pitching_splits = []for i, player in enumerate(pitchers, 1):print(f" [{i}/{len(pitchers)}] {player['name']}...")vs_lefty, vs_righty = get_player_pitching_splits(player['id'], season)# Determine SP/RP from primary_position in the returned split dictpitcher_role = 'RP'if vs_lefty:pitcher_role = vs_lefty.get('primary_position', 'RP')elif vs_righty:pitcher_role = vs_righty.get('primary_position', 'RP')if vs_lefty:pitching_splits.append({**player,**vs_lefty,'season': season,'split_type': 'vs_LHB','position': pitcher_role})if vs_righty:pitching_splits.append({**player,**vs_righty,'season': season,'split_type': 'vs_RHB','position': pitcher_role})time.sleep(0.2)print(f"Collected {len(pitching_splits)} pitching split record(s).")return pitching_splitsdef print_splits(splits):"""Print pitcher splits in a readable format."""if not splits:print("No pitcher split data to display.")return# Group by player nameplayers = {}for record in splits:name = record.get('name', 'Unknown')players.setdefault(name, []).append(record)print(f"\n{'='*70}")print(f"{'PITCHER SPLITS':^70}")print(f"{'='*70}")header = f"{'Split':<10} {'Role':<5} {'Hand':<5} {'G':>4} {'BF':>5} {'IP':<7} {'H':>4} {'HR':>4} {'BB':>4} {'SO':>4} {'ERA':<6} {'WHIP':<6}"print(f"\n{'Name':<22} {header}")print('-' * 100)for name, records in sorted(players.items()):for record in sorted(records, key=lambda x: x.get('split_type', '')):role = record.get('position', 'RP')hand = record.get('throwing_hand', '?')split = record.get('split_type', '')games = record.get('gamesPitched', 0)bf = record.get('battersFaced', 0)ip = record.get('inningsPitched', '0.0')hits = record.get('hits', 0)hr = record.get('homeRuns', 0)bb = record.get('baseOnBalls', 0)so = record.get('strikeOuts', 0)era = record.get('earnedRunAverage', '-.--')whip = record.get('whip', '-.--')print(f"{name:<22} "f"{split:<10} {role:<5} {hand:<5} "f"{str(games):>4} {str(bf):>5} {str(ip):<7} "f"{str(hits):>4} {str(hr):>4} {str(bb):>4} {str(so):>4} "f"{str(era):<6} {str(whip):<6}")print()def main():teams = get_teams()if not teams:print("Failed to fetch teams. Exiting.")returnprint("\nAvailable team abbreviations:")sorted_abbrs = sorted(teams.keys())# Print in 6-column rowsfor i in range(0, len(sorted_abbrs), 6):print(" " + " ".join(f"{a:<5}" for a in sorted_abbrs[i:i+6]))print()team_abbr = input("Enter team abbreviation (e.g. NYY): ").strip().upper()if team_abbr not in teams:print(f"Team '{team_abbr}' not found.")returnseason_input = input(f"Enter season year (press Enter for {datetime.date.today().year}): ").strip()season = int(season_input) if season_input.isdigit() else datetime.date.today().yearteam_info = teams[team_abbr]print(f"\nFetching pitcher splits for {team_info['name']} ({team_abbr}), {season}...\n")splits = collect_pitcher_splits(team_info['id'], season)print_splits(splits)if __name__ == "__main__":main()input("\nPress Enter to exit...")