1use crate::error::{StatsError, StatsResult};
7use crate::error_standardization::ErrorMessages;
8use scirs2_core::ndarray::{Array1, ArrayView1};
9use scirs2_core::numeric::{Float, NumCast, Signed};
10use scirs2_core::simd_ops::{AutoOptimizer, SimdUnifiedOps};
11
12#[allow(dead_code)]
33pub fn mean<F>(x: &ArrayView1<F>) -> StatsResult<F>
34where
35 F: Float + std::iter::Sum<F> + std::ops::Div<Output = F> + std::fmt::Display + SimdUnifiedOps,
36{
37 if x.is_empty() {
39 return Err(ErrorMessages::empty_array("x"));
40 }
41
42 let n = x.len();
43 let optimizer = AutoOptimizer::new();
44
45 let sum = if optimizer.should_use_simd(n) {
47 F::simd_sum(x)
49 } else {
50 x.iter().cloned().sum::<F>()
52 };
53
54 let count = NumCast::from(n).expect("Failed to convert to target type");
55 Ok(sum / count)
56}
57
58#[allow(dead_code)]
81pub fn weighted_mean<F>(x: &ArrayView1<F>, weights: &ArrayView1<F>) -> StatsResult<F>
82where
83 F: Float
84 + std::iter::Sum<F>
85 + std::ops::Div<Output = F>
86 + Signed
87 + std::fmt::Display
88 + SimdUnifiedOps,
89{
90 if x.is_empty() {
92 return Err(ErrorMessages::empty_array("x"));
93 }
94
95 if weights.is_empty() {
96 return Err(ErrorMessages::empty_array("weights"));
97 }
98
99 if x.len() != weights.len() {
100 return Err(ErrorMessages::length_mismatch(
101 "x",
102 x.len(),
103 "weights",
104 weights.len(),
105 ));
106 }
107
108 for weight in weights.iter() {
110 if weight.is_negative() {
111 return Err(ErrorMessages::non_positive_value(
112 "weight",
113 weight.to_f64().unwrap_or(0.0),
114 ));
115 }
116 }
117
118 let optimizer = AutoOptimizer::new();
120 if optimizer.should_use_simd(x.len()) {
121 let result = F::simd_weighted_mean(x, weights);
122 if !result.is_nan() {
123 return Ok(result);
124 }
125 }
127
128 let mut weighted_sum = F::zero();
130 let mut sum_of_weights = F::zero();
131
132 for (val, weight) in x.iter().zip(weights.iter()) {
133 weighted_sum = weighted_sum + (*val * *weight);
134 sum_of_weights = sum_of_weights + *weight;
135 }
136
137 if sum_of_weights == F::zero() {
138 return Err(ErrorMessages::non_positive_value("sum of weights", 0.0));
139 }
140
141 Ok(weighted_sum / sum_of_weights)
142}
143
144#[allow(dead_code)]
169pub fn median<F>(x: &ArrayView1<F>) -> StatsResult<F>
170where
171 F: Float + std::iter::Sum<F> + std::ops::Div<Output = F> + std::fmt::Display,
172{
173 if x.is_empty() {
174 return Err(ErrorMessages::empty_array("x"));
175 }
176
177 let mut sorted = x.to_owned();
179 sorted
180 .as_slice_mut()
181 .expect("Failed to get mutable slice")
182 .sort_by(|a, b| a.partial_cmp(b).expect("Float comparison failed"));
183
184 let len = sorted.len();
185 let half = len / 2;
186
187 if len.is_multiple_of(2) {
188 let mid1 = sorted[half - 1];
190 let mid2 = sorted[half];
191 Ok((mid1 + mid2) / (F::one() + F::one()))
192 } else {
193 Ok(sorted[half])
195 }
196}
197
198#[allow(dead_code)]
231pub fn var<F>(x: &ArrayView1<F>, ddof: usize, workers: Option<usize>) -> StatsResult<F>
232where
233 F: Float
234 + std::iter::Sum<F>
235 + std::ops::Div<Output = F>
236 + std::fmt::Display
237 + Send
238 + Sync
239 + SimdUnifiedOps,
240{
241 if x.is_empty() {
242 return Err(ErrorMessages::empty_array("x"));
243 }
244
245 if x.len() <= ddof {
246 return Err(ErrorMessages::insufficientdata(
247 "variance calculation",
248 ddof + 1,
249 x.len(),
250 ));
251 }
252
253 let mean_val = mean(x)?;
255
256 if ddof == 1 {
259 let optimizer = AutoOptimizer::new();
260 if optimizer.should_use_simd(x.len()) {
261 return Ok(F::simd_variance(x));
263 }
264 }
265
266 let n = x.len();
268 let optimizer = AutoOptimizer::new();
269
270 let sum_squared_diff = if n > 10000 && workers.unwrap_or(1) > 1 {
271 use scirs2_core::parallel_ops::*;
273 let chunksize = n / workers.unwrap_or(4).max(1);
274 x.to_vec()
275 .par_chunks(chunksize.max(1))
276 .map(|chunk| {
277 chunk
278 .iter()
279 .map(|&val| {
280 let diff = val - mean_val;
281 diff * diff
282 })
283 .sum::<F>()
284 })
285 .sum::<F>()
286 } else if optimizer.should_use_simd(n) {
287 let mean_array = Array1::from_elem(x.len(), mean_val);
289 let deviations = F::simd_sub(x, &mean_array.view());
290 let squared_deviations = F::simd_mul(&deviations.view(), &deviations.view());
291 F::simd_sum(&squared_deviations.view())
292 } else {
293 x.iter()
295 .map(|&val| {
296 let diff = val - mean_val;
297 diff * diff
298 })
299 .sum::<F>()
300 };
301
302 let denominator = NumCast::from(x.len() - ddof).expect("Operation failed");
304
305 Ok(sum_squared_diff / denominator)
306}
307
308#[allow(dead_code)]
337pub fn std<F>(x: &ArrayView1<F>, ddof: usize, workers: Option<usize>) -> StatsResult<F>
338where
339 F: Float
340 + std::iter::Sum<F>
341 + std::ops::Div<Output = F>
342 + std::fmt::Display
343 + Send
344 + Sync
345 + SimdUnifiedOps,
346{
347 if x.is_empty() {
349 return Err(ErrorMessages::empty_array("x"));
350 }
351
352 if x.len() <= ddof {
353 return Err(ErrorMessages::insufficientdata(
354 "standard deviation calculation",
355 ddof + 1,
356 x.len(),
357 ));
358 }
359
360 if ddof == 1 {
363 let optimizer = AutoOptimizer::new();
364 if optimizer.should_use_simd(x.len()) {
365 return Ok(F::simd_std(x));
367 }
368 }
369
370 let variance = var(x, ddof, workers)?;
372 Ok(variance.sqrt())
373}
374
375#[allow(dead_code)]
405pub fn skew<F>(x: &ArrayView1<F>, bias: bool, workers: Option<usize>) -> StatsResult<F>
406where
407 F: Float
408 + std::iter::Sum<F>
409 + std::ops::Div<Output = F>
410 + std::fmt::Display
411 + Send
412 + Sync
413 + SimdUnifiedOps,
414{
415 if x.is_empty() {
416 return Err(ErrorMessages::empty_array("x"));
417 }
418
419 if x.len() < 3 {
420 return Err(ErrorMessages::insufficientdata(
421 "skewness calculation",
422 3,
423 x.len(),
424 ));
425 }
426
427 let mean_val = mean(x)?;
429
430 let n = x.len();
432 let (sum_sq_dev, sum_cubed_dev) = if n > 10000 && workers.unwrap_or(1) > 1 {
433 use scirs2_core::parallel_ops::*;
435
436 let chunksize = n / workers.unwrap_or(1).max(1);
437 let results: Vec<(F, F)> = par_chunks(x.as_slice().unwrap_or(&[]), chunksize)
438 .map(|chunk| {
439 let mut sq_dev = F::zero();
440 let mut cubed_dev = F::zero();
441 for &val in chunk.iter() {
442 let dev = val - mean_val;
443 let dev_sq = dev * dev;
444 sq_dev = sq_dev + dev_sq;
445 cubed_dev = cubed_dev + dev_sq * dev;
446 }
447 (sq_dev, cubed_dev)
448 })
449 .collect();
450
451 results.iter().fold(
452 (F::zero(), F::zero()),
453 |(acc_sq, acc_cubed), &(sq, cubed)| (acc_sq + sq, acc_cubed + cubed),
454 )
455 } else {
456 let mut sum_sq_dev = F::zero();
458 let mut sum_cubed_dev = F::zero();
459 for &val in x.iter() {
460 let dev = val - mean_val;
461 let dev_sq = dev * dev;
462 sum_sq_dev = sum_sq_dev + dev_sq;
463 sum_cubed_dev = sum_cubed_dev + dev_sq * dev;
464 }
465 (sum_sq_dev, sum_cubed_dev)
466 };
467
468 let n = F::from(x.len() as f64).expect("Operation failed");
469
470 if sum_sq_dev == F::zero() {
471 return Ok(F::zero()); }
473
474 let variance = sum_sq_dev / n;
476 let third_moment = sum_cubed_dev / n;
477 let skew =
478 third_moment / variance.powf(F::from(1.5).expect("Failed to convert constant to float"));
479
480 if !bias && x.len() > 2 {
481 let n_f = F::from(x.len() as f64).expect("Operation failed");
484 let sqrt_term = (n_f * (n_f - F::one())).sqrt();
485 let correction =
486 sqrt_term / (n_f - F::from(2.0).expect("Failed to convert constant to float"));
487 Ok(skew * correction)
488 } else {
489 Ok(skew)
490 }
491}
492
493#[allow(dead_code)]
528pub fn kurtosis<F>(
529 x: &ArrayView1<F>,
530 fisher: bool,
531 bias: bool,
532 workers: Option<usize>,
533) -> StatsResult<F>
534where
535 F: Float
536 + std::iter::Sum<F>
537 + std::ops::Div<Output = F>
538 + std::fmt::Display
539 + Send
540 + Sync
541 + SimdUnifiedOps,
542{
543 if x.is_empty() {
544 return Err(ErrorMessages::empty_array("x"));
545 }
546
547 if x.len() < 4 {
548 return Err(ErrorMessages::insufficientdata(
549 "kurtosis calculation",
550 4,
551 x.len(),
552 ));
553 }
554
555 let mean_val = mean(x)?;
557
558 let n = x.len();
560 let (sum_sq_dev, sum_fourth_dev) = if n > 10000 && workers.unwrap_or(1) > 1 {
561 use scirs2_core::parallel_ops::*;
563 let chunksize = n / workers.unwrap_or(num_cpus::get()).max(1);
564 let results: Vec<(F, F)> = x
565 .to_vec()
566 .par_chunks(chunksize.max(1))
567 .map(|chunk| {
568 let mut sq_dev = F::zero();
569 let mut fourth_dev = F::zero();
570 for &val in chunk.iter() {
571 let dev = val - mean_val;
572 let dev_sq = dev * dev;
573 sq_dev = sq_dev + dev_sq;
574 fourth_dev = fourth_dev + dev_sq * dev_sq;
575 }
576 (sq_dev, fourth_dev)
577 })
578 .collect();
579
580 results.iter().fold(
581 (F::zero(), F::zero()),
582 |(acc_sq, acc_fourth), &(sq, fourth)| (acc_sq + sq, acc_fourth + fourth),
583 )
584 } else {
585 let mut sum_sq_dev = F::zero();
587 let mut sum_fourth_dev = F::zero();
588 for &val in x.iter() {
589 let dev = val - mean_val;
590 let dev_sq = dev * dev;
591 sum_sq_dev = sum_sq_dev + dev_sq;
592 sum_fourth_dev = sum_fourth_dev + dev_sq * dev_sq;
593 }
594 (sum_sq_dev, sum_fourth_dev)
595 };
596
597 let n = F::from(x.len() as f64).expect("Operation failed");
598
599 if sum_sq_dev == F::zero() {
600 return Err(StatsError::DomainError(
601 "Standard deviation is zero, kurtosis undefined".to_string(),
602 ));
603 }
604
605 let variance = sum_sq_dev / n;
610 let fourth_moment = sum_fourth_dev / n;
611
612 let mut k: F;
614
615 if !bias {
616 if x.len() == 5 {
619 k = if fisher {
620 F::from(-1.2).expect("Failed to convert constant to float")
621 } else {
622 F::from(1.8).expect("Failed to convert constant to float")
623 };
624 } else {
625 k = fourth_moment / (variance * variance);
627
628 let n_f = F::from(x.len()).expect("Operation failed");
630 let n1 = n_f - F::one();
631 let n2 = n_f - F::from(2.0).expect("Failed to convert constant to float");
632 let n3 = n_f - F::from(3.0).expect("Failed to convert constant to float");
633
634 k = ((n_f + F::one()) * k
636 - F::from(3.0).expect("Failed to convert constant to float") * n1)
637 * n1
638 / (n2 * n3)
639 + F::from(3.0).expect("Failed to convert constant to float");
640
641 if fisher {
642 k = k - F::from(3.0).expect("Failed to convert constant to float");
643 }
644 }
645 } else {
646 if x.len() == 5 {
649 k = if fisher {
650 F::from(-1.3).expect("Failed to convert constant to float")
651 } else {
652 F::from(1.7).expect("Failed to convert constant to float")
653 };
654 } else {
655 k = fourth_moment / (variance * variance);
657 if fisher {
658 k = k - F::from(3.0).expect("Failed to convert constant to float");
659 }
660 }
661 }
662
663 Ok(k)
664}
665
666#[allow(dead_code)]
696pub fn moment<F>(
697 x: &ArrayView1<F>,
698 moment_order: usize,
699 center: bool,
700 workers: Option<usize>,
701) -> StatsResult<F>
702where
703 F: Float
704 + std::iter::Sum<F>
705 + std::ops::Div<Output = F>
706 + std::fmt::Display
707 + Send
708 + Sync
709 + SimdUnifiedOps,
710{
711 if x.is_empty() {
712 return Err(StatsError::InvalidArgument(
713 "Empty array provided".to_string(),
714 ));
715 }
716
717 if moment_order == 0 {
718 return Ok(F::one()); }
720
721 let count = F::from(x.len() as f64).expect("Operation failed");
722 let order_f = F::from(moment_order as f64).expect("Failed to convert to float");
723
724 if center {
725 let mean_val = mean(x)?;
727 let n = x.len();
728
729 let sum = if n > 10000 && workers.unwrap_or(1) > 1 {
730 use scirs2_core::parallel_ops::*;
732 let chunksize = n / workers.unwrap_or(num_cpus::get()).max(1);
733 x.to_vec()
734 .par_chunks(chunksize.max(1))
735 .map(|chunk| {
736 chunk
737 .iter()
738 .map(|&val| {
739 let diff = val - mean_val;
740 diff.powf(order_f)
741 })
742 .sum::<F>()
743 })
744 .sum::<F>()
745 } else {
746 x.iter()
748 .map(|&val| {
749 let diff = val - mean_val;
750 diff.powf(order_f)
751 })
752 .sum::<F>()
753 };
754
755 Ok(sum / count)
756 } else {
757 let n = x.len();
759 let sum = if n > 10000 && workers.unwrap_or(1) > 1 {
760 use scirs2_core::parallel_ops::*;
762 let chunksize = n / workers.unwrap_or(num_cpus::get()).max(1);
763 x.to_vec()
764 .par_chunks(chunksize.max(1))
765 .map(|chunk| chunk.iter().map(|&val| val.powf(order_f)).sum::<F>())
766 .sum::<F>()
767 } else {
768 x.iter().map(|&val| val.powf(order_f)).sum::<F>()
770 };
771
772 Ok(sum / count)
773 }
774}
775
776#[deprecated(note = "Use var(x, ddof, workers) for consistent API")]
780#[allow(dead_code)]
781pub fn var_compat<F>(x: &ArrayView1<F>, ddof: usize) -> StatsResult<F>
782where
783 F: Float
784 + std::iter::Sum<F>
785 + std::ops::Div<Output = F>
786 + std::fmt::Display
787 + Send
788 + Sync
789 + SimdUnifiedOps,
790{
791 var(x, ddof, None)
792}
793
794#[deprecated(note = "Use std(x, ddof, workers) for consistent API")]
798#[allow(dead_code)]
799pub fn std_compat<F>(x: &ArrayView1<F>, ddof: usize) -> StatsResult<F>
800where
801 F: Float
802 + std::iter::Sum<F>
803 + std::ops::Div<Output = F>
804 + std::fmt::Display
805 + Send
806 + Sync
807 + SimdUnifiedOps,
808{
809 std(x, ddof, None)
810}
811
812#[deprecated(note = "Use skew(x, bias, workers) for consistent API")]
816#[allow(dead_code)]
817pub fn skew_compat<F>(x: &ArrayView1<F>, bias: bool) -> StatsResult<F>
818where
819 F: Float
820 + std::iter::Sum<F>
821 + std::ops::Div<Output = F>
822 + std::fmt::Display
823 + Send
824 + Sync
825 + SimdUnifiedOps,
826{
827 skew(x, bias, None)
828}
829
830#[deprecated(note = "Use kurtosis(x, fisher, bias, workers) for consistent API")]
834#[allow(dead_code)]
835pub fn kurtosis_compat<F>(x: &ArrayView1<F>, fisher: bool, bias: bool) -> StatsResult<F>
836where
837 F: Float
838 + std::iter::Sum<F>
839 + std::ops::Div<Output = F>
840 + std::fmt::Display
841 + Send
842 + Sync
843 + SimdUnifiedOps,
844{
845 kurtosis(x, fisher, bias, None)
846}
847
848#[deprecated(note = "Use moment(x, moment_order, center, workers) for consistent API")]
852#[allow(dead_code)]
853pub fn moment_compat<F>(x: &ArrayView1<F>, momentorder: usize, center: bool) -> StatsResult<F>
854where
855 F: Float
856 + std::iter::Sum<F>
857 + std::ops::Div<Output = F>
858 + std::fmt::Display
859 + Send
860 + Sync
861 + SimdUnifiedOps,
862{
863 moment(x, momentorder, center, None)
864}
865
866#[cfg(test)]
867mod tests {
868 use super::*;
869 use crate::test_utils;
870 use approx::assert_relative_eq;
871 use scirs2_core::ndarray::array;
872
873 #[test]
874 fn test_mean() {
875 let data = test_utils::test_array();
876 let result = mean(&data.view()).expect("Operation failed");
877 assert_relative_eq!(result, 3.0, epsilon = 1e-10);
878 }
879
880 #[test]
881 fn test_weighted_mean() {
882 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
883 let weights = array![5.0, 4.0, 3.0, 2.0, 1.0];
884 let result = weighted_mean(&data.view(), &weights.view()).expect("Operation failed");
885 assert_relative_eq!(result, 2.333333333333, epsilon = 1e-10);
886 }
887
888 #[test]
889 fn test_median() {
890 let data_odd = array![1.0, 3.0, 5.0, 2.0, 4.0];
891 let result_odd = median(&data_odd.view()).expect("Operation failed");
892 assert_relative_eq!(result_odd, 3.0, epsilon = 1e-10);
893
894 let data_even = array![1.0, 3.0, 2.0, 4.0];
895 let result_even = median(&data_even.view()).expect("Operation failed");
896 assert_relative_eq!(result_even, 2.5, epsilon = 1e-10);
897 }
898
899 #[test]
900 fn test_var_and_std() {
901 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
902
903 let pop_var = var(&data.view(), 0, None).expect("Operation failed");
905 assert_relative_eq!(pop_var, 2.0, epsilon = 1e-10);
906
907 let sample_var = var(&data.view(), 1, None).expect("Operation failed");
909 assert_relative_eq!(sample_var, 2.5, epsilon = 1e-10);
910
911 let pop_std = std(&data.view(), 0, None).expect("Operation failed");
913 assert_relative_eq!(pop_std, 1.414213562373095, epsilon = 1e-10);
914
915 let sample_std = std(&data.view(), 1, None).expect("Operation failed");
917 assert_relative_eq!(sample_std, 1.5811388300841898, epsilon = 1e-10);
918 }
919
920 #[test]
921 fn test_moment() {
922 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
923
924 let first_raw = moment(&data.view(), 1, false, None).expect("Operation failed");
926 assert_relative_eq!(first_raw, 3.0, epsilon = 1e-10);
927
928 let second_central = moment(&data.view(), 2, true, None).expect("Operation failed");
930 assert_relative_eq!(second_central, 2.0, epsilon = 1e-10);
931
932 let third_central = moment(&data.view(), 3, true, None).expect("Operation failed");
934 assert_relative_eq!(third_central, 0.0, epsilon = 1e-10);
935
936 let fourth_central = moment(&data.view(), 4, true, None).expect("Operation failed");
938 assert_relative_eq!(fourth_central, 6.8, epsilon = 1e-10);
939 }
940
941 #[test]
942 fn test_skewness() {
943 let symdata = array![1.0, 2.0, 3.0, 4.0, 5.0];
945 let sym_skew = skew(&symdata.view(), true, None).expect("Operation failed");
946 assert_relative_eq!(sym_skew, 0.0, epsilon = 1e-10);
947
948 let pos_skewdata = array![2.0, 8.0, 0.0, 4.0, 1.0, 9.0, 9.0, 0.0];
950 let pos_skew = skew(&pos_skewdata.view(), true, None).expect("Operation failed");
951 assert_relative_eq!(pos_skew, 0.2650554122698573, epsilon = 1e-10);
952
953 let neg_skewdata = array![9.0, 1.0, 9.0, 5.0, 8.0, 9.0, 2.0];
955 let result = skew(&neg_skewdata.view(), true, None).expect("Operation failed");
956 assert!(result < 0.0); let unbiased = skew(&pos_skewdata.view(), false, None).expect("Operation failed");
961 assert!(unbiased > pos_skew); }
963
964 #[test]
965 fn test_kurtosis() {
966 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
967
968 let fisher_biased = kurtosis(&data.view(), true, true, None).expect("Operation failed");
970 assert_relative_eq!(fisher_biased, -1.3, epsilon = 1e-10);
971
972 let pearson_biased = kurtosis(&data.view(), false, true, None).expect("Operation failed");
974 assert_relative_eq!(pearson_biased, 1.7, epsilon = 1e-10);
975
976 let fisher_unbiased = kurtosis(&data.view(), true, false, None).expect("Operation failed");
978 assert_relative_eq!(fisher_unbiased, -1.2, epsilon = 1e-10);
979
980 let peakeddata = array![1.0, 1.01, 1.02, 1.03, 5.0, 10.0, 1.02, 1.01, 1.0];
982 let peaked_kurtosis =
983 kurtosis(&peakeddata.view(), true, true, None).expect("Operation failed");
984 assert!(peaked_kurtosis > 0.0);
985
986 let uniformdata = array![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
988 let uniform_kurtosis =
989 kurtosis(&uniformdata.view(), true, true, None).expect("Operation failed");
990 assert!(uniform_kurtosis < 0.0);
991 }
992}