quanta/
stats.rs

1/// Estimates the arithmetic mean (and the error) for a set of samples.
2///
3/// This type is written and maintained internally as it is trivial to implement and doesn't warrant
4/// a separate dependency.  As well, we add some features like exposing the sample count,
5/// calculating the mean + error value, etc, that existing crates don't do.
6///
7/// Based on [Welford's algorithm][welfords] which computes the mean incrementally, with constant
8/// time and space complexity.
9///
10/// [welfords]: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford%27s_online_algorithm
11#[derive(Default)]
12pub(crate) struct Variance {
13    mean: f64,
14    mean2: f64,
15    n: u64,
16}
17
18impl Variance {
19    #[inline]
20    pub fn add(&mut self, sample: f64) {
21        self.n += 1;
22        let n_f = self.n as f64;
23        let delta_sq = (sample - self.mean).powi(2);
24        self.mean2 += ((n_f - 1.0) * delta_sq) / n_f;
25        self.mean += (sample - self.mean) / n_f;
26    }
27
28    #[inline]
29    pub fn mean(&self) -> f64 {
30        self.mean
31    }
32
33    #[inline]
34    pub fn mean_error(&self) -> f64 {
35        if self.n < 2 {
36            return 0.0;
37        }
38
39        let n_f = self.n as f64;
40        let sd = (self.mean2 / (n_f - 1.0)).sqrt();
41        sd / n_f.sqrt()
42    }
43
44    #[inline]
45    pub fn mean_with_error(&self) -> f64 {
46        let mean = self.mean.abs();
47        mean + self.mean_error().abs()
48    }
49
50    #[inline]
51    pub fn has_significant_result(&self) -> bool {
52        self.n >= 2
53    }
54
55    #[inline]
56    pub fn samples(&self) -> u64 {
57        self.n
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::Variance;
64
65    #[test]
66    fn basic() {
67        let inputs = &[5.0, 10.0, 12.0, 15.0, 20.0];
68        let mut variance = Variance::default();
69        for input in inputs {
70            variance.add(*input);
71        }
72
73        assert_eq!(variance.mean(), 12.4);
74
75        let expected_mean_error = 2.5019;
76        assert!((variance.mean_error() - expected_mean_error).abs() < 0.001);
77    }
78}