#[must_use]
pub fn mean(values: &[f64]) -> Option<f64> {
if values.is_empty() {
return None;
}
Some(values.iter().sum::<f64>() / values.len() as f64)
}
#[must_use]
pub fn median(values: &[f64]) -> Option<f64> {
if values.is_empty() {
return None;
}
let mut sorted = values.to_vec();
sorted.sort_by(f64::total_cmp);
let mid = sorted.len() / 2;
if sorted.len().is_multiple_of(2) {
Some((sorted[mid - 1] + sorted[mid]) / 2.0)
} else {
Some(sorted[mid])
}
}
#[must_use]
pub fn std_dev(values: &[f64]) -> Option<f64> {
let avg = mean(values)?;
let variance = values.iter().map(|v| (v - avg).powi(2)).sum::<f64>() / values.len() as f64;
Some(variance.sqrt())
}
#[must_use]
pub fn trimmed(values: &[f64], fraction: f64) -> Vec<f64> {
if values.is_empty() {
return Vec::new();
}
let fraction = fraction.clamp(0.0, 0.25);
let mut sorted = values.to_vec();
sorted.sort_by(f64::total_cmp);
let cut = ((sorted.len() as f64) * fraction).floor() as usize;
let end = sorted.len() - cut;
if cut >= end {
return vec![sorted[sorted.len() / 2]];
}
sorted[cut..end].to_vec()
}
#[must_use]
pub fn successive_diff_mean(values: &[f64]) -> Option<f64> {
if values.len() < 2 {
return None;
}
let sum: f64 = values.windows(2).map(|w| (w[1] - w[0]).abs()).sum();
Some(sum / (values.len() - 1) as f64)
}