1/// The quartiles
2#[derive(Clone, Debug)]
3pub struct Quartiles {
4 lower_fence: f64,
5 lower: f64,
6 median: f64,
7 upper: f64,
8 upper_fence: f64,
9}
10
11impl Quartiles {
12 // Extract a value representing the `pct` percentile of a
13 // sorted `s`, using linear interpolation.
14 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 /// ```
46 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 /// ```
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 /// ```
96 pub fn median(&self) -> f64 {
97 self.median
98 }
99}
100
101#[cfg(test)]
102mod test {
103 use super::*;
104
105 #[test]
106 #[should_panic]
107 fn test_empty_input() {
108 let empty_array: [i32; 0] = [];
109 Quartiles::new(&empty_array);
110 }
111
112 #[test]
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