ndarray_stats/quantile/
interpolate.rs

1//! Interpolation strategies.
2use noisy_float::types::N64;
3use num_traits::{Float, FromPrimitive, NumOps, ToPrimitive};
4
5fn float_quantile_index(q: N64, len: usize) -> N64 {
6    q * ((len - 1) as f64)
7}
8
9/// Returns the fraction that the quantile is between the lower and higher indices.
10///
11/// This ranges from 0, where the quantile exactly corresponds the lower index,
12/// to 1, where the quantile exactly corresponds to the higher index.
13fn float_quantile_index_fraction(q: N64, len: usize) -> N64 {
14    float_quantile_index(q, len).fract()
15}
16
17/// Returns the index of the value on the lower side of the quantile.
18pub(crate) fn lower_index(q: N64, len: usize) -> usize {
19    float_quantile_index(q, len).floor().to_usize().unwrap()
20}
21
22/// Returns the index of the value on the higher side of the quantile.
23pub(crate) fn higher_index(q: N64, len: usize) -> usize {
24    float_quantile_index(q, len).ceil().to_usize().unwrap()
25}
26
27/// Used to provide an interpolation strategy to [`quantile_axis_mut`].
28///
29/// [`quantile_axis_mut`]: ../trait.QuantileExt.html#tymethod.quantile_axis_mut
30pub trait Interpolate<T> {
31    /// Returns `true` iff the lower value is needed to compute the
32    /// interpolated value.
33    #[doc(hidden)]
34    fn needs_lower(q: N64, len: usize) -> bool;
35
36    /// Returns `true` iff the higher value is needed to compute the
37    /// interpolated value.
38    #[doc(hidden)]
39    fn needs_higher(q: N64, len: usize) -> bool;
40
41    /// Computes the interpolated value.
42    ///
43    /// **Panics** if `None` is provided for the lower value when it's needed
44    /// or if `None` is provided for the higher value when it's needed.
45    #[doc(hidden)]
46    fn interpolate(lower: Option<T>, higher: Option<T>, q: N64, len: usize) -> T;
47
48    private_decl! {}
49}
50
51/// Select the higher value.
52pub struct Higher;
53/// Select the lower value.
54pub struct Lower;
55/// Select the nearest value.
56pub struct Nearest;
57/// Select the midpoint of the two values (`(lower + higher) / 2`).
58pub struct Midpoint;
59/// Linearly interpolate between the two values
60/// (`lower + (higher - lower) * fraction`, where `fraction` is the
61/// fractional part of the index surrounded by `lower` and `higher`).
62pub struct Linear;
63
64impl<T> Interpolate<T> for Higher {
65    fn needs_lower(_q: N64, _len: usize) -> bool {
66        false
67    }
68    fn needs_higher(_q: N64, _len: usize) -> bool {
69        true
70    }
71    fn interpolate(_lower: Option<T>, higher: Option<T>, _q: N64, _len: usize) -> T {
72        higher.unwrap()
73    }
74    private_impl! {}
75}
76
77impl<T> Interpolate<T> for Lower {
78    fn needs_lower(_q: N64, _len: usize) -> bool {
79        true
80    }
81    fn needs_higher(_q: N64, _len: usize) -> bool {
82        false
83    }
84    fn interpolate(lower: Option<T>, _higher: Option<T>, _q: N64, _len: usize) -> T {
85        lower.unwrap()
86    }
87    private_impl! {}
88}
89
90impl<T> Interpolate<T> for Nearest {
91    fn needs_lower(q: N64, len: usize) -> bool {
92        float_quantile_index_fraction(q, len) < 0.5
93    }
94    fn needs_higher(q: N64, len: usize) -> bool {
95        !<Self as Interpolate<T>>::needs_lower(q, len)
96    }
97    fn interpolate(lower: Option<T>, higher: Option<T>, q: N64, len: usize) -> T {
98        if <Self as Interpolate<T>>::needs_lower(q, len) {
99            lower.unwrap()
100        } else {
101            higher.unwrap()
102        }
103    }
104    private_impl! {}
105}
106
107impl<T> Interpolate<T> for Midpoint
108where
109    T: NumOps + Clone + FromPrimitive,
110{
111    fn needs_lower(_q: N64, _len: usize) -> bool {
112        true
113    }
114    fn needs_higher(_q: N64, _len: usize) -> bool {
115        true
116    }
117    fn interpolate(lower: Option<T>, higher: Option<T>, _q: N64, _len: usize) -> T {
118        let denom = T::from_u8(2).unwrap();
119        let lower = lower.unwrap();
120        let higher = higher.unwrap();
121        lower.clone() + (higher.clone() - lower.clone()) / denom.clone()
122    }
123    private_impl! {}
124}
125
126impl<T> Interpolate<T> for Linear
127where
128    T: NumOps + Clone + FromPrimitive + ToPrimitive,
129{
130    fn needs_lower(_q: N64, _len: usize) -> bool {
131        true
132    }
133    fn needs_higher(_q: N64, _len: usize) -> bool {
134        true
135    }
136    fn interpolate(lower: Option<T>, higher: Option<T>, q: N64, len: usize) -> T {
137        let fraction = float_quantile_index_fraction(q, len).to_f64().unwrap();
138        let lower = lower.unwrap();
139        let higher = higher.unwrap();
140        let lower_f64 = lower.to_f64().unwrap();
141        let higher_f64 = higher.to_f64().unwrap();
142        lower.clone() + T::from_f64(fraction * (higher_f64 - lower_f64)).unwrap()
143    }
144    private_impl! {}
145}