#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PredictionErrors {
pub n: usize,
pub mean_error: f64,
pub mean_absolute_error: f64,
pub rmse: f64,
pub cv_rmse: Option<f64>,
pub nmbe: Option<f64>,
pub parameters: usize,
}
pub fn prediction_errors(
observed: &[f64],
predicted: &[f64],
parameters: usize,
) -> Option<PredictionErrors> {
let n = observed.len();
if n == 0 || n != predicted.len() || parameters >= n {
return None;
}
if observed.iter().chain(predicted).any(|v| !v.is_finite()) {
return None;
}
let nf = n as f64;
let df = (n - parameters) as f64;
let errors = observed.iter().zip(predicted).map(|(o, p)| o - p);
let sum: f64 = errors.clone().sum();
let sum_abs: f64 = errors.clone().map(f64::abs).sum();
let sum_sq: f64 = errors.map(|e| e * e).sum();
let mean_observed = observed.iter().sum::<f64>() / nf;
let rmse = (sum_sq / df).sqrt();
let relative = |v: f64| (mean_observed != 0.0).then(|| v / mean_observed);
Some(PredictionErrors {
n,
mean_error: sum / nf,
mean_absolute_error: sum_abs / nf,
rmse,
cv_rmse: relative(rmse),
nmbe: relative(sum / df),
parameters,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_definitions_by_hand() {
let observed = [11.0, 9.0, 12.0, 8.0];
let predicted = [10.0, 10.0, 10.0, 8.0];
let e = prediction_errors(&observed, &predicted, 0).unwrap();
assert_eq!(e.n, 4);
assert!((e.mean_error - 0.5).abs() < 1e-15);
assert!((e.mean_absolute_error - 1.0).abs() < 1e-15);
assert!((e.rmse - (6.0f64 / 4.0).sqrt()).abs() < 1e-15);
assert!((e.cv_rmse.unwrap() - (1.5f64).sqrt() / 10.0).abs() < 1e-15);
assert!((e.nmbe.unwrap() - 2.0 / 40.0).abs() < 1e-15);
let e = prediction_errors(&observed, &predicted, 2).unwrap();
assert!((e.rmse - 3.0f64.sqrt()).abs() < 1e-15);
assert!((e.nmbe.unwrap() - 2.0 / 20.0).abs() < 1e-15);
assert!((e.mean_error - 0.5).abs() < 1e-15, "the means stay over n");
}
#[test]
fn ratios_need_a_nonzero_mean() {
let e = prediction_errors(&[1.0, -1.0], &[0.0, 0.0], 0).unwrap();
assert_eq!((e.cv_rmse, e.nmbe), (None, None));
assert!((e.rmse - 1.0).abs() < 1e-15);
}
#[test]
fn refusals() {
assert!(prediction_errors(&[], &[], 0).is_none());
assert!(prediction_errors(&[1.0], &[1.0, 2.0], 0).is_none());
assert!(prediction_errors(&[1.0, 2.0], &[1.0, 2.0], 2).is_none());
assert!(prediction_errors(&[1.0, f64::NAN], &[1.0, 2.0], 0).is_none());
}
#[test]
fn a_change_of_unit_scales_the_absolute_measures_only() {
let observed = [101.0, 98.5, 103.2, 99.9, 100.4];
let predicted = [100.0, 99.0, 102.0, 100.5, 100.0];
let a = prediction_errors(&observed, &predicted, 1).unwrap();
for k in [1e-9, 1e9] {
let o: Vec<f64> = observed.iter().map(|v| v * k).collect();
let p: Vec<f64> = predicted.iter().map(|v| v * k).collect();
let b = prediction_errors(&o, &p, 1).unwrap();
assert!((b.rmse / k - a.rmse).abs() < 1e-9 * a.rmse);
assert!((b.cv_rmse.unwrap() - a.cv_rmse.unwrap()).abs() < 1e-12);
assert!((b.nmbe.unwrap() - a.nmbe.unwrap()).abs() < 1e-12);
}
}
}