#[derive(Debug, Clone, Copy)]
pub struct Fit {
count: u64,
mean_x: f64,
mean_y: f64,
m2_x: f64,
m2_y: f64,
co: f64,
}
impl Fit {
pub fn new() -> Fit {
Fit {
count: 0,
mean_x: 0.0,
mean_y: 0.0,
m2_x: 0.0,
m2_y: 0.0,
co: 0.0,
}
}
pub fn xy(x: &[f64], y: &[f64]) -> Fit {
assert_eq!(x.len(), y.len(), "Fit::xy requires slices of equal length");
let mut fit = Fit::new();
for (&x, &y) in x.iter().zip(y) {
fit.add(x, y);
}
fit
}
pub fn add(&mut self, x: f64, y: f64) {
if !x.is_finite() || !y.is_finite() {
return;
}
self.count += 1;
let count = self.count as f64;
let delta_x = x - self.mean_x;
let delta_y = y - self.mean_y;
self.mean_x += delta_x / count;
self.mean_y += delta_y / count;
self.m2_x += delta_x * (x - self.mean_x);
self.m2_y += delta_y * (y - self.mean_y);
self.co += delta_x * (y - self.mean_y);
}
pub fn merge(&mut self, other: &Fit) {
if other.count == 0 {
return;
}
if self.count == 0 {
*self = *other;
return;
}
let total = self.count + other.count;
let weight = self.count as f64 * other.count as f64 / total as f64;
let delta_x = other.mean_x - self.mean_x;
let delta_y = other.mean_y - self.mean_y;
self.m2_x += other.m2_x + delta_x * delta_x * weight;
self.m2_y += other.m2_y + delta_y * delta_y * weight;
self.co += other.co + delta_x * delta_y * weight;
self.mean_x += delta_x * other.count as f64 / total as f64;
self.mean_y += delta_y * other.count as f64 / total as f64;
self.count = total;
}
pub fn count(&self) -> u64 {
self.count
}
pub fn slope(&self) -> Option<f64> {
(self.count >= 2 && self.m2_x > 0.0).then(|| self.co / self.m2_x)
}
pub fn intercept(&self) -> Option<f64> {
self.slope().map(|slope| self.mean_y - slope * self.mean_x)
}
pub fn predict(&self, x: f64) -> Option<f64> {
self.slope()
.map(|slope| self.mean_y + slope * (x - self.mean_x))
}
pub fn r_squared(&self) -> Option<f64> {
self.slope()?;
if self.m2_y == 0.0 {
return Some(1.0);
}
Some((self.co * self.co / (self.m2_x * self.m2_y)).clamp(0.0, 1.0))
}
pub fn standard_error(&self, x: f64) -> Option<f64> {
self.slope()?;
if self.count < 3 {
return None;
}
let residual = (self.m2_y - self.co * self.co / self.m2_x).max(0.0);
let variance = residual / (self.count - 2) as f64;
let offset = x - self.mean_x;
let leverage = 1.0 / self.count as f64 + offset * offset / self.m2_x;
Some((variance * leverage).sqrt())
}
}
impl Default for Fit {
fn default() -> Fit {
Fit::new()
}
}
#[cfg(test)]
#[path = "tests/fit_tests.rs"]
mod tests;