Skip to main content

radiate_utils/stats/
slope.rs

1use crate::{Float, stats::statistics::Adder};
2#[cfg(feature = "serde")]
3use serde::{Deserialize, Serialize};
4
5#[derive(PartialEq, Clone)]
6#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7pub struct Slope<F: Float> {
8    sum_y: Adder<F>,
9    sum_xy: Adder<F>,
10    count: u32,
11}
12
13impl<F: Float> Slope<F> {
14    pub fn new() -> Self {
15        Self {
16            sum_y: Adder::<F>::default(),
17            sum_xy: Adder::<F>::default(),
18            count: 0,
19        }
20    }
21
22    pub fn count(&self) -> u32 {
23        self.count
24    }
25
26    pub fn add(&mut self, value: F) {
27        let x = F::from(self.count).unwrap_or(F::ZERO);
28        self.sum_y.add(value);
29        self.sum_xy.add(x * value);
30        self.count += 1;
31    }
32
33    pub fn value(&self) -> Option<F> {
34        if self.count < 2 {
35            return None;
36        }
37
38        let n = F::from(self.count)?;
39        let one = F::ONE;
40        let two = F::from(2.0)?;
41        let six = F::from(6.0)?;
42
43        let sum_x = n * (n - one) / two;
44        let sum_x2 = n * (n - one) * (two * n - one) / six;
45
46        let sum_y = self.sum_y.value();
47        let sum_xy = self.sum_xy.value();
48
49        let numerator = n * sum_xy - sum_x * sum_y;
50        let denominator = n * sum_x2 - sum_x * sum_x;
51
52        if denominator.abs() < F::EPS {
53            None
54        } else {
55            Some(numerator / denominator)
56        }
57    }
58
59    pub fn clear(&mut self) {
60        self.sum_y = Adder::default();
61        self.sum_xy = Adder::default();
62        self.count = 0;
63    }
64}
65
66impl<F: Float> Extend<F> for Slope<F> {
67    fn extend<T: IntoIterator<Item = F>>(&mut self, iter: T) {
68        for value in iter {
69            self.add(value);
70        }
71    }
72}
73
74impl<'a, F: Float> FromIterator<&'a F> for Slope<F> {
75    fn from_iter<T: IntoIterator<Item = &'a F>>(iter: T) -> Self {
76        let mut slope = Slope::new();
77        for &value in iter {
78            slope.add(value);
79        }
80        slope
81    }
82}
83
84impl<F: Float> FromIterator<F> for Slope<F> {
85    fn from_iter<T: IntoIterator<Item = F>>(iter: T) -> Self {
86        let mut slope = Slope::new();
87        slope.extend(iter);
88        slope
89    }
90}
91
92impl<F: Float> Default for Slope<F> {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98impl<F: Float> std::fmt::Debug for Slope<F> {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        f.debug_struct("Slope")
101            .field("sum_y", &self.sum_y.value())
102            .field("sum_xy", &self.sum_xy.value())
103            .field("count", &self.count)
104            .finish()
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_slope_increasing_line() {
114        let mut slope = Slope::<f32>::new();
115        slope.add(1.0);
116        slope.add(2.0);
117        slope.add(3.0);
118        slope.add(4.0);
119
120        let value = slope.value().unwrap();
121        assert!((value - 1.0).abs() < 1e-6);
122    }
123
124    #[test]
125    fn test_slope_flat_line() {
126        let mut slope = Slope::<f32>::new();
127        slope.add(5.0);
128        slope.add(5.0);
129        slope.add(5.0);
130        slope.add(5.0);
131
132        let value = slope.value().unwrap();
133        assert!(value.abs() < 1e-6);
134    }
135
136    #[test]
137    fn test_slope_decreasing_line() {
138        let mut slope = Slope::<f32>::new();
139        slope.add(4.0);
140        slope.add(3.0);
141        slope.add(2.0);
142        slope.add(1.0);
143
144        let value = slope.value().unwrap();
145        assert!((value + 1.0).abs() < 1e-6);
146    }
147
148    #[test]
149    fn test_slope_two_points() {
150        let mut slope = Slope::<f32>::new();
151        slope.add(2.0);
152        slope.add(6.0);
153
154        let value = slope.value().unwrap();
155        assert!((value - 4.0).abs() < 1e-6);
156    }
157}