1use std::fmt::Display;
2
3use average::{self, concatenate, Estimate, Mean, Variance};
4use itertools::Itertools;
5
6use crate::data::ReductionFunc;
7
8pub trait VecAggregation {
9 fn median(&mut self) -> Option<f64>;
10}
11
12concatenate!(AggStats, [Mean, mean], [Variance, sample_variance]);
13
14pub fn aggregate_measurements(measurements: impl Iterator<Item = f64>) -> Stats {
15 let s: AggStats = measurements.collect();
16 Stats {
17 mean: s.mean(),
18 stddev: s.sample_variance().sqrt(),
19 len: s.mean.len() as usize,
20 }
21}
22
23#[derive(Debug)]
24pub struct Stats {
25 pub mean: f64,
26 pub stddev: f64,
27 pub len: usize,
28}
29
30impl Display for Stats {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 write!(f, "μ: {} σ: {} n: {}", self.mean, self.stddev, self.len)
33 }
34}
35
36impl Stats {
37 pub fn significantly_different_from(&self, other: &Stats, sigma: f64) -> bool {
38 assert!(self.len == 1);
39 assert!(other.len >= 1);
40 (self.mean - other.mean).abs() / other.stddev > sigma
41 }
42}
43
44impl VecAggregation for Vec<f64> {
45 fn median(&mut self) -> Option<f64> {
46 self.sort_by(f64::total_cmp);
47 match self.len() {
48 0 => None,
49 even if even % 2 == 0 => {
50 let left = self[even / 2 - 1];
51 let right = self[even / 2];
52 Some((left + right) / 2.0)
53 }
54 odd => Some(self[odd / 2]),
55 }
56 }
57}
58
59pub trait NumericReductionFunc: Iterator<Item = f64> {
60 fn aggregate_by(&mut self, fun: ReductionFunc) -> Option<Self::Item> {
61 match fun {
62 ReductionFunc::Min => self.reduce(f64::min),
63 ReductionFunc::Max => self.reduce(f64::max),
64 ReductionFunc::Median => self.collect_vec().median(),
65 ReductionFunc::Mean => {
66 let stats: AggStats = self.collect();
67 if stats.mean.is_empty() {
68 None
69 } else {
70 Some(stats.mean())
71 }
72 }
73 }
74 }
75}
76
77impl<T> NumericReductionFunc for T where T: Iterator<Item = f64> {}
78
79#[cfg(test)]
80mod test {
81 use super::*;
82
83 #[test]
84 fn no_floating_error() {
85 let measurements = (0..100).map(|_| 0.1).collect_vec();
86 let stats = aggregate_measurements(measurements.into_iter());
87 assert_eq!(stats.mean, 0.1);
89 assert_eq!(stats.len, 100);
90 let naive_mean = (0..100).map(|_| 0.1).sum::<f64>() / 100.0;
91 assert_ne!(naive_mean, 0.1);
92 }
93
94 #[test]
95 fn single_measurement() {
96 let measurements = vec![1.0];
97 let stats = aggregate_measurements(measurements.into_iter());
98 assert_eq!(stats.len, 1);
99 assert_eq!(stats.mean, 1.0);
100 assert_eq!(stats.stddev, 0.0);
101 }
102
103 #[test]
104 fn no_measurement() {
105 let measurements = vec![];
106 let stats = aggregate_measurements(measurements.into_iter());
107 assert_eq!(stats.len, 0);
108 assert_eq!(stats.mean, 0.0);
109 assert_eq!(stats.stddev, 0.0);
110 }
111
112 #[test]
113 fn z_score_with_zero_stddev() {
114 let stddev = 0.0;
115 let mean = 30.0;
116 let higher_val = 50.0;
117 let lower_val = 10.0;
118 let z_high = ((higher_val - mean) / stddev as f64).abs();
119 let z_low = ((lower_val - mean) / stddev as f64).abs();
120 assert_eq!(z_high, f64::INFINITY);
121 assert_eq!(z_low, f64::INFINITY);
122 }
123
124 #[test]
125 fn verify_stats() {
126 let empty_vec = [];
127 assert_eq!(None, empty_vec.into_iter().aggregate_by(ReductionFunc::Min));
128 assert_eq!(None, empty_vec.into_iter().aggregate_by(ReductionFunc::Max));
129 assert_eq!(
130 None,
131 empty_vec.into_iter().aggregate_by(ReductionFunc::Median)
132 );
133 assert_eq!(
134 None,
135 empty_vec.into_iter().aggregate_by(ReductionFunc::Mean)
136 );
137
138 let single_el_vec = [3.0];
139 assert_eq!(
140 Some(3.0),
141 single_el_vec.into_iter().aggregate_by(ReductionFunc::Min)
142 );
143 assert_eq!(
144 Some(3.0),
145 single_el_vec.into_iter().aggregate_by(ReductionFunc::Max)
146 );
147 assert_eq!(
148 Some(3.0),
149 single_el_vec
150 .into_iter()
151 .aggregate_by(ReductionFunc::Median)
152 );
153 assert_eq!(
154 Some(3.0),
155 single_el_vec.into_iter().aggregate_by(ReductionFunc::Mean)
156 );
157
158 let two_el_vec = [3.0, 1.0];
159 assert_eq!(
160 Some(1.0),
161 two_el_vec.into_iter().aggregate_by(ReductionFunc::Min)
162 );
163 assert_eq!(
164 Some(3.0),
165 two_el_vec.into_iter().aggregate_by(ReductionFunc::Max)
166 );
167 assert_eq!(
168 Some(2.0),
169 two_el_vec.into_iter().aggregate_by(ReductionFunc::Median)
170 );
171 assert_eq!(
172 Some(2.0),
173 two_el_vec.into_iter().aggregate_by(ReductionFunc::Mean)
174 );
175
176 let three_el_vec = [2.0, 6.0, 1.0];
177 assert_eq!(
178 Some(1.0),
179 three_el_vec.into_iter().aggregate_by(ReductionFunc::Min)
180 );
181 assert_eq!(
182 Some(6.0),
183 three_el_vec.into_iter().aggregate_by(ReductionFunc::Max)
184 );
185 assert_eq!(
186 Some(2.0),
187 three_el_vec.into_iter().aggregate_by(ReductionFunc::Median)
188 );
189 assert_eq!(
190 Some(3.0),
191 three_el_vec.into_iter().aggregate_by(ReductionFunc::Mean)
192 );
193 }
194}