1 /// The quartiles 2 #[derive(Clone, Debug)] 3 pub struct Quartiles { 4 lower_fence: f64, 5 lower: f64, 6 median: f64, 7 upper: f64, 8 upper_fence: f64, 9 } 10 11 impl Quartiles { 12 // Extract a value representing the `pct` percentile of a 13 // sorted `s`, using linear interpolation. percentile_of_sorted<T: Into<f64> + Copy>(s: &[T], pct: f64) -> f6414 fn percentile_of_sorted<T: Into<f64> + Copy>(s: &[T], pct: f64) -> f64 { 15 assert!(!s.is_empty()); 16 if s.len() == 1 { 17 return s[0].into(); 18 } 19 assert!(0_f64 <= pct); 20 let hundred = 100_f64; 21 assert!(pct <= hundred); 22 if (pct - hundred).abs() < std::f64::EPSILON { 23 return s[s.len() - 1].into(); 24 } 25 let length = (s.len() - 1) as f64; 26 let rank = (pct / hundred) * length; 27 let lower_rank = rank.floor(); 28 let d = rank - lower_rank; 29 let n = lower_rank as usize; 30 let lo = s[n].into(); 31 let hi = s[n + 1].into(); 32 lo + (hi - lo) * d 33 } 34 35 /// Create a new quartiles struct with the values calculated from the argument. 36 /// 37 /// - `s`: The array of the original values 38 /// - **returns** The newly created quartiles 39 /// 40 /// ```rust 41 /// use plotters::prelude::*; 42 /// 43 /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); 44 /// assert_eq!(quartiles.median(), 37.5); 45 /// ``` new<T: Into<f64> + Copy + PartialOrd>(s: &[T]) -> Self46 pub fn new<T: Into<f64> + Copy + PartialOrd>(s: &[T]) -> Self { 47 let mut s = s.to_owned(); 48 s.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap()); 49 50 let lower = Quartiles::percentile_of_sorted(&s, 25_f64); 51 let median = Quartiles::percentile_of_sorted(&s, 50_f64); 52 let upper = Quartiles::percentile_of_sorted(&s, 75_f64); 53 let iqr = upper - lower; 54 let lower_fence = lower - 1.5 * iqr; 55 let upper_fence = upper + 1.5 * iqr; 56 Self { 57 lower_fence, 58 lower, 59 median, 60 upper, 61 upper_fence, 62 } 63 } 64 65 /// Get the quartiles values. 66 /// 67 /// - **returns** The array [lower fence, lower quartile, median, upper quartile, upper fence] 68 /// 69 /// ```rust 70 /// use plotters::prelude::*; 71 /// 72 /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); 73 /// let values = quartiles.values(); 74 /// assert_eq!(values, [-9.0, 20.25, 37.5, 39.75, 69.0]); 75 /// ``` values(&self) -> [f32; 5]76 pub fn values(&self) -> [f32; 5] { 77 [ 78 self.lower_fence as f32, 79 self.lower as f32, 80 self.median as f32, 81 self.upper as f32, 82 self.upper_fence as f32, 83 ] 84 } 85 86 /// Get the quartiles median. 87 /// 88 /// - **returns** The median 89 /// 90 /// ```rust 91 /// use plotters::prelude::*; 92 /// 93 /// let quartiles = Quartiles::new(&[7, 15, 36, 39, 40, 41]); 94 /// assert_eq!(quartiles.median(), 37.5); 95 /// ``` median(&self) -> f6496 pub fn median(&self) -> f64 { 97 self.median 98 } 99 } 100 101 #[cfg(test)] 102 mod test { 103 use super::*; 104 105 #[test] 106 #[should_panic] test_empty_input()107 fn test_empty_input() { 108 let empty_array: [i32; 0] = []; 109 Quartiles::new(&empty_array); 110 } 111 112 #[test] test_low_inputs()113 fn test_low_inputs() { 114 assert_eq!( 115 Quartiles::new(&[15.0]).values(), 116 [15.0, 15.0, 15.0, 15.0, 15.0] 117 ); 118 assert_eq!( 119 Quartiles::new(&[10, 20]).values(), 120 [5.0, 12.5, 15.0, 17.5, 25.0] 121 ); 122 assert_eq!( 123 Quartiles::new(&[10, 20, 30]).values(), 124 [0.0, 15.0, 20.0, 25.0, 40.0] 125 ); 126 } 127 } 128