use core::cmp::Ordering::Equal;
use num_traits::Float;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum ScalingMethod {
MAR,
#[default]
MAD,
Mean,
}
impl ScalingMethod {
pub fn compute<T: Float>(&self, vals: &mut [T]) -> T {
match self {
Self::MAR => self.compute_mar(vals),
Self::MAD => self.compute_mad(vals),
Self::Mean => self.compute_mean(vals),
}
}
#[inline]
fn compute_mean<T: Float>(&self, vals: &mut [T]) -> T {
if vals.is_empty() {
return T::zero();
}
let n = T::from(vals.len()).unwrap_or(T::one());
let mut sum = T::zero();
for val in vals.iter() {
sum = sum + val.abs();
}
sum / n
}
#[inline]
fn compute_mad<T: Float>(&self, vals: &mut [T]) -> T {
if vals.is_empty() {
return T::zero();
}
let median: T = Self::median_inplace(vals);
for val in vals.iter_mut() {
*val = (*val - median).abs();
}
Self::median_inplace(vals)
}
#[inline]
fn compute_mar<T: Float>(&self, vals: &mut [T]) -> T {
if vals.is_empty() {
return T::zero();
}
for val in vals.iter_mut() {
*val = val.abs();
}
Self::median_inplace(vals)
}
#[inline]
fn median_inplace<T: Float>(vals: &mut [T]) -> T {
let n = vals.len();
if n == 0 {
return T::zero();
}
let mid = n / 2;
if n.is_multiple_of(2) {
vals.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap_or(Equal));
let upper = vals[mid];
let mut lower = vals[0];
let mut i = 1;
while i < mid {
if vals[i] > lower {
lower = vals[i];
}
i += 1;
}
(lower + upper) / T::from(2.0).unwrap_or(T::one() + T::one())
} else {
vals.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap_or(Equal));
vals[mid]
}
}
}