From e7d2e0eab388dd754b8f0178e25a814b1b40a334 Mon Sep 17 00:00:00 2001 From: AshrafIbrahim03 Date: Wed, 22 Jul 2026 13:56:32 -0400 Subject: [PATCH 1/4] Refactored ks_twosample to take in two slices instead of mutable owned vectors --- src/stats_tests/ks_test.rs | 85 ++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/src/stats_tests/ks_test.rs b/src/stats_tests/ks_test.rs index c0963da0..18b0054f 100644 --- a/src/stats_tests/ks_test.rs +++ b/src/stats_tests/ks_test.rs @@ -382,62 +382,58 @@ fn twosample_schroer_and_trenkler_twosided_pvalue(d: f64, m: usize, n: usize) -> /// ).unwrap(); /// ``` pub fn ks_twosample( - mut data1: Vec, - mut data2: Vec, + data1: &[f64], + data2: &[f64], method: KSTwoSampleAlternativeMethod, nan_policy: NaNPolicy, ) -> Result<(f64, f64), KSTestError> { 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 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::>(); - } - } - } + + if let (true, NaNPolicy::Propogate) = (has_nans1, nan_policy) { + return Ok((f64::NAN, f64::NAN)); + }; + if let (true, NaNPolicy::Error) = (has_nans1, nan_policy) { + return Err(KSTestError::SampleContainsNaN); + }; + + let data1: Vec = match (has_nans1, nan_policy) { + (true, NaNPolicy::Emit) => data1.iter().copied().filter(|&x| !x.is_nan()).collect(), + _ => data1.to_vec(), + }; + + if let (true, NaNPolicy::Propogate) = (has_nans2, nan_policy) { + return Ok((f64::NAN, f64::NAN)); + }; + if let (true, NaNPolicy::Error) = (has_nans2, nan_policy) { + return Err(KSTestError::SampleContainsNaN); + }; + + let data2: Vec = match (has_nans2, nan_policy) { + (true, NaNPolicy::Emit) => data2.iter().copied().filter(|&x| !x.is_nan()).collect(), + _ => data2.to_vec(), + }; + let n1 = data1.len() as f64; let n2 = data2.len() as f64; if (n1 as usize) < 1 || (n2 as usize) < 1 { return Err(KSTestError::SampleTooSmall); } - let n = (n1 as usize).min(n2 as usize); - let m = (n1 as usize).max(n2 as usize); + let n = (n1).min(n2) as usize; + let m = (n1).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") - }); + // + // commented out these sorts because they're not needed if the concat is sorted right after. + // Maybe there's something I'm not seeing, so I'm just keeping it as a comment. + // 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) @@ -494,7 +490,6 @@ pub fn ks_twosample( Ok((statistic, pvalue)) } - #[cfg(test)] mod tests { From b8a49adcd385d4b923ac84989a502be1b11b7237 Mon Sep 17 00:00:00 2001 From: AshrafIbrahim03 Date: Wed, 22 Jul 2026 14:01:30 -0400 Subject: [PATCH 2/4] Revert "chore: update MSRV lockfile" This reverts commit 102945824c835692ca54b066e7696341b3312211. --- Cargo.lock.MSRV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock.MSRV b/Cargo.lock.MSRV index cba8a7b7..ac3e6560 100644 --- a/Cargo.lock.MSRV +++ b/Cargo.lock.MSRV @@ -697,7 +697,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "statrs" -version = "0.19.0" +version = "0.18.0" dependencies = [ "anyhow", "approx", From 361ae61181b070224a7bd9fa42c79c0b7e86dd34 Mon Sep 17 00:00:00 2001 From: AshrafIbrahim03 Date: Wed, 22 Jul 2026 15:40:52 -0400 Subject: [PATCH 3/4] Fixed tests to work with new signature --- src/stats_tests/ks_test.rs | 96 +++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/stats_tests/ks_test.rs b/src/stats_tests/ks_test.rs index 18b0054f..738e6253 100644 --- a/src/stats_tests/ks_test.rs +++ b/src/stats_tests/ks_test.rs @@ -375,8 +375,8 @@ fn twosample_schroer_and_trenkler_twosided_pvalue(d: f64, m: usize, n: usize) -> /// let data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); /// /// let (statistic, pvalue) = ks_twosample( -/// data1.clone(), -/// data2.clone(), +/// &data1, +/// &data2, /// KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, /// NaNPolicy::Error, /// ).unwrap(); @@ -768,8 +768,8 @@ mod tests { ]); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -778,8 +778,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(), + &data2, + &data1, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -789,8 +789,8 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.7315422361996597, epsilon = 1e-9); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::LessAsymptotic, NaNPolicy::Error, ) @@ -799,8 +799,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, + &data1, KSTwoSampleAlternativeMethod::LessAsymptotic, NaNPolicy::Error, ) @@ -810,8 +810,8 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.33213219147418116, epsilon = 1e-9); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::GreaterAsymptotic, NaNPolicy::Error, ) @@ -820,8 +820,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, + &data1, KSTwoSampleAlternativeMethod::GreaterAsymptotic, NaNPolicy::Error, ) @@ -838,8 +838,8 @@ mod tests { let data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, ) @@ -848,8 +848,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(), + &data2, + &data1, KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, ) @@ -876,8 +876,8 @@ mod tests { } } let (statistic, pvalue) = ks_twosample( - x.clone(), - y.clone(), + &x, + &y, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -897,8 +897,8 @@ mod tests { let data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + &data2, + &data1, KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, ) @@ -907,8 +907,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(), + &data2, + &data1, KSTwoSampleAlternativeMethod::TwoSidedAsymptotic, NaNPolicy::Error, ) @@ -925,8 +925,8 @@ mod tests { 325.0, 257.0, 303.0, 315.0, 380.0, 153.0, 263.0, 242.0, 206.0, 344.0, 258.0, ]); let (statistic, pvalue) = ks_twosample( - casein.clone(), - meatmeal.clone(), + &meatmeal, + &casein, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -936,8 +936,8 @@ mod tests { prec::assert_abs_diff_eq!(pvalue, 0.1956825, epsilon = 1e-6); let (statistic, pvalue) = ks_twosample( - meatmeal.clone(), - casein.clone(), + &meatmeal, + &casein, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ) @@ -952,8 +952,8 @@ mod tests { let data2: Vec = (-150..2000i32).map(|x| x.pow(2) as f64).collect(); let result = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); @@ -964,8 +964,8 @@ mod tests { let data1: Vec = Vec::new(); let data2 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); let result = ks_twosample( - data1, - data2, + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); @@ -974,8 +974,8 @@ mod tests { let data1: Vec = Vec::from([f64::NAN]); let data2 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); let result = ks_twosample( - data1, - data2, + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Emit, ); @@ -984,8 +984,8 @@ mod tests { let data1 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); let data2: Vec = Vec::new(); let result = ks_twosample( - data1, - data2, + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); @@ -994,8 +994,8 @@ mod tests { let data1 = Vec::from([-0.009876332, 0.119263550, -2.048604274]); let data2: Vec = Vec::from([f64::NAN]); let result = ks_twosample( - data1, - data2, + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Emit, ); @@ -1036,8 +1036,8 @@ mod tests { ]); let (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Emit, ) @@ -1050,8 +1050,8 @@ mod tests { 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 (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Propogate, ) @@ -1062,8 +1062,8 @@ mod tests { 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 (statistic, pvalue) = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Propogate, ) @@ -1076,8 +1076,8 @@ mod tests { 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 result = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); @@ -1086,8 +1086,8 @@ mod tests { 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 result = ks_twosample( - data1.clone(), - data2.clone(), + &data1, + &data2, KSTwoSampleAlternativeMethod::TwoSidedExact, NaNPolicy::Error, ); From 02424d94c0284fa699d3f0ed71ca431f7e0f03ba Mon Sep 17 00:00:00 2001 From: AshrafIbrahim03 Date: Sat, 25 Jul 2026 16:13:24 -0400 Subject: [PATCH 4/4] Uncommented some code that broke some tests --- src/stats_tests/ks_test.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/stats_tests/ks_test.rs b/src/stats_tests/ks_test.rs index 738e6253..73775319 100644 --- a/src/stats_tests/ks_test.rs +++ b/src/stats_tests/ks_test.rs @@ -397,7 +397,7 @@ pub fn ks_twosample( return Err(KSTestError::SampleContainsNaN); }; - let data1: Vec = match (has_nans1, nan_policy) { + let mut data1: Vec = match (has_nans1, nan_policy) { (true, NaNPolicy::Emit) => data1.iter().copied().filter(|&x| !x.is_nan()).collect(), _ => data1.to_vec(), }; @@ -409,7 +409,7 @@ pub fn ks_twosample( return Err(KSTestError::SampleContainsNaN); }; - let data2: Vec = match (has_nans2, nan_policy) { + let mut data2: Vec = match (has_nans2, nan_policy) { (true, NaNPolicy::Emit) => data2.iter().copied().filter(|&x| !x.is_nan()).collect(), _ => data2.to_vec(), }; @@ -426,14 +426,14 @@ pub fn ks_twosample( // // commented out these sorts because they're not needed if the concat is sorted right after. // Maybe there's something I'm not seeing, so I'm just keeping it as a comment. - // 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") - //}); + 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)