1use crate::quantile::Quantile;
2use crate::sorted_window::SortedWindow;
3
4use crate::stats::Univariate;
5use num::{Float, FromPrimitive};
6use serde::{Deserialize, Serialize};
7use std::ops::{AddAssign, SubAssign};
8#[derive(Clone, Debug, Serialize, Deserialize)]
24pub struct IQR<F: Float + FromPrimitive + AddAssign + SubAssign> {
25 pub q_inf: Quantile<F>,
26 pub q_sup: Quantile<F>,
27}
28
29impl<F: Float + FromPrimitive + AddAssign + SubAssign> IQR<F> {
30 pub fn new(q_inf: F, q_sup: F) -> Result<Self, &'static str> {
31 if q_inf >= q_sup {
32 return Err("q_inf must be strictly less than q_sup");
33 }
34
35 Ok(Self {
36 q_inf: Quantile::new(q_inf).unwrap(),
37 q_sup: Quantile::new(q_sup).unwrap(),
38 })
39 }
40}
41
42impl<F> Default for IQR<F>
43where
44 F: Float + FromPrimitive + AddAssign + SubAssign,
45{
46 fn default() -> Self {
47 Self {
48 q_inf: Quantile::new(F::from_f64(0.25).unwrap()).unwrap(),
49 q_sup: Quantile::new(F::from_f64(0.75).unwrap()).unwrap(),
50 }
51 }
52}
53
54impl<F: Float + FromPrimitive + AddAssign + SubAssign> Univariate<F> for IQR<F> {
55 fn update(&mut self, x: F) {
56 self.q_inf.update(x);
57 self.q_sup.update(x);
58 }
59 fn get(&self) -> F {
60 self.q_sup.get() - self.q_inf.get()
61 }
62}
63
64#[derive(Serialize, Deserialize)]
84pub struct RollingIQR<F: Float + FromPrimitive + AddAssign + SubAssign> {
85 sorted_window: SortedWindow<F>,
86 q_inf: F,
87 q_sup: F,
88 window_size: usize,
89 lower_inf: usize,
90 higher_inf: usize,
91 frac_inf: F,
92 lower_sup: usize,
93 higher_sup: usize,
94 frac_sup: F,
95}
96
97impl<F: Float + FromPrimitive + AddAssign + SubAssign> RollingIQR<F> {
98 pub fn new(q_inf: F, q_sup: F, window_size: usize) -> Result<Self, &'static str> {
99 if F::from_f64(0.).unwrap() > q_inf && F::from_f64(1.).unwrap() < q_inf {
100 return Err("q_inf should be betweek 0 and 1");
101 }
102
103 if F::from_f64(0.).unwrap() > q_sup && F::from_f64(1.).unwrap() < q_sup {
104 return Err("q_sup should be betweek 0 and 1");
105 }
106 if q_inf >= q_sup {
107 return Err("q_inf must be strictly less than q_sup");
108 }
109
110 let idx_inf = q_inf * (F::from_usize(window_size).unwrap() - F::from_f64(1.).unwrap());
111 let lower_inf = idx_inf.floor().to_usize().unwrap();
112 let mut higher_inf = lower_inf + 1;
113 if higher_inf > window_size - 1 {
114 higher_inf = lower_inf.saturating_sub(1); }
116
117 let frac_inf = idx_inf - F::from_usize(lower_inf).unwrap();
118
119 let idx_sup = q_sup * (F::from_usize(window_size).unwrap() - F::from_f64(1.).unwrap());
120 let lower_sup = idx_sup.floor().to_usize().unwrap();
121 let mut higher_sup = lower_sup + 1;
122 if higher_sup > window_size - 1 {
123 higher_sup = lower_sup.saturating_sub(1); }
125
126 let frac_sup = idx_sup - F::from_usize(lower_sup).unwrap();
127 Ok(Self {
128 sorted_window: SortedWindow::new(window_size),
129 q_inf,
130 q_sup,
131 window_size,
132 lower_inf,
133 higher_inf,
134 frac_inf,
135 lower_sup,
136 higher_sup,
137 frac_sup,
138 })
139 }
140 fn prepare(&self, q: F, is_inf: bool) -> (usize, usize, F) {
141 if self.sorted_window.len() < self.window_size {
142 let idx =
143 q * (F::from_usize(self.sorted_window.len()).unwrap() - F::from_f64(1.).unwrap());
144 let lower = idx.floor().to_usize().unwrap();
145 let mut higher = lower + 1;
146 if higher > self.sorted_window.len() - 1 {
147 higher = self.sorted_window.len().saturating_sub(1); }
149
150 let frac = idx - F::from_usize(lower).unwrap();
151 return (lower, higher, frac);
152 }
153 if is_inf {
154 return (self.lower_inf, self.higher_inf, self.frac_inf);
155 }
156 (self.lower_sup, self.higher_sup, self.frac_sup)
157 }
158}
159
160impl<F: Float + FromPrimitive + AddAssign + SubAssign> Univariate<F> for RollingIQR<F> {
161 fn update(&mut self, x: F) {
162 self.sorted_window.push_back(x);
163 }
164 fn get(&self) -> F {
165 let (lower_inf, higher_inf, frac_inf) = self.prepare(self.q_inf, true);
166 let (lower_sup, higher_sup, frac_sup) = self.prepare(self.q_sup, false);
167
168 let quantile_inf = self.sorted_window[lower_inf]
169 + (self.sorted_window[higher_inf] - self.sorted_window[lower_inf]) * frac_inf;
170 let quantile_sup = self.sorted_window[lower_sup]
171 + (self.sorted_window[higher_sup] - self.sorted_window[lower_sup]) * frac_sup;
172
173 quantile_sup - quantile_inf
174 }
175}
176#[cfg(test)]
177mod test {
178 #[test]
179 fn rolling_iqr_edge_case() {
180 use crate::iqr::RollingIQR;
181 use crate::stats::Univariate;
182 let mut rolling_iqr: RollingIQR<f64> = RollingIQR::new(0.99_f64, 1.0_f64, 1).unwrap();
183 for i in 0..=1000 {
184 rolling_iqr.update(i as f64);
185 rolling_iqr.get();
187 }
188 assert_eq!(rolling_iqr.get(), 0.0);
189 }
190}