pub fn linear_regression<I>(points: I) -> (f64, f64)
where
I: Iterator<Item = (f64, f64)> + Clone,
{
let points_vec: Vec<(f64, f64)> = points.collect();
let num_points = points_vec.len() as f64;
if num_points == 0.0 {
return (0.0, 0.0);
}
let (sum_x, sum_xx, sum_xy, sum_y) = points_vec.iter().fold(
(0.0, 0.0, 0.0, 0.0),
|(sum_x, sum_xx, sum_xy, sum_y), &(x, y)| {
(sum_x + x, sum_xx + x * x, sum_xy + x * y, sum_y + y)
},
);
let denominator = num_points * sum_xx - sum_x * sum_x;
let slope = if denominator == 0.0 {
0.0
} else {
(num_points * sum_xy - sum_x * sum_y) / denominator
};
let intercept = (sum_y - slope * sum_x) / num_points;
(slope, intercept)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linear_regression_perfect_line() {
let points = vec![(0.0, 1.0), (1.0, 3.0), (2.0, 5.0), (3.0, 7.0)];
let (slope, intercept) = linear_regression(points.into_iter());
assert!((slope - 2.0).abs() < 1e-10);
assert!((intercept - 1.0).abs() < 1e-10);
}
#[test]
fn test_linear_regression_horizontal_line() {
let points = vec![(0.0, 5.0), (1.0, 5.0), (2.0, 5.0), (3.0, 5.0)];
let (slope, intercept) = linear_regression(points.into_iter());
assert!((slope - 0.0).abs() < 1e-10);
assert!((intercept - 5.0).abs() < 1e-10);
}
#[test]
fn test_linear_regression_empty() {
let points: Vec<(f64, f64)> = vec![];
let (slope, intercept) = linear_regression(points.into_iter());
assert_eq!(slope, 0.0);
assert_eq!(intercept, 0.0);
}
#[test]
fn test_linear_regression_noisy_data() {
let points = vec![
(0.0, 0.1),
(1.0, 1.05),
(2.0, 1.98),
(3.0, 3.02),
(4.0, 3.95),
];
let (slope, intercept) = linear_regression(points.into_iter());
assert!((slope - 1.0).abs() < 0.1);
assert!(intercept.abs() < 0.1);
}
}