use crate::error::{Result, StatError};
pub fn mean(data: &[f64]) -> Result<f64> {
if data.is_empty() {
return Err(StatError::EmptyData);
}
let sum: f64 = data.iter().sum();
Ok(sum / data.len() as f64)
}
pub fn stable_mean(data: &[f64]) -> Result<f64> {
if data.is_empty() {
return Err(StatError::EmptyData);
}
let sum: f64 = data.iter().enumerate().fold(0_f64, |mean_km1, (k, xk)| {
mean_km1 + (xk - mean_km1) / (k + 1) as f64
});
Ok(sum)
}
pub fn variance(data: &[f64]) -> Result<f64> {
let n = data.len();
if n < 2 {
return Err(StatError::InsufficientData { needed: 2, got: n });
}
let mean_val = mean(data)?;
let sum_sq: f64 = data.iter().map(|x| (x - mean_val).powi(2)).sum();
Ok(sum_sq / (n - 1) as f64)
}
pub fn stable_variance(data: &[f64]) -> Result<f64> {
let n = data.len();
if n < 2 {
return Err(StatError::InsufficientData { needed: 2, got: n });
}
let (_mean, snd_moment): (f64, f64) =
data.iter()
.enumerate()
.fold((0_f64, 0_f64), |(mean, snd_moment), (count, xk)| {
let delta = xk - mean;
let mean = mean + delta / (count + 1) as f64;
let delta2 = xk - mean;
let snd_moment = snd_moment + delta * delta2;
(mean, snd_moment)
});
Ok(snd_moment / (n as f64 - 1.0_f64))
}
pub fn median(data: &[f64]) -> Result<f64> {
if data.is_empty() {
return Err(StatError::EmptyData);
}
let mut sorted = data.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let n = sorted.len();
if n % 2 == 1 {
Ok(sorted[n / 2])
} else {
Ok((sorted[n / 2 - 1] + sorted[n / 2]) / 2.0)
}
}
pub fn trimmed_mean(data: &[f64], trim: f64) -> Result<f64> {
if !(0.0..0.5).contains(&trim) {
return Err(StatError::InvalidParameter(format!(
"trim must be in [0, 0.5), got {}",
trim
)));
}
if data.is_empty() {
return Err(StatError::EmptyData);
}
let mut sorted = data.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let n = sorted.len();
let k = (n as f64 * trim).floor() as usize;
let trimmed = &sorted[k..n - k];
if trimmed.is_empty() {
return Err(StatError::EmptyData);
}
mean(trimmed)
}
pub fn std_dev(data: &[f64]) -> Result<f64> {
Ok(variance(data)?.sqrt())
}
pub fn skewness(data: &[f64]) -> Result<f64> {
let n = data.len();
if n < 3 {
return Err(StatError::InsufficientData { needed: 3, got: n });
}
let mean_val = mean(data)?;
let n_f = n as f64;
let ss2: f64 = data.iter().map(|x| (x - mean_val).powi(2)).sum();
let ss3: f64 = data.iter().map(|x| (x - mean_val).powi(3)).sum();
if ss2 < 1e-28 {
return Ok(0.0);
}
let y = n_f.sqrt() * ss3 / ss2.powf(1.5);
let g1 = y * (n_f * (n_f - 1.0)).sqrt() / (n_f - 2.0);
Ok(g1)
}
pub fn kurtosis(data: &[f64]) -> Result<f64> {
let n = data.len();
if n < 4 {
return Err(StatError::InsufficientData { needed: 4, got: n });
}
let mean_val = mean(data)?;
let n_f = n as f64;
let ss2: f64 = data.iter().map(|x| (x - mean_val).powi(2)).sum();
let ss4: f64 = data.iter().map(|x| (x - mean_val).powi(4)).sum();
if ss2 < 1e-28 {
return Ok(0.0);
}
let r = n_f * ss4 / (ss2 * ss2);
let g2 = ((n_f + 1.0) * (r - 3.0) + 6.0) * (n_f - 1.0) / ((n_f - 2.0) * (n_f - 3.0));
Ok(g2)
}