use num_traits::{Num, NumCast};
pub fn quantile<T>(data: &[T], q: f64) -> f64
where
T: Num + NumCast + Copy,
{
let n = data.len();
if n == 0 {
panic!("Input data slice must not be empty");
}
if q < 0.0 || q > 1.0 {
panic!("Quantile q must be in the range [0.0, 1.0]");
}
let index = q * ((n - 1) as f64);
let lower_index = index.floor() as usize;
let upper_index = index.ceil() as usize;
let fraction = index - lower_index as f64;
let lower_value = data[lower_index].to_f64().unwrap();
let upper_value = data[upper_index].to_f64().unwrap();
lower_value + fraction * (upper_value - lower_value)
}
#[cfg(test)]
mod tests {
use super::quantile;
#[test]
#[should_panic(expected = "Input data slice must not be empty")]
fn calculate_quantile_panics_on_empty_input() {
let data: Vec<i32> = vec![];
quantile(&data, 0.5);
}
#[test]
#[should_panic(expected = "Quantile q must be in the range [0.0, 1.0]")]
fn calculate_quantile_panics_on_negative_q() {
let data: Vec<i32> = vec![1];
quantile(&data, -0.1);
}
#[test]
#[should_panic(expected = "Quantile q must be in the range [0.0, 1.0]")]
fn calculate_quantile_panics_on_greater_than_one_q() {
let data: Vec<i32> = vec![1];
quantile(&data, 1.1);
}
#[test]
fn calculate_quantile_works() {
let data = vec![1, 2, 3, 4, 5];
let q0 = quantile(&data, 0.0);
assert_eq!(q0, 1.0);
let q25 = quantile(&data, 0.25);
assert_eq!(q25, 2.0);
let q50 = quantile(&data, 0.5);
assert_eq!(q50, 3.0);
let q75 = quantile(&data, 0.75);
assert_eq!(q75, 4.0);
let q100 = quantile(&data, 1.0);
assert_eq!(q100, 5.0);
assert_eq!(quantile(&data, 0.1), 1.4);
assert!(f64::abs(quantile(&data, 0.33) - 2.3200000000000003) < 1e-14);
}
#[test]
fn calculate_quantile_with_interpolation() {
let data = vec![10, 20, 30, 40, 50, 60];
let q25 = quantile(&data, 0.25);
assert_eq!(q25, 22.5);
let q75 = quantile(&data, 0.75);
assert_eq!(q75, 47.5);
}
#[test]
fn calculate_quantile_with_even_length() {
let data = vec![1, 2, 3, 4];
let median = quantile(&data, 0.5);
assert_eq!(median, 2.5);
}
#[test]
fn calculate_quantile_with_odd_length() {
let data = vec![1, 2, 3, 4, 5];
let median = quantile(&data, 0.5);
assert_eq!(median, 3.0);
}
#[test]
fn calculate_quantile_single_element() {
let data = vec![42];
assert_eq!(quantile(&data, 0.0), 42.0);
assert_eq!(quantile(&data, 0.25), 42.0);
assert_eq!(quantile(&data, 0.5), 42.0);
assert_eq!(quantile(&data, 0.75), 42.0);
assert_eq!(quantile(&data, 1.0), 42.0);
}
#[test]
fn calculate_quantile_two_elements() {
let data = vec![10, 20];
assert_eq!(quantile(&data, 0.0), 10.0);
assert_eq!(quantile(&data, 0.25), 12.5);
assert_eq!(quantile(&data, 0.5), 15.0);
assert_eq!(quantile(&data, 0.75), 17.5);
assert_eq!(quantile(&data, 1.0), 20.0);
}
#[test]
fn calculate_quantile_matches_numpy_example() {
let data = vec![2, 5, 8, 11, 14, 17, 20, 23];
assert_eq!(quantile(&data, 0.25), 7.25);
assert_eq!(quantile(&data, 0.5), 12.5);
assert_eq!(quantile(&data, 0.75), 17.75);
}
}