1use std::fmt;
2
3use num_traits::ToPrimitive;
4use serde::{Deserialize, Serialize};
5
6use crate::Commute;
7
8#[inline]
10pub fn stddev<I, T>(x: I) -> f64
11where
12 I: IntoIterator<Item = T>,
13 T: ToPrimitive,
14{
15 x.into_iter().collect::<OnlineStats>().stddev()
16}
17
18#[inline]
20pub fn variance<I, T>(x: I) -> f64
21where
22 I: IntoIterator<Item = T>,
23 T: ToPrimitive,
24{
25 x.into_iter().collect::<OnlineStats>().variance()
26}
27
28#[inline]
30pub fn mean<I, T>(x: I) -> f64
31where
32 I: IntoIterator<Item = T>,
33 T: ToPrimitive,
34{
35 x.into_iter().collect::<OnlineStats>().mean()
36}
37
38#[allow(clippy::unsafe_derive_deserialize)]
43#[derive(Clone, Copy, Serialize, Deserialize, PartialEq)]
44pub struct OnlineStats {
45 size: u64, mean: f64, q: f64, hg_sums: bool, harmonic_sum: f64, geometric_sum: f64, n_positive: u64, n_zero: u64, n_negative: u64, }
60
61impl OnlineStats {
62 #[must_use]
66 pub fn new() -> OnlineStats {
67 Default::default()
68 }
69
70 #[must_use]
72 pub fn from_slice<T: ToPrimitive>(samples: &[T]) -> OnlineStats {
73 samples
75 .iter()
76 .map(|n| unsafe { n.to_f64().unwrap_unchecked() })
77 .collect()
78 }
79
80 #[must_use]
82 pub const fn mean(&self) -> f64 {
83 if self.is_empty() { f64::NAN } else { self.mean }
84 }
85
86 #[must_use]
88 pub fn stddev(&self) -> f64 {
89 self.variance().sqrt()
90 }
91
92 #[must_use]
95 pub const fn variance(&self) -> f64 {
96 if self.is_empty() {
97 f64::NAN
98 } else {
99 self.q / (self.size as f64)
100 }
101 }
102
103 #[must_use]
105 pub fn harmonic_mean(&self) -> f64 {
106 if self.is_empty() || self.n_zero > 0 || self.n_negative > 0 {
107 f64::NAN
108 } else {
109 (self.size as f64) / self.harmonic_sum
110 }
111 }
112
113 #[must_use]
115 pub fn geometric_mean(&self) -> f64 {
116 if self.is_empty()
117 || self.n_negative > 0
118 || self.geometric_sum.is_nan()
119 || self.geometric_sum == f64::INFINITY
120 {
121 f64::NAN
122 } else if self.n_zero > 0 || self.geometric_sum == f64::NEG_INFINITY {
123 0.0
126 } else {
127 (self.geometric_sum / (self.size as f64)).exp()
128 }
129 }
130
131 #[must_use]
154 pub const fn n_counts(&self) -> (u64, u64, u64) {
155 (self.n_negative, self.n_zero, self.n_positive)
156 }
157
158 #[inline]
165 pub fn add<T: ToPrimitive>(&mut self, sample: &T) {
166 let sample = unsafe { sample.to_f64().unwrap_unchecked() };
168
169 if sample.is_nan() {
170 return;
171 }
172
173 self.size += 1;
176 let delta = sample - self.mean;
177
178 self.mean = delta.mul_add(1.0 / (self.size as f64), self.mean);
180
181 self.q = delta.mul_add(sample - self.mean, self.q);
183
184 if sample > 0.0 {
186 if self.hg_sums {
187 self.harmonic_sum += 1.0 / sample;
189 self.geometric_sum += sample.ln();
190 }
191 self.n_positive += 1;
192 } else {
193 if sample.is_sign_negative() {
195 self.n_negative += 1;
196 } else {
197 self.n_zero += 1;
198 }
199 self.hg_sums = false;
200 }
201 }
202
203 #[inline]
208 pub fn add_f64(&mut self, sample: f64) {
209 if sample.is_nan() {
210 return;
211 }
212
213 self.size += 1;
214 let delta = sample - self.mean;
215
216 self.mean = delta.mul_add(1.0 / (self.size as f64), self.mean);
217 self.q = delta.mul_add(sample - self.mean, self.q);
218
219 if sample > 0.0 {
221 if self.hg_sums {
222 self.harmonic_sum += 1.0 / sample;
223 self.geometric_sum += sample.ln();
224 }
225 self.n_positive += 1;
226 } else {
227 if sample.is_sign_negative() {
229 self.n_negative += 1;
230 } else {
231 self.n_zero += 1;
232 }
233 self.hg_sums = false;
234 }
235 }
236
237 #[inline]
241 pub fn add_null(&mut self) {
242 self.add_f64(0.0);
243 }
244
245 #[inline]
247 #[must_use]
248 pub const fn len(&self) -> usize {
249 self.size as usize
250 }
251
252 #[inline]
254 #[must_use]
255 pub const fn is_empty(&self) -> bool {
256 self.size == 0
257 }
258}
259
260impl Commute for OnlineStats {
261 #[inline]
262 fn merge(&mut self, v: OnlineStats) {
263 if v.is_empty() {
264 return;
265 }
266
267 let (s1, s2) = (self.size as f64, v.size as f64);
269 let total = s1 + s2;
270 let delta = self.mean - v.mean;
271 let meandiffsq = delta * delta;
272
273 self.size += v.size;
274
275 self.mean = (v.mean - self.mean).mul_add(s2 / total, self.mean);
278
279 self.q += meandiffsq.mul_add(s1 * s2 / total, v.q);
282
283 self.hg_sums = self.hg_sums && v.hg_sums;
284 self.harmonic_sum += v.harmonic_sum;
285 self.geometric_sum += v.geometric_sum;
286
287 self.n_zero += v.n_zero;
288 self.n_negative += v.n_negative;
289 self.n_positive += v.n_positive;
290 }
291}
292
293impl Default for OnlineStats {
294 fn default() -> OnlineStats {
295 OnlineStats {
296 size: 0,
297 mean: 0.0,
298 q: 0.0,
299 harmonic_sum: 0.0,
300 geometric_sum: 0.0,
301 n_zero: 0,
302 n_negative: 0,
303 n_positive: 0,
304 hg_sums: true,
305 }
306 }
307}
308
309impl fmt::Debug for OnlineStats {
310 #[inline]
311 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
312 write!(f, "{:.10} +/- {:.10}", self.mean(), self.stddev())
313 }
314}
315
316impl<T: ToPrimitive> FromIterator<T> for OnlineStats {
317 #[inline]
318 fn from_iter<I: IntoIterator<Item = T>>(it: I) -> OnlineStats {
319 let mut v = OnlineStats::new();
320 v.extend(it);
321 v
322 }
323}
324
325impl<T: ToPrimitive> Extend<T> for OnlineStats {
326 #[inline]
327 fn extend<I: IntoIterator<Item = T>>(&mut self, it: I) {
328 for sample in it {
329 self.add(&sample);
330 }
331 }
332}
333
334#[cfg(test)]
335mod test {
336 use super::{OnlineStats, mean, stddev, variance};
337 use {crate::Commute, crate::merge_all};
338
339 #[test]
340 fn online() {
341 let expected = OnlineStats::from_slice(&[1usize, 2, 3, 2, 4, 6]);
343
344 let var1 = OnlineStats::from_slice(&[1usize, 2, 3]);
345 let var2 = OnlineStats::from_slice(&[2usize, 4, 6]);
346 let mut got = var1;
347 got.merge(var2);
348 assert_eq!(expected.stddev(), got.stddev());
349 assert_eq!(expected.mean(), got.mean());
350 assert_eq!(expected.variance(), got.variance());
351 }
352
353 #[test]
354 fn online_empty() {
355 let expected = OnlineStats::new();
356 assert!(expected.is_empty());
357 }
358
359 #[test]
360 fn online_many() {
361 let expected = OnlineStats::from_slice(&[1usize, 2, 3, 2, 4, 6, 3, 6, 9]);
363
364 let vars = vec![
365 OnlineStats::from_slice(&[1usize, 2, 3]),
366 OnlineStats::from_slice(&[2usize, 4, 6]),
367 OnlineStats::from_slice(&[3usize, 6, 9]),
368 ];
369 assert_eq!(
370 expected.stddev(),
371 merge_all(vars.clone().into_iter()).unwrap().stddev()
372 );
373 assert_eq!(
374 expected.mean(),
375 merge_all(vars.clone().into_iter()).unwrap().mean()
376 );
377 assert_eq!(
378 expected.variance(),
379 merge_all(vars.into_iter()).unwrap().variance()
380 );
381 }
382
383 #[test]
384 fn test_means() {
385 let mut stats = OnlineStats::new();
386 stats.extend(vec![2.0f64, 4.0, 8.0]);
387
388 assert!((stats.mean() - 4.666666666667).abs() < 1e-10);
390
391 assert_eq!("3.42857143", format!("{:.8}", stats.harmonic_mean()));
393
394 assert!((stats.geometric_mean() - 4.0).abs() < 1e-10);
396 }
397
398 #[test]
399 fn test_means_with_negative() {
400 let mut stats = OnlineStats::new();
401 stats.extend(vec![-2.0f64, 2.0]);
402
403 assert!(stats.mean().abs() < 1e-10);
405
406 assert!(stats.geometric_mean().is_nan());
408
409 assert!(stats.harmonic_mean().is_nan());
411 }
412
413 #[test]
414 fn test_means_with_zero() {
415 let mut stats = OnlineStats::new();
416 stats.extend(vec![0.0f64, 4.0, 8.0]);
417
418 assert!((stats.mean() - 4.0).abs() < 1e-10);
420
421 assert!(stats.geometric_mean().abs() < 1e-10);
423
424 assert!(stats.harmonic_mean().is_nan());
426 }
427
428 #[test]
429 fn test_means_with_zero_and_negative_values() {
430 let mut stats = OnlineStats::new();
431 stats.extend(vec![-10i32, -5, 0, 5, 10]);
432
433 assert!(stats.mean().abs() < 1e-10);
435
436 assert!(stats.geometric_mean().is_nan());
438
439 assert!(stats.harmonic_mean().is_nan());
441 }
442
443 #[test]
444 fn test_means_single_value() {
445 let mut stats = OnlineStats::new();
446 stats.extend(vec![5.0f64]);
447
448 assert!((stats.mean() - 5.0).abs() < 1e-10);
450 assert!((stats.geometric_mean() - 5.0).abs() < 1e-10);
451 assert!((stats.harmonic_mean() - 5.0).abs() < 1e-10);
452 }
453
454 #[test]
455 fn test_means_empty() {
456 let stats = OnlineStats::new();
457
458 assert!(stats.mean().is_nan());
460 assert!(stats.geometric_mean().is_nan());
461 assert!(stats.harmonic_mean().is_nan());
462 }
463
464 #[test]
467 fn test_mean_wrapper_basic() {
468 let result = mean(vec![1.0f64, 2.0, 3.0, 4.0, 5.0]);
470 assert!((result - 3.0).abs() < 1e-10);
471
472 let result = mean(vec![1i32, 2, 3, 4, 5]);
474 assert!((result - 3.0).abs() < 1e-10);
475
476 let result = mean(vec![10u32, 20, 30]);
478 assert!((result - 20.0).abs() < 1e-10);
479 }
480
481 #[test]
482 fn test_mean_wrapper_empty() {
483 let result = mean(Vec::<f64>::new());
484 assert!(result.is_nan());
485 }
486
487 #[test]
488 fn test_mean_wrapper_single_element() {
489 assert!((mean(vec![42.0f64]) - 42.0).abs() < 1e-10);
490 assert!((mean(vec![100i32]) - 100.0).abs() < 1e-10);
491 assert!((mean(vec![0u8]) - 0.0).abs() < 1e-10);
492 }
493
494 #[test]
495 fn test_mean_wrapper_negative_values() {
496 let result = mean(vec![-5.0f64, 5.0]);
497 assert!(result.abs() < 1e-10); let result = mean(vec![-10i32, -20, -30]);
500 assert!((result - (-20.0)).abs() < 1e-10);
501 }
502
503 #[test]
504 fn test_mean_wrapper_various_numeric_types() {
505 assert!((mean(vec![1u8, 2, 3]) - 2.0).abs() < 1e-10);
507 assert!((mean(vec![1u16, 2, 3]) - 2.0).abs() < 1e-10);
508 assert!((mean(vec![1u64, 2, 3]) - 2.0).abs() < 1e-10);
509 assert!((mean(vec![1i8, 2, 3]) - 2.0).abs() < 1e-10);
510 assert!((mean(vec![1i16, 2, 3]) - 2.0).abs() < 1e-10);
511 assert!((mean(vec![1i64, 2, 3]) - 2.0).abs() < 1e-10);
512 assert!((mean(vec![1.0f32, 2.0, 3.0]) - 2.0).abs() < 1e-6);
513 assert!((mean(vec![1usize, 2, 3]) - 2.0).abs() < 1e-10);
514 assert!((mean(vec![1isize, 2, 3]) - 2.0).abs() < 1e-10);
515 }
516
517 #[test]
518 fn test_variance_wrapper_basic() {
519 let result = variance(vec![1.0f64, 2.0, 3.0, 4.0, 5.0]);
521 assert!((result - 2.0).abs() < 1e-10);
522
523 let result = variance(vec![1i32, 2, 3, 4, 5]);
525 assert!((result - 2.0).abs() < 1e-10);
526 }
527
528 #[test]
529 fn test_variance_wrapper_empty() {
530 let result = variance(Vec::<f64>::new());
531 assert!(result.is_nan());
532 }
533
534 #[test]
535 fn test_variance_wrapper_single_element() {
536 assert!(variance(vec![42.0f64]).abs() < 1e-10);
538 assert!(variance(vec![100i32]).abs() < 1e-10);
539 }
540
541 #[test]
542 fn test_variance_wrapper_identical_values() {
543 let result = variance(vec![5.0f64, 5.0, 5.0, 5.0]);
545 assert!(result.abs() < 1e-10);
546 }
547
548 #[test]
549 fn test_variance_wrapper_various_numeric_types() {
550 let expected = 2.0 / 3.0;
552 assert!((variance(vec![1u8, 2, 3]) - expected).abs() < 1e-10);
553 assert!((variance(vec![1u16, 2, 3]) - expected).abs() < 1e-10);
554 assert!((variance(vec![1i32, 2, 3]) - expected).abs() < 1e-10);
555 assert!((variance(vec![1i64, 2, 3]) - expected).abs() < 1e-10);
556 assert!((variance(vec![1usize, 2, 3]) - expected).abs() < 1e-10);
557 }
558
559 #[test]
560 fn test_stddev_wrapper_basic() {
561 let result = stddev(vec![1.0f64, 2.0, 3.0, 4.0, 5.0]);
563 assert!((result - 2.0f64.sqrt()).abs() < 1e-10);
564
565 let result = stddev(vec![1i32, 2, 3, 4, 5]);
567 assert!((result - 2.0f64.sqrt()).abs() < 1e-10);
568 }
569
570 #[test]
571 fn test_stddev_wrapper_empty() {
572 let result = stddev(Vec::<f64>::new());
573 assert!(result.is_nan());
574 }
575
576 #[test]
577 fn test_stddev_wrapper_single_element() {
578 assert!(stddev(vec![42.0f64]).abs() < 1e-10);
580 assert!(stddev(vec![100i32]).abs() < 1e-10);
581 }
582
583 #[test]
584 fn test_stddev_wrapper_identical_values() {
585 let result = stddev(vec![5.0f64, 5.0, 5.0, 5.0]);
587 assert!(result.abs() < 1e-10);
588 }
589
590 #[test]
591 fn test_stddev_wrapper_various_numeric_types() {
592 let expected = (2.0f64 / 3.0).sqrt();
594 assert!((stddev(vec![1u8, 2, 3]) - expected).abs() < 1e-10);
595 assert!((stddev(vec![1u16, 2, 3]) - expected).abs() < 1e-10);
596 assert!((stddev(vec![1i32, 2, 3]) - expected).abs() < 1e-10);
597 assert!((stddev(vec![1i64, 2, 3]) - expected).abs() < 1e-10);
598 assert!((stddev(vec![1usize, 2, 3]) - expected).abs() < 1e-10);
599 }
600
601 #[test]
602 fn test_wrapper_functions_consistency() {
603 let data = vec![1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
605 let stats = OnlineStats::from_slice(&data);
606
607 assert!((mean(data.clone()) - stats.mean()).abs() < 1e-10);
608 assert!((variance(data.clone()) - stats.variance()).abs() < 1e-10);
609 assert!((stddev(data) - stats.stddev()).abs() < 1e-10);
610 }
611
612 #[test]
613 fn test_wrapper_functions_with_iterators() {
614 let arr = [1, 2, 3, 4, 5];
616
617 assert!((mean(arr) - 3.0).abs() < 1e-10);
619
620 assert!((mean(1..=5) - 3.0).abs() < 1e-10);
622
623 let result = mean((1..=5).map(|x| x * 2));
625 assert!((result - 6.0).abs() < 1e-10);
626 }
627
628 #[test]
631 fn test_n_counts_basic() {
632 let mut stats = OnlineStats::new();
633 stats.extend(vec![-5, -3, 0, 0, 2, 4, 6]);
634
635 let (neg, zero, pos) = stats.n_counts();
636 assert_eq!(neg, 2, "Should have 2 negative values");
637 assert_eq!(zero, 2, "Should have 2 zero values");
638 assert_eq!(pos, 3, "Should have 3 positive values");
639 }
640
641 #[test]
642 fn test_n_counts_all_positive() {
643 let mut stats = OnlineStats::new();
644 stats.extend(vec![1.0, 2.0, 3.0, 4.0]);
645
646 let (neg, zero, pos) = stats.n_counts();
647 assert_eq!(neg, 0);
648 assert_eq!(zero, 0);
649 assert_eq!(pos, 4);
650 }
651
652 #[test]
653 fn test_n_counts_all_negative() {
654 let mut stats = OnlineStats::new();
655 stats.extend(vec![-1.0, -2.0, -3.0]);
656
657 let (neg, zero, pos) = stats.n_counts();
658 assert_eq!(neg, 3);
659 assert_eq!(zero, 0);
660 assert_eq!(pos, 0);
661 }
662
663 #[test]
664 fn test_n_counts_all_zeros() {
665 let mut stats = OnlineStats::new();
666 stats.extend(vec![0.0, 0.0, 0.0]);
667
668 let (neg, zero, pos) = stats.n_counts();
669 assert_eq!(neg, 0);
670 assert_eq!(zero, 3);
671 assert_eq!(pos, 0);
672 }
673
674 #[test]
675 fn test_n_counts_with_merge() {
676 let mut stats1 = OnlineStats::new();
677 stats1.extend(vec![-2, 0, 3]);
678
679 let mut stats2 = OnlineStats::new();
680 stats2.extend(vec![-1, 5, 7]);
681
682 stats1.merge(stats2);
683
684 let (neg, zero, pos) = stats1.n_counts();
685 assert_eq!(neg, 2, "Should have 2 negative values after merge");
686 assert_eq!(zero, 1, "Should have 1 zero value after merge");
687 assert_eq!(pos, 3, "Should have 3 positive values after merge");
688 }
689
690 #[test]
691 fn test_n_counts_empty() {
692 let stats = OnlineStats::new();
693
694 let (neg, zero, pos) = stats.n_counts();
695 assert_eq!(neg, 0);
696 assert_eq!(zero, 0);
697 assert_eq!(pos, 0);
698 }
699
700 #[test]
701 fn test_n_counts_negative_zero() {
702 let mut stats = OnlineStats::new();
703 stats.extend(vec![-0.0f64, 0.0]);
706
707 let (neg, zero, pos) = stats.n_counts();
708 assert_eq!(neg, 1, "-0.0 has negative sign bit");
709 assert_eq!(zero, 1, "+0.0 is zero");
710 assert_eq!(pos, 0);
711 }
712
713 #[test]
714 fn test_n_counts_floats_boundary() {
715 let mut stats = OnlineStats::new();
716 stats.extend(vec![-0.0001f64, 0.0, 0.0001]);
718
719 let (neg, zero, pos) = stats.n_counts();
720 assert_eq!(neg, 1);
721 assert_eq!(zero, 1);
722 assert_eq!(pos, 1);
723 }
724
725 #[test]
726 fn test_nan_skipped() {
727 let mut stats = OnlineStats::new();
728 stats.add(&1.0f64);
729 stats.add(&f64::NAN);
730 stats.add(&3.0f64);
731 assert_eq!(stats.len(), 2);
733 assert!((stats.mean() - 2.0).abs() < 1e-10);
734 }
735
736 #[test]
737 fn test_nan_only() {
738 let mut stats = OnlineStats::new();
739 stats.add(&f64::NAN);
740 stats.add(&f64::NAN);
741 assert_eq!(stats.len(), 0);
742 assert!(stats.mean().is_nan());
743 assert!(stats.variance().is_nan());
744 assert!(stats.stddev().is_nan());
745 }
746
747 #[test]
748 fn test_infinity_add() {
749 let mut stats = OnlineStats::new();
750 stats.add(&f64::INFINITY);
751 assert_eq!(stats.len(), 1);
752 assert!(stats.mean().is_infinite());
753 }
754
755 #[test]
756 fn test_neg_infinity_add() {
757 let mut stats = OnlineStats::new();
758 stats.add(&f64::NEG_INFINITY);
759 assert_eq!(stats.len(), 1);
760 assert!(stats.mean().is_infinite());
761 }
762
763 #[test]
764 fn test_infinity_mixed() {
765 let mut stats = OnlineStats::new();
766 stats.add(&f64::INFINITY);
767 stats.add(&f64::NEG_INFINITY);
768 assert!(stats.mean().is_nan());
770 }
771
772 #[test]
773 fn test_add_f64_nan_skipped() {
774 let mut stats = OnlineStats::new();
775 stats.add_f64(1.0);
776 stats.add_f64(f64::NAN);
777 stats.add_f64(3.0);
778 assert_eq!(stats.len(), 2);
779 assert!((stats.mean() - 2.0).abs() < 1e-10);
780 }
781
782 #[test]
783 fn test_geometric_mean_infinity() {
784 let mut stats = OnlineStats::new();
785 stats.add(&f64::INFINITY);
786 assert!(stats.geometric_mean().is_nan());
788 }
789
790 #[test]
791 fn test_harmonic_mean_infinity() {
792 let mut stats = OnlineStats::new();
793 stats.add(&f64::INFINITY);
794 stats.add(&1.0f64);
795 assert!((stats.harmonic_mean() - 2.0).abs() < 1e-10);
797 }
798}