online_statistics/
iqr.rs

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/// Computes the interquartile range.
9/// # Arguments
10/// * `q_inf` - Desired inferior quantile, must be between 0 and 1. Defaults to `0.25`.
11/// * `q_sup` -  Desired superior quantile, must be between 0 and 1. Defaults to `0.75`.
12/// # Examples
13/// ```
14/// use online_statistics::iqr::IQR;
15/// use online_statistics::stats::Univariate;
16/// let mut running_iqr: IQR<f64> = IQR::default();
17/// for i in 1..=100{
18///     running_iqr.update(i as f64);
19/// }
20/// assert_eq!(running_iqr.get(), 50.0);
21/// ```
22///
23#[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/// Rolling interquartile range.
65/// # Arguments
66/// * `q_inf` - Desired inferior quantile, must be between 0 and 1.
67/// * `q_sup` -  Desired superior quantile, must be between 0 and 1.
68/// * `window_size` - Size of the rolling window.
69/// # Examples
70/// ```
71/// use online_statistics::iqr::RollingIQR;
72/// use online_statistics::stats::Univariate;
73/// let mut rolling_iqr: RollingIQR<f64> = RollingIQR::new(0.25_f64, 0.75_f64, 101).unwrap();
74/// for i in 0..=100{
75///     rolling_iqr.update(i as f64);
76///     //println!("{}", rolling_iqr.get());
77///     rolling_iqr.get();
78/// }
79/// assert_eq!(rolling_iqr.get(), 50.0);
80/// ```
81///
82
83#[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); // Avoid attempt to subtract with overflow
115        }
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); // Avoid attempt to subtract with overflow
124        }
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); // Avoid attempt to subtract with overflow
148            }
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            //println!("{}", rolling_iqr.get());
186            rolling_iqr.get();
187        }
188        assert_eq!(rolling_iqr.get(), 0.0);
189    }
190}