Skip to content

Commit 2b1862e

Browse files
authored
Merge pull request #16 from ss77995ss/feature/catcher-throwing
[Feature][Statcast] Catcher throwing
2 parents 8c23041 + 95942d5 commit 2b1862e

File tree

11 files changed

+137
-7
lines changed

11 files changed

+137
-7
lines changed

docs/catcher_throwing.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Statcast Catcher Throwing
2+
3+
## `catcher_throwing`
4+
5+
Function to get catcher throwing data from each stolen base attempt for a specific catcher. Based on Baseball Savant's [Catcher Throwing](https://baseballsavant.mlb.com/leaderboard/catcher-throwing).
6+
7+
**Examples**
8+
9+
```python
10+
from baseball_stats_python import catcher_throwing
11+
12+
# Get Will Smith's catcher throwing data
13+
catcher_throwing('669257')
14+
15+
# Get Will Smith's catcher throwing data in 2023
16+
catcher_throwing('669257', season='2023')
17+
18+
# Get Will Smith's catcher throwing data in playoffs
19+
catcher_throwing('669257', game_type=GameType.PLAYOFFS)
20+
```
21+
22+
**Arguments**
23+
24+
| Argument | Data Type | Description |
25+
| --------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26+
| catcher_id (Required) | `str` | The MLBAM ID of the catcher. |
27+
| game_type | `str` or `GameType` | The game type to filter by. Can be `R` for regular season, `PO` for playoffs, or `All` for all games. Check enum [GameType](../enums/statcast_leaderboard.py) |
28+
| season | `str` | The season to filter by. The earliest season available is 2016. |
29+
30+
**Return**
31+
32+
A DataFrame with columns that related to the [Catcher Throwing](https://baseballsavant.mlb.com/leaderboard/catcher-throwing) leaderboard. The DataFrame will represent each stolen base attempt for a specific catcher which contains data like `pop_time`, `throw_type`, `r_primary_lead`, etc.

example.py

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
)
88
from src.baseball_stats_python.enums.minor import MinorGameType
99
from src.baseball_stats_python.enums.statcast import GameType, MlbTeam, Month
10+
from src.baseball_stats_python.statcast.catcher_throwing import catcher_throwing
1011

1112

1213
def example():
@@ -35,3 +36,6 @@ def mlbam_id_example():
3536
# example()
3637
# minor_example()
3738
# mlbam_id_example()
39+
40+
df = catcher_throwing('669257', game_type=123)
41+
print(df)

