#[inline]
pub fn percentile_rank(values: &[f64], target: f64) -> f64 {
if values.is_empty() {
return 0.0;
}
let n = values.len();
let mut count_below = 0;
let mut count_equal = 0;
for &val in values {
if val < target {
count_below += 1;
} else if (val - target).abs() < f64::EPSILON {
count_equal += 1;
}
}
((count_below as f64 + 0.5 * count_equal as f64) / n as f64) * 100.0
}
pub fn quickselect_nth(values: &mut [f64], k: usize) -> f64 {
if values.is_empty() {
return 0.0;
}
if k >= values.len() {
return *values.last().unwrap();
}
quickselect_nth_impl(values, 0, values.len() - 1, k)
}
fn quickselect_nth_impl(values: &mut [f64], left: usize, right: usize, k: usize) -> f64 {
if left == right {
return values[left];
}
let pivot_index = partition(values, left, right);
if k == pivot_index {
values[k]
} else if k < pivot_index {
quickselect_nth_impl(values, left, pivot_index.saturating_sub(1), k)
} else {
quickselect_nth_impl(values, pivot_index + 1, right, k)
}
}
fn partition(values: &mut [f64], left: usize, right: usize) -> usize {
let pivot = values[right];
let mut i = left;
for j in left..right {
if values[j] <= pivot {
values.swap(i, j);
i += 1;
}
}
values.swap(i, right);
i
}
#[inline]
pub fn median(values: &mut [f64]) -> f64 {
if values.is_empty() {
return 0.0;
}
quickselect_nth(values, values.len() / 2)
}
pub fn quartiles(values: &mut [f64]) -> (f64, f64, f64) {
if values.is_empty() {
return (0.0, 0.0, 0.0);
}
let n = values.len();
(
quickselect_nth(values, n / 4),
quickselect_nth(values, n / 2),
quickselect_nth(values, 3 * n / 4),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_percentile_rank_basic() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let rank1 = percentile_rank(&values, 1.0);
assert!((rank1 - 10.0).abs() < 5.0, "rank1 = {}", rank1);
let rank3 = percentile_rank(&values, 3.0);
assert!((rank3 - 50.0).abs() < 5.0, "rank3 = {}", rank3);
let rank5 = percentile_rank(&values, 5.0);
assert!((rank5 - 90.0).abs() < 5.0, "rank5 = {}", rank5);
}
#[test]
fn test_percentile_rank_duplicates() {
let values = vec![1.0, 2.0, 2.0, 2.0, 5.0];
let rank = percentile_rank(&values, 2.0);
assert!(rank > 20.0 && rank < 80.0, "rank = {}", rank);
}
#[test]
fn test_percentile_rank_empty() {
let values: Vec<f64> = vec![];
assert_eq!(percentile_rank(&values, 1.0), 0.0);
}
#[test]
fn test_percentile_rank_single() {
let values = vec![42.0];
assert!((percentile_rank(&values, 42.0) - 50.0).abs() < 1.0);
}
#[test]
fn test_percentile_rank_extremes() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!(percentile_rank(&values, 0.0) < 10.0);
assert!(percentile_rank(&values, 10.0) > 90.0);
}
#[test]
fn test_quickselect_median() {
let mut values = vec![5.0, 1.0, 3.0, 2.0, 4.0];
assert_eq!(quickselect_nth(&mut values, 2), 3.0);
}
#[test]
fn test_quickselect_first() {
let mut values = vec![5.0, 1.0, 3.0, 2.0, 4.0];
assert_eq!(quickselect_nth(&mut values, 0), 1.0);
}
#[test]
fn test_quickselect_last() {
let mut values = vec![5.0, 1.0, 3.0, 2.0, 4.0];
assert_eq!(quickselect_nth(&mut values, 4), 5.0);
}
#[test]
fn test_median_odd_length() {
let mut values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(median(&mut values), 3.0);
}
#[test]
fn test_median_even_length() {
let mut values = vec![1.0, 2.0, 3.0, 4.0];
let med = median(&mut values);
assert!(med >= 2.0 && med <= 3.0);
}
#[test]
fn test_median_empty() {
let mut values: Vec<f64> = vec![];
assert_eq!(median(&mut values), 0.0);
}
#[test]
fn test_quartiles_basic() {
let mut values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let (q1, q2, q3) = quartiles(&mut values);
assert!(q1 >= 1.0 && q1 <= 3.0, "q1 = {}", q1);
assert!(q2 >= 3.0 && q2 <= 6.0, "q2 = {}", q2);
assert!(q3 >= 5.0 && q3 <= 8.0, "q3 = {}", q3);
assert!(q1 < q2);
assert!(q2 < q3);
}
#[test]
fn test_quartiles_small() {
let mut values = vec![1.0, 2.0, 3.0];
let (q1, q2, q3) = quartiles(&mut values);
assert!(q1 <= q2);
assert!(q2 <= q3);
}
#[test]
fn test_quartiles_empty() {
let mut values: Vec<f64> = vec![];
let (q1, q2, q3) = quartiles(&mut values);
assert_eq!((q1, q2, q3), (0.0, 0.0, 0.0));
}
#[test]
fn test_percentile_rank_large_dataset() {
let values: Vec<f64> = (0..100).map(|x| x as f64).collect();
assert!((percentile_rank(&values, 0.0) - 0.5).abs() < 2.0);
assert!((percentile_rank(&values, 25.0) - 25.5).abs() < 2.0);
assert!((percentile_rank(&values, 50.0) - 50.5).abs() < 2.0);
assert!((percentile_rank(&values, 75.0) - 75.5).abs() < 2.0);
assert!((percentile_rank(&values, 99.0) - 99.5).abs() < 2.0);
}
}