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(since = "0.1.0", 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(since = "0.1.0", 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(
816 since = "0.1.0",
817 note = "Use skew(x, bias, workers) for consistent API"
818)]
819#[allow(dead_code)]
820pub fn skew_compat<F>(x: &ArrayView1<F>, bias: bool) -> StatsResult<F>
821where
822 F: Float
823 + std::iter::Sum<F>
824 + std::ops::Div<Output = F>
825 + std::fmt::Display
826 + Send
827 + Sync
828 + SimdUnifiedOps,
829{
830 skew(x, bias, None)
831}
832
833#[deprecated(
837 since = "0.1.0",
838 note = "Use kurtosis(x, fisher, bias, workers) for consistent API"
839)]
840#[allow(dead_code)]
841pub fn kurtosis_compat<F>(x: &ArrayView1<F>, fisher: bool, bias: bool) -> StatsResult<F>
842where
843 F: Float
844 + std::iter::Sum<F>
845 + std::ops::Div<Output = F>
846 + std::fmt::Display
847 + Send
848 + Sync
849 + SimdUnifiedOps,
850{
851 kurtosis(x, fisher, bias, None)
852}
853
854#[deprecated(
858 since = "0.1.0",
859 note = "Use moment(x, moment_order, center, workers) for consistent API"
860)]
861#[allow(dead_code)]
862pub fn moment_compat<F>(x: &ArrayView1<F>, momentorder: usize, center: bool) -> StatsResult<F>
863where
864 F: Float
865 + std::iter::Sum<F>
866 + std::ops::Div<Output = F>
867 + std::fmt::Display
868 + Send
869 + Sync
870 + SimdUnifiedOps,
871{
872 moment(x, momentorder, center, None)
873}
874
875#[cfg(test)]
876mod tests {
877 use super::*;
878 use crate::test_utils;
879 use approx::assert_relative_eq;
880 use scirs2_core::ndarray::array;
881
882 #[test]
883 fn test_mean() {
884 let data = test_utils::test_array();
885 let result = mean(&data.view()).expect("Operation failed");
886 assert_relative_eq!(result, 3.0, epsilon = 1e-10);
887 }
888
889 #[test]
890 fn test_weighted_mean() {
891 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
892 let weights = array![5.0, 4.0, 3.0, 2.0, 1.0];
893 let result = weighted_mean(&data.view(), &weights.view()).expect("Operation failed");
894 assert_relative_eq!(result, 2.333333333333, epsilon = 1e-10);
895 }
896
897 #[test]
898 fn test_median() {
899 let data_odd = array![1.0, 3.0, 5.0, 2.0, 4.0];
900 let result_odd = median(&data_odd.view()).expect("Operation failed");
901 assert_relative_eq!(result_odd, 3.0, epsilon = 1e-10);
902
903 let data_even = array![1.0, 3.0, 2.0, 4.0];
904 let result_even = median(&data_even.view()).expect("Operation failed");
905 assert_relative_eq!(result_even, 2.5, epsilon = 1e-10);
906 }
907
908 #[test]
909 fn test_var_and_std() {
910 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
911
912 let pop_var = var(&data.view(), 0, None).expect("Operation failed");
914 assert_relative_eq!(pop_var, 2.0, epsilon = 1e-10);
915
916 let sample_var = var(&data.view(), 1, None).expect("Operation failed");
918 assert_relative_eq!(sample_var, 2.5, epsilon = 1e-10);
919
920 let pop_std = std(&data.view(), 0, None).expect("Operation failed");
922 assert_relative_eq!(pop_std, 1.414213562373095, epsilon = 1e-10);
923
924 let sample_std = std(&data.view(), 1, None).expect("Operation failed");
926 assert_relative_eq!(sample_std, 1.5811388300841898, epsilon = 1e-10);
927 }
928
929 #[test]
930 fn test_moment() {
931 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
932
933 let first_raw = moment(&data.view(), 1, false, None).expect("Operation failed");
935 assert_relative_eq!(first_raw, 3.0, epsilon = 1e-10);
936
937 let second_central = moment(&data.view(), 2, true, None).expect("Operation failed");
939 assert_relative_eq!(second_central, 2.0, epsilon = 1e-10);
940
941 let third_central = moment(&data.view(), 3, true, None).expect("Operation failed");
943 assert_relative_eq!(third_central, 0.0, epsilon = 1e-10);
944
945 let fourth_central = moment(&data.view(), 4, true, None).expect("Operation failed");
947 assert_relative_eq!(fourth_central, 6.8, epsilon = 1e-10);
948 }
949
950 #[test]
951 fn test_skewness() {
952 let symdata = array![1.0, 2.0, 3.0, 4.0, 5.0];
954 let sym_skew = skew(&symdata.view(), true, None).expect("Operation failed");
955 assert_relative_eq!(sym_skew, 0.0, epsilon = 1e-10);
956
957 let pos_skewdata = array![2.0, 8.0, 0.0, 4.0, 1.0, 9.0, 9.0, 0.0];
959 let pos_skew = skew(&pos_skewdata.view(), true, None).expect("Operation failed");
960 assert_relative_eq!(pos_skew, 0.2650554122698573, epsilon = 1e-10);
961
962 let neg_skewdata = array![9.0, 1.0, 9.0, 5.0, 8.0, 9.0, 2.0];
964 let result = skew(&neg_skewdata.view(), true, None).expect("Operation failed");
965 assert!(result < 0.0); let unbiased = skew(&pos_skewdata.view(), false, None).expect("Operation failed");
970 assert!(unbiased > pos_skew); }
972
973 #[test]
974 fn test_kurtosis() {
975 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
976
977 let fisher_biased = kurtosis(&data.view(), true, true, None).expect("Operation failed");
979 assert_relative_eq!(fisher_biased, -1.3, epsilon = 1e-10);
980
981 let pearson_biased = kurtosis(&data.view(), false, true, None).expect("Operation failed");
983 assert_relative_eq!(pearson_biased, 1.7, epsilon = 1e-10);
984
985 let fisher_unbiased = kurtosis(&data.view(), true, false, None).expect("Operation failed");
987 assert_relative_eq!(fisher_unbiased, -1.2, epsilon = 1e-10);
988
989 let peakeddata = array![1.0, 1.01, 1.02, 1.03, 5.0, 10.0, 1.02, 1.01, 1.0];
991 let peaked_kurtosis =
992 kurtosis(&peakeddata.view(), true, true, None).expect("Operation failed");
993 assert!(peaked_kurtosis > 0.0);
994
995 let uniformdata = array![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
997 let uniform_kurtosis =
998 kurtosis(&uniformdata.view(), true, true, None).expect("Operation failed");
999 assert!(uniform_kurtosis < 0.0);
1000 }
1001}