irox_stats/fitting.rs
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
// SPDX-License-Identifier: MIT
// Copyright 2024 IROX Contributors
//
///
/// Simple linear regression from provided data.
///
/// ```
/// use irox_stats::sampling::Sample;
/// use irox_time::epoch::UnixTimestamp;
/// use irox_stats::fitting::LinearRegression;
///
/// let data = &[
/// Sample::new(0., UnixTimestamp::from_seconds(0)),
/// Sample::new(0.5, UnixTimestamp::from_seconds_f64(0.5)),
/// Sample::new(1., UnixTimestamp::from_seconds(1)),
/// ];
///
/// let reg = LinearRegression::from_data(
/// data.iter(),
/// |s| s.time.get_offset().as_seconds_f64(),
/// |s| s.value,
/// );
/// assert!(reg.is_some());
/// let Some(reg) = reg else {
/// return;
/// };
/// assert_eq!(1.0, reg.slope);
/// assert_eq!(0.5, reg.mean_x);
/// assert_eq!(0.5, reg.mean_y);
/// ```
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct LinearRegression {
pub slope: f64,
pub mean_x: f64,
pub mean_y: f64,
}
impl LinearRegression {
pub fn from_data<
'a,
S: 'a,
I: Iterator<Item = S> + Clone,
X: Fn(&S) -> f64,
Y: Fn(&S) -> f64,
>(
data: I,
x_accessor: X,
y_accessor: Y,
) -> Option<LinearRegression> {
let mut mean_x = 0f64;
let mut mean_y = 0f64;
let mut count = 0f64;
for s in data.clone() {
let x = x_accessor(&s);
let y = y_accessor(&s);
mean_y += y;
mean_x += x;
count += 1.0;
}
if count <= 1. {
return None;
}
mean_x /= count;
mean_y /= count;
let mut xsum = 0f64;
let mut linear = 0f64;
for s in data {
let x = x_accessor(&s);
let y = y_accessor(&s);
let dy = y - mean_y;
let dx = x - mean_x;
xsum += dx * dx;
linear += dx * dy;
}
if xsum <= 0.0 {
return None;
}
Some(LinearRegression {
slope: linear / xsum,
mean_x,
mean_y,
})
}
}
#[cfg(test)]
mod test {
use crate::fitting::LinearRegression;
use crate::sampling::Sample;
use irox_time::epoch::UnixTimestamp;
#[test]
pub fn test() {
let data = &[
Sample::new(0., UnixTimestamp::from_seconds(0)),
Sample::new(0.5, UnixTimestamp::from_seconds_f64(0.5)),
Sample::new(1., UnixTimestamp::from_seconds(1)),
];
let reg = LinearRegression::from_data(
data.iter(),
|s| s.time.get_offset().as_seconds_f64(),
|s| s.value,
);
assert!(reg.is_some());
let Some(reg) = reg else {
return;
};
assert_eq!(1.0, reg.slope);
assert_eq!(0.5, reg.mean_x);
assert_eq!(0.5, reg.mean_y);
}
}