wowlab_types/stats/summary/
batch.rs1use super::Streaming;
2use crate::constants::HUNDRED;
3
4const PERCENTILE_50: usize = 50;
5const PERCENTILE_MAX: usize = 100;
6const PERCENTILE_75: usize = 75;
7const PERCENTILE_25: usize = 25;
8
9#[derive(Debug)]
11pub struct Batch {
12 data: Vec<f64>,
13 sorted: bool,
14 streaming: Streaming,
15}
16
17impl Batch {
18 #[must_use]
19 pub fn new(values: Vec<f64>) -> Self {
20 let streaming: Streaming = values.iter().copied().collect();
21
22 Self {
23 data: values,
24 sorted: false,
25 streaming,
26 }
27 }
28
29 #[inline]
30 #[must_use]
31 pub fn count(&self) -> usize {
32 self.data.len()
33 }
34
35 #[inline]
36 #[must_use]
37 pub fn mean(&self) -> f64 {
38 self.streaming.mean()
39 }
40
41 #[inline]
42 #[must_use]
43 pub fn variance(&self) -> f64 {
44 self.streaming.variance()
45 }
46
47 #[inline]
48 #[must_use]
49 pub fn std_dev(&self) -> f64 {
50 self.streaming.std_dev()
51 }
52
53 #[inline]
54 #[must_use]
55 pub fn min(&self) -> f64 {
56 self.streaming.min()
57 }
58
59 #[inline]
60 #[must_use]
61 pub fn max(&self) -> f64 {
62 self.streaming.max()
63 }
64
65 #[inline]
66 #[must_use]
67 pub fn cv(&self) -> f64 {
68 self.streaming.cv()
69 }
70
71 pub fn median(&mut self) -> f64 {
72 self.percentile(PERCENTILE_50)
73 }
74
75 #[expect(
77 clippy::cast_possible_truncation,
78 clippy::cast_precision_loss,
79 clippy::cast_sign_loss,
80 reason = "percentile positions are clamped to the nonnegative bounds of the data vector"
81 )]
82 pub fn percentile(&mut self, p: usize) -> f64 {
83 if self.data.is_empty() {
84 return f64::NAN;
85 }
86
87 self.ensure_sorted();
88
89 let p = p.min(PERCENTILE_MAX) as f64 / HUNDRED;
90 let idx = p * (self.data.len() - 1) as f64;
91 let lo = idx.floor() as usize;
92 let hi = idx.ceil() as usize;
93
94 if lo == hi {
95 self.data.get(lo).copied().unwrap_or(f64::NAN)
96 } else {
97 let frac = idx - lo as f64;
98 let lo_val = self.data.get(lo).copied().unwrap_or(f64::NAN);
99 let hi_val = self.data.get(hi).copied().unwrap_or(f64::NAN);
100
101 lo_val * (1.0 - frac) + hi_val * frac
102 }
103 }
104
105 pub fn iqr(&mut self) -> f64 {
107 self.percentile(PERCENTILE_75) - self.percentile(PERCENTILE_25)
108 }
109
110 fn ensure_sorted(&mut self) {
111 if !self.sorted {
112 self.data
113 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
114 self.sorted = true;
115 }
116 }
117}
118
119pub type Summary = Batch;