-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from ss77995ss/feature/baserun-stealing
[Feature][Statcast] Runner Basestealing
- Loading branch information
Showing
5 changed files
with
147 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Statcast Runner Basestealing | ||
|
||
## `runner_basestealing` | ||
|
||
Function to get baserunner stealing base data from each stolen base attempt. Attempts include successful stolen bases (`SB`), advances via balk (`BK`), caught stealing (`CS`), and pickoffs (`PK`). Also pickoff attempts over three times and not successful are included (`FB`). Based on Baseball Savant's [Runner Basestealing](https://baseballsavant.mlb.com/leaderboard/basestealing-run-value). | ||
|
||
**Examples** | ||
|
||
```python | ||
from baseball_stats_python import runner_basestealing | ||
|
||
# Get Shohei Ohtani's runner basestealing data | ||
runner_basestealing('660271') | ||
|
||
# Get Shohei Ohtani's runner basestealing data in 2023 | ||
runner_basestealing('660271', season='2023') | ||
|
||
# Get Shohei Ohtani's catcher throwing data in playoffs | ||
catcher_throwing('660271', game_type=GameType.PLAYOFFS) | ||
``` | ||
|
||
**Arguments** | ||
|
||
| Argument | Data Type | Description | | ||
| -------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| runner_id (Required) | `str` | The MLBAM ID of the catcher. | | ||
| 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) | | ||
| season | `str` | The season to filter by. The earliest season available is 2016. | | ||
| pitch_hand | `str` or `Hand` | The pitch hand to filter by. Default is "all". Check enum [Hand](../enums/statcast_leaderboard.py) | | ||
| prior_pk | `str` | The number of prior pick-off attempts from pitcher before the stolen base opportunity. Default is "all". Can be "all", "1", "2", or "3". "3" is include all prior pick-off attempts over 3. | | ||
|
||
**Return** | ||
|
||
A DataFrame with columns that related to the [Runner Basestealing](https://baseballsavant.mlb.com/leaderboard/basestealing-run-value) leaderboard. The DataFrame will represent each stolen base attempt for a specific runner which contains data like `r_primary_lead`, `r_secondary_lead`, `run_value`, `r_sec_minus_prim_lead`, `runner_moved_cd`, etc. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import pandas as pd | ||
import requests | ||
|
||
from ..constants import DEFAULT_SEASON | ||
from ..enums.statcast_leaderboard import GameType, Hand | ||
from ..utils.statcast_leaderboard import get_hand_param_str, get_prior_pk_param_str | ||
|
||
session = requests.Session() | ||
|
||
API_URL = ( | ||
'https://baseballsavant.mlb.com/leaderboard/services/basestealing-running-game' | ||
) | ||
|
||
|
||
def get_run_value(df: pd.DataFrame) -> float: | ||
if df['is_runner_cs']: | ||
return -0.45 | ||
if df['is_runner_sb']: | ||
return 0.2 | ||
if df['is_runner_pk']: | ||
return -0.45 | ||
if df['is_runner_bk']: | ||
return 0.2 | ||
if df['is_runner_fb']: | ||
return 0.2 | ||
|
||
raise ValueError(f'Invalid DataFrame: {df}') | ||
|
||
|
||
def runner_basestealing( | ||
runner_id: str, | ||
game_type: str | GameType = GameType.REGULAR_SEASON, | ||
season: str = str(DEFAULT_SEASON), | ||
pitch_hand: str | Hand = Hand.ALL, | ||
prior_pk: str = 'all', | ||
) -> pd.DataFrame: | ||
""" | ||
Get basestealing data from each stolen base opportunity for a specific runner. | ||
ref: https://baseballsavant.mlb.com/leaderboard/basestealing-run-value | ||
Args: | ||
runner_id (str): The MLBAM ID of the runner. (Required) | ||
game_type (str | GameType): The game type to filter by. Default is "Regular". | ||
season (str): The season to filter by. The earliest season available is 2016. | ||
pitch_hand (str | Hand): The pitch hand to filter by. Default is "all". | ||
prior_pk (str): The number of prior pick-off attempts from pitcher before the stolen base opportunity. Default is "all". | ||
Can be "all", "1", "2", or "3". "3" is include all prior pick-off attempts over 3. | ||
Returns: | ||
pd.DataFrame: A DataFrame containing the basestealing data. | ||
""" | ||
|
||
if not runner_id: | ||
raise ValueError('runner_id is required') | ||
|
||
if not isinstance(game_type, str) and not isinstance(game_type, GameType): | ||
raise ValueError(f'Invalid type for game_type: {type(game_type)}') | ||
|
||
if not GameType.has_value(game_type): | ||
raise ValueError(f'Invalid game type: {game_type}') | ||
|
||
if int(season) < 2016: | ||
raise ValueError( | ||
f'Invalid season: {season}, The earliest season available is 2016' | ||
) | ||
|
||
params = { | ||
'game_type': game_type, | ||
'season': season, | ||
'n': 0, | ||
'pitch_hand': get_hand_param_str(pitch_hand), | ||
'prior_pk': get_prior_pk_param_str(prior_pk), | ||
} | ||
|
||
response = session.get(f'{API_URL}/{runner_id}', params=params) | ||
|
||
if response.status_code == 200: | ||
result = response.json() | ||
df = pd.DataFrame(result['data']) | ||
df['run_value'] = df.apply(get_run_value, axis=1) | ||
return df | ||
else: | ||
raise Exception( | ||
f'Failed to fetch data: {response.status_code} - {response.text}' | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from ..enums.statcast_leaderboard import Hand | ||
|
||
|
||
def get_hand_param_str(hand: str | Hand) -> str: | ||
if not isinstance(hand, str) and not isinstance(hand, Hand): | ||
raise ValueError(f'Invalid type for hand: {type(hand)}') | ||
|
||
if not Hand.has_value(hand): | ||
raise ValueError(f'Invalid hand: {hand}') | ||
|
||
return f'{hand}' | ||
|
||
|
||
def get_prior_pk_param_str(prior_pk: str) -> str: | ||
if not isinstance(prior_pk, str): | ||
raise ValueError(f'Invalid type for prior_pk: {type(prior_pk)}') | ||
|
||
if prior_pk not in ['all', '1', '2', '3']: | ||
raise ValueError(f'Invalid prior_pk: {prior_pk}') | ||
|
||
return prior_pk |