#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WelfordStats {
count: u64,
mean: f64,
m2: f64,
min: Option<f64>,
max: Option<f64>,
}
impl WelfordStats {
pub fn new() -> Self {
Self::default()
}
pub fn observe(&mut self, value: f64) {
self.count += 1;
let delta = value - self.mean;
self.mean += delta / self.count as f64;
let delta2 = value - self.mean;
self.m2 += delta * delta2;
self.min = Some(self.min.map_or(value, |m| m.min(value)));
self.max = Some(self.max.map_or(value, |m| m.max(value)));
}
pub fn count(&self) -> u64 {
self.count
}
pub fn mean(&self) -> f64 {
if self.count == 0 { 0.0 } else { self.mean }
}
pub fn variance_population(&self) -> f64 {
if self.count < 2 {
0.0
} else {
self.m2 / self.count as f64
}
}
pub fn variance_sample(&self) -> f64 {
if self.count < 2 {
0.0
} else {
self.m2 / (self.count - 1) as f64
}
}
pub fn std(&self) -> f64 {
self.variance_sample().sqrt()
}
pub fn min(&self) -> f64 {
self.min.unwrap_or(0.0)
}
pub fn max(&self) -> f64 {
self.max.unwrap_or(0.0)
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn clear(&mut self) {
*self = Self::default();
}
pub fn merge(&mut self, other: &WelfordStats) {
if other.count == 0 {
return;
}
if self.count == 0 {
*self = *other;
return;
}
let n_a = self.count as f64;
let n_b = other.count as f64;
let delta = other.mean - self.mean;
let total = n_a + n_b;
let new_mean = self.mean + delta * (n_b / total);
let new_m2 = self.m2 + other.m2 + delta * delta * n_a * n_b / total;
self.mean = new_mean;
self.m2 = new_m2;
self.count = total as u64;
match (self.min, other.min) {
(Some(a), Some(b)) => self.min = Some(a.min(b)),
(None, Some(b)) => self.min = Some(b),
_ => {}
}
match (self.max, other.max) {
(Some(a), Some(b)) => self.max = Some(a.max(b)),
(None, Some(b)) => self.max = Some(b),
_ => {}
}
}
}
impl crate::correlate::Mergeable for WelfordStats {
fn merge(&mut self, other: Self) {
WelfordStats::merge(self, &other);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_stats_safe_defaults() {
let s = WelfordStats::new();
assert_eq!(s.count(), 0);
assert_eq!(s.mean(), 0.0);
assert_eq!(s.std(), 0.0);
assert_eq!(s.min(), 0.0);
assert_eq!(s.max(), 0.0);
assert!(s.is_empty());
}
#[test]
fn single_observation_pins_min_max_mean() {
let mut s = WelfordStats::new();
s.observe(42.0);
assert_eq!(s.count(), 1);
assert_eq!(s.mean(), 42.0);
assert_eq!(s.min(), 42.0);
assert_eq!(s.max(), 42.0);
assert_eq!(s.std(), 0.0);
}
#[test]
fn mean_matches_arithmetic() {
let mut s = WelfordStats::new();
for v in [1.0, 2.0, 3.0, 4.0, 5.0] {
s.observe(v);
}
assert_eq!(s.count(), 5);
assert!((s.mean() - 3.0).abs() < 1e-12);
assert!((s.variance_sample() - 2.5).abs() < 1e-12);
assert!((s.std() - 2.5_f64.sqrt()).abs() < 1e-12);
assert_eq!(s.min(), 1.0);
assert_eq!(s.max(), 5.0);
}
#[test]
fn merge_matches_serial_observe() {
let mut serial = WelfordStats::new();
for v in [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] {
serial.observe(v);
}
let mut a = WelfordStats::new();
for v in [1.0, 2.0, 3.0, 4.0] {
a.observe(v);
}
let mut b = WelfordStats::new();
for v in [5.0, 6.0, 7.0, 8.0] {
b.observe(v);
}
a.merge(&b);
assert_eq!(a.count(), serial.count());
assert!((a.mean() - serial.mean()).abs() < 1e-12);
assert!((a.variance_sample() - serial.variance_sample()).abs() < 1e-12);
assert_eq!(a.min(), serial.min());
assert_eq!(a.max(), serial.max());
}
#[test]
fn merge_empty_self_inherits_other() {
let mut a = WelfordStats::new();
let mut b = WelfordStats::new();
b.observe(10.0);
b.observe(20.0);
a.merge(&b);
assert_eq!(a.count(), 2);
assert_eq!(a.mean(), 15.0);
assert_eq!(a.min(), 10.0);
assert_eq!(a.max(), 20.0);
}
#[test]
fn merge_empty_other_no_op() {
let mut a = WelfordStats::new();
a.observe(7.0);
let b = WelfordStats::new();
a.merge(&b);
assert_eq!(a.count(), 1);
assert_eq!(a.mean(), 7.0);
}
#[test]
fn clear_resets_to_default() {
let mut s = WelfordStats::new();
s.observe(1.0);
s.observe(2.0);
s.clear();
assert!(s.is_empty());
assert_eq!(s.mean(), 0.0);
}
#[test]
fn population_vs_sample_variance() {
let mut s = WelfordStats::new();
for v in [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0] {
s.observe(v);
}
assert!((s.variance_population() - 4.0).abs() < 1e-12);
assert!((s.variance_sample() - 32.0 / 7.0).abs() < 1e-12);
}
}