Skip to main content

mlb_api/requests/stats/
derived.rs

1use crate::stats::raw::OmittedStatError;
2use crate::stats::units::{CountingStat, InningsPitched, PercentageStat, ThreeDecimalPlaceRateStat, TwoDecimalPlaceRateStat};
3
4type Result<T, E = OmittedStatError> = core::result::Result<T, E>;
5
6macro_rules! wrap {
7    ($expr:expr) => {
8        (move || Result::<_>::Ok($expr))().unwrap_or_default()
9    };
10}
11
12/// # AVG (BA) - Batting Average
13/// Describes the probability of a hit within an at bat, aka: the amount of hits per at bat
14///
15/// Hitters: Higher is better.
16/// Pitchers: Lower is better.
17#[must_use]
18pub fn avg(hits: Result<CountingStat>, at_bats: Result<CountingStat>) -> ThreeDecimalPlaceRateStat {
19    wrap!(ThreeDecimalPlaceRateStat::new(hits? as f64 / at_bats? as f64))
20}
21
22/// # SLG - Slugging
23/// Describes the amount of bases averaged per each at bat.
24///
25/// Hitters: Higher is better.
26/// Pitchers: Lower is better.
27#[must_use]
28pub fn slg(total_bases: Result<CountingStat>, at_bats: Result<CountingStat>) -> ThreeDecimalPlaceRateStat {
29    wrap!(ThreeDecimalPlaceRateStat::new(total_bases? as f64 / at_bats? as f64))
30}
31
32/// # OBP - On-Base Percentage
33/// Describes the probability of getting on base by any form, HBP, Walk, Intentional Walk, etc. per each PA.
34///
35/// Hitters: Higher is better.
36/// Pitchers: Lower is better.
37#[must_use]
38pub fn obp(
39    hits: Result<CountingStat>,
40    base_on_balls: Result<CountingStat>,
41    intentional_walks: Result<CountingStat>,
42    hit_by_pitch: Result<CountingStat>,
43    at_bats: Result<CountingStat>,
44    sac_bunts: Result<CountingStat>,
45    sac_hits: Result<CountingStat>,
46) -> ThreeDecimalPlaceRateStat {
47    wrap!(ThreeDecimalPlaceRateStat::new((hits? + base_on_balls? + intentional_walks? + hit_by_pitch?) as f64 / (at_bats? + base_on_balls? + intentional_walks? + hit_by_pitch? + sac_bunts? + sac_hits?) as f64))
48}
49
50/// # OPS - On-Base Plus Slugging
51/// Adds OBP and SLG values together to make a new stat (yes, this means both components are weighted equally)
52/// Typically this is used as a trivial way to rank performance, however if possible, using `wOBA`-like stats is recommended as they are generally more accurate.
53///
54/// Hitters: Higher is better.
55/// Pitchers: Lower is better.
56#[must_use]
57pub fn ops(obp: Result<ThreeDecimalPlaceRateStat>, slg: Result<ThreeDecimalPlaceRateStat>) -> ThreeDecimalPlaceRateStat {
58    wrap!(ThreeDecimalPlaceRateStat::new(*obp? + *slg?))
59}
60
61/// # SB% - Stolen Base Percentage
62/// Describes the probability of a stolen base, given an attempt
63///
64/// Hitters: Higher is better.
65/// Pitchers: Lower is better.
66#[must_use]
67pub fn stolen_base_pct(stolen_bases: Result<CountingStat>, caught_stealing: Result<CountingStat>) -> PercentageStat {
68    wrap!(PercentageStat::new(stolen_bases? as f64 / (stolen_bases? + caught_stealing?) as f64))
69}
70
71/// # CS% - Caught Stealing Percentage
72/// Describes the probability of failing to steal a base, given an attempt
73///
74/// Hitters: Lower is better.
75/// Pitchers: Higher is better.
76#[must_use]
77pub fn caught_stealing_pct(stolen_bases: Result<CountingStat>, caught_stealing: Result<CountingStat>) -> PercentageStat {
78    wrap!(PercentageStat::new(caught_stealing? as f64 / (stolen_bases? + caught_stealing?) as f64))
79}
80
81/// # BABIP - Batting Average on Balls in Play
82/// Describes the batting average, only sampling balls that are in play.\
83/// This stat is typically used as a "luck-indicator" stat. Being around .400 or greater is generally considered lucky, however below .300 or so is considered unlucky.\
84/// Using expected stats (ex: `xwOBA` or `xAVG`) and comparing to the actual-outcome stats (ex: `wOBA` and `AVG`) generally gives a clearer indicator of luck, however these numbers are harder to find.
85///
86/// Hitters: Higher is better.
87/// Pitchers: Lower is better.
88#[must_use]
89pub fn babip(
90    hits: Result<CountingStat>,
91    home_runs: Result<CountingStat>,
92    at_bats: Result<CountingStat>,
93    strikeouts: Result<CountingStat>,
94    sac_flies: Result<CountingStat>,
95) -> ThreeDecimalPlaceRateStat {
96    wrap!(ThreeDecimalPlaceRateStat::new((hits? - home_runs?) as f64 / (at_bats? - strikeouts? - home_runs? - sac_flies?) as f64))
97}
98
99/// # BB%
100/// Percentage of plate appearances that end in a walk (unintentional)
101///
102/// Hitters: Higher is better.
103/// Pitchers: Lower is better.
104#[must_use]
105pub fn bb_pct(base_on_balls: Result<CountingStat>, plate_appearances: Result<CountingStat>) -> PercentageStat {
106    wrap!(PercentageStat::new(base_on_balls? as f64 / plate_appearances? as f64))
107}
108
109/// # K%
110/// Percentage of plate appearances that end in a strikeout
111///
112/// Hitters: Lower is better.
113/// Pitchers: Higher is better.
114#[must_use]
115pub fn k_pct(strikeouts: Result<CountingStat>, plate_appearances: Result<CountingStat>) -> PercentageStat {
116    wrap!(PercentageStat::new(strikeouts? as f64 / plate_appearances? as f64))
117}
118
119/// # Extra Bases
120pub fn extra_bases(doubles: Result<CountingStat>, triples: Result<CountingStat>, home_runs: Result<CountingStat>) -> Result<CountingStat> {
121    Ok(doubles? + triples? * 2 + home_runs? * 3)
122}
123
124/// # ISO - Isolated Power
125/// Describes the amount of extra bases hit per at bat.
126///
127/// Hitters: Higher is better.
128/// Pitchers: Lower is better.
129#[must_use]
130pub fn iso(extra_bases: Result<CountingStat>, at_bats: Result<CountingStat>) -> ThreeDecimalPlaceRateStat {
131    wrap!(ThreeDecimalPlaceRateStat::new(extra_bases? as f64 / at_bats? as f64))
132}
133
134/// # K/BB Ratio (Strikeout to Walk Ratio)
135/// Ratio between strikeouts and walks
136///
137/// Hitters: Lower is better.
138/// Pitchers: Higher is better.
139#[must_use]
140pub fn strikeout_to_walk_ratio(strikeouts: Result<CountingStat>, base_on_balls: Result<CountingStat>) -> TwoDecimalPlaceRateStat {
141    wrap!(TwoDecimalPlaceRateStat::new(strikeouts? as f64 / base_on_balls? as f64))
142}
143
144/// # Whiff%
145/// Percentage of swings that miss.
146///
147/// Hitters: Lower is better.
148/// Pitchers: Higher is better.
149#[must_use]
150pub fn whiff_pct(whiffs: Result<CountingStat>, total_swings: Result<CountingStat>) -> PercentageStat {
151    wrap!(PercentageStat::new(whiffs? as f64 / total_swings? as f64))
152}
153
154/// # ERA - Earned Run Average
155/// The expected number of earned runs to be given up over nine innings of pitching.
156///
157/// Pitchers: Lower is better.
158#[must_use]
159pub fn era(earned_runs: Result<CountingStat>, innings_pitched: Result<InningsPitched>) -> TwoDecimalPlaceRateStat {
160    wrap!(TwoDecimalPlaceRateStat::new(earned_runs? as f64 * 27.0 / innings_pitched?.as_outs() as f64))
161}
162
163/// # WHIP - Walks & Hits per Inning Pitched
164/// Described in title.
165///
166/// Pitchers: Lower is better.
167#[must_use]
168pub fn whip(hits: Result<CountingStat>, base_on_balls: Result<CountingStat>, innings_pitched: Result<InningsPitched>) -> TwoDecimalPlaceRateStat {
169    wrap!(TwoDecimalPlaceRateStat::new(((base_on_balls? + hits?) as f64) / innings_pitched?.as_fraction()))
170}
171
172/// # WPCT - Win %
173/// Percentage of decisions that are pitcher wins
174///
175/// Pitchers: Higher is better.
176#[must_use]
177pub fn win_pct(wins: Result<CountingStat>, losses: Result<CountingStat>) -> ThreeDecimalPlaceRateStat {
178    wrap!(ThreeDecimalPlaceRateStat::new(wins? as f64 / (wins? + losses?) as f64))
179}
180
181/// # P/IP - Pitches per Inning Pitched
182/// Described in title.
183///
184/// Pitchers: Lower is better.*
185///
186/// \* High K% pitchers often have higher P/IP compared to GB% pitchers - even if you find similar wOBA and ERA values for both.
187#[must_use]
188pub fn pitches_per_inning_pitched(number_of_pitches: Result<CountingStat>, innings_pitched: Result<InningsPitched>) -> TwoDecimalPlaceRateStat {
189    wrap!(TwoDecimalPlaceRateStat::new(number_of_pitches? as f64 / innings_pitched?.as_fraction()))
190}
191
192/// # K/9 - Strikeouts per 9 Innings Pitched
193/// Described in title.
194/// K% is often preferred due to better edge cases.
195///
196/// Pitchers: Higher is better.
197#[must_use]
198pub fn k_per_9(strikeouts: Result<CountingStat>, innings_pitched: Result<InningsPitched>) -> TwoDecimalPlaceRateStat {
199    wrap!(TwoDecimalPlaceRateStat::new(strikeouts? as f64 * 27.0 / innings_pitched?.as_outs() as f64))
200}
201
202/// # BB/9 - Walks per 9 Innings Pitched
203/// Described in title.
204/// BB% is often preferred due to better edge cases.
205///
206/// Pitchers: Lower is better.
207#[must_use]
208pub fn bb_per_9(base_on_balls: Result<CountingStat>, innings_pitched: Result<InningsPitched>) -> TwoDecimalPlaceRateStat {
209    wrap!(TwoDecimalPlaceRateStat::new(base_on_balls? as f64 * 27.0 / innings_pitched?.as_outs() as f64))
210}
211
212/// # H/9 - Hits per 9 Innings
213/// Described in title.
214///
215/// Pitchers: Lower is better.
216#[must_use]
217pub fn hits_per_9(hits: Result<CountingStat>, innings_pitched: Result<InningsPitched>) -> TwoDecimalPlaceRateStat {
218    wrap!(TwoDecimalPlaceRateStat::new(hits? as f64 * 27.0 / innings_pitched?.as_outs() as f64))
219}
220
221/// # RA/9 - Runs scored per 9 Innings Pitched
222/// Described in title.
223///
224/// Pitchers: Lower is better.
225#[must_use]
226pub fn runs_scored_per_9(runs: Result<CountingStat>, innings_pitched: Result<InningsPitched>) -> TwoDecimalPlaceRateStat {
227    wrap!(TwoDecimalPlaceRateStat::new(runs? as f64 * 27.0 / innings_pitched?.as_outs() as f64))
228}
229
230/// # HR/9 - Home Runs per 9 Innings
231/// Described in title.
232///
233/// Pitchers: Lower is better.
234#[must_use]
235pub fn home_runs_per_9(home_runs: Result<CountingStat>, innings_pitched: Result<InningsPitched>) -> TwoDecimalPlaceRateStat {
236    wrap!(TwoDecimalPlaceRateStat::new(home_runs? as f64 / innings_pitched?.as_outs() as f64))
237}