From 0d4c3b6fc61903b9aa645ec82999b240c05ea947 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Sat, 9 Mar 2019 16:45:06 -0500 Subject: [PATCH 1/6] Move *index* functions out of Interpolate trait The behavior of these functions should be independent of the interpolation strategy. --- src/quantile/interpolate.rs | 52 ++++++++++++++++++++++++------------- src/quantile/mod.rs | 10 +++---- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/quantile/interpolate.rs b/src/quantile/interpolate.rs index f0fd1f6a..702e39a6 100644 --- a/src/quantile/interpolate.rs +++ b/src/quantile/interpolate.rs @@ -5,30 +5,46 @@ use noisy_float::types::N64; use num_traits::{Float, FromPrimitive, ToPrimitive}; use std::ops::{Add, Div}; +fn float_quantile_index(q: N64, len: usize) -> N64 { + q * ((len - 1) as f64) +} + +/// Returns the fraction that the quantile is between the lower and higher indices. +/// +/// This ranges from 0, where the quantile exactly corresponds the lower index, +/// to 1, where the quantile exactly corresponds to the higher index. +fn float_quantile_index_fraction(q: N64, len: usize) -> N64 { + float_quantile_index(q, len).fract() +} + +/// Returns the index of the value on the lower side of the quantile. +pub(crate) fn lower_index(q: N64, len: usize) -> usize { + float_quantile_index(q, len).floor().to_usize().unwrap() +} + +/// Returns the index of the value on the higher side of the quantile. +pub(crate) fn higher_index(q: N64, len: usize) -> usize { + float_quantile_index(q, len).ceil().to_usize().unwrap() +} + /// Used to provide an interpolation strategy to [`quantile_axis_mut`]. /// /// [`quantile_axis_mut`]: ../trait.QuantileExt.html#tymethod.quantile_axis_mut pub trait Interpolate { - #[doc(hidden)] - fn float_quantile_index(q: N64, len: usize) -> N64 { - q * ((len - 1) as f64) - } - #[doc(hidden)] - fn lower_index(q: N64, len: usize) -> usize { - Self::float_quantile_index(q, len).floor().to_usize().unwrap() - } - #[doc(hidden)] - fn higher_index(q: N64, len: usize) -> usize { - Self::float_quantile_index(q, len).ceil().to_usize().unwrap() - } - #[doc(hidden)] - fn float_quantile_index_fraction(q: N64, len: usize) -> N64 { - Self::float_quantile_index(q, len).fract() - } + /// Returns `true` iff the lower value is needed to compute the + /// interpolated value. #[doc(hidden)] fn needs_lower(q: N64, len: usize) -> bool; + + /// Returns `true` iff the higher value is needed to compute the + /// interpolated value. #[doc(hidden)] fn needs_higher(q: N64, len: usize) -> bool; + + /// Computes the interpolated value. + /// + /// **Panics** if `None` is provided for the lower value when it's needed + /// or if `None` is provided for the higher value when it's needed. #[doc(hidden)] fn interpolate( lower: Option>, @@ -89,7 +105,7 @@ impl Interpolate for Lower { impl Interpolate for Nearest { fn needs_lower(q: N64, len: usize) -> bool { - >::float_quantile_index_fraction(q, len) < 0.5 + float_quantile_index_fraction(q, len) < 0.5 } fn needs_higher(q: N64, len: usize) -> bool { !>::needs_lower(q, len) @@ -151,7 +167,7 @@ impl Interpolate for Linear where D: Dimension, { - let fraction = >::float_quantile_index_fraction(q, len).to_f64().unwrap(); + let fraction = float_quantile_index_fraction(q, len).to_f64().unwrap(); let mut a = lower.unwrap(); let b = higher.unwrap(); azip!(mut a, ref b in { diff --git a/src/quantile/mod.rs b/src/quantile/mod.rs index 66517fb3..5d66eb2a 100644 --- a/src/quantile/mod.rs +++ b/src/quantile/mod.rs @@ -1,4 +1,4 @@ -use self::interpolate::Interpolate; +use self::interpolate::{higher_index, lower_index, Interpolate}; use super::sort::get_many_from_sorted_mut_unchecked; use std::cmp; use noisy_float::types::N64; @@ -208,10 +208,10 @@ where let mut searched_indexes = IndexSet::new(); for q in deduped_qs.iter() { if I::needs_lower(*q, axis_len) { - searched_indexes.insert(I::lower_index(*q, axis_len)); + searched_indexes.insert(lower_index(*q, axis_len)); } if I::needs_higher(*q, axis_len) { - searched_indexes.insert(I::higher_index(*q, axis_len)); + searched_indexes.insert(higher_index(*q, axis_len)); } } let searched_indexes: Vec = searched_indexes.into_iter().collect(); @@ -229,14 +229,14 @@ where let result = I::interpolate( match I::needs_lower(*q, axis_len) { true => { - let lower_index = &I::lower_index(*q, axis_len); + let lower_index = &lower_index(*q, axis_len); Some(values.map(|x| x.get(lower_index).unwrap().clone())) }, false => None, }, match I::needs_higher(*q, axis_len) { true => { - let higher_index = &I::higher_index(*q, axis_len); + let higher_index = &higher_index(*q, axis_len); Some(values.map(|x| x.get(higher_index).unwrap().clone())) }, false => None, From 1e846e5fd03e7b757f7d2bca57f9ae400aff9535 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Sat, 9 Mar 2019 17:03:47 -0500 Subject: [PATCH 2/6] Reduce indentation in quantiles_axis_mut --- src/quantile/mod.rs | 90 ++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/quantile/mod.rs b/src/quantile/mod.rs index 5d66eb2a..3f40cf04 100644 --- a/src/quantile/mod.rs +++ b/src/quantile/mod.rs @@ -196,58 +196,58 @@ where let axis_len = self.len_of(axis); if axis_len == 0 { - None - } else { - let mut deduped_qs: Vec = qs.to_vec(); - deduped_qs.sort_by(|a, b| a.partial_cmp(b).unwrap()); - deduped_qs.dedup(); + return None; + } - // IndexSet preserves insertion order: - // - indexes will stay sorted; - // - we avoid index duplication. - let mut searched_indexes = IndexSet::new(); - for q in deduped_qs.iter() { - if I::needs_lower(*q, axis_len) { - searched_indexes.insert(lower_index(*q, axis_len)); - } - if I::needs_higher(*q, axis_len) { - searched_indexes.insert(higher_index(*q, axis_len)); - } + let mut deduped_qs: Vec = qs.to_vec(); + deduped_qs.sort_by(|a, b| a.partial_cmp(b).unwrap()); + deduped_qs.dedup(); + + // IndexSet preserves insertion order: + // - indexes will stay sorted; + // - we avoid index duplication. + let mut searched_indexes = IndexSet::new(); + for q in deduped_qs.iter() { + if I::needs_lower(*q, axis_len) { + searched_indexes.insert(lower_index(*q, axis_len)); + } + if I::needs_higher(*q, axis_len) { + searched_indexes.insert(higher_index(*q, axis_len)); } - let searched_indexes: Vec = searched_indexes.into_iter().collect(); + } + let searched_indexes: Vec = searched_indexes.into_iter().collect(); - // Retrieve the values corresponding to each index for each slice along the specified axis - let values = self.map_axis_mut( - axis, - |mut x| get_many_from_sorted_mut_unchecked(&mut x, &searched_indexes) - ); + // Retrieve the values corresponding to each index for each slice along the specified axis + let values = self.map_axis_mut( + axis, + |mut x| get_many_from_sorted_mut_unchecked(&mut x, &searched_indexes) + ); - // Combine the retrieved values according to specified interpolation strategy to - // get the desired quantiles - let mut results = IndexMap::new(); - for q in qs { - let result = I::interpolate( - match I::needs_lower(*q, axis_len) { - true => { - let lower_index = &lower_index(*q, axis_len); - Some(values.map(|x| x.get(lower_index).unwrap().clone())) - }, - false => None, + // Combine the retrieved values according to specified interpolation strategy to + // get the desired quantiles + let mut results = IndexMap::new(); + for q in qs { + let result = I::interpolate( + match I::needs_lower(*q, axis_len) { + true => { + let lower_index = &lower_index(*q, axis_len); + Some(values.map(|x| x.get(lower_index).unwrap().clone())) }, - match I::needs_higher(*q, axis_len) { - true => { - let higher_index = &higher_index(*q, axis_len); - Some(values.map(|x| x.get(higher_index).unwrap().clone())) - }, - false => None, + false => None, + }, + match I::needs_higher(*q, axis_len) { + true => { + let higher_index = &higher_index(*q, axis_len); + Some(values.map(|x| x.get(higher_index).unwrap().clone())) }, - *q, - axis_len - ); - results.insert(*q, result); - } - Some(results) + false => None, + }, + *q, + axis_len + ); + results.insert(*q, result); } + Some(results) } fn quantile_axis_mut(&mut self, axis: Axis, q: N64) -> Option> From e8675fb3b2858b5b616a462896668d7b1a59538e Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Sat, 9 Mar 2019 17:09:52 -0500 Subject: [PATCH 3/6] Reduce indentation in quantile_axis_skipnan_mut --- src/quantile/mod.rs | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/quantile/mod.rs b/src/quantile/mod.rs index 3f40cf04..f7aca2b6 100644 --- a/src/quantile/mod.rs +++ b/src/quantile/mod.rs @@ -270,26 +270,24 @@ where S: DataMut, I: Interpolate, { - if self.len_of(axis) > 0 { - Some( - self.map_axis_mut(axis, |lane| { - let mut not_nan = A::remove_nan_mut(lane); - A::from_not_nan_opt(if not_nan.is_empty() { - None - } else { - Some( - not_nan - .quantile_axis_mut::(Axis(0), q) - .unwrap() - .into_raw_vec() - .remove(0), - ) - }) - }) - ) - } else { - None + if self.len_of(axis) == 0 { + return None; } + let quantile = self.map_axis_mut(axis, |lane| { + let mut not_nan = A::remove_nan_mut(lane); + A::from_not_nan_opt(if not_nan.is_empty() { + None + } else { + Some( + not_nan + .quantile_axis_mut::(Axis(0), q) + .unwrap() + .into_raw_vec() + .remove(0), + ) + }) + }); + Some(quantile) } } From 1b297fc04c6771b4d030425e95065ec3981f5f93 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Sat, 9 Mar 2019 17:11:43 -0500 Subject: [PATCH 4/6] Use .into_scalar() method --- src/quantile/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/quantile/mod.rs b/src/quantile/mod.rs index f7aca2b6..6ff83fb6 100644 --- a/src/quantile/mod.rs +++ b/src/quantile/mod.rs @@ -282,8 +282,7 @@ where not_nan .quantile_axis_mut::(Axis(0), q) .unwrap() - .into_raw_vec() - .remove(0), + .into_scalar(), ) }) }); From 4e2ebf1a3717af07817d59dd476f4f7875ffc1b6 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Sat, 9 Mar 2019 17:31:40 -0500 Subject: [PATCH 5/6] Improve docs of partition_mut --- src/sort.rs | 48 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/sort.rs b/src/sort.rs index f75862e5..a9122652 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -49,15 +49,16 @@ where A: Ord + Clone, S: DataMut; - /// Return the index of `self[partition_index]` if `self` were to be sorted - /// in increasing order. + /// Partitions the array in increasing order based on the value initially + /// located at `pivot_index` and returns the new index of the value. /// - /// `self` elements are rearranged in such a way that `self[partition_index]` - /// is in the position it would be in an array sorted in increasing order. - /// All elements smaller than `self[partition_index]` are moved to its - /// left and all elements equal or greater than `self[partition_index]` - /// are moved to its right. - /// The ordering of the elements in the two partitions is undefined. + /// The elements are rearranged in such a way that the value initially + /// located at `pivot_index` is moved to the position it would be in an + /// array sorted in increasing order. The return value is the new index of + /// the value after rearrangement. All elements smaller than the value are + /// moved to its left and all elements equal or greater than the value are + /// moved to its right. The ordering of the elements in the two partitions + /// is undefined. /// /// `self` is shuffled **in place** to operate the desired partition: /// no copy of the array is allocated. @@ -67,7 +68,36 @@ where /// Average number of element swaps: n/6 - 1/3 (see /// [link](https://cs.stackexchange.com/questions/11458/quicksort-partitioning-hoare-vs-lomuto/11550)) /// - /// **Panics** if `partition_index` is greater than or equal to `n`. + /// **Panics** if `pivot_index` is greater than or equal to `n`. + /// + /// # Example + /// + /// ``` + /// extern crate ndarray; + /// extern crate ndarray_stats; + /// + /// use ndarray::array; + /// use ndarray_stats::Sort1dExt; + /// + /// # fn main() { + /// let mut data = array![3, 1, 4, 5, 2]; + /// let pivot_index = 2; + /// let pivot_value = data[pivot_index]; + /// + /// // Partition by the value located at `pivot_index`. + /// let new_index = data.partition_mut(pivot_index); + /// // The pivot value is now located at `new_index`. + /// assert_eq!(data[new_index], pivot_value); + /// // Elements less than that value are moved to the left. + /// for i in 0..new_index { + /// assert!(data[i] < pivot_value); + /// } + /// // Elements greater than or equal to that value are moved to the right. + /// for i in (new_index + 1)..data.len() { + /// assert!(data[i] >= pivot_value); + /// } + /// # } + /// ``` fn partition_mut(&mut self, pivot_index: usize) -> usize where A: Ord + Clone, From 16854f55cbcee582e354d621aa10fdf41d139d39 Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Sat, 9 Mar 2019 17:34:52 -0500 Subject: [PATCH 6/6] Reformat quantiles_axis_mut --- src/quantile/mod.rs | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/quantile/mod.rs b/src/quantile/mod.rs index 6ff83fb6..4664a098 100644 --- a/src/quantile/mod.rs +++ b/src/quantile/mod.rs @@ -227,25 +227,18 @@ where // get the desired quantiles let mut results = IndexMap::new(); for q in qs { - let result = I::interpolate( - match I::needs_lower(*q, axis_len) { - true => { - let lower_index = &lower_index(*q, axis_len); - Some(values.map(|x| x.get(lower_index).unwrap().clone())) - }, - false => None, - }, - match I::needs_higher(*q, axis_len) { - true => { - let higher_index = &higher_index(*q, axis_len); - Some(values.map(|x| x.get(higher_index).unwrap().clone())) - }, - false => None, - }, - *q, - axis_len - ); - results.insert(*q, result); + let lower = if I::needs_lower(*q, axis_len) { + Some(values.map(|x| x[&lower_index(*q, axis_len)].clone())) + } else { + None + }; + let higher = if I::needs_higher(*q, axis_len) { + Some(values.map(|x| x[&higher_index(*q, axis_len)].clone())) + } else { + None + }; + let interpolated = I::interpolate(lower, higher, *q, axis_len); + results.insert(*q, interpolated); } Some(results) }