use crate::array::Array;
use crate::error::{NumRs2Error, Result};
use num_traits::{Float, NumCast};
pub fn quantile<T: Float + Clone + NumCast + std::fmt::Display + Send + Sync>(
a: &Array<T>,
q: &Array<T>,
method: Option<&str>,
) -> Result<Array<T>> {
let method_str = method.unwrap_or("linear");
let data = a.to_vec();
if data.is_empty() {
return Err(NumRs2Error::InvalidOperation(
"Cannot compute quantiles of an empty array".to_string(),
));
}
let q_data = q.to_vec();
let mut result = Vec::with_capacity(q_data.len());
let mut sorted_data = data.clone();
sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = sorted_data.len();
for &q_val in &q_data {
if q_val < T::zero() || q_val > T::one() {
return Err(NumRs2Error::InvalidOperation(format!(
"Quantile value {} out of bounds [0, 1]",
q_val
)));
}
let idx_float = q_val * T::from(n - 1).expect("n-1 should be representable");
let idx_lower = idx_float.floor();
let idx_upper = idx_float.ceil();
let idx_lower_usize = idx_lower
.to_usize()
.expect("lower index should be convertible");
let idx_upper_usize = idx_upper
.to_usize()
.expect("upper index should be convertible");
let quantile = match method_str {
"linear" => {
if idx_lower == idx_upper {
sorted_data[idx_lower_usize]
} else {
let fraction = idx_float - idx_lower;
let lower_val = sorted_data[idx_lower_usize];
let upper_val = sorted_data[idx_upper_usize];
lower_val + fraction * (upper_val - lower_val)
}
},
"lower" => sorted_data[idx_lower_usize],
"higher" => sorted_data[idx_upper_usize],
"nearest" => {
if idx_float - idx_lower < idx_upper - idx_float {
sorted_data[idx_lower_usize]
} else {
sorted_data[idx_upper_usize]
}
},
"midpoint" => {
if idx_lower == idx_upper {
sorted_data[idx_lower_usize]
} else {
let lower_val = sorted_data[idx_lower_usize];
let upper_val = sorted_data[idx_upper_usize];
(lower_val + upper_val) / T::from(2.0).expect("2.0 should be representable")
}
},
_ => return Err(NumRs2Error::InvalidOperation(
format!("Invalid method '{}'. Must be one of 'linear', 'lower', 'higher', 'nearest', 'midpoint'", method_str)
))
};
result.push(quantile);
}
Ok(Array::from_vec(result))
}
pub fn percentile<T: Float + Clone + NumCast + std::fmt::Display + Send + Sync>(
a: &Array<T>,
q: &Array<T>,
method: Option<&str>,
) -> Result<Array<T>> {
let quantiles = q.map(|x| x / T::from(100.0).expect("100.0 should be representable"));
quantile(a, &quantiles, method)
}