criterion/stats/univariate/outliers/
tukey.rs1use std::iter::IntoIterator;
41use std::ops::{Deref, Index};
42use std::slice;
43
44use crate::stats::float::Float;
45use crate::stats::univariate::Sample;
46
47use self::Label::*;
48
49#[derive(Clone, Copy)]
58pub struct LabeledSample<'a, A>
59where
60 A: Float,
61{
62 fences: (A, A, A, A),
63 sample: &'a Sample<A>,
64}
65
66impl<'a, A> LabeledSample<'a, A>
67where
68 A: Float,
69{
70 #[cfg_attr(feature = "cargo-clippy", allow(clippy::similar_names))]
74 pub fn count(&self) -> (usize, usize, usize, usize, usize) {
75 let (mut los, mut lom, mut noa, mut him, mut his) = (0, 0, 0, 0, 0);
76
77 for (_, label) in self {
78 match label {
79 LowSevere => {
80 los += 1;
81 }
82 LowMild => {
83 lom += 1;
84 }
85 NotAnOutlier => {
86 noa += 1;
87 }
88 HighMild => {
89 him += 1;
90 }
91 HighSevere => {
92 his += 1;
93 }
94 }
95 }
96
97 (los, lom, noa, him, his)
98 }
99
100 pub fn fences(&self) -> (A, A, A, A) {
102 self.fences
103 }
104
105 pub fn iter(&self) -> Iter<'a, A> {
107 Iter {
108 fences: self.fences,
109 iter: self.sample.iter(),
110 }
111 }
112}
113
114impl<'a, A> Deref for LabeledSample<'a, A>
115where
116 A: Float,
117{
118 type Target = Sample<A>;
119
120 fn deref(&self) -> &Sample<A> {
121 self.sample
122 }
123}
124
125impl<'a, A> Index<usize> for LabeledSample<'a, A>
127where
128 A: Float,
129{
130 type Output = Label;
131
132 #[cfg_attr(feature = "cargo-clippy", allow(clippy::similar_names))]
133 fn index(&self, i: usize) -> &Label {
134 static LOW_SEVERE: Label = LowSevere;
135 static LOW_MILD: Label = LowMild;
136 static HIGH_MILD: Label = HighMild;
137 static HIGH_SEVERE: Label = HighSevere;
138 static NOT_AN_OUTLIER: Label = NotAnOutlier;
139
140 let x = self.sample[i];
141 let (lost, lomt, himt, hist) = self.fences;
142
143 if x < lost {
144 &LOW_SEVERE
145 } else if x > hist {
146 &HIGH_SEVERE
147 } else if x < lomt {
148 &LOW_MILD
149 } else if x > himt {
150 &HIGH_MILD
151 } else {
152 &NOT_AN_OUTLIER
153 }
154 }
155}
156
157impl<'a, 'b, A> IntoIterator for &'b LabeledSample<'a, A>
158where
159 A: Float,
160{
161 type Item = (A, Label);
162 type IntoIter = Iter<'a, A>;
163
164 fn into_iter(self) -> Iter<'a, A> {
165 self.iter()
166 }
167}
168
169pub struct Iter<'a, A>
171where
172 A: Float,
173{
174 fences: (A, A, A, A),
175 iter: slice::Iter<'a, A>,
176}
177
178impl<'a, A> Iterator for Iter<'a, A>
179where
180 A: Float,
181{
182 type Item = (A, Label);
183
184 #[cfg_attr(feature = "cargo-clippy", allow(clippy::similar_names))]
185 fn next(&mut self) -> Option<(A, Label)> {
186 self.iter.next().map(|&x| {
187 let (lost, lomt, himt, hist) = self.fences;
188
189 let label = if x < lost {
190 LowSevere
191 } else if x > hist {
192 HighSevere
193 } else if x < lomt {
194 LowMild
195 } else if x > himt {
196 HighMild
197 } else {
198 NotAnOutlier
199 };
200
201 (x, label)
202 })
203 }
204
205 fn size_hint(&self) -> (usize, Option<usize>) {
206 self.iter.size_hint()
207 }
208}
209
210pub enum Label {
212 HighMild,
214 HighSevere,
216 LowMild,
218 LowSevere,
220 NotAnOutlier,
222}
223
224impl Label {
225 pub fn is_high(&self) -> bool {
227 matches!(*self, HighMild | HighSevere)
228 }
229
230 pub fn is_mild(&self) -> bool {
232 matches!(*self, HighMild | LowMild)
233 }
234
235 pub fn is_low(&self) -> bool {
237 matches!(*self, LowMild | LowSevere)
238 }
239
240 pub fn is_outlier(&self) -> bool {
242 matches!(*self, NotAnOutlier)
243 }
244
245 pub fn is_severe(&self) -> bool {
247 matches!(*self, HighSevere | LowSevere)
248 }
249}
250
251pub fn classify<A>(sample: &Sample<A>) -> LabeledSample<'_, A>
255where
256 A: Float,
257 usize: cast::From<A, Output = Result<usize, cast::Error>>,
258{
259 let (q1, _, q3) = sample.percentiles().quartiles();
260 let iqr = q3 - q1;
261
262 let k_m = A::cast(1.5_f32);
264 let k_s = A::cast(3);
266
267 LabeledSample {
268 fences: (
269 q1 - k_s * iqr,
270 q1 - k_m * iqr,
271 q3 + k_m * iqr,
272 q3 + k_s * iqr,
273 ),
274 sample,
275 }
276}