diff --git a/src/lib.rs b/src/lib.rs index 1c9bfeaa..ae664cf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,5 +65,7 @@ pub mod euclid; pub mod function; pub mod generate; pub mod prec; +pub mod sorted_iterator; +pub mod sorted_slice; pub mod statistics; pub mod stats_tests; diff --git a/src/sorted_collection.rs b/src/sorted_collection.rs new file mode 100644 index 00000000..7553d13a --- /dev/null +++ b/src/sorted_collection.rs @@ -0,0 +1,118 @@ +use std::{ + cmp::Ordering, + ops::{Deref, Index}, + slice::SliceIndex, +}; + +pub enum SortError { + NotSorted, +} + +#[derive(Clone, Debug)] +pub enum Collection<'a, T> { + Ref(&'a [T]), + Owned(Vec), +} + +#[derive(Clone, Debug)] +pub struct SortedCollection<'a, T> { + sorted: Collection<'a, T>, +} + +impl<'a, T> SortedCollection<'a, T> +where + T: Ord, +{ + pub fn from_slice(coll: &'a [T]) -> Result { + match coll + .windows(2) + .all(|sl| sl[0].cmp(&sl[1]) == Ordering::Less) + { + true => Ok(Self { + sorted: Collection::Ref(coll), + }), + false => Err(SortError::NotSorted), + } + } + pub fn from_mut_vec(mut coll: Vec) -> Self { + coll.sort(); + Self { + sorted: Collection::Owned(coll), + } + } + pub fn iter(&'a self) -> std::slice::Iter<'a, T> { + match self.sorted { + Collection::Ref(items) => items.iter(), + Collection::Owned(ref items) => items.iter(), + } + } +} + +impl<'a, T> TryFrom<&'a [T]> for SortedCollection<'a, T> +where + T: Ord, +{ + type Error = SortError; + + fn try_from(value: &'a [T]) -> Result { + Self::from_slice(value) + } +} + +impl<'a, T> TryFrom<&'a Box<[T]>> for SortedCollection<'a, T> +where + T: Ord, +{ + type Error = SortError; + + fn try_from(value: &'a Box<[T]>) -> Result { + Self::from_slice(value.as_ref()) + } +} + +impl<'a, T> AsRef<[T]> for SortedCollection<'a, T> { + fn as_ref(&self) -> &[T] { + match self.sorted { + Collection::Ref(items) => items, + Collection::Owned(ref items) => items.as_ref(), + } + } +} + +pub struct SortedSlice<'a, T>(&'a [T]); + +impl<'a, T> TryInto> for &'a [T] +where + T: Ord, +{ + type Error = SortError; + + fn try_into(self) -> Result, Self::Error> { + match self.is_sorted() { + true => Ok(SortedSlice(self)), + false => Err(SortError::NotSorted), + } + } +} + +impl<'a, T, I> Index for SortedSlice<'a, T> +where + T: Ord, + I: SliceIndex<[T]>, +{ + type Output = >::Output; + + fn index(&self, index: I) -> &Self::Output { + self.0.index(index) + } +} +impl<'a, T> Deref for SortedSlice<'a, T> +where + T: Ord, +{ + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} diff --git a/src/sorted_iterator.rs b/src/sorted_iterator.rs new file mode 100644 index 00000000..6c02270f --- /dev/null +++ b/src/sorted_iterator.rs @@ -0,0 +1,38 @@ +use std::marker::PhantomData; + +use crate::stats_tests::NaNPolicy; + +pub trait SortedIterator { + fn sorted_iter(&self, policy: NaNPolicy) -> Sorted; +} + +impl SortedIterator for Vec { + fn sorted_iter(&self, policy: NaNPolicy) -> Sorted { + Sorted::new(self, policy) + } +} + +/// TODO iron out implementation details later because this is not optimal retrieval of sorted data +pub struct Sorted { + sorted_iter: std::vec::IntoIter, + policy: NaNPolicy, +} + +impl Sorted { + pub fn new(data: &[f64], policy: NaNPolicy) -> Self { + let mut cloned = Vec::from(data); + cloned.sort_by(|a, b| a.total_cmp(b)); + Self { + sorted_iter: cloned.into_iter(), + policy, + } + } +} + +impl Iterator for Sorted { + type Item = f64; + + fn next(&mut self) -> Option { + self.sorted_iter.next() + } +} diff --git a/src/sorted_slice.rs b/src/sorted_slice.rs new file mode 100644 index 00000000..642537e2 --- /dev/null +++ b/src/sorted_slice.rs @@ -0,0 +1,65 @@ +use std::{ + cmp::Ordering, + ops::{Deref, Index}, + slice::SliceIndex, +}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub enum SortError { + NotSorted, +} +impl core::fmt::Display for SortError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SortError::NotSorted => write!(f, "Sample is not sorted."), + } + } +} + +pub struct SortedSlice<'a, T>(&'a [T]); + +impl<'a, T> SortedSlice<'a, T> { + pub fn iter(&self) -> std::slice::Iter<'_, T> { + self.0.iter() + } + pub fn into_iter(self) -> std::slice::Iter<'a, T> { + self.0.into_iter() + } +} + +impl<'a> TryInto> for &'a [f64] { + type Error = SortError; + + fn try_into(self) -> Result, Self::Error> { + match ( + self.is_sorted_by(|a, b| a.total_cmp(b) != Ordering::Greater), + self.is_empty(), + ) { + (_, true) => Ok(SortedSlice(self)), + (true, false) => Ok(SortedSlice(self)), + (false, false) => Err(SortError::NotSorted), + } + } +} + +impl<'a, T, I> Index for SortedSlice<'a, T> +where + T: Ord, + I: SliceIndex<[T]>, +{ + type Output = >::Output; + + fn index(&self, index: I) -> &Self::Output { + self.0.index(index) + } +} +impl<'a, T> Deref for SortedSlice<'a, T> +where + T: Ord, +{ + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} diff --git a/src/stats_tests/ks_test.rs b/src/stats_tests/ks_test.rs index 6f3cdf47..753ec8c4 100644 --- a/src/stats_tests/ks_test.rs +++ b/src/stats_tests/ks_test.rs @@ -11,6 +11,7 @@ use num_traits::clamp; use crate::distribution::ContinuousCDF; use crate::function::factorial; +use crate::sorted_slice::{SortError, SortedSlice}; use super::NaNPolicy; @@ -27,6 +28,13 @@ pub enum KSTestError { /// `KSOneSampleAlternativeMethod::TwoSidedExact`selected with the size of the data (`n`) being /// too large ExactAndTooLarge, + SortedSliceError(SortError), +} + +impl From for KSTestError { + fn from(value: SortError) -> Self { + value.into() + } } impl core::fmt::Display for KSTestError { @@ -48,6 +56,7 @@ impl core::fmt::Display for KSTestError { f, "`KSOneSampleAlternativeMethod::TwoSidedExact`selected with the size of the data (`n`) being too large" ), + KSTestError::SortedSliceError(sort_error) => sort_error.fmt(f), } } } @@ -193,50 +202,44 @@ fn onesample_marsaglia_et_al_twosided_pvalue(d: f64, n: f64) -> Result = (-150..=150).map(|i| i as f64 * 0.01).collect(); -/// +/// let mut data: Vec = (-150..=150).map(|i| i as f64 * 0.01).collect(); +/// data.sort_by(|a,b| a.total_cmp(b)); /// let (statistic, pvalue) = ks_onesample( -/// data.clone(), +/// data.as_slice().try_into().unwrap(), /// &Normal::default(), /// KSOneSampleAlternativeMethod::TwoSidedAsymptotic, /// NaNPolicy::Error, /// ) /// .unwrap(); /// ``` -pub fn ks_onesample( - mut data: Vec, +pub fn ks_onesample<'a, T, S>( + data: S, distribution: &T, method: KSOneSampleAlternativeMethod, nan_policy: NaNPolicy, ) -> Result<(f64, f64), KSTestError> where T: ContinuousCDF, + S: TryInto, Error = SortError>, { + let data = data.try_into()?; let has_nans = data.iter().any(|x| x.is_nan()); - if has_nans { - match nan_policy { - NaNPolicy::Propogate => { - return Ok((f64::NAN, f64::NAN)); - } - NaNPolicy::Error => { - return Err(KSTestError::SampleContainsNaN); - } - NaNPolicy::Emit => { - data = data.into_iter().filter(|x| !x.is_nan()).collect::>(); - } - } - } + let data: Vec = match (has_nans, nan_policy) { + (true, NaNPolicy::Propogate) => return Ok((f64::NAN, f64::NAN)), + (true, NaNPolicy::Error) => return Err(KSTestError::SampleContainsNaN), + (true, NaNPolicy::Emit) => data + .into_iter() + .filter(|x| !x.is_nan()) + .copied() + .collect::>(), + (false, _) => data.into_iter().copied().collect(), + }; let n = data.len() as f64; if (n as usize) < 1 { return Err(KSTestError::SampleTooSmall); } - data.sort_by(|a, b| { - a.partial_cmp(b) - .expect("nans should be filtered out by this point so it should always work") - }); - let theoretical_cdf = data .iter() .map(|x| distribution.cdf(*x)) @@ -373,56 +376,55 @@ fn twosample_schroer_and_trenkler_twosided_pvalue(d: f64, m: usize, n: usize) -> /// use statrs::stats_tests::ks_test::{ks_twosample, KSTwoSampleAlternativeMethod}; /// use statrs::stats_tests::NaNPolicy; /// -/// let data1: Vec = (0..2000i32).map(|x| x.pow(2) as f64).collect(); -/// let data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); +/// let mut data1: Vec = (0..2000i32).map(|x| x.pow(2) as f64).collect(); +/// let mut data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); +/// data1.sort_by(|a,b| a.total_cmp(b)); +/// data2.sort_by(|a,b| a.total_cmp(b)); /// /// let (statistic, pvalue) = ks_twosample( -/// data1.clone(), -/// data2.clone(), +/// data1.as_slice().try_into().unwrap(), +/// data2.as_slice().try_into().unwrap(), /// KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, /// NaNPolicy::Error, /// ).unwrap(); /// ``` -pub fn ks_twosample( - mut data1: Vec, - mut data2: Vec, +pub fn ks_twosample<'a, S>( + data1: S, + data2: S, method: KSTwoSampleAlternativeMethod, nan_policy: NaNPolicy, -) -> Result<(f64, f64), KSTestError> { +) -> Result<(f64, f64), KSTestError> +where + S: TryInto, Error = SortError>, +{ + let data1: SortedSlice = data1.try_into()?; + let data2: SortedSlice = data2.try_into()?; let has_nans1 = data1.iter().any(|x| x.is_nan()); - if has_nans1 { - match nan_policy { - NaNPolicy::Propogate => { - return Ok((f64::NAN, f64::NAN)); - } - NaNPolicy::Error => { - return Err(KSTestError::SampleContainsNaN); - } - NaNPolicy::Emit => { - data1 = data1 - .into_iter() - .filter(|x| !x.is_nan()) - .collect::>(); - } - } - } + + let data1: Vec = match (has_nans1, nan_policy) { + (true, NaNPolicy::Propogate) => return Ok((f64::NAN, f64::NAN)), + (true, NaNPolicy::Error) => return Err(KSTestError::SampleContainsNaN), + (true, NaNPolicy::Emit) => data1 + .into_iter() + .filter(|x| !x.is_nan()) + .copied() + .collect::>(), + (false, _) => data1.into_iter().copied().collect(), + }; + let has_nans2 = data2.iter().any(|x| x.is_nan()); - if has_nans2 { - match nan_policy { - NaNPolicy::Propogate => { - return Ok((f64::NAN, f64::NAN)); - } - NaNPolicy::Error => { - return Err(KSTestError::SampleContainsNaN); - } - NaNPolicy::Emit => { - data2 = data2 - .into_iter() - .filter(|x| !x.is_nan()) - .collect::>(); - } - } - } + + let data2: Vec = match (has_nans2, nan_policy) { + (true, NaNPolicy::Propogate) => return Ok((f64::NAN, f64::NAN)), + (true, NaNPolicy::Error) => return Err(KSTestError::SampleContainsNaN), + (true, NaNPolicy::Emit) => data2 + .into_iter() + .filter(|x| !x.is_nan()) + .copied() + .collect::>(), + (false, _) => data2.into_iter().copied().collect(), + }; + let n1 = data1.len() as f64; let n2 = data2.len() as f64; if (n1 as usize) < 1 || (n2 as usize) < 1 { @@ -432,14 +434,6 @@ pub fn ks_twosample( let m = (n1 as usize).max(n2 as usize); // calculate the test statistic - data1.sort_by(|a, b| { - a.partial_cmp(b) - .expect("nans should be filtered out by this point so it should always work") - }); - data2.sort_by(|a, b| { - a.partial_cmp(b) - .expect("nans should be filtered out by this point so it should always work") - }); let mut data_all = [data1.clone(), data2.clone()].concat(); data_all.sort_by(|a, b| { a.partial_cmp(b) @@ -506,13 +500,15 @@ mod tests { #[test] fn test_ks_onesample_against_scipy() { - let data = Vec::from([ + let mut data = Vec::from([ 0.7, 0.8, 1.1, 2.0, 3.9, 4.2, 4.3, 4.9, 5.1, 5.2, 5.3, 5.5, 5.7, 5.8, 6.0, ]); let mean = data.iter().mean(); + data.sort_by(|a, b| a.total_cmp(b)); + let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Exp::new(1.0 / mean).unwrap(), KSOneSampleAlternativeMethod::Less, NaNPolicy::Error, @@ -524,7 +520,7 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.01768990758651141, epsilon = 1e-9); let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Exp::new(1.0 / mean).unwrap(), KSOneSampleAlternativeMethod::Greater, NaNPolicy::Error, @@ -535,7 +531,7 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.18683781649758202, epsilon = 1e-9); let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Exp::new(1.0 / mean).unwrap(), KSOneSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, @@ -546,7 +542,7 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.047499850721610656, epsilon = 1e-9); let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Exp::new(1.0 / mean).unwrap(), KSOneSampleAlternativeMethod::TwoSidedApproximate, NaNPolicy::Error, @@ -557,7 +553,7 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.03537981517302282, epsilon = 1e-9); let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Exp::new(1.0 / mean).unwrap(), KSOneSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, @@ -569,10 +565,11 @@ mod tests { } #[test] fn test_ks_onesample_against_r() { - let data: Vec = (-150..=150).map(|i| i as f64 * 0.01).collect(); + let mut data: Vec = (-150..=150).map(|i| i as f64 * 0.01).collect(); + data.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Normal::default(), KSOneSampleAlternativeMethod::Less, NaNPolicy::Error, @@ -583,7 +580,7 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.06508, epsilon = 1e-3); let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Normal::default(), KSOneSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, @@ -606,9 +603,10 @@ mod tests { // prec::assert_abs_diff_eq!(pvalue, 0.1301, epsilon = 1e-3); // ensure that the ks test can handle non trivial small sizes - let data_small_enough: Vec = (0..140).map(|i| i as f64 * 0.01).collect(); + let mut data_small_enough: Vec = (0..140).map(|i| i as f64 * 0.01).collect(); + data_small_enough.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_onesample( - data_small_enough, + data_small_enough.as_slice(), &Uniform::default(), KSOneSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, @@ -618,7 +616,7 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 1.311e-10, epsilon = 1e-12); let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Uniform::default(), KSOneSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, @@ -640,18 +638,20 @@ mod tests { } #[test] fn test_ks_onesample_bad_data_data_too_small() { - let data: Vec = Vec::new(); + let mut data: Vec = Vec::new(); + data.sort_by(|a, b| a.total_cmp(b)); let result = ks_onesample( - data, + data.as_slice(), &Normal::default(), KSOneSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); assert_eq!(result, Err(KSTestError::SampleTooSmall)); - let data: Vec = Vec::from([f64::NAN, f64::NAN]); + let mut data: Vec = Vec::from([f64::NAN, f64::NAN]); + data.sort_by(|a, b| a.total_cmp(b)); let result = ks_onesample( - data, + data.as_slice(), &Normal::default(), KSOneSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Emit, @@ -660,9 +660,10 @@ mod tests { } #[test] fn test_ks_onesample_bad_data_exact_too_large() { - let data: Vec = (-150..=150).map(|i| i as f64 * 0.01).collect(); + let mut data: Vec = (-150..=150).map(|i| i as f64 * 0.01).collect(); + data.sort_by(|a, b| a.total_cmp(b)); let result = ks_onesample( - data, + data.as_slice(), &Normal::default(), KSOneSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, @@ -673,8 +674,9 @@ mod tests { fn test_ks_onesample_bad_data_exact_with_ties() { let mut data: Vec = (-10..=10).map(|i| i as f64 * 0.01).collect(); data[0] = data[1]; + data.sort_by(|a, b| a.total_cmp(b)); let result = ks_onesample( - data, + data.as_slice(), &Normal::default(), KSOneSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, @@ -683,7 +685,7 @@ mod tests { } #[test] fn test_ks_onesample_nan_in_data_w_emit() { - let data = Vec::from([ + let mut data = Vec::from([ 0.7, 0.8, 1.1, @@ -703,8 +705,9 @@ mod tests { ]); let mean = data.iter().filter(|x| !x.is_nan()).mean(); + data.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_onesample( - data.clone(), + data.as_slice(), &Exp::new(1.0 / mean).unwrap(), KSOneSampleAlternativeMethod::Less, NaNPolicy::Emit, @@ -719,8 +722,9 @@ mod tests { fn test_ks_onesample_nan_in_data_w_propogate() { let mut data: Vec = (-10..=10).map(|i| i as f64 * 0.01).collect(); data[0] = f64::NAN; + data.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_onesample( - data, + data.as_slice(), &Normal::default(), KSOneSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Propogate, @@ -733,8 +737,9 @@ mod tests { fn test_ks_onesample_nan_in_data_w_error() { let mut data: Vec = (-10..=10).map(|i| i as f64 * 0.01).collect(); data[0] = f64::NAN; + data.sort_by(|a, b| a.total_cmp(b)); let result = ks_onesample( - data, + data.as_slice(), &Normal::default(), KSOneSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, @@ -744,7 +749,7 @@ mod tests { #[test] fn test_ks_twosample_against_scipy() { - let data1 = Vec::from([ + let mut data1: Vec = Vec::from([ 0.75857220, 0.45485367, -1.79747176, @@ -756,7 +761,7 @@ mod tests { -1.16067678, -0.49210878, ]); - let data2 = Vec::from([ + let mut data2: Vec = Vec::from([ -0.009876332, 0.119263550, -2.048604274, @@ -774,9 +779,12 @@ mod tests { 1.636287642, ]); + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); + let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -785,8 +793,8 @@ mod tests { prec::assert_abs_diff_eq!(statistic, 0.26666666666666666, epsilon = 1e-9); prec::assert_abs_diff_eq!(pvalue, 0.7315422361996597, epsilon = 1e-9); let (statistic, pvalue) = ks_twosample( - data2.clone(), - data1.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -796,8 +804,8 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.7315422361996597, epsilon = 1e-9); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::LessAsymptotic, NaNPolicy::Error, ) @@ -806,8 +814,8 @@ mod tests { prec::assert_abs_diff_eq!(statistic, 0.1, epsilon = 1e-9); prec::assert_abs_diff_eq!(pvalue, 0.8078867967299911, epsilon = 1e-9); let (statistic, pvalue) = ks_twosample( - data2.clone(), - data1.clone(), + data2.as_slice(), + data1.as_slice(), KSTwoSampleAlternativeMethod::LessAsymptotic, NaNPolicy::Error, ) @@ -817,8 +825,8 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.33213219147418116, epsilon = 1e-9); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::GreaterAsymptotic, NaNPolicy::Error, ) @@ -827,8 +835,8 @@ mod tests { prec::assert_abs_diff_eq!(statistic, 0.26666666666666666, epsilon = 1e-9); prec::assert_abs_diff_eq!(pvalue, 0.33213219147418116, epsilon = 1e-9); let (statistic, pvalue) = ks_twosample( - data2.clone(), - data1.clone(), + data2.as_slice(), + data1.as_slice(), KSTwoSampleAlternativeMethod::GreaterAsymptotic, NaNPolicy::Error, ) @@ -841,12 +849,14 @@ mod tests { // sample "automatic" implementation which may be an exact calculation // for smaller `n`s and certain `D` values // - let data1: Vec = (0..2000i32).map(|x| x.pow(2) as f64).collect(); - let data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); + let mut data1: Vec = (0..2000i32).map(|x| x.pow(2) as f64).collect(); + let mut data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, ) @@ -855,8 +865,8 @@ mod tests { prec::assert_abs_diff_eq!(statistic, 0.06450000000000002, epsilon = 1e-9); prec::assert_abs_diff_eq!(pvalue, 0.0003435848163318721, epsilon = 1e-4); let (statistic, pvalue) = ks_twosample( - data2.clone(), - data1.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, ) @@ -882,9 +892,11 @@ mod tests { y.push((i + 1) as f64) } } + x.sort_by(|a, b| a.total_cmp(b)); + y.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_twosample( - x.clone(), - y.clone(), + x.as_slice(), + y.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -900,12 +912,15 @@ mod tests { } #[test] fn test_ks_twosample_against_r() { - let data1: Vec = (0..2000i32).map(|x| x.pow(2) as f64).collect(); - let data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); + let mut data1: Vec = (0..2000i32).map(|x| x.pow(2) as f64).collect(); + let mut data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); + + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, ) @@ -914,8 +929,8 @@ mod tests { prec::assert_abs_diff_eq!(statistic, 0.06450000000000002, epsilon = 1e-9); prec::assert_abs_diff_eq!(pvalue, 0.0003604729, epsilon = 1e-9); let (statistic, pvalue) = ks_twosample( - data2.clone(), - data1.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, ) @@ -925,15 +940,18 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.0003604729, epsilon = 1e-9); // test against R's `chickwts` built-in dataset from an annon source - let casein = Vec::from([ + let mut casein: Vec = Vec::from([ 368.0, 390.0, 379.0, 260.0, 404.0, 318.0, 352.0, 359.0, 216.0, 222.0, 283.0, 332.0, ]); - let meatmeal = Vec::from([ + let mut meatmeal: Vec = Vec::from([ 325.0, 257.0, 303.0, 315.0, 380.0, 153.0, 263.0, 242.0, 206.0, 344.0, 258.0, ]); + + casein.sort_by(|a, b| a.total_cmp(b)); + meatmeal.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_twosample( - casein.clone(), - meatmeal.clone(), + casein.as_slice(), + meatmeal.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -943,8 +961,8 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.1956825, epsilon = 1e-6); let (statistic, pvalue) = ks_twosample( - meatmeal.clone(), - casein.clone(), + meatmeal.as_slice(), + casein.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -955,12 +973,15 @@ mod tests { } #[test] fn test_ks_twosample_bad_data_exact_too_large() { - let data1: Vec = (0..2000i32).map(|x| x.pow(2) as f64).collect(); - let data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); + let mut data1: Vec = (0..2000i32).map(|x| x.pow(2) as f64).collect(); + let mut data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); + + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let result = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); @@ -968,41 +989,53 @@ mod tests { } #[test] fn test_ks_twosample_bad_data_data_too_small() { - let data1: Vec = Vec::new(); - let data2 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + let mut data1: Vec = Vec::new(); + let mut data2: Vec = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); + let result = ks_twosample( - data1, - data2, + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); assert_eq!(result, Err(KSTestError::SampleTooSmall)); - let data1: Vec = Vec::from([f64::NAN]); - let data2 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + let mut data1: Vec = Vec::from([f64::NAN]); + let mut data2: Vec = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let result = ks_twosample( - data1, - data2, + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Emit, ); assert_eq!(result, Err(KSTestError::SampleTooSmall)); - let data1 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); - let data2: Vec = Vec::new(); + let mut data1: Vec = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + let mut data2: Vec = Vec::new(); + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let result = ks_twosample( - data1, - data2, + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); assert_eq!(result, Err(KSTestError::SampleTooSmall)); - let data1 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); - let data2: Vec = Vec::from([f64::NAN]); + let mut data1: Vec = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + let mut data2: Vec = Vec::from([f64::NAN]); + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); + let result = ks_twosample( - data1, - data2, + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Emit, ); @@ -1010,7 +1043,7 @@ mod tests { } #[test] fn test_ks_twosample_nan_in_data_w_emit() { - let data1 = Vec::from([ + let mut data1 = Vec::from([ 0.75857220, 0.45485367, -1.79747176, @@ -1023,7 +1056,7 @@ mod tests { -0.49210878, f64::NAN, ]); - let data2 = Vec::from([ + let mut data2 = Vec::from([ -0.009876332, 0.119263550, -2.048604274, @@ -1041,10 +1074,12 @@ mod tests { 1.636287642, f64::NAN, ]); + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Emit, ) @@ -1054,11 +1089,15 @@ mod tests { } #[test] fn test_ks_twosample_nan_in_data_w_propogate() { - let data1 = Vec::from([0.75857220, -0.01541150, -1.16067678, -0.49210878, f64::NAN]); - let data2 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + let mut data1: Vec = + Vec::from([0.75857220, -0.01541150, -1.16067678, -0.49210878, f64::NAN]); + let mut data2: Vec = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Propogate, ) @@ -1066,11 +1105,14 @@ mod tests { assert!(statistic.is_nan()); assert!(pvalue.is_nan()); - let data1 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); - let data2 = Vec::from([0.75857220, -0.01541150, -1.16067678, -0.49210878, f64::NAN]); + let mut data1: Vec = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + let mut data2: Vec = + Vec::from([0.75857220, -0.01541150, -1.16067678, -0.49210878, f64::NAN]); + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Propogate, ) @@ -1080,21 +1122,27 @@ mod tests { } #[test] fn test_ks_twosample_nan_in_data_w_error() { - let data1 = Vec::from([0.75857220, -0.01541150, -1.16067678, -0.49210878, f64::NAN]); - let data2 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + let mut data1: Vec = + Vec::from([0.75857220, -0.01541150, -1.16067678, -0.49210878, f64::NAN]); + let mut data2: Vec = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let result = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); assert_eq!(result, Err(KSTestError::SampleContainsNaN)); - let data1 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); - let data2 = Vec::from([0.75857220, -0.01541150, -1.16067678, -0.49210878, f64::NAN]); + let mut data1: Vec = Vec::from([-0.009876332, 0.119263550, -2.048604274]); + let mut data2: Vec = + Vec::from([0.75857220, -0.01541150, -1.16067678, -0.49210878, f64::NAN]); + data1.sort_by(|a, b| a.total_cmp(b)); + data2.sort_by(|a, b| a.total_cmp(b)); let result = ks_twosample( - data1.clone(), - data2.clone(), + data1.as_slice(), + data2.as_slice(), KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, );