use super::{percentile_sorted, sorted_finite, StatsError};
#[derive(Debug, Clone, PartialEq)]
pub struct Quartiles {
pub q1: f64,
pub median: f64,
pub q3: f64,
pub iqr: f64,
pub lower_whisker: f64,
pub upper_whisker: f64,
pub min: f64,
pub max: f64,
pub outliers: Vec<f64>,
}
impl Quartiles {
pub fn lower_fence(&self) -> f64 {
self.q1 - 1.5 * self.iqr
}
pub fn upper_fence(&self) -> f64 {
self.q3 + 1.5 * self.iqr
}
}
pub fn quartiles(data: &[f64]) -> Result<Quartiles, StatsError> {
let sorted = sorted_finite(data);
if sorted.is_empty() {
return Err(StatsError::EmptyInput);
}
let q1 = percentile_sorted(&sorted, 0.25);
let median = percentile_sorted(&sorted, 0.50);
let q3 = percentile_sorted(&sorted, 0.75);
let iqr = q3 - q1;
let lower_fence = q1 - 1.5 * iqr;
let upper_fence = q3 + 1.5 * iqr;
let lower_whisker = sorted
.iter()
.copied()
.find(|&x| x >= lower_fence)
.unwrap_or(sorted[0]);
let upper_whisker = sorted
.iter()
.rev()
.copied()
.find(|&x| x <= upper_fence)
.unwrap_or(sorted[sorted.len() - 1]);
let fences = lower_fence..=upper_fence;
let outliers: Vec<f64> = sorted
.iter()
.copied()
.filter(|x| !fences.contains(x))
.collect();
Ok(Quartiles {
q1,
median,
q3,
iqr,
lower_whisker,
upper_whisker,
min: sorted[0],
max: sorted[sorted.len() - 1],
outliers,
})
}