traquer/statistic/
regression.rs

1//! Regression Functions
2//!
3//! Provides functions for estimating the relationships between a dependent variable
4//! and one or more independent variables. Potentially useful for forecasting and
5//! determing causal relationships.[[1]](https://en.wikipedia.org/wiki/Regression_analysis)
6use std::iter;
7
8use itertools::izip;
9use num_traits::cast::ToPrimitive;
10
11/// Mean Squared Error
12///
13/// Measures average squared difference between estimated and actual values.
14///
15/// ```math
16/// MSE = \frac{1}{T}\sum_{t=1}^{T} (X(t) - \hat{X}(t))^2
17/// ```
18///
19/// ## Sources
20///
21/// [[1]](https://en.wikipedia.org/wiki/Mean_squared_error)
22///
23/// # Examples
24///
25/// ```
26/// use traquer::statistic::regression;
27///
28/// regression::mse(&[1.0,2.0,3.0,4.0,5.0], &[1.0,2.0,3.0,4.0,5.0]).collect::<Vec<f64>>();
29/// ```
30pub fn mse<'a, T: ToPrimitive>(data: &'a [T], estimate: &'a [T]) -> impl Iterator<Item = f64> + 'a {
31    data.iter()
32        .enumerate()
33        .zip(estimate)
34        .scan(0.0, |state, ((cnt, observe), est)| {
35            *state += (observe.to_f64().unwrap() - est.to_f64().unwrap()).powi(2);
36            Some(*state / (cnt + 1) as f64)
37        })
38}
39
40/// Root Mean Squared Error
41///
42/// Square root of MSE, but normalises it to same units as input.
43///
44/// ```math
45/// RMSE = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (Y_i – \hat{Y}_i)^2}
46/// ```
47///
48/// ## Sources
49///
50/// [[1]](https://en.wikipedia.org/wiki/Root_mean_square_deviation)
51///
52/// # Examples
53///
54/// ```
55/// use traquer::statistic::regression;
56///
57/// regression::rmse(&[1.0,2.0,3.0,4.0,5.0], &[1.0,2.0,3.0,4.0,5.0]).collect::<Vec<f64>>();
58/// ```
59pub fn rmse<'a, T: ToPrimitive>(
60    data: &'a [T],
61    estimate: &'a [T],
62) -> impl Iterator<Item = f64> + 'a {
63    data.iter()
64        .enumerate()
65        .zip(estimate)
66        .scan(0.0, |state, ((cnt, observe), est)| {
67            *state += (observe.to_f64().unwrap() - est.to_f64().unwrap()).powi(2);
68            Some((*state / (cnt + 1) as f64).sqrt())
69        })
70}
71
72/// Mean Absolute Error
73///
74/// Measures the average magnitude of the errors in a set of predictions. Less sensitive to
75/// outliers compared to MSE and RMSE.
76///
77/// ```math
78/// MAE = \frac{1}{n} \sum_{i=1}^{n} |Y_i – \hat{Y}_i|
79/// ```
80///
81/// ## Sources
82///
83/// [[1]](https://en.wikipedia.org/wiki/Mean_absolute_error)
84///
85/// # Examples
86///
87/// ```
88/// use traquer::statistic::regression;
89///
90/// regression::mae(&[1.0,2.0,3.0,4.0,5.0], &[1.0,2.0,3.0,4.0,5.0]).collect::<Vec<f64>>();
91/// ```
92pub fn mae<'a, T: ToPrimitive>(data: &'a [T], estimate: &'a [T]) -> impl Iterator<Item = f64> + 'a {
93    data.iter()
94        .enumerate()
95        .zip(estimate)
96        .scan(0.0, |state, ((cnt, observe), est)| {
97            *state += (observe.to_f64().unwrap() - est.to_f64().unwrap()).abs();
98            Some(*state / (cnt + 1) as f64)
99        })
100}
101
102/// Mean Absolute Percentage Error
103///
104/// Measures the error as a percentage of the actual value.
105///
106/// ```math
107/// MAPE = \frac{100%}{n} \sum_{i=1}^{n} \left|\frac{Y_i – \hat{Y}_i}{Y_i}\right|
108/// ```
109///
110/// ## Sources
111///
112/// [[1]](https://en.wikipedia.org/wiki/Mean_absolute_percentage_error)
113///
114/// # Examples
115///
116/// ```
117/// use traquer::statistic::regression;
118///
119/// regression::mape(&[1.0,2.0,3.0,4.0,5.0], &[1.0,2.0,3.0,4.0,5.0]).collect::<Vec<f64>>();
120/// ```
121pub fn mape<'a, T: ToPrimitive>(
122    data: &'a [T],
123    estimate: &'a [T],
124) -> impl Iterator<Item = f64> + 'a {
125    data.iter()
126        .enumerate()
127        .zip(estimate)
128        .scan(0.0, |state, ((cnt, observe), est)| {
129            *state += ((observe.to_f64().unwrap() - est.to_f64().unwrap())
130                / observe.to_f64().unwrap())
131            .abs();
132            Some(100.0 * *state / (cnt + 1) as f64)
133        })
134}
135
136/// Symmetric Mean Absolute Percentage Error
137///
138/// Similar to MAPE but attempts to limit the overweighting of negative errors in MAPE. Computed so
139/// range is bound to between 0 and 100.
140///
141/// ```math
142/// SMAPE = \frac{100}{n} \sum_{t=1}^n \frac{|F_t-A_t|}{|A_t|+|F_t|}
143/// ```
144///
145/// ## Sources
146///
147/// [[1]](https://en.wikipedia.org/wiki/Symmetric_mean_absolute_percentage_error)
148///
149/// # Examples
150///
151/// ```
152/// use traquer::statistic::regression;
153///
154/// regression::smape(&[1.0,2.0,3.0,4.0,5.0], &[1.0,2.0,3.0,4.0,5.0]).collect::<Vec<f64>>();
155/// ```
156pub fn smape<'a, T: ToPrimitive>(
157    data: &'a [T],
158    estimate: &'a [T],
159) -> impl Iterator<Item = f64> + 'a {
160    data.iter()
161        .enumerate()
162        .zip(estimate)
163        .scan(0.0, |state, ((cnt, observe), est)| {
164            *state += ((observe.to_f64().unwrap() - est.to_f64().unwrap()).abs()
165                / ((observe.to_f64().unwrap().abs() + est.to_f64().unwrap().abs()) / 2.0))
166                .max(0.0);
167            Some(100.0 * *state / (cnt + 1) as f64)
168        })
169}
170
171/// Mean Directional Accuracy
172///
173/// Measure of prediction accuracy of a forecasting method in statistics. It compares the forecast
174/// direction (upward or downward) to the actual realized direction.
175///
176/// ```math
177/// MDA = \frac{1}{N}\sum_t \mathbf{1}_{\sgn(A_t - A_{t-1}) = \sgn(F_t - A_{t-1})}
178/// ```
179///
180/// ## Sources
181///
182/// [[1]](https://en.wikipedia.org/wiki/Mean_directional_accuracy)
183///
184/// # Examples
185///
186/// ```
187/// use traquer::statistic::regression;
188///
189/// regression::mda(&[1.0,2.0,3.0,4.0,5.0], &[1.0,2.0,3.0,4.0,5.0]).collect::<Vec<f64>>();
190/// ```
191pub fn mda<'a, T: ToPrimitive>(data: &'a [T], estimate: &'a [T]) -> impl Iterator<Item = f64> + 'a {
192    iter::once(f64::NAN).chain(data[1..].iter().enumerate().zip(&estimate[1..]).scan(
193        (0.0, data[0].to_f64().unwrap()),
194        |state, ((cnt, observe), est)| {
195            let dir = ((observe.to_f64().unwrap() - state.1).signum()
196                == (est.to_f64().unwrap() - state.1).signum()) as u8 as f64;
197            *state = (state.0 + dir, observe.to_f64().unwrap());
198            Some(state.0 / (cnt + 1) as f64)
199        },
200    ))
201}
202
203/// Mean Absolute Scaled Error
204///
205/// A forecasting accuracy metric that compares the mean absolute error (MAE) of your prediction
206/// to the MAE of a naive forecasting method, such as predicting the previous period's value. It
207/// helps determine if your forecasting model outperforms a simple baseline model.
208///
209/// ```math
210/// MASE = \mathrm{mean}\left( \frac{\left| e_j \right|}{\frac{1}{T-1}\sum_{t=2}^T \left| Y_t-Y_{t-1}\right|} \right) = \frac{\frac{1}{J}\sum_{j}\left| e_j \right|}{\frac{1}{T-1}\sum_{t=2}^T \left| Y_t-Y_{t-1}\right|}
211/// ```
212///
213/// ## Sources
214///
215/// [[1]](https://en.wikipedia.org/wiki/Mean_absolute_scaled_error)
216/// [[2]](https://otexts.com/fpp2/accuracy.html#scaled-errors)
217///
218/// # Examples
219///
220/// ```
221/// use traquer::statistic::regression;
222///
223/// regression::mase(&[1.0,2.0,3.0,4.0,5.0], &[1.0,2.0,3.0,4.0,5.0]).collect::<Vec<f64>>();
224/// ```
225pub fn mase<'a, T: ToPrimitive>(
226    data: &'a [T],
227    estimate: &'a [T],
228) -> impl Iterator<Item = f64> + 'a {
229    let mae_est = mae(data, estimate);
230    let mae_naive = data.windows(2).zip(1..).scan(0.0, |state, (w, cnt)| {
231        *state += (w[1].to_f64().unwrap() - w[0].to_f64().unwrap()).abs();
232        Some(*state / cnt as f64)
233    });
234
235    iter::once(f64::NAN).chain(
236        mae_est
237            .skip(1)
238            .zip(mae_naive)
239            .map(|(est, naive)| est / naive),
240    )
241}
242
243/// Envelope-weighted Mean Absolute Error
244///
245/// A scale-independent, symmetric, error metric
246///
247/// ## Sources
248///
249/// [[1]](https://typethepipe.com/post/energy-forecasting-error-metrics/)
250/// [[2]](https://pdfs.semanticscholar.org/cf04/65bce25d78ccda6d8c5d12e141099aa606f4.pdf)
251///
252/// # Examples
253///
254/// ```
255/// use traquer::statistic::regression;
256///
257/// regression::emae(&[1.0,2.0,3.0,4.0,5.0], &[1.0,2.0,3.0,4.0,5.0]).collect::<Vec<f64>>();
258/// ```
259pub fn emae<'a, T: ToPrimitive>(
260    data: &'a [T],
261    estimate: &'a [T],
262) -> impl Iterator<Item = f64> + 'a {
263    izip!(data, estimate, 1..).scan((0.0, 0.0), |state, (actual, est, n)| {
264        let actual = actual.to_f64().unwrap();
265        let est = est.to_f64().unwrap();
266        *state = (state.0 + (actual - est).abs(), state.1 + actual.max(est));
267        Some(state.0 / state.1 * 100. / n as f64)
268    })
269}