src/baseball_stats_python/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .statcast.catcher_throwing import catcher_throwing
12
from .statcast.minor_statcast_search import (
23
minor_statcast_batter_search,
34
minor_statcast_pitcher_search,
@@ -18,4 +19,5 @@
1819
'minor_statcast_pitcher_search',
1920
'minor_statcast_batter_search',
2021
'mlbam_id_search',
22+
'catcher_throwing',
2123
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DEFAULT_SEASON = 2024

src/baseball_stats_python/constants/common.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .enum_base import EnumBase
2+
3+
4+
class GameType(EnumBase):
5+
REGULAR_SEASON = 'R'
6+
PLAYOFFS = 'PO'
7+
ALL = 'All'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import pandas as pd
2+
import requests
3+
4+
from ..constants import DEFAULT_SEASON
5+
from ..enums.statcast_leaderboard import GameType
6+
7+
session = requests.Session()
8+
9+
API_URL = 'https://baseballsavant.mlb.com/leaderboard/services/catcher-throwing'
10+
11+
12+
def catcher_throwing(
13+
catcher_id: str,
14+
game_type: str | GameType = GameType.REGULAR_SEASON,
15+
season: str = str(DEFAULT_SEASON),
16+
) -> pd.DataFrame:
17+
"""
18+
Get catcher throwing data from each stolen base attempt for a specific catcher.
19+
ref: https://baseballsavant.mlb.com/leaderboard/catcher-throwing
20+
21+
Args:
22+
catcher_id (str): The MLBAM ID of the catcher. (Required)
23+
game_type (str): The game type to filter by.
24+
n (str): The number of results to return.
25+
season (str): The season to filter by. The earliest season available is 2016.
26+
27+
Returns:
28+
pd.DataFrame: A DataFrame containing the catcher throwing data.
29+
"""
30+
31+
if not catcher_id:
32+
raise ValueError('catcher_id is required')
33+
34+
if not isinstance(game_type, str) and not isinstance(game_type, GameType):
35+
raise ValueError(f'Invalid type for game_type: {type(game_type)}')
36+
37+
if not GameType.has_value(game_type):
38+
raise ValueError(f'Invalid game type: {game_type}')
39+
40+
if int(season) < 2016:
41+
raise ValueError(
42+
f'Invalid season: {season}, The earliest season available is 2016'
43+
)
44+
45+
params = {
46+
'gameType': game_type,
47+
'season': season,
48+
'n': 0,
49+
}
50+
51+
response = session.get(f'{API_URL}/{catcher_id}', params=params)
52+
53+
if response.status_code == 200:
54+
result = response.json()
55+
return pd.DataFrame(result['data'])
56+
else:
57+
raise Exception(
58+
f'Failed to fetch data: {response.status_code} - {response.text}'
59+
)

src/baseball_stats_python/statcast/mlbam_id_search.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
def mlbam_id_search(player_name: str, debug: bool = False) -> pd.DataFrame:
1414
"""
15-
Search for MLBAM ID(s) by player name.
15+
Search for MLBAM ID(s) by player's name.
1616
1717
Args:
1818
player_name (str): The player name to search for. (Required)

src/baseball_stats_python/utils/statcast.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
from ..constants import DEFAULT_SEASON
12
from ..enums.statcast import GameType, MlbTeam, Month
23

3-
CURRENT_SEASON = 2024
44
START_SEASON = 2008
55
STATCAST_START_SEASON = 2015
66

7-
ALL_SEASONS = [str(year) for year in range(START_SEASON, CURRENT_SEASON + 1)]
7+
ALL_SEASONS = [str(year) for year in range(START_SEASON, DEFAULT_SEASON + 1)]
88
STATCAST_SEASONS = [
9-
str(year) for year in range(STATCAST_START_SEASON, CURRENT_SEASON + 1)
9+
str(year) for year in range(STATCAST_START_SEASON, DEFAULT_SEASON + 1)
1010
]
1111

1212

@@ -20,7 +20,7 @@ def get_season_param_str(season: str | list[str]) -> str:
2020
return '|'.join(season)
2121

2222
if season == '':
23-
return str(CURRENT_SEASON)
23+
return str(DEFAULT_SEASON)
2424
if season == 'all':
2525
return '|'.join(ALL_SEASONS)
2626
if season == 'statcast':
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import pytest
2+
3+
from baseball_stats_python.statcast.catcher_throwing import catcher_throwing
4+
5+
6+
def test_catcher_throwing_invalid():
7+
with pytest.raises(ValueError) as e:
8+
catcher_throwing('')
9+
assert str(e.value) == 'catcher_id is required'
10+
11+
with pytest.raises(ValueError) as e:
12+
catcher_throwing('669257', 'invalid')
13+
assert str(e.value) == 'Invalid game type: invalid'
14+
15+
with pytest.raises(ValueError) as e:
16+
catcher_throwing('669257', 123)
17+
assert str(e.value) == f'Invalid type for game_type: {int}'
18+
19+
with pytest.raises(ValueError) as e:
20+
catcher_throwing('669257', season='2015')
21+
assert str(e.value) == 'Invalid season: 2015, The earliest season available is 2016'
22+
23+
with pytest.raises(ValueError) as e:
24+
catcher_throwing('669257', game_type='RRR')
25+
assert str(e.value) == 'Invalid game type: RRR'

tests/utils/test_statcast.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import pytest
44

5+
from baseball_stats_python.constants import DEFAULT_SEASON
56
from baseball_stats_python.enums.statcast import GameType, MlbTeam, Month
67
from baseball_stats_python.utils.statcast import (
7-
CURRENT_SEASON,
88
get_game_type_param_str,
99
get_month_param_str,
1010
get_season_param_str,
@@ -15,7 +15,7 @@
1515
def test_get_season_param_str():
1616
assert get_season_param_str('2024') == '2024'
1717
assert get_season_param_str(['2024', '2023']) == '2024|2023'
18-
assert get_season_param_str('') == str(CURRENT_SEASON)
18+
assert get_season_param_str('') == str(DEFAULT_SEASON)
1919

2020

2121
def test_get_season_param_str_invalid():

0 commit comments

Comments
 (0)