diff --git a/docs/statcast_fielding.md b/docs/statcast_fielding.md index 4dfe745d..bbacf325 100644 --- a/docs/statcast_fielding.md +++ b/docs/statcast_fielding.md @@ -2,7 +2,7 @@ **Note:** Statcast data is liable to change unexpectedly due to the large number of observations. Please keep that in mind when pulling data. # Statcast Fielding Outs Above Average -`statcast_outs_above_average(year: int, pos: Union[int, str], min_att: Union[int, str] = "q")` +`statcast_outs_above_average(year: int, pos: Union[int, str], min_att: Union[int, str] = "q", view: str = "Fielder")` This function retrieves outs above average (OAA) for the given year, position, and attempts. OAA is a Statcast metric based on the "cumulative effect of all individual plays a fielder has been credited or debited with, making it a range-based metric of fielding skill that accounts for the number of plays made and the difficulty of them". @@ -10,6 +10,7 @@ This function retrieves outs above average (OAA) for the given year, position, a `year:` The year for which you wish to retrieve batted ball against data. Format: YYYY. `pos:` The position you are interested in. Valid positions include "all", "IF", "OF", and position names, numbers, or abbreviations. Position numbers may be entered as integers or strings, e.g. 6 or "6" for shortstops. Pitchers and catchers are not included. `min_att:` The minimum number of fielding attempts for the player to be included in the result. Statcast's default is players, which is 1 fielding attempt per game played for 2B, SS, 3B, and OF and 1 fielding attempt per every other game played for 1B. +`view:` The perspective by which the OAA numbers should be returned. Statcast default is fielders, which returns typical OOA statistics for all eligible fielders. Valid views include "Fielder" (default) "Pitcher" (OOA of defense behind pitcher), "Fielding_Team", "Batter" (OOA of Defense when player is at-bat), and "Batting_Team". The argument "min_att" is ignored on team based views. ## Examples of Valid Queries ```python @@ -23,8 +24,13 @@ data = statcast_outs_above_average(2019, pos = "cf") # Shortstops who qualified in 2019 data = statcast_outs_above_average(2019, pos = 6) + # Infielders with at least 100 fielding attempts in 2019 data = statcast_outs_above_average(2019, pos = "IF", min_att = 100) + +# Infield defense stats behind particular pitchers in 2021 +data = statcast_outs_above_average(2021, pos = "IF", view = "Pitcher") + ``` # Statcast Fielding Outfield Directional OAA diff --git a/pybaseball/statcast_fielding.py b/pybaseball/statcast_fielding.py index b13a1d03..f872ebf6 100644 --- a/pybaseball/statcast_fielding.py +++ b/pybaseball/statcast_fielding.py @@ -7,13 +7,31 @@ from . import cache from .utils import norm_positions, sanitize_statcast_columns +"""Scrapes outs above average from baseball savant for a given year and position + + Args: + year (int): Season to pull + pos (Union[int, str]): Numerical position (e.g. 3 for 1B, 4 for 2B). Catchers not supported + min_att (Union[int, str], optional): Integer number of attempts required or "q" for qualified. + Defaults to "q". + view (str, optional): Perspective of defensive metrics. String argument supports "Fielder", "Pitcher", "Fielding_Team", "Batter", and "Batting_Team" + Defaults to "Fielder" + + Raises: + ValueError: Failure if catcher is passed + + Returns: + pd.DataFrame: Dataframe of defensive OAA for the given year and position for players who have met + the given threshold +""" + @cache.df_cache() -def statcast_outs_above_average(year: int, pos: Union[int, str], min_att: Union[int, str] = "q") -> pd.DataFrame: +def statcast_outs_above_average(year: int, pos: Union[int, str], min_att: Union[int, str] = "q", view: str = "Fielder") -> pd.DataFrame: pos = norm_positions(pos) # catcher is not included in this leaderboard if pos == "2": raise ValueError("This particular leaderboard does not include catchers!") - url = f"https://baseballsavant.mlb.com/leaderboard/outs_above_average?type=Fielder&year={year}&team=&range=year&min={min_att}&pos={pos}&roles=&viz=show&csv=true" + url = f"https://baseballsavant.mlb.com/leaderboard/outs_above_average?type={view}&year={year}&team=&range=year&min={min_att}&pos={pos}&roles=&viz=show&csv=true" res = requests.get(url, timeout=None).content data = pd.read_csv(io.StringIO(res.decode('utf-8'))) data = sanitize_statcast_columns(data) diff --git a/tests/integration/pybaseball/test_statcast_fielding.py b/tests/integration/pybaseball/test_statcast_fielding.py index 5292a53b..88f0cc45 100644 --- a/tests/integration/pybaseball/test_statcast_fielding.py +++ b/tests/integration/pybaseball/test_statcast_fielding.py @@ -20,6 +20,18 @@ def test_statcast_outs_above_average() -> None: assert len(result.columns) == 17 assert len(result) > 0 +def test_statcast_outs_above_average_view() -> None: + min_att = 50 + pos = "of" + view = "Pitcher" + result: pd.DataFrame = statcast_outs_above_average(2019, pos, min_att, view) + + assert result is not None + assert not result.empty + + assert len(result.columns) == 17 + assert len(result) > 0 + def test_statcast_outfield_directional_oaa() -> None: min_opp = 50 result: pd.DataFrame = statcast_outfield_directional_oaa(2019, min_opp) @@ -76,3 +88,6 @@ def test_statcast_catcher_framing() -> None: assert len(result) > 0 assert len(result.loc[result.n_called_pitches < min_called_p]) == 0 + +#test_statcast_outs_above_average_view() +test_statcast_outs_above_average() \ No newline at end of file