Skip to main content

oxihuman_core/
running_statistics.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Welford's online algorithm for mean/variance/std.
6
7/// Online running statistics (Welford's method).
8pub struct RunningStatistics {
9    count: u64,
10    mean: f64,
11    m2: f64,
12    min: f64,
13    max: f64,
14}
15
16/// Construct a new RunningStatistics.
17pub fn new_running_stats() -> RunningStatistics {
18    RunningStatistics {
19        count: 0,
20        mean: 0.0,
21        m2: 0.0,
22        min: f64::INFINITY,
23        max: f64::NEG_INFINITY,
24    }
25}
26
27impl RunningStatistics {
28    /// Add a new value.
29    pub fn add(&mut self, x: f64) {
30        self.count += 1;
31        let delta = x - self.mean;
32        self.mean += delta / self.count as f64;
33        let delta2 = x - self.mean;
34        self.m2 += delta * delta2;
35        if x < self.min {
36            self.min = x;
37        }
38        if x > self.max {
39            self.max = x;
40        }
41    }
42
43    /// Add multiple values.
44    pub fn add_slice(&mut self, xs: &[f64]) {
45        for &x in xs {
46            self.add(x);
47        }
48    }
49
50    /// Number of values added.
51    pub fn count(&self) -> u64 {
52        self.count
53    }
54
55    /// Current mean.
56    pub fn mean(&self) -> f64 {
57        self.mean
58    }
59
60    /// Sample variance (n-1 denominator).
61    pub fn variance(&self) -> f64 {
62        if self.count < 2 {
63            return 0.0;
64        }
65        self.m2 / (self.count - 1) as f64
66    }
67
68    /// Population variance (n denominator).
69    pub fn pop_variance(&self) -> f64 {
70        if self.count == 0 {
71            return 0.0;
72        }
73        self.m2 / self.count as f64
74    }
75
76    /// Sample standard deviation.
77    pub fn std_dev(&self) -> f64 {
78        self.variance().sqrt()
79    }
80
81    /// Minimum value seen.
82    pub fn min(&self) -> Option<f64> {
83        if self.count == 0 {
84            None
85        } else {
86            Some(self.min)
87        }
88    }
89
90    /// Maximum value seen.
91    pub fn max(&self) -> Option<f64> {
92        if self.count == 0 {
93            None
94        } else {
95            Some(self.max)
96        }
97    }
98
99    /// Reset all statistics.
100    pub fn reset(&mut self) {
101        self.count = 0;
102        self.mean = 0.0;
103        self.m2 = 0.0;
104        self.min = f64::INFINITY;
105        self.max = f64::NEG_INFINITY;
106    }
107}
108
109/// Compute mean of a slice (convenience).
110pub fn slice_mean(xs: &[f64]) -> f64 {
111    if xs.is_empty() {
112        return 0.0;
113    }
114    xs.iter().sum::<f64>() / xs.len() as f64
115}
116
117/// Compute sample variance of a slice.
118pub fn slice_variance(xs: &[f64]) -> f64 {
119    let mut rs = new_running_stats();
120    rs.add_slice(xs);
121    rs.variance()
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_empty_count() {
130        /* new stats has zero count */
131        let rs = new_running_stats();
132        assert_eq!(rs.count(), 0);
133    }
134
135    #[test]
136    fn test_mean_single() {
137        /* mean of single value is that value */
138        let mut rs = new_running_stats();
139        rs.add(7.0);
140        assert!((rs.mean() - 7.0).abs() < 1e-12);
141    }
142
143    #[test]
144    fn test_mean_multiple() {
145        /* mean of [1,2,3,4,5] = 3 */
146        let mut rs = new_running_stats();
147        rs.add_slice(&[1.0, 2.0, 3.0, 4.0, 5.0]);
148        assert!((rs.mean() - 3.0).abs() < 1e-12);
149    }
150
151    #[test]
152    fn test_variance() {
153        /* sample variance of [2, 4, 4, 4, 5, 5, 7, 9] = 4 */
154        let mut rs = new_running_stats();
155        rs.add_slice(&[2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]);
156        assert!(
157            (rs.variance() - 4.571428).abs() < 0.01,
158            "var={}",
159            rs.variance()
160        );
161    }
162
163    #[test]
164    fn test_std_dev_constant() {
165        /* std dev of constant data is 0 */
166        let mut rs = new_running_stats();
167        rs.add_slice(&[5.0, 5.0, 5.0, 5.0]);
168        assert!(rs.std_dev() < 1e-12);
169    }
170
171    #[test]
172    fn test_min_max() {
173        /* min and max are tracked correctly */
174        let mut rs = new_running_stats();
175        rs.add_slice(&[3.0, 1.0, 4.0, 1.0, 5.0]);
176        assert!((rs.min().expect("should succeed") - 1.0).abs() < 1e-12);
177        assert!((rs.max().expect("should succeed") - 5.0).abs() < 1e-12);
178    }
179
180    #[test]
181    fn test_reset() {
182        /* reset clears all state */
183        let mut rs = new_running_stats();
184        rs.add_slice(&[1.0, 2.0, 3.0]);
185        rs.reset();
186        assert_eq!(rs.count(), 0);
187        assert!(rs.min().is_none());
188    }
189
190    #[test]
191    fn test_slice_mean() {
192        /* slice_mean convenience function */
193        assert!((slice_mean(&[10.0, 20.0, 30.0]) - 20.0).abs() < 1e-12);
194    }
195
196    #[test]
197    fn test_slice_variance() {
198        /* slice_variance convenience function */
199        let v = slice_variance(&[1.0, 2.0, 3.0]);
200        assert!((v - 1.0).abs() < 1e-9, "v={v}");
201    }
202}