#[derive(Clone, Copy, Debug, Default)]
pub struct MeanSD {
size: f64,
mean: f64,
m2: f64,
}
impl MeanSD {
pub fn update(&mut self, x: f64) {
let delta = x - self.mean;
self.size += 1.0;
self.mean += delta / self.size;
self.m2 += delta * (x - self.mean);
}
#[must_use]
pub const fn size(&self) -> f64 {
self.size
}
#[must_use]
pub const fn mean(&self) -> f64 {
self.mean
}
#[must_use]
pub fn pstdev(&self) -> f64 {
if self.size > 1.0 {
let variance = self.m2 / self.size;
variance.sqrt()
} else {
0.0
}
}
#[must_use]
pub fn sstdev(&self) -> f64 {
if self.size > 1.0 {
let variance = self.m2 / (self.size - 1.0);
variance.sqrt()
} else {
0.0
}
}
#[must_use]
pub fn psem(&self) -> f64 {
self.pstdev() / self.size.sqrt()
}
#[must_use]
pub fn ssem(&self) -> f64 {
self.sstdev() / self.size.sqrt()
}
}
#[cfg(test)]
impl MeanSD {
pub(crate) fn tupled(&self) -> (f64, f64, f64, f64) {
(self.size, self.mean, self.sstdev(), self.pstdev())
}
}
#[allow(clippy::default_trait_access)]
#[cfg(test)]
mod tests {
use super::*;
use float_cmp::approx_eq;
#[test]
fn empty() {
let meansd = MeanSD::default();
let (n, mean, s_stdev, p_stdev) = meansd.tupled();
assert!(approx_eq!(f64, 0.0, n));
assert!(approx_eq!(f64, 0.0, mean));
assert!(approx_eq!(f64, 0.0, s_stdev));
assert!(approx_eq!(f64, 0.0, p_stdev));
}
#[test]
fn single() {
let mut meansd = MeanSD::default();
meansd.update(1.0);
let (n, mean, s_stdev, p_stdev) = meansd.tupled();
assert!(approx_eq!(f64, 1.0, n));
assert!(approx_eq!(f64, 1.0, mean));
assert!(approx_eq!(f64, 0.0, s_stdev));
assert!(approx_eq!(f64, 0.0, p_stdev));
}
#[test]
fn small_positive() {
let mut meansd = MeanSD::default();
meansd.update(1.0);
meansd.update(2.0);
meansd.update(3.0);
let (n, mean, sstdev, _) = meansd.tupled();
assert!(approx_eq!(f64, 3.0, n));
assert!(approx_eq!(f64, 2.0, mean));
assert!(approx_eq!(f64, 1.0, sstdev));
}
#[test]
fn small_negative() {
let mut meansd = MeanSD::default();
meansd.update(-1.0);
meansd.update(-2.0);
meansd.update(-3.0);
let (n, mean, sstdev, _) = meansd.tupled();
assert!(approx_eq!(f64, 3.0, n));
assert!(approx_eq!(f64, -2.0, mean));
assert!(approx_eq!(f64, 1.0, sstdev));
}
#[test]
fn small_mixed() {
let mut meansd = MeanSD::default();
meansd.update(-1.0);
meansd.update(0.0);
meansd.update(1.0);
let (n, mean, sstdev, _) = meansd.tupled();
assert!(approx_eq!(f64, 3.0, n));
assert!(approx_eq!(f64, 0.0, mean));
assert!(approx_eq!(f64, 1.0, sstdev));
}
#[test]
fn population() {
let mut meansd = MeanSD::default();
meansd.update(2.0);
meansd.update(4.0);
meansd.update(4.0);
meansd.update(4.0);
meansd.update(5.0);
meansd.update(5.0);
meansd.update(7.0);
meansd.update(9.0);
let (n, mean, _, pstdev) = meansd.tupled();
assert!(approx_eq!(f64, 8.0, n));
assert!(approx_eq!(f64, 5.0, mean));
assert!(approx_eq!(f64, 2.0, pstdev));
}
}