use crate::accum::moments::{comoment_pairs, CoMoment, Moments, Variance};
use crate::accum::{from_slice, quantile_sorted, Accumulator};
use crate::accum::simple::{Count, MinMax, Sum};
use crate::error::StatError;
use crate::nan::{clean, NanPolicy};
fn omit(xs: &[f64]) -> Result<Vec<f64>, StatError> { clean(xs, NanPolicy::Omit) }
pub fn count(xs: &[f64]) -> usize {
let c: Count = from_slice(xs);
c.finalize() as usize
}
pub fn sum(xs: &[f64]) -> f64 {
let s: Sum = from_slice(xs);
s.finalize()
}
pub fn min(xs: &[f64]) -> Result<f64, StatError> {
let mm: MinMax = from_slice(xs);
mm.finalize().map(|(lo, _)| lo).ok_or(StatError::AllNaN)
}
pub fn max(xs: &[f64]) -> Result<f64, StatError> {
let mm: MinMax = from_slice(xs);
mm.finalize().map(|(_, hi)| hi).ok_or(StatError::AllNaN)
}
pub fn median(xs: &[f64]) -> Result<f64, StatError> {
let mut v = omit(xs)?; v.sort_by(f64::total_cmp); Ok(quantile_sorted(&v, 0.50)) }
pub fn range(xs: &[f64]) -> Result<f64, StatError> {
let mm: MinMax = from_slice(xs);
mm.finalize().map(|(lo, hi)| hi - lo).ok_or(StatError::AllNaN)
}
pub fn mean(xs: &[f64]) -> Result<f64, StatError> {
let v = omit(xs)?;
Ok(v.iter().sum::<f64>() / v.len() as f64)
}
fn variance_of(xs: &[f64]) -> Result<Variance, StatError> {
let v = omit(xs)?;
if v.len() < 2 { return Err(StatError::TooFewObservations { needed: 2, got: v.len() }); }
Ok(Variance::from_slice_two_pass(&v)) }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ddof {
Sample,
Population,
}
pub fn var(xs: &[f64], ddof: Ddof) -> Result<f64, StatError> {
let v = variance_of(xs)?;
Ok(match ddof {
Ddof::Sample => v.var_sample(),
Ddof::Population => v.var_pop(),
})
}
pub fn sd(xs: &[f64], ddof: Ddof) -> Result<f64, StatError> { Ok(var(xs, ddof)?.sqrt()) }
pub fn skewness(xs: &[f64]) -> Result<f64, StatError> {
let v = omit(xs)?;
if v.len() < 2 { return Err(StatError::TooFewObservations { needed: 2, got: v.len() }); }
let m: Moments = from_slice(&v);
Ok(m.skewness())
}
pub fn kurtosis(xs: &[f64]) -> Result<f64, StatError> {
let v = omit(xs)?;
if v.len() < 2 { return Err(StatError::TooFewObservations { needed: 2, got: v.len() }); }
let m: Moments = from_slice(&v);
Ok(m.kurtosis_excess())
}
fn comoment_of(a: &[f64], b: &[f64]) -> Result<CoMoment, StatError> {
let c = comoment_pairs(a, b)?;
if c.count() < 2 { return Err(StatError::TooFewObservations { needed: 2, got: c.count() as usize }); }
Ok(c)
}
pub fn cov(a: &[f64], b: &[f64]) -> Result<f64, StatError> { Ok(comoment_of(a, b)?.covariance_sample()) }
pub fn pearson(a: &[f64], b: &[f64]) -> Result<f64, StatError> { Ok(comoment_of(a, b)?.pearson()) }
#[derive(Debug, Clone, PartialEq)]
pub struct Describe {
pub n: usize,
pub min: f64,
pub q1: f64,
pub median: f64,
pub q3: f64,
pub max: f64,
pub mean: f64,
pub sd: f64,
pub skewness: f64,
pub kurtosis: f64,
}
pub fn describe(xs: &[f64]) -> Result<Describe, StatError> {
let mut v = omit(xs)?;
if v.len() < 2 { return Err(StatError::TooFewObservations { needed: 2, got: v.len() }); }
v.sort_by(f64::total_cmp); let m: Moments = from_slice(&v);
Ok(Describe {
n: v.len(),
min: v[0],
q1: quantile_sorted(&v, 0.25),
median: quantile_sorted(&v, 0.50),
q3: quantile_sorted(&v, 0.75),
max: v[v.len() - 1],
mean: m.mean(),
sd: m.var_sample().sqrt(),
skewness: m.skewness(),
kurtosis: m.kurtosis_excess(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mean_omits_nan() {
assert!((mean(&[1.0, f64::NAN, 3.0]).unwrap() - 2.0).abs() < 1e-15);
}
#[test]
fn mean_empty_errors() {
assert_eq!(mean(&[]), Err(crate::StatError::EmptyInput));
}
#[test]
fn sd_known() {
assert!((sd(&[2.,4.,4.,4.,5.,5.,7.,9.], Ddof::Sample).unwrap() - (32.0_f64/7.0).sqrt()).abs() < 1e-13);
}
#[test]
fn var_needs_two() {
assert_eq!(var(&[1.0], Ddof::Sample), Err(crate::StatError::TooFewObservations { needed: 2, got: 1 }));
}
#[test]
fn pearson_mismatch_errors() {
assert_eq!(pearson(&[1.,2.], &[1.,2.,3.]),
Err(crate::StatError::MismatchedLengths { a: 2, b: 3 }));
}
#[test]
fn pearson_known() {
let r = pearson(&[1., 2., 3., 4., 5.], &[2., 1., 4., 3., 6.]).unwrap();
assert!((r - 0.8219949365267865).abs() < 1e-13, "r {r}");
}
#[test]
fn covariance_known() {
assert!((cov(&[1., 2., 3.], &[2., 4., 6.]).unwrap() - 2.0).abs() < 1e-13);
}
#[test]
fn range_known() {
assert!((range(&[1., 3., 2.]).unwrap() - 2.0).abs() < 1e-13);
}
#[test]
fn median_even_n_interpolates() {
assert!((median(&[3., 1., 4., 1., 5., 9.]).unwrap() - 3.5).abs() < 1e-12);
}
#[test]
fn median_odd_n_is_middle() {
assert!((median(&[1., 2., 3.]).unwrap() - 2.0).abs() < 1e-12);
}
#[test]
fn median_omits_nan() {
assert!((median(&[1., f64::NAN, 3.]).unwrap() - 2.0).abs() < 1e-12);
}
#[test]
fn median_empty_errors() {
assert_eq!(median(&[]), Err(crate::StatError::EmptyInput));
}
#[test]
fn median_all_nan_errors() {
assert_eq!(median(&[f64::NAN]), Err(crate::StatError::AllNaN));
}
}
#[cfg(test)]
mod describe_tests {
use super::*;
#[test]
fn describe_five_number() {
let d = describe(&[1.,2.,3.,4.,5.,6.,7.,8.,9.]).unwrap();
assert_eq!(d.n, 9);
assert_eq!(d.min, 1.0);
assert_eq!(d.max, 9.0);
assert!((d.q1 - 3.0).abs() < 1e-12);
assert!((d.median - 5.0).abs() < 1e-12);
assert!((d.q3 - 7.0).abs() < 1e-12);
assert!((d.mean - 5.0).abs() < 1e-12);
assert!((d.sd - 2.7386127875258306).abs() < 1e-12, "sd {}", d.sd); assert!(d.skewness.abs() < 1e-12, "skew {}", d.skewness); assert!((d.kurtosis - (-1.23)).abs() < 1e-9, "kurt {}", d.kurtosis); }
#[test]
fn describe_empty_errors() {
assert_eq!(describe(&[]), Err(crate::StatError::EmptyInput));
}
}