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() { f64::NAN } else { self.q / (self.size as f64) }
97 }
98
99 #[must_use]
101 pub fn harmonic_mean(&self) -> f64 {
102 if self.is_empty() || self.n_zero > 0 || self.n_negative > 0 {
103 f64::NAN
104 } else {
105 (self.size as f64) / self.harmonic_sum
106 }
107 }
108
109 #[must_use]
111 pub fn geometric_mean(&self) -> f64 {
112 if self.is_empty()
113 || self.n_negative > 0
114 || self.geometric_sum.is_nan()
115 || self.geometric_sum == f64::INFINITY
116 {
117 f64::NAN
118 } else if self.n_zero > 0 || self.geometric_sum == f64::NEG_INFINITY {
119 0.0
122 } else {
123 (self.geometric_sum / (self.size as f64)).exp()
124 }
125 }
126
127 #[must_use]
150 pub const fn n_counts(&self) -> (u64, u64, u64) {
151 (self.n_negative, self.n_zero, self.n_positive)
152 }
153
154 #[inline]
161 pub fn add<T: ToPrimitive>(&mut self, sample: &T) {
162 let sample = unsafe { sample.to_f64().unwrap_unchecked() };
164
165 if sample.is_nan() {
166 return;
167 }
168
169 self.size += 1;
172 let delta = sample - self.mean;
173
174 self.mean = delta.mul_add(1.0 / (self.size as f64), self.mean);
176
177 self.q = delta.mul_add(sample - self.mean, self.q);
179
180 if sample > 0.0 {
182 if self.hg_sums {
183 self.harmonic_sum += 1.0 / sample;
185 self.geometric_sum += sample.ln();
186 }
187 self.n_positive += 1;
188 } else {
189 if sample.is_sign_negative() {
191 self.n_negative += 1;
192 } else {
193 self.n_zero += 1;
194 }
195 self.hg_sums = false;
196 }
197 }
198
199 #[inline]
204 pub fn add_f64(&mut self, sample: f64) {
205 if sample.is_nan() {
206 return;
207 }
208
209 self.size += 1;
210 let delta = sample - self.mean;
211
212 self.mean = delta.mul_add(1.0 / (self.size as f64), self.mean);
213 self.q = delta.mul_add(sample - self.mean, self.q);
214
215 if sample > 0.0 {
217 if self.hg_sums {
218 self.harmonic_sum += 1.0 / sample;
219 self.geometric_sum += sample.ln();
220 }
221 self.n_positive += 1;
222 } else {
223 if sample.is_sign_negative() {
225 self.n_negative += 1;
226 } else {
227 self.n_zero += 1;
228 }
229 self.hg_sums = false;
230 }
231 }
232
233 #[inline]
237 pub fn add_null(&mut self) {
238 self.add_f64(0.0);
239 }
240
241 #[inline]
243 #[must_use]
244 pub const fn len(&self) -> usize {
245 self.size as usize
246 }
247
248 #[inline]
250 #[must_use]
251 pub const fn is_empty(&self) -> bool {
252 self.size == 0
253 }
254}
255
256impl Commute for OnlineStats {
257 #[inline]
258 fn merge(&mut self, v: OnlineStats) {
259 if v.is_empty() {
260 return;
261 }
262
263 let (s1, s2) = (self.size as f64, v.size as f64);
265 let total = s1 + s2;
266 let delta = self.mean - v.mean;
267 let meandiffsq = delta * delta;
268
269 self.size += v.size;
270
271 self.mean = (v.mean - self.mean).mul_add(s2 / total, self.mean);
274
275 self.q += meandiffsq.mul_add(s1 * s2 / total, v.q);
278
279 self.hg_sums = self.hg_sums && v.hg_sums;
280 self.harmonic_sum += v.harmonic_sum;
281 self.geometric_sum += v.geometric_sum;
282
283 self.n_zero += v.n_zero;
284 self.n_negative += v.n_negative;
285 self.n_positive += v.n_positive;
286 }
287}
288
289impl Default for OnlineStats {
290 fn default() -> OnlineStats {
291 OnlineStats {
292 size: 0,
293 mean: 0.0,
294 q: 0.0,
295 harmonic_sum: 0.0,
296 geometric_sum: 0.0,
297 n_zero: 0,
298 n_negative: 0,
299 n_positive: 0,
300 hg_sums: true,
301 }
302 }
303}
304
305impl fmt::Debug for OnlineStats {
306 #[inline]
307 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
308 write!(f, "{:.10} +/- {:.10}", self.mean(), self.stddev())
309 }
310}
311
312impl<T: ToPrimitive> FromIterator<T> for OnlineStats {
313 #[inline]
314 fn from_iter<I: IntoIterator<Item = T>>(it: I) -> OnlineStats {
315 let mut v = OnlineStats::new();
316 v.extend(it);
317 v
318 }
319}
320
321impl<T: ToPrimitive> Extend<T> for OnlineStats {
322 #[inline]
323 fn extend<I: IntoIterator<Item = T>>(&mut self, it: I) {
324 for sample in it {
325 self.add(&sample);
326 }
327 }
328}
329
330#[cfg(test)]
331mod test {
332 use super::{OnlineStats, mean, stddev, variance};
333 use {crate::Commute, crate::merge_all};
334
335 #[test]
336 fn online() {
337 let expected = OnlineStats::from_slice(&[1usize, 2, 3, 2, 4, 6]);
339
340 let var1 = OnlineStats::from_slice(&[1usize, 2, 3]);
341 let var2 = OnlineStats::from_slice(&[2usize, 4, 6]);
342 let mut got = var1;
343 got.merge(var2);
344 assert_eq!(expected.stddev(), got.stddev());
345 assert_eq!(expected.mean(), got.mean());
346 assert_eq!(expected.variance(), got.variance());
347 }
348
349 #[test]
350 fn online_empty() {
351 let expected = OnlineStats::new();
352 assert!(expected.is_empty());
353 }
354
355 #[test]
356 fn online_many() {
357 let expected = OnlineStats::from_slice(&[1usize, 2, 3, 2, 4, 6, 3, 6, 9]);
359
360 let vars = vec![
361 OnlineStats::from_slice(&[1usize, 2, 3]),
362 OnlineStats::from_slice(&[2usize, 4, 6]),
363 OnlineStats::from_slice(&[3usize, 6, 9]),
364 ];
365 assert_eq!(
366 expected.stddev(),
367 merge_all(vars.clone().into_iter()).unwrap().stddev()
368 );
369 assert_eq!(
370 expected.mean(),
371 merge_all(vars.clone().into_iter()).unwrap().mean()
372 );
373 assert_eq!(
374 expected.variance(),
375 merge_all(vars.into_iter()).unwrap().variance()
376 );
377 }
378
379 #[test]
380 fn test_means() {
381 let mut stats = OnlineStats::new();
382 stats.extend(vec![2.0f64, 4.0, 8.0]);
383
384 assert!((stats.mean() - 4.666666666667).abs() < 1e-10);
386
387 assert_eq!("3.42857143", format!("{:.8}", stats.harmonic_mean()));
389
390 assert!((stats.geometric_mean() - 4.0).abs() < 1e-10);
392 }
393
394 #[test]
395 fn test_means_with_negative() {
396 let mut stats = OnlineStats::new();
397 stats.extend(vec![-2.0f64, 2.0]);
398
399 assert!(stats.mean().abs() < 1e-10);
401
402 assert!(stats.geometric_mean().is_nan());
404
405 assert!(stats.harmonic_mean().is_nan());
407 }
408
409 #[test]
410 fn test_means_with_zero() {
411 let mut stats = OnlineStats::new();
412 stats.extend(vec![0.0f64, 4.0, 8.0]);
413
414 assert!((stats.mean() - 4.0).abs() < 1e-10);
416
417 assert!(stats.geometric_mean().abs() < 1e-10);
419
420 assert!(stats.harmonic_mean().is_nan());
422 }
423
424 #[test]
425 fn test_means_with_zero_and_negative_values() {
426 let mut stats = OnlineStats::new();
427 stats.extend(vec![-10i32, -5, 0, 5, 10]);
428
429 assert!(stats.mean().abs() < 1e-10);
431
432 assert!(stats.geometric_mean().is_nan());
434
435 assert!(stats.harmonic_mean().is_nan());
437 }
438
439 #[test]
440 fn test_means_single_value() {
441 let mut stats = OnlineStats::new();
442 stats.extend(vec![5.0f64]);
443
444 assert!((stats.mean() - 5.0).abs() < 1e-10);
446 assert!((stats.geometric_mean() - 5.0).abs() < 1e-10);
447 assert!((stats.harmonic_mean() - 5.0).abs() < 1e-10);
448 }
449
450 #[test]
451 fn test_means_empty() {
452 let stats = OnlineStats::new();
453
454 assert!(stats.mean().is_nan());
456 assert!(stats.geometric_mean().is_nan());
457 assert!(stats.harmonic_mean().is_nan());
458 }
459
460 #[test]
463 fn test_mean_wrapper_basic() {
464 let result = mean(vec![1.0f64, 2.0, 3.0, 4.0, 5.0]);
466 assert!((result - 3.0).abs() < 1e-10);
467
468 let result = mean(vec![1i32, 2, 3, 4, 5]);
470 assert!((result - 3.0).abs() < 1e-10);
471
472 let result = mean(vec![10u32, 20, 30]);
474 assert!((result - 20.0).abs() < 1e-10);
475 }
476
477 #[test]
478 fn test_mean_wrapper_empty() {
479 let result = mean(Vec::<f64>::new());
480 assert!(result.is_nan());
481 }
482
483 #[test]
484 fn test_mean_wrapper_single_element() {
485 assert!((mean(vec![42.0f64]) - 42.0).abs() < 1e-10);
486 assert!((mean(vec![100i32]) - 100.0).abs() < 1e-10);
487 assert!((mean(vec![0u8]) - 0.0).abs() < 1e-10);
488 }
489
490 #[test]
491 fn test_mean_wrapper_negative_values() {
492 let result = mean(vec![-5.0f64, 5.0]);
493 assert!(result.abs() < 1e-10); let result = mean(vec![-10i32, -20, -30]);
496 assert!((result - (-20.0)).abs() < 1e-10);
497 }
498
499 #[test]
500 fn test_mean_wrapper_various_numeric_types() {
501 assert!((mean(vec![1u8, 2, 3]) - 2.0).abs() < 1e-10);
503 assert!((mean(vec![1u16, 2, 3]) - 2.0).abs() < 1e-10);
504 assert!((mean(vec![1u64, 2, 3]) - 2.0).abs() < 1e-10);
505 assert!((mean(vec![1i8, 2, 3]) - 2.0).abs() < 1e-10);
506 assert!((mean(vec![1i16, 2, 3]) - 2.0).abs() < 1e-10);
507 assert!((mean(vec![1i64, 2, 3]) - 2.0).abs() < 1e-10);
508 assert!((mean(vec![1.0f32, 2.0, 3.0]) - 2.0).abs() < 1e-6);
509 assert!((mean(vec![1usize, 2, 3]) - 2.0).abs() < 1e-10);
510 assert!((mean(vec![1isize, 2, 3]) - 2.0).abs() < 1e-10);
511 }
512
513 #[test]
514 fn test_variance_wrapper_basic() {
515 let result = variance(vec![1.0f64, 2.0, 3.0, 4.0, 5.0]);
517 assert!((result - 2.0).abs() < 1e-10);
518
519 let result = variance(vec![1i32, 2, 3, 4, 5]);
521 assert!((result - 2.0).abs() < 1e-10);
522 }
523
524 #[test]
525 fn test_variance_wrapper_empty() {
526 let result = variance(Vec::<f64>::new());
527 assert!(result.is_nan());
528 }
529
530 #[test]
531 fn test_variance_wrapper_single_element() {
532 assert!(variance(vec![42.0f64]).abs() < 1e-10);
534 assert!(variance(vec![100i32]).abs() < 1e-10);
535 }
536
537 #[test]
538 fn test_variance_wrapper_identical_values() {
539 let result = variance(vec![5.0f64, 5.0, 5.0, 5.0]);
541 assert!(result.abs() < 1e-10);
542 }
543
544 #[test]
545 fn test_variance_wrapper_various_numeric_types() {
546 let expected = 2.0 / 3.0;
548 assert!((variance(vec![1u8, 2, 3]) - expected).abs() < 1e-10);
549 assert!((variance(vec![1u16, 2, 3]) - expected).abs() < 1e-10);
550 assert!((variance(vec![1i32, 2, 3]) - expected).abs() < 1e-10);
551 assert!((variance(vec![1i64, 2, 3]) - expected).abs() < 1e-10);
552 assert!((variance(vec![1usize, 2, 3]) - expected).abs() < 1e-10);
553 }
554
555 #[test]
556 fn test_stddev_wrapper_basic() {
557 let result = stddev(vec![1.0f64, 2.0, 3.0, 4.0, 5.0]);
559 assert!((result - 2.0f64.sqrt()).abs() < 1e-10);
560
561 let result = stddev(vec![1i32, 2, 3, 4, 5]);
563 assert!((result - 2.0f64.sqrt()).abs() < 1e-10);
564 }
565
566 #[test]
567 fn test_stddev_wrapper_empty() {
568 let result = stddev(Vec::<f64>::new());
569 assert!(result.is_nan());
570 }
571
572 #[test]
573 fn test_stddev_wrapper_single_element() {
574 assert!(stddev(vec![42.0f64]).abs() < 1e-10);
576 assert!(stddev(vec![100i32]).abs() < 1e-10);
577 }
578
579 #[test]
580 fn test_stddev_wrapper_identical_values() {
581 let result = stddev(vec![5.0f64, 5.0, 5.0, 5.0]);
583 assert!(result.abs() < 1e-10);
584 }
585
586 #[test]
587 fn test_stddev_wrapper_various_numeric_types() {
588 let expected = (2.0f64 / 3.0).sqrt();
590 assert!((stddev(vec![1u8, 2, 3]) - expected).abs() < 1e-10);
591 assert!((stddev(vec![1u16, 2, 3]) - expected).abs() < 1e-10);
592 assert!((stddev(vec![1i32, 2, 3]) - expected).abs() < 1e-10);
593 assert!((stddev(vec![1i64, 2, 3]) - expected).abs() < 1e-10);
594 assert!((stddev(vec![1usize, 2, 3]) - expected).abs() < 1e-10);
595 }
596
597 #[test]
598 fn test_wrapper_functions_consistency() {
599 let data = vec![1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
601 let stats = OnlineStats::from_slice(&data);
602
603 assert!((mean(data.clone()) - stats.mean()).abs() < 1e-10);
604 assert!((variance(data.clone()) - stats.variance()).abs() < 1e-10);
605 assert!((stddev(data) - stats.stddev()).abs() < 1e-10);
606 }
607
608 #[test]
609 fn test_wrapper_functions_with_iterators() {
610 let arr = [1, 2, 3, 4, 5];
612
613 assert!((mean(arr) - 3.0).abs() < 1e-10);
615
616 assert!((mean(1..=5) - 3.0).abs() < 1e-10);
618
619 let result = mean((1..=5).map(|x| x * 2));
621 assert!((result - 6.0).abs() < 1e-10);
622 }
623
624 #[test]
627 fn test_n_counts_basic() {
628 let mut stats = OnlineStats::new();
629 stats.extend(vec![-5, -3, 0, 0, 2, 4, 6]);
630
631 let (neg, zero, pos) = stats.n_counts();
632 assert_eq!(neg, 2, "Should have 2 negative values");
633 assert_eq!(zero, 2, "Should have 2 zero values");
634 assert_eq!(pos, 3, "Should have 3 positive values");
635 }
636
637 #[test]
638 fn test_n_counts_all_positive() {
639 let mut stats = OnlineStats::new();
640 stats.extend(vec![1.0, 2.0, 3.0, 4.0]);
641
642 let (neg, zero, pos) = stats.n_counts();
643 assert_eq!(neg, 0);
644 assert_eq!(zero, 0);
645 assert_eq!(pos, 4);
646 }
647
648 #[test]
649 fn test_n_counts_all_negative() {
650 let mut stats = OnlineStats::new();
651 stats.extend(vec![-1.0, -2.0, -3.0]);
652
653 let (neg, zero, pos) = stats.n_counts();
654 assert_eq!(neg, 3);
655 assert_eq!(zero, 0);
656 assert_eq!(pos, 0);
657 }
658
659 #[test]
660 fn test_n_counts_all_zeros() {
661 let mut stats = OnlineStats::new();
662 stats.extend(vec![0.0, 0.0, 0.0]);
663
664 let (neg, zero, pos) = stats.n_counts();
665 assert_eq!(neg, 0);
666 assert_eq!(zero, 3);
667 assert_eq!(pos, 0);
668 }
669
670 #[test]
671 fn test_n_counts_with_merge() {
672 let mut stats1 = OnlineStats::new();
673 stats1.extend(vec![-2, 0, 3]);
674
675 let mut stats2 = OnlineStats::new();
676 stats2.extend(vec![-1, 5, 7]);
677
678 stats1.merge(stats2);
679
680 let (neg, zero, pos) = stats1.n_counts();
681 assert_eq!(neg, 2, "Should have 2 negative values after merge");
682 assert_eq!(zero, 1, "Should have 1 zero value after merge");
683 assert_eq!(pos, 3, "Should have 3 positive values after merge");
684 }
685
686 #[test]
687 fn test_n_counts_empty() {
688 let stats = OnlineStats::new();
689
690 let (neg, zero, pos) = stats.n_counts();
691 assert_eq!(neg, 0);
692 assert_eq!(zero, 0);
693 assert_eq!(pos, 0);
694 }
695
696 #[test]
697 fn test_n_counts_negative_zero() {
698 let mut stats = OnlineStats::new();
699 stats.extend(vec![-0.0f64, 0.0]);
702
703 let (neg, zero, pos) = stats.n_counts();
704 assert_eq!(neg, 1, "-0.0 has negative sign bit");
705 assert_eq!(zero, 1, "+0.0 is zero");
706 assert_eq!(pos, 0);
707 }
708
709 #[test]
710 fn test_n_counts_floats_boundary() {
711 let mut stats = OnlineStats::new();
712 stats.extend(vec![-0.0001f64, 0.0, 0.0001]);
714
715 let (neg, zero, pos) = stats.n_counts();
716 assert_eq!(neg, 1);
717 assert_eq!(zero, 1);
718 assert_eq!(pos, 1);
719 }
720
721 #[test]
722 fn test_nan_skipped() {
723 let mut stats = OnlineStats::new();
724 stats.add(&1.0f64);
725 stats.add(&f64::NAN);
726 stats.add(&3.0f64);
727 assert_eq!(stats.len(), 2);
729 assert!((stats.mean() - 2.0).abs() < 1e-10);
730 }
731
732 #[test]
733 fn test_nan_only() {
734 let mut stats = OnlineStats::new();
735 stats.add(&f64::NAN);
736 stats.add(&f64::NAN);
737 assert_eq!(stats.len(), 0);
738 assert!(stats.mean().is_nan());
739 assert!(stats.variance().is_nan());
740 assert!(stats.stddev().is_nan());
741 }
742
743 #[test]
744 fn test_infinity_add() {
745 let mut stats = OnlineStats::new();
746 stats.add(&f64::INFINITY);
747 assert_eq!(stats.len(), 1);
748 assert!(stats.mean().is_infinite());
749 }
750
751 #[test]
752 fn test_neg_infinity_add() {
753 let mut stats = OnlineStats::new();
754 stats.add(&f64::NEG_INFINITY);
755 assert_eq!(stats.len(), 1);
756 assert!(stats.mean().is_infinite());
757 }
758
759 #[test]
760 fn test_infinity_mixed() {
761 let mut stats = OnlineStats::new();
762 stats.add(&f64::INFINITY);
763 stats.add(&f64::NEG_INFINITY);
764 assert!(stats.mean().is_nan());
766 }
767
768 #[test]
769 fn test_add_f64_nan_skipped() {
770 let mut stats = OnlineStats::new();
771 stats.add_f64(1.0);
772 stats.add_f64(f64::NAN);
773 stats.add_f64(3.0);
774 assert_eq!(stats.len(), 2);
775 assert!((stats.mean() - 2.0).abs() < 1e-10);
776 }
777
778 #[test]
779 fn test_geometric_mean_infinity() {
780 let mut stats = OnlineStats::new();
781 stats.add(&f64::INFINITY);
782 assert!(stats.geometric_mean().is_nan());
784 }
785
786 #[test]
787 fn test_harmonic_mean_infinity() {
788 let mut stats = OnlineStats::new();
789 stats.add(&f64::INFINITY);
790 stats.add(&1.0f64);
791 assert!((stats.harmonic_mean() - 2.0).abs() < 1e-10);
793 }
794}