statrs 0.19.0

Statistical computing library for Rust
Documentation
use crate::statistics::*;
use core::borrow::Borrow;
use core::f64;
#[cfg(not(feature = "std"))]
use num_traits::Float as _;

impl<T> Statistics<f64> for T
where
    T: IntoIterator,
    T::Item: Borrow<f64>,
{
    fn min(self) -> f64 {
        let mut iter = self.into_iter();
        match iter.next() {
            None => f64::NAN,
            Some(x) => iter.map(|x| *x.borrow()).fold(*x.borrow(), |acc, x| {
                if x < acc || x.is_nan() { x } else { acc }
            }),
        }
    }

    fn max(self) -> f64 {
        let mut iter = self.into_iter();
        match iter.next() {
            None => f64::NAN,
            Some(x) => iter.map(|x| *x.borrow()).fold(*x.borrow(), |acc, x| {
                if x > acc || x.is_nan() { x } else { acc }
            }),
        }
    }

    fn abs_min(self) -> f64 {
        let mut iter = self.into_iter();
        match iter.next() {
            None => f64::NAN,
            Some(init) => iter
                .map(|x| x.borrow().abs())
                .fold(init.borrow().abs(), |acc, x| {
                    if x < acc || x.is_nan() { x } else { acc }
                }),
        }
    }

    fn abs_max(self) -> f64 {
        let mut iter = self.into_iter();
        match iter.next() {
            None => f64::NAN,
            Some(init) => iter
                .map(|x| x.borrow().abs())
                .fold(init.borrow().abs(), |acc, x| {
                    if x > acc || x.is_nan() { x } else { acc }
                }),
        }
    }

    fn mean(self) -> f64 {
        self.into_iter()
            .fold(OnlineMoments::<2>::default(), |acc, x| {
                acc.push(*x.borrow())
            })
            .mean()
            .unwrap_or(f64::NAN)
    }

    fn geometric_mean(self) -> f64 {
        let mut i = 0.0;
        let mut sum = 0.0;
        for x in self {
            i += 1.0;
            sum += x.borrow().ln();
        }
        if i > 0.0 { (sum / i).exp() } else { f64::NAN }
    }

    fn harmonic_mean(self) -> f64 {
        let mut i = 0.0;
        let mut sum = 0.0;
        for x in self {
            i += 1.0;

            let borrow = *x.borrow();
            if borrow < 0f64 {
                return f64::NAN;
            }
            sum += 1.0 / borrow;
        }
        if i > 0.0 { i / sum } else { f64::NAN }
    }

    fn variance(self) -> f64 {
        self.into_iter()
            .fold(OnlineMoments::<2>::default(), |acc, x| {
                acc.push(*x.borrow())
            })
            .variance()
            .unwrap_or(f64::NAN)
    }

    fn std_dev(self) -> f64 {
        self.variance().sqrt()
    }

    fn population_variance(self) -> f64 {
        self.into_iter()
            .fold(OnlineMoments::<2>::default(), |acc, x| {
                acc.push(*x.borrow())
            })
            .population_variance()
            .unwrap_or(f64::NAN)
    }

    fn population_std_dev(self) -> f64 {
        self.population_variance().sqrt()
    }

    fn covariance(self, other: Self) -> f64 {
        let mut n = 0.0;
        let mut mean1 = 0.0;
        let mut mean2 = 0.0;
        let mut comoment = 0.0;

        let mut iter = other.into_iter();
        for x in self {
            let borrow = *x.borrow();
            let borrow2 = match iter.next() {
                None => panic!("Iterators must have the same length"),
                Some(x) => *x.borrow(),
            };
            let old_mean2 = mean2;
            n += 1.0;
            mean1 += (borrow - mean1) / n;
            mean2 += (borrow2 - mean2) / n;
            comoment += (borrow - mean1) * (borrow2 - old_mean2);
        }
        if iter.next().is_some() {
            panic!("Iterators must have the same length");
        }

        if n > 1.0 {
            comoment / (n - 1.0)
        } else {
            f64::NAN
        }
    }

    fn population_covariance(self, other: Self) -> f64 {
        let mut n = 0.0;
        let mut mean1 = 0.0;
        let mut mean2 = 0.0;
        let mut comoment = 0.0;

        let mut iter = other.into_iter();
        for x in self {
            let borrow = *x.borrow();
            let borrow2 = match iter.next() {
                None => panic!("Iterators must have the same length"),
                Some(x) => *x.borrow(),
            };
            let old_mean2 = mean2;
            n += 1.0;
            mean1 += (borrow - mean1) / n;
            mean2 += (borrow2 - mean2) / n;
            comoment += (borrow - mean1) * (borrow2 - old_mean2);
        }
        if iter.next().is_some() {
            panic!("Iterators must have the same length")
        }
        if n > 0.0 { comoment / n } else { f64::NAN }
    }

    fn quadratic_mean(self) -> f64 {
        let mut i = 0.0;
        let mut mean = 0.0;
        for x in self {
            let borrow = *x.borrow();
            i += 1.0;
            mean += (borrow * borrow - mean) / i;
        }
        if i > 0.0 { mean.sqrt() } else { f64::NAN }
    }
}

#[rustfmt::skip]
#[cfg(test)]
mod tests {
    use core::f64::consts;
    use crate::generate::{InfinitePeriodic, InfiniteSinusoidal};
    use crate::prec;
    use crate::statistics::Statistics;

    #[test]
    fn test_empty_data_returns_nan() {
        let data = [0.0; 0];
        assert!(data.min().is_nan());
        assert!(data.max().is_nan());
        assert!(data.mean().is_nan());
        assert!(data.quadratic_mean().is_nan());
        assert!(data.variance().is_nan());
        assert!(data.population_variance().is_nan());
    }

    // TODO: test github issue 137 (Math.NET)

    #[test]
    fn test_large_samples() {
        let shorter = || InfinitePeriodic::default(4.0, 1.0).take(4*4096);
        let longer = || InfinitePeriodic::default(4.0, 1.0).take(4*32768);
        let s_mean = shorter().mean();
        let s_qmean = shorter().quadratic_mean();
        let l_mean = longer().mean();
        let l_qmean = longer().quadratic_mean();

        prec::assert_abs_diff_eq!(s_mean, 0.375, epsilon = 1e-14);
        prec::assert_abs_diff_eq!(l_mean, 0.375, epsilon = 1e-14);
        prec::assert_abs_diff_eq!(s_qmean, (0.21875f64).sqrt(), epsilon = 1e-14);
        prec::assert_abs_diff_eq!(l_qmean, (0.21875f64).sqrt(), epsilon = 1e-14);
    }

    #[test]
    fn test_quadratic_mean_of_sinusoidal() {
        let data = InfiniteSinusoidal::default(64.0, 16.0, 2.0).take(128);
        let qmean = data.quadratic_mean();

        prec::assert_abs_diff_eq!(qmean, 2.0 / consts::SQRT_2, epsilon = 1e-15);
    }
}