#[derive(Debug)]
struct P2 {
q: [f64; 5],
n: [f64; 5],
np: [f64; 5],
dn: [f64; 5],
}
impl P2 {
fn new(p: f64) -> Self {
let mut p2 = Self {
q: [0.0; 5],
n: [0.0, 1.0, 2.0, 3.0, 4.0],
np: [0.0; 5],
dn: [0.0; 5],
};
p2.np[1] = 2.0 * p;
p2.np[2] = 4.0 * p;
p2.np[3] = 2.0 + 2.0 * p;
p2.np[4] = 4.0;
p2.dn[1] = p / 2.0;
p2.dn[2] = p;
p2.dn[3] = (p + 1.0) / 2.0;
p2.dn[4] = 1.0;
p2
}
#[allow(clippy::needless_range_loop, clippy::manual_memcpy)]
fn quantile(&mut self, data: &[f64]) -> f64 {
let size = data.len();
if size < 5 {
return 0.0;
}
for i in 0..5 {
self.q[i] = data[i];
}
sort5(&mut self.q);
for j in 5..size {
let xj = data[j];
let _k = if xj < self.q[0] {
self.q[0] = xj;
0 } else if xj > self.q[4] {
self.q[4] = xj;
3 } else {
let mut k = 0;
while k < 4 && xj > self.q[k] {
k += 1;
}
k = k.saturating_sub(1);
for i in (k + 1)..5 {
self.n[i] += 1.0;
}
for i in 0..5 {
self.np[i] += self.dn[i];
}
for i in 1..4 {
let d = self.np[i] - self.n[i];
if (d >= 1.0 && (self.n[i + 1] - self.n[i]) > 1.0)
|| (d <= -1.0 && (self.n[i - 1] - self.n[i]) < -1.0)
{
let d_sign = sign(d);
let mut qp = self.parabolic(i, d_sign as i32);
if !(self.q[i - 1] < qp && qp < self.q[i + 1]) {
qp = self.linear(i, d_sign as i32);
}
self.q[i] = qp;
self.n[i] += d_sign;
}
}
k
};
}
self.q[2] }
fn linear(&self, i: usize, d: i32) -> f64 {
let i_d = (i as i32 + d) as usize;
self.q[i] + (d as f64) * (self.q[i_d] - self.q[i]) / (self.n[i_d] - self.n[i])
}
fn parabolic(&self, i: usize, d: i32) -> f64 {
let d_f = d as f64;
self.q[i]
+ (d_f / (self.n[i + 1] - self.n[i - 1]))
* ((self.n[i] - self.n[i - 1] + d_f) * (self.q[i + 1] - self.q[i])
/ (self.n[i + 1] - self.n[i])
+ (self.n[i + 1] - self.n[i] - d_f) * (self.q[i] - self.q[i - 1])
/ (self.n[i] - self.n[i - 1]))
}
}
fn sign(d: f64) -> f64 {
if d > 0.0 {
1.0
} else if d < 0.0 {
-1.0
} else {
0.0
}
}
fn sort5(a: &mut [f64; 5]) {
if a[1] < a[0] {
a.swap(0, 1);
}
if a[3] < a[2] {
a.swap(2, 3);
}
if a[0] < a[2] {
a.swap(1, 2);
a.swap(2, 3);
} else {
a.swap(1, 2);
a.swap(0, 1);
}
if a[4] < a[1] {
if a[4] < a[0] {
a.swap(4, 3);
a.swap(3, 2);
a.swap(2, 1);
a.swap(1, 0);
} else {
a.swap(4, 3);
a.swap(3, 2);
a.swap(2, 1);
}
} else if a[4] < a[2] {
a.swap(4, 3);
a.swap(3, 2);
} else {
a.swap(4, 3);
}
if a[4] < a[2] {
if a[4] < a[1] {
a.swap(4, 3);
a.swap(3, 2);
a.swap(2, 1);
} else {
a.swap(4, 3);
a.swap(3, 2);
}
} else if a[4] < a[3] {
a.swap(4, 3);
}
}
pub fn p2_quantile(p: f64, data: &[f64]) -> f64 {
let mut p2 = P2::new(p);
p2.quantile(data)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_sign() {
assert_relative_eq!(sign(5.0), 1.0);
assert_relative_eq!(sign(-3.0), -1.0);
assert_relative_eq!(sign(0.0), 0.0);
}
#[test]
fn test_sort5() {
let mut a = [5.0, 2.0, 8.0, 1.0, 9.0];
sort5(&mut a);
assert_eq!(a, [1.0, 2.0, 5.0, 8.0, 9.0]);
let mut b = [3.0, 3.0, 1.0, 2.0, 2.0];
sort5(&mut b);
assert_eq!(b, [1.0, 2.0, 2.0, 3.0, 3.0]);
}
#[test]
fn test_p2_quantile_small_data() {
let data = [1.0, 2.0, 3.0];
let result = p2_quantile(0.5, &data);
assert_relative_eq!(result, 0.0); }
#[test]
fn test_p2_quantile_median() {
let data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
let result = p2_quantile(0.5, &data);
assert!((result - 5.5).abs() < 3.0); }
#[test]
#[ignore] fn test_p2_quantile_quartiles() {
let data: Vec<f64> = (1..=100).map(|x| x as f64).collect();
let q1 = p2_quantile(0.25, &data);
assert!((q1 - 25.0).abs() < 25.0);
let q3 = p2_quantile(0.75, &data);
assert!((q3 - 75.0).abs() < 25.0); }
#[test]
fn test_p2_quantile_identical_values() {
let data = vec![5.0; 20];
let result = p2_quantile(0.5, &data);
assert_relative_eq!(result, 5.0, epsilon = 1e-10);
}
#[test]
#[ignore] fn test_p2_level_0_998() {
let data: Vec<f64> = (1..=1000).map(|x| x as f64).collect();
let result = p2_quantile(0.998, &data);
assert!((result - 998.0).abs() < 100.0); }
}