1#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct PredictionErrors {
22 pub n: usize,
24 pub mean_error: f64,
26 pub mean_absolute_error: f64,
28 pub rmse: f64,
30 pub cv_rmse: Option<f64>,
33 pub nmbe: Option<f64>,
36 pub parameters: usize,
38}
39
40pub fn prediction_errors(
59 observed: &[f64],
60 predicted: &[f64],
61 parameters: usize,
62) -> Option<PredictionErrors> {
63 let n = observed.len();
64 if n == 0 || n != predicted.len() || parameters >= n {
65 return None;
66 }
67 if observed.iter().chain(predicted).any(|v| !v.is_finite()) {
68 return None;
69 }
70 let nf = n as f64;
71 let df = (n - parameters) as f64;
72 let errors = observed.iter().zip(predicted).map(|(o, p)| o - p);
73 let sum: f64 = errors.clone().sum();
74 let sum_abs: f64 = errors.clone().map(f64::abs).sum();
75 let sum_sq: f64 = errors.map(|e| e * e).sum();
76 let mean_observed = observed.iter().sum::<f64>() / nf;
77 let rmse = (sum_sq / df).sqrt();
78 let relative = |v: f64| (mean_observed != 0.0).then(|| v / mean_observed);
79 Some(PredictionErrors {
80 n,
81 mean_error: sum / nf,
82 mean_absolute_error: sum_abs / nf,
83 rmse,
84 cv_rmse: relative(rmse),
85 nmbe: relative(sum / df),
86 parameters,
87 })
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn the_definitions_by_hand() {
96 let observed = [11.0, 9.0, 12.0, 8.0];
98 let predicted = [10.0, 10.0, 10.0, 8.0];
99 let e = prediction_errors(&observed, &predicted, 0).unwrap();
100 assert_eq!(e.n, 4);
101 assert!((e.mean_error - 0.5).abs() < 1e-15);
102 assert!((e.mean_absolute_error - 1.0).abs() < 1e-15);
103 assert!((e.rmse - (6.0f64 / 4.0).sqrt()).abs() < 1e-15);
104 assert!((e.cv_rmse.unwrap() - (1.5f64).sqrt() / 10.0).abs() < 1e-15);
105 assert!((e.nmbe.unwrap() - 2.0 / 40.0).abs() < 1e-15);
106 let e = prediction_errors(&observed, &predicted, 2).unwrap();
108 assert!((e.rmse - 3.0f64.sqrt()).abs() < 1e-15);
109 assert!((e.nmbe.unwrap() - 2.0 / 20.0).abs() < 1e-15);
110 assert!((e.mean_error - 0.5).abs() < 1e-15, "the means stay over n");
111 }
112
113 #[test]
114 fn ratios_need_a_nonzero_mean() {
115 let e = prediction_errors(&[1.0, -1.0], &[0.0, 0.0], 0).unwrap();
116 assert_eq!((e.cv_rmse, e.nmbe), (None, None));
117 assert!((e.rmse - 1.0).abs() < 1e-15);
118 }
119
120 #[test]
121 fn refusals() {
122 assert!(prediction_errors(&[], &[], 0).is_none());
123 assert!(prediction_errors(&[1.0], &[1.0, 2.0], 0).is_none());
124 assert!(prediction_errors(&[1.0, 2.0], &[1.0, 2.0], 2).is_none());
125 assert!(prediction_errors(&[1.0, f64::NAN], &[1.0, 2.0], 0).is_none());
126 }
127
128 #[test]
129 fn a_change_of_unit_scales_the_absolute_measures_only() {
130 let observed = [101.0, 98.5, 103.2, 99.9, 100.4];
131 let predicted = [100.0, 99.0, 102.0, 100.5, 100.0];
132 let a = prediction_errors(&observed, &predicted, 1).unwrap();
133 for k in [1e-9, 1e9] {
134 let o: Vec<f64> = observed.iter().map(|v| v * k).collect();
135 let p: Vec<f64> = predicted.iter().map(|v| v * k).collect();
136 let b = prediction_errors(&o, &p, 1).unwrap();
137 assert!((b.rmse / k - a.rmse).abs() < 1e-9 * a.rmse);
138 assert!((b.cv_rmse.unwrap() - a.cv_rmse.unwrap()).abs() < 1e-12);
139 assert!((b.nmbe.unwrap() - a.nmbe.unwrap()).abs() < 1e-12);
140 }
141 }
142}