use crate::error::{PeacoQCError, Result};
pub const MAD_SCALE_FACTOR: f64 = 1.4826;
pub fn median_mad(data: &[f64]) -> Result<(f64, f64)> {
if data.is_empty() {
return Err(PeacoQCError::StatsError("Empty data".to_string()));
}
let med = calc_median(data)?;
let abs_devs: Vec<f64> = data.iter().map(|&x| (x - med).abs()).collect();
let mad = calc_median(&abs_devs)?;
Ok((med, mad))
}
pub fn median_mad_scaled(data: &[f64]) -> Result<(f64, f64)> {
let (med, raw_mad) = median_mad(data)?;
Ok((med, raw_mad * MAD_SCALE_FACTOR))
}
pub fn mad_scaled(data: &[f64]) -> Result<f64> {
let (_, scaled_mad) = median_mad_scaled(data)?;
Ok(scaled_mad)
}
pub fn calc_median(data: &[f64]) -> Result<f64> {
if data.is_empty() {
return Err(PeacoQCError::StatsError("Empty data".to_string()));
}
let mut sorted = data.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let len = sorted.len();
let median_value = if len % 2 == 0 {
(sorted[len / 2 - 1] + sorted[len / 2]) / 2.0
} else {
sorted[len / 2]
};
Ok(median_value)
}
pub fn median(data: &[f64]) -> Result<f64> {
calc_median(data)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_median_odd() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = median(&data).unwrap();
assert_relative_eq!(result, 3.0);
}
#[test]
fn test_median_even() {
let data = vec![1.0, 2.0, 3.0, 4.0];
let result = median(&data).unwrap();
assert_relative_eq!(result, 2.5);
}
#[test]
fn test_median_mad_raw() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 100.0]; let (med, mad) = median_mad(&data).unwrap();
assert_relative_eq!(med, 3.5);
assert!(mad > 0.0);
}
#[test]
fn test_median_mad_scaled() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let (med, raw_mad) = median_mad(&data).unwrap();
let (med2, scaled_mad) = median_mad_scaled(&data).unwrap();
assert_relative_eq!(med, med2);
assert_relative_eq!(scaled_mad, raw_mad * MAD_SCALE_FACTOR);
}
#[test]
fn test_mad_scale_factor() {
let data = vec![-2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0];
let (_, scaled_mad) = median_mad_scaled(&data).unwrap();
assert!(
(scaled_mad - 1.4826).abs() < 0.01,
"scaled_mad = {}",
scaled_mad
);
}
#[test]
fn test_mad_scaled_function() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let (_, expected) = median_mad_scaled(&data).unwrap();
let actual = mad_scaled(&data).unwrap();
assert_relative_eq!(actual, expected);
}
}