use std::cmp::Ordering;
use ndarray::{Array, ArrayBase, ArrayD, ArrayView1, AsArray, Axis, Dimension, IxDyn, ViewRepr};
use crate::error::ImgalError;
use crate::traits::numeric::AsNumeric;
pub fn linear_percentile<'a, T, A, D>(
data: A,
percentile: f64,
axis: Option<usize>,
epsilon: Option<f64>,
) -> Result<ArrayD<f64>, ImgalError>
where
A: AsArray<'a, T, D>,
D: Dimension,
T: 'a + AsNumeric,
{
let view: ArrayBase<ViewRepr<&'a T>, D> = data.into();
if view.is_empty() {
return Err(ImgalError::InvalidParameterEmptyArray { param_name: "data" });
}
let per_arr = match axis {
Some(ax) => {
if ax >= view.ndim() {
return Err(ImgalError::InvalidAxis {
axis_idx: ax,
dim_len: view.ndim(),
});
}
let mut shape = view.shape().to_vec();
shape.remove(ax);
let mut arr = ArrayD::<f64>::zeros(IxDyn(&shape));
let lanes = view.lanes(Axis(ax));
lanes.into_iter().zip(arr.iter_mut()).for_each(|(ln, pr)| {
*pr = linear_percentile_1d(ln, percentile, epsilon);
});
arr
}
None => {
let val_arr = view.to_owned().into_flat();
let per = linear_percentile_1d(val_arr.view(), percentile, epsilon);
Array::from_vec(vec![per]).into_dyn()
}
};
Ok(per_arr)
}
fn linear_percentile_1d<T>(data: ArrayView1<T>, percentile: f64, epsilon: Option<f64>) -> f64
where
T: AsNumeric,
{
let epsilon = epsilon.unwrap_or(1e-12);
let p_clamp = percentile.clamp(0.0, 100.0);
let mut val_arr = data.to_vec();
let p = p_clamp / 100.0;
let h = (val_arr.len() as f64 - 1.0) * p;
let j = h.floor() as usize;
let gamma = h - j as f64;
if gamma.abs() < epsilon {
val_arr.select_nth_unstable_by(j, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));
return val_arr[j].to_f64();
}
val_arr.select_nth_unstable_by(j, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));
let v_j = val_arr[j].to_f64();
val_arr.select_nth_unstable_by(j + 1, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));
let v_j1 = val_arr[j + 1].to_f64();
(1.0 - gamma) * v_j + gamma * v_j1
}