1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
Copyright 2021 Peter Dunne */

//! Provides methods to performs linear fit of two input arrays, x and y
use super::stats;

/// Linear regression of x,y data
/// Returns an array of [slope, offset]
/// ```
/// use crate::common::fit::fit;
/// use float_cmp::approx_eq;
/// let x = [1.0, 2.0, 3.0];
/// let y = [3.0, 5.0, 7.0];
///
/// let result = fit(&x, &y);
/// let comparison = [2.0, 1.0];
///
/// assert!(approx_eq!(f64, result[0], comparison[0]) &&  approx_eq!(f64, result[0], comparison[0]) );
/// ```
///
pub fn fit(x: &[f64], y: &[f64]) -> [f64; 2] {
    let slope = stats::covariance(x, y) / stats::variance(x);
    let intercept = stats::mean(y) - slope * stats::mean(x);
    [slope, intercept]
}

/// Gives predicted value using `model` for a given x
/// ```
/// use crate::common::fit::predict;
/// use float_cmp::approx_eq;
///
/// let model = [2.0, 3.5];
/// let x = 1.5;
/// let result = predict(&x, &model);
/// let comparison = 6.5;
/// assert!(approx_eq!(f64, result, comparison));
///```
pub fn predict(x: &f64, model: &[f64; 2]) -> f64 {
    x * model[0] + model[1]
}

/// Calculates RMS for a model
fn root_mean_squared_error(actual: &[f64], predicted: &[f64]) -> f64 {
    let length = actual.len();

    let sum_error_iter: f64 = predicted
        .iter()
        .zip(actual.iter())
        .map(|(x, y)| (x - y).powi(2))
        .sum();

    let mse = (sum_error_iter / length as f64).sqrt();
    mse
}

fn rsquared(y: &[f64], rms: &f64) -> f64 {
    1.0 - (rms / stats::variance(y))
}

/// Evaluates all data in a model, returning the root mean squared error (RMSE), and the R-Squared goodness of fit
///
/// ```
/// use crate::common::fit::{fit,evaluate};
/// use float_cmp::approx_eq;
///
/// let x = [1.05, 1.992, 3.03];
/// let y = [2.993, 4.92, 6.99];
///
/// let model = fit(&x, &y);
/// let result = evaluate(&x, &y, &model);
///
/// let comparison = [1.19675583971723e-2, 0.99550];
/// println!("{:.e}", result[0]);
/// println!("{}", result[1]);
/// assert!(approx_eq!(f64, result[0], comparison[0]) &&  approx_eq!(f64, result[0], comparison[0]) );
///```
pub fn evaluate(x: &[f64], y: &[f64], model: &[f64; 2]) -> [f64; 2] {
    let y_predicted: Vec<f64> = x.iter().map(|y| predict(y, model)).collect();
    let rms = root_mean_squared_error(y, &y_predicted);
    [rms, rsquared(y, &rms)]
}