/// The quartiles #[derive(Clone, Debug)] pub struct Quartiles { lower_fence: f64, lower: f64, median: f64, upper: f64, upper_fence: f64, } impl Quartiles { // Extract a value representing the `pct` percentile of a // sorted `s`, using linear interpolation. fn percentile_of_sorted + Copy>(s: &[T], pct: f64) -> f64 { assert!(!s.is_empty()); if s.len() == 1 { return s[0].into(); } assert!(0_f64 <= pct); let hundred = 100_f64; assert!(pct <= hundred); if (pct - hundred).abs() < std::f64::EPSILON { return s[s.len() - 1].into(); } let length = (s.len() - 1) as f64; let rank = (pct / hundred) * length; let lower_rank = rank.floor(); let d = rank - lower_rank; let n = lower_rank as usize; let lo = s[n].into(); let hi = s[n + 1].into(); lo + (hi - lo) * d } /// Create a new quartiles struct with the values calculated from the argument. /// /// - `s`: The array of the original values /// - **returns** The newly created quartiles /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// assert_eq!(quartiles.median(), 37.5); /// ``` pub fn new + Copy + PartialOrd>(s: &[T]) -> Self { let mut s = s.to_owned(); s.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap()); let lower = Quartiles::percentile_of_sorted(&s, 25_f64); let median = Quartiles::percentile_of_sorted(&s, 50_f64); let upper = Quartiles::percentile_of_sorted(&s, 75_f64); let iqr = upper - lower; let lower_fence = lower - 1.5 * iqr; let upper_fence = upper + 1.5 * iqr; Self { lower_fence, lower, median, upper, upper_fence, } } /// Get the quartiles values. /// /// - **returns** The array [lower fence, lower quartile, median, upper quartile, upper fence] /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// let values = quartiles.values(); /// assert_eq!(values, [-9.0, 20.25, 37.5, 39.75, 69.0]); /// ``` pub fn values(&self) -> [f32; 5] { [ self.lower_fence as f32, self.lower as f32, self.median as f32, self.upper as f32, self.upper_fence as f32, ] } /// Get the quartiles median. /// /// - **returns** The median /// /// ```rust /// use plotters::prelude::*; /// /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); /// assert_eq!(quartiles.median(), 37.5); /// ``` pub fn median(&self) -> f64 { self.median } } #[cfg(test)] mod test { use super::*; #[test] #[should_panic] fn test_empty_input() { let empty_array: [i32; 0] = []; Quartiles::new(&empty_array); } #[test] fn test_low_inputs() { assert_eq!( Quartiles::new(&[15.0]).values(), [15.0, 15.0, 15.0, 15.0, 15.0] ); assert_eq!( Quartiles::new(&[10, 20]).values(), [5.0, 12.5, 15.0, 17.5, 25.0] ); assert_eq!( Quartiles::new(&[10, 20, 30]).values(), [0.0, 15.0, 20.0, 25.0, 40.0] ); } }