quick_stats/
lib.rs

1pub mod full {
2    #[derive(Debug)]
3    pub struct Stats {
4        pub min: f64,
5        pub max: f64,
6        pub mean: f64,
7        /// Population variance
8        pub var: f64,
9        /// Sample variance
10        pub sample_var: f64,
11        pub stddev: f64,
12        pub sample_stddev: f64,
13        pub p1: f64,
14        pub p5: f64,
15        pub p50: f64,
16        pub p95: f64,
17        pub p99: f64,
18    }
19
20    impl Stats {
21        pub fn compute(values: &[f64]) -> Self {
22            let mut sorted = values.to_vec();
23            sorted.sort_unstable_by(|a, b| a.total_cmp(b));
24            let min = sorted[0];
25            let max = sorted[sorted.len() - 1];
26            let mean = sorted.iter().sum::<f64>() / values.len() as f64;
27            let var = compute_var(values, mean, false);
28            let sample_var = compute_var(values, mean, true);
29            let stddev = var.sqrt();
30            let sample_stddev = sample_var.sqrt();
31            let p1 = percentile_of_sorted(&sorted, 1.0);
32            let p5 = percentile_of_sorted(&sorted, 5.0);
33            let p50 = percentile_of_sorted(&sorted, 50.0);
34            let p95 = percentile_of_sorted(&sorted, 95.0);
35            let p99 = percentile_of_sorted(&sorted, 99.0);
36            Self {
37                min,
38                max,
39                mean,
40                var,
41                sample_var,
42                stddev,
43                sample_stddev,
44                p1,
45                p5,
46                p50,
47                p95,
48                p99,
49            }
50        }
51    }
52
53    // Helper function: extract a value representing the `pct` percentile of a sorted sample-set, using
54    // linear interpolation. If samples are not sorted, return nonsensical value.
55    fn percentile_of_sorted(sorted_samples: &[f64], pct: f64) -> f64 {
56        assert!(!sorted_samples.is_empty());
57        if sorted_samples.len() == 1 {
58            return sorted_samples[0];
59        }
60        let zero: f64 = 0.0;
61        assert!(zero <= pct);
62        let hundred = 100_f64;
63        assert!(pct <= hundred);
64        if pct == hundred {
65            return sorted_samples[sorted_samples.len() - 1];
66        }
67        let length = (sorted_samples.len() - 1) as f64;
68        let rank = (pct / hundred) * length;
69        let lrank = rank.floor();
70        let d = rank - lrank;
71        let n = lrank as usize;
72        let lo = sorted_samples[n];
73        let hi = sorted_samples[n + 1];
74        lo + (hi - lo) * d
75    }
76
77    fn compute_var(values: &[f64], mean: f64, sample: bool) -> f64 {
78        if values.len() < 2 {
79            0.0
80        } else {
81            let mut v: f64 = 0.0;
82            for s in values {
83                let x = *s - mean;
84                v += x * x;
85            }
86            // N.B., this is _supposed to be_ len-1, not len. If you
87            // change it back to len, you will be calculating a
88            // population variance, not a sample variance.
89            let denom = if sample {
90                values.len() - 1
91            } else {
92                values.len()
93            } as f64;
94            v / denom
95        }
96    }
97
98    #[cfg(test)]
99    mod tests {
100        use super::*;
101
102        #[test]
103        fn it_works() {
104            let values = [0.0, 1.0, 3.0, 4.0];
105            dbg!(Stats::compute(&values));
106        }
107    }
108}