nodedb_query/ts_functions/
stddev.rs1#[derive(Debug)]
9pub struct TsStddevAccum {
10 n: u64,
11 mean: f64,
12 m2: f64,
13}
14
15impl TsStddevAccum {
16 pub fn new() -> Self {
17 Self {
18 n: 0,
19 mean: 0.0,
20 m2: 0.0,
21 }
22 }
23
24 pub fn update(&mut self, value: f64) {
25 self.n += 1;
26 let delta = value - self.mean;
27 self.mean += delta / self.n as f64;
28 let delta2 = value - self.mean;
29 self.m2 += delta * delta2;
30 }
31
32 pub fn update_batch(&mut self, values: &[f64]) {
33 for &v in values {
34 if !v.is_nan() {
35 self.update(v);
36 }
37 }
38 }
39
40 pub fn evaluate_population(&self) -> Option<f64> {
42 if self.n < 2 {
43 return None;
44 }
45 Some((self.m2 / self.n as f64).sqrt())
46 }
47
48 pub fn evaluate_sample(&self) -> Option<f64> {
50 if self.n < 2 {
51 return None;
52 }
53 Some((self.m2 / (self.n - 1) as f64).sqrt())
54 }
55
56 pub fn state(&self) -> [f64; 3] {
58 [self.n as f64, self.mean, self.m2]
59 }
60
61 pub fn merge(&mut self, other: &Self) {
63 if other.n == 0 {
64 return;
65 }
66 if self.n == 0 {
67 self.n = other.n;
68 self.mean = other.mean;
69 self.m2 = other.m2;
70 return;
71 }
72 let n_a = self.n as f64;
73 let n_b = other.n as f64;
74 let n_ab = n_a + n_b;
75 let delta = other.mean - self.mean;
76
77 self.m2 += other.m2 + delta * delta * n_a * n_b / n_ab;
78 self.mean = (n_a * self.mean + n_b * other.mean) / n_ab;
79 self.n += other.n;
80 }
81
82 pub fn merge_state(&mut self, state: &[f64; 3]) {
84 let other = Self {
85 n: state[0] as u64,
86 mean: state[1],
87 m2: state[2],
88 };
89 self.merge(&other);
90 }
91
92 pub fn size(&self) -> usize {
93 std::mem::size_of::<Self>()
94 }
95}
96
97impl Default for TsStddevAccum {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103pub fn ts_stddev(values: &[f64]) -> Option<f64> {
105 let mut accum = TsStddevAccum::new();
106 accum.update_batch(values);
107 accum.evaluate_population()
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn known_values() {
116 let vals = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
118 assert!((ts_stddev(&vals).unwrap() - 2.0).abs() < 1e-10);
119 }
120
121 #[test]
122 fn constant_values() {
123 assert!((ts_stddev(&[5.0, 5.0, 5.0, 5.0]).unwrap()).abs() < 1e-12);
124 }
125
126 #[test]
127 fn sample_vs_population() {
128 let mut acc = TsStddevAccum::new();
129 acc.update_batch(&[2.0, 4.0]);
130 let pop = acc.evaluate_population().unwrap();
131 let samp = acc.evaluate_sample().unwrap();
132 assert!(samp > pop); }
134
135 #[test]
136 fn too_few() {
137 assert!(ts_stddev(&[1.0]).is_none());
138 assert!(ts_stddev(&[]).is_none());
139 }
140
141 #[test]
142 fn nan_skipped() {
143 let r = ts_stddev(&[2.0, f64::NAN, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]);
144 assert!(r.is_some());
145 }
146
147 #[test]
148 fn merge_preserves_result() {
149 let mut a = TsStddevAccum::new();
150 a.update_batch(&[2.0, 4.0, 4.0, 4.0]);
151 let mut b = TsStddevAccum::new();
152 b.update_batch(&[5.0, 5.0, 7.0, 9.0]);
153 a.merge(&b);
154 assert!((a.evaluate_population().unwrap() - 2.0).abs() < 1e-10);
155 }
156}