rusty_ai/metrics/
errors.rs

1use std::error::Error;
2
3use nalgebra::DVector;
4
5use crate::data::dataset::RealNumber;
6
7/// A trait for computing regression metrics.
8pub trait RegressionMetrics<T: RealNumber> {
9    /// Computes the mean squared error (MSE) between the true values and the predicted values.
10    ///
11    /// # Arguments
12    ///
13    /// * `y_true` - The true values.
14    /// * `y_pred` - The predicted values.
15    ///
16    /// # Returns
17    ///
18    /// The mean squared error.
19    ///
20    /// # Errors
21    ///
22    /// Returns an error if the lengths of `y_true` and `y_pred` are different.
23    fn mse(&self, y_true: &DVector<T>, y_pred: &DVector<T>) -> Result<T, Box<dyn Error>> {
24        if y_true.len() != y_pred.len() {
25            return Err("Predictions and labels are of different sizes.".into());
26        }
27
28        let n = T::from_usize(y_true.len()).unwrap();
29        let errors = y_pred - y_true;
30        let errors_sq = errors.component_mul(&errors);
31
32        Ok(errors_sq.sum() / n)
33    }
34
35    /// Computes the mean absolute error (MAE) between the true values and the predicted values.
36    ///
37    /// # Arguments
38    ///
39    /// * `y_true` - The true values.
40    /// * `y_pred` - The predicted values.
41    ///
42    /// # Returns
43    ///
44    /// The mean absolute error.
45    ///
46    /// # Errors
47    ///
48    /// Returns an error if the lengths of `y_true` and `y_pred` are different.
49    fn mae(&self, y_true: &DVector<T>, y_pred: &DVector<T>) -> Result<T, Box<dyn Error>> {
50        if y_true.len() != y_pred.len() {
51            return Err("Predictions and labels are of different sizes.".into());
52        }
53        let n = T::from_usize(y_true.len()).unwrap();
54        let abs_errors_sum = y_pred
55            .iter()
56            .zip(y_true.iter())
57            .map(|(&y_p, &y_t)| (y_p - y_t).abs())
58            .fold(T::from_f64(0.0).unwrap(), |acc, x| acc + x);
59
60        Ok(abs_errors_sum / n)
61    }
62
63    /// Computes the coefficient of determination (R^2) between the true values and the predicted values.
64    ///
65    /// # Arguments
66    ///
67    /// * `y_true` - The true values.
68    /// * `y_pred` - The predicted values.
69    ///
70    /// # Returns
71    ///
72    /// The coefficient of determination (R^2).
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if the lengths of `y_true` and `y_pred` are different.
77    fn r2(&self, y_true: &DVector<T>, y_pred: &DVector<T>) -> Result<T, Box<dyn Error>> {
78        if y_true.len() != y_pred.len() {
79            return Err("Predictions and labels are of different sizes.".into());
80        }
81        let n = T::from_usize(y_true.len()).unwrap();
82
83        let y_true_mean = y_true.sum() / n;
84
85        let y_true_mean_vec = DVector::from_element(y_true.len(), y_true_mean);
86
87        let mse_model = self.mse(y_true, y_pred)?;
88        let mse_base = self.mse(&y_true_mean_vec, y_true)?;
89
90        Ok(T::from_f64(1.0).unwrap() - (mse_model / mse_base))
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use nalgebra::DVector;
98
99    struct MockRegressor;
100
101    impl RegressionMetrics<f64> for MockRegressor {}
102
103    #[test]
104    fn test_mse() {
105        let regressor = MockRegressor;
106        let y_true = DVector::from_vec(vec![1.0, 2.0, 3.0]);
107        let y_pred = DVector::from_vec(vec![1.1, 1.9, 3.2]);
108
109        let mse = regressor.mse(&y_true, &y_pred).unwrap();
110        let expected_mse = ((0.1 * 0.1) + (0.1 * 0.1) + (0.2 * 0.2)) / 3.0;
111        assert!((mse - expected_mse).abs() < 1e-6);
112    }
113
114    #[test]
115    fn test_mae() {
116        let regressor = MockRegressor;
117        let y_true = DVector::from_vec(vec![1.0, 2.0, 3.0]);
118        let y_pred = DVector::from_vec(vec![1.1, 1.9, 3.2]);
119
120        let mae = regressor.mae(&y_true, &y_pred).unwrap();
121        let expected_mae = (0.1 + 0.1 + 0.2) / 3.0;
122        assert!((mae - expected_mae).abs() < 1e-6);
123    }
124
125    #[test]
126    fn test_r2() {
127        let regressor = MockRegressor;
128        let y_true = DVector::from_vec(vec![1.0, 2.0, 3.0]);
129        let y_pred = DVector::from_vec(vec![1.1, 1.9, 3.2]);
130
131        let r2 = regressor.r2(&y_true, &y_pred).unwrap();
132
133        let y_true_mean = y_true.mean();
134        let tss: f64 = y_true.iter().map(|&y| (y - y_true_mean).powi(2)).sum();
135
136        let rss: f64 = y_true
137            .iter()
138            .zip(y_pred.iter())
139            .map(|(&y_t, &y_p)| (y_t - y_p).powi(2))
140            .sum();
141
142        // Calculate expected R2
143        let expected_r2 = 1.0 - (rss / tss);
144
145        assert!((r2 - expected_r2).abs() < 1e-6);
146    }
147
148    #[test]
149    fn test_different_length_error() {
150        let regressor = MockRegressor;
151        let y_true = DVector::from_vec(vec![1.0, 2.0, 3.0]);
152        let y_pred = DVector::from_vec(vec![1.1, 1.9]);
153
154        assert!(regressor.mse(&y_true, &y_pred).is_err());
155        assert!(regressor.mae(&y_true, &y_pred).is_err());
156        assert!(regressor.r2(&y_true, &y_pred).is_err());
157    }
158}