scirs2_core/ndarray_ext/stats/descriptive.rs
1//! Descriptive statistics for ndarray arrays
2//!
3//! This module provides descriptive statistical functions such as mean, median,
4//! standard deviation, variance, min, max, etc. for ndarray arrays.
5
6use ::ndarray::{Array, ArrayView, Axis, Dimension, Ix1, Ix2};
7use num_traits::{Float, FromPrimitive};
8
9/// Calculate the mean of array elements (2D arrays)
10///
11/// # Errors
12///
13/// Returns an error if the array is empty or if conversion fails.
14///
15/// # Panics
16///
17/// Panics if type conversion from usize fails.
18///
19/// # Arguments
20///
21/// * `array` - The input 2D array
22/// * `axis` - Optional axis along which to compute the mean (None for global mean)
23///
24/// # Returns
25///
26/// The mean of the array elements
27///
28/// # Examples
29///
30/// ```
31/// use ndarray::{array, Axis};
32/// use scirs2_core::ndarray_ext::stats::mean_2d;
33///
34/// let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
35///
36/// // Global mean
37/// let global_mean = mean_2d(&a.view(), None).expect("Operation failed");
38/// assert_eq!(global_mean[0], 3.5);
39///
40/// // Mean along axis 0 (columns)
41/// let col_means = mean_2d(&a.view(), Some(Axis(0))).expect("Operation failed");
42/// assert_eq!(col_means.len(), 3);
43/// assert_eq!(col_means[0], 2.5);
44/// assert_eq!(col_means[1], 3.5);
45/// assert_eq!(col_means[2], 4.5);
46/// ```
47#[allow(dead_code)]
48pub fn mean_2d<T>(
49 array: &ArrayView<T, Ix2>,
50 axis: Option<Axis>,
51) -> Result<Array<T, Ix1>, &'static str>
52where
53 T: Clone + Float + FromPrimitive,
54{
55 if array.is_empty() {
56 return Err("Cannot compute mean of an empty array");
57 }
58
59 if let Some(ax) = axis {
60 let (rows, cols) = (array.shape()[0], array.shape()[1]);
61
62 match ax.index() {
63 0 => {
64 // Mean along axis 0 (columns)
65 let mut result = Array::<T, Ix1>::zeros(cols);
66 let n = T::from_usize(rows).expect("Operation failed");
67
68 for j in 0..cols {
69 let mut sum = T::zero();
70 for i in 0..rows {
71 sum = sum + array[[i, j]];
72 }
73 result[j] = sum / n;
74 }
75
76 Ok(result)
77 }
78 1 => {
79 // Mean along axis 1 (rows)
80 let mut result = Array::<T, Ix1>::zeros(rows);
81 let n = T::from_usize(cols).expect("Operation failed");
82
83 for i in 0..rows {
84 let mut sum = T::zero();
85 for j in 0..cols {
86 sum = sum + array[[i, j]];
87 }
88 result[0] = sum / n;
89 }
90
91 Ok(result)
92 }
93 _ => Err("Axis index out of bounds for 2D array"),
94 }
95 } else {
96 // Global mean
97 let total_elements = array.len();
98 let mut sum = T::zero();
99
100 for &val in array {
101 sum = sum + val;
102 }
103
104 let count = T::from_usize(total_elements).ok_or("Cannot convert array length to T")?;
105 Ok(Array::from_elem(1, sum / count))
106 }
107}
108
109/// Calculate the median of array elements (2D arrays)
110///
111/// # Errors
112///
113/// Returns an error if the array is empty.
114///
115/// # Panics
116///
117/// Panics if partial comparison fails or type conversion fails.
118///
119/// # Arguments
120///
121/// * `array` - The input 2D array
122/// * `axis` - Optional axis along which to compute the median (None for global median)
123///
124/// # Returns
125///
126/// The median of the array elements
127///
128/// # Examples
129///
130/// ```
131/// use ndarray::{array, Axis};
132/// use scirs2_core::ndarray_ext::stats::median_2d;
133///
134/// let a = array![[1.0, 3.0, 5.0], [2.0, 4.0, 6.0]];
135///
136/// // Global median
137/// let global_median = median_2d(&a.view(), None).expect("Operation failed");
138/// assert_eq!(global_median[0], 3.5);
139///
140/// // Median along axis 0 (columns)
141/// let col_medians = median_2d(&a.view(), Some(Axis(0))).expect("Operation failed");
142/// assert_eq!(col_medians.len(), 3);
143/// assert_eq!(col_medians[0], 1.5);
144/// assert_eq!(col_medians[1], 3.5);
145/// assert_eq!(col_medians[2], 5.5);
146/// ```
147#[allow(dead_code)]
148pub fn median_2d<T>(
149 array: &ArrayView<T, Ix2>,
150 axis: Option<Axis>,
151) -> Result<Array<T, Ix1>, &'static str>
152where
153 T: Clone + Float + FromPrimitive,
154{
155 if array.is_empty() {
156 return Err("Cannot compute median of an empty array");
157 }
158
159 if let Some(ax) = axis {
160 let (rows, cols) = (array.shape()[0], array.shape()[1]);
161
162 match ax.index() {
163 0 => {
164 // Median along axis 0 (columns)
165 let mut result = Array::<T, Ix1>::zeros(cols);
166
167 for j in 0..cols {
168 let mut column_values = Vec::with_capacity(rows);
169 for i in 0..rows {
170 column_values.push(array[[i, j]]);
171 }
172
173 column_values
174 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
175
176 let median_value = if column_values.len() % 2 == 0 {
177 let mid = column_values.len() / 2;
178 (column_values[mid - 1] + column_values[mid])
179 / T::from_f64(2.0).expect("Operation failed")
180 } else {
181 column_values[column_values.len() / 2]
182 };
183
184 result[j] = median_value;
185 }
186
187 Ok(result)
188 }
189 1 => {
190 // Median along axis 1 (rows)
191 let mut result = Array::<T, Ix1>::zeros(rows);
192
193 for i in 0..rows {
194 let mut row_values = Vec::with_capacity(cols);
195 for j in 0..cols {
196 row_values.push(array[[i, j]]);
197 }
198
199 row_values
200 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
201
202 let median_value = if row_values.len() % 2 == 0 {
203 let mid = row_values.len() / 2;
204 (row_values[mid - 1] + row_values[mid])
205 / T::from_f64(2.0).expect("Operation failed")
206 } else {
207 row_values[row_values.len() / 2]
208 };
209
210 result[0] = median_value;
211 }
212
213 Ok(result)
214 }
215 _ => Err("Axis index out of bounds for 2D array"),
216 }
217 } else {
218 // Global median
219 let mut values: Vec<_> = array.iter().copied().collect();
220 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
221
222 let median_value = if values.len() % 2 == 0 {
223 let mid = values.len() / 2;
224 (values[mid - 1] + values[mid]) / T::from_f64(2.0).expect("Operation failed")
225 } else {
226 values[values.len() / 2]
227 };
228
229 Ok(Array::from_elem(1, median_value))
230 }
231}
232
233/// Calculate the standard deviation of array elements (2D arrays)
234///
235/// # Errors
236///
237/// Returns an error if the array is empty or variance calculation fails.
238///
239/// # Panics
240///
241/// Panics if variance calculation panics.
242///
243/// # Arguments
244///
245/// * `array` - The input 2D array
246/// * `axis` - Optional axis along which to compute the std dev (None for global std dev)
247/// * `ddof` - Delta degrees of freedom (default 0)
248///
249/// # Returns
250///
251/// The standard deviation of the array elements
252///
253/// # Examples
254///
255/// ```
256/// use ndarray::{array, Axis};
257/// use scirs2_core::ndarray_ext::stats::std_dev_2d;
258///
259/// let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
260///
261/// // Global standard deviation
262/// let global_std = std_dev_2d(&a.view(), None, 1).expect("Operation failed");
263/// assert!((global_std[0] - 1.87082869339_f64).abs() < 1e-10);
264///
265/// // Standard deviation along axis 0 (columns)
266/// let col_stds = std_dev_2d(&a.view(), Some(Axis(0)), 1).expect("Operation failed");
267/// assert_eq!(col_stds.len(), 3);
268/// ```
269#[allow(dead_code)]
270pub fn std_dev_2d<T>(
271 array: &ArrayView<T, Ix2>,
272 axis: Option<Axis>,
273 ddof: usize,
274) -> Result<Array<T, Ix1>, &'static str>
275where
276 T: Clone + Float + FromPrimitive,
277{
278 let var_result = variance_2d(array, axis, ddof)?;
279 Ok(var_result.mapv(|x| x.sqrt()))
280}
281
282/// Calculate the variance of array elements (2D arrays)
283///
284/// # Errors
285///
286/// Returns an error if the array is empty or if conversion fails.
287///
288/// # Panics
289///
290/// Panics if type conversion from usize fails.
291///
292/// # Arguments
293///
294/// * `array` - The input 2D array
295/// * `axis` - Optional axis along which to compute the variance (None for global variance)
296/// * `ddof` - Delta degrees of freedom (default 0)
297///
298/// # Returns
299///
300/// The variance of the array elements
301///
302/// # Examples
303///
304/// ```
305/// use ndarray::{array, Axis};
306/// use scirs2_core::ndarray_ext::stats::variance_2d;
307///
308/// let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
309///
310/// // Global variance
311/// let global_var = variance_2d(&a.view(), None, 1).expect("Operation failed");
312/// assert!((global_var[0] - 3.5_f64).abs() < 1e-10);
313///
314/// // Variance along axis 0 (columns)
315/// let col_vars = variance_2d(&a.view(), Some(Axis(0)), 1).expect("Operation failed");
316/// assert_eq!(col_vars.len(), 3);
317/// ```
318#[allow(dead_code)]
319pub fn variance_2d<T>(
320 array: &ArrayView<T, Ix2>,
321 axis: Option<Axis>,
322 ddof: usize,
323) -> Result<Array<T, Ix1>, &'static str>
324where
325 T: Clone + Float + FromPrimitive,
326{
327 if array.is_empty() {
328 return Err("Cannot compute variance of an empty array");
329 }
330
331 if let Some(ax) = axis {
332 let (rows, cols) = (array.shape()[0], array.shape()[1]);
333
334 match ax.index() {
335 0 => {
336 // Variance along axis 0 (columns)
337 let means = mean_2d(array, Some(ax))?;
338
339 if rows <= ddof {
340 return Err("Not enough data points for variance calculation with given ddof");
341 }
342
343 let mut result = Array::<T, Ix1>::zeros(cols);
344
345 for j in 0..cols {
346 let mut sum_sq_diff = T::zero();
347 for i in 0..rows {
348 let diff = array[[i, j]] - means[j];
349 sum_sq_diff = sum_sq_diff + (diff * diff);
350 }
351
352 let divisor = T::from_usize(rows - ddof).expect("Operation failed");
353 result[j] = sum_sq_diff / divisor;
354 }
355
356 Ok(result)
357 }
358 1 => {
359 // Variance along axis 1 (rows)
360 let means = mean_2d(array, Some(ax))?;
361
362 if cols <= ddof {
363 return Err("Not enough data points for variance calculation with given ddof");
364 }
365
366 let mut result = Array::<T, Ix1>::zeros(rows);
367
368 for i in 0..rows {
369 let mut sum_sq_diff = T::zero();
370 for j in 0..cols {
371 let diff = array[[i, j]] - means[i];
372 sum_sq_diff = sum_sq_diff + (diff * diff);
373 }
374
375 let divisor = T::from_usize(cols - ddof).expect("Operation failed");
376 result[0] = sum_sq_diff / divisor;
377 }
378
379 Ok(result)
380 }
381 _ => Err("Axis index out of bounds for 2D array"),
382 }
383 } else {
384 // Global variance
385 let total_elements = array.len();
386
387 if total_elements <= ddof {
388 return Err("Not enough data points for variance calculation with given ddof");
389 }
390
391 // Calculate global mean
392 let global_mean = mean_2d(array, None)?[0];
393
394 // Calculate sum of squared differences from the mean
395 let mut sum_sq_diff = T::zero();
396 for &val in array {
397 let diff = val - global_mean;
398 sum_sq_diff = sum_sq_diff + (diff * diff);
399 }
400
401 let divisor = T::from_usize(total_elements - ddof).expect("Operation failed");
402
403 Ok(Array::from_elem(1, sum_sq_diff / divisor))
404 }
405}
406
407// Need to implement the following functions:
408// min_2d, max_2d, sum_2d, product_2d, percentile_2d, mean, median, variance, std_dev, min, max, percentile
409
410// Let's continue with min_2d and max_2d
411
412/// Calculate the minimum value(s) of array elements (2D arrays)
413///
414/// # Errors
415///
416/// Returns an error if the array is empty.
417///
418/// # Arguments
419///
420/// * `array` - The input 2D array
421/// * `axis` - Optional axis along which to compute the minimum (None for global minimum)
422///
423/// # Returns
424///
425/// The minimum value(s) of the array elements
426#[allow(dead_code)]
427pub fn min_2d<T>(
428 array: &ArrayView<T, Ix2>,
429 axis: Option<Axis>,
430) -> Result<Array<T, Ix1>, &'static str>
431where
432 T: Clone + Float,
433{
434 if array.is_empty() {
435 return Err("Cannot compute minimum of an empty array");
436 }
437
438 match axis {
439 Some(ax) => {
440 let (rows, cols) = (array.shape()[0], array.shape()[1]);
441
442 match ax.index() {
443 0 => {
444 // Min along axis 0 (columns)
445 let mut result = Array::<T, Ix1>::zeros(cols);
446
447 for j in 0..cols {
448 let mut min_val = array[[0, j]];
449 for i in 1..rows {
450 if array[[i, j]] < min_val {
451 min_val = array[[i, j]];
452 }
453 }
454 result[j] = min_val;
455 }
456
457 Ok(result)
458 }
459 1 => {
460 // Min along axis 1 (rows)
461 let mut result = Array::<T, Ix1>::zeros(rows);
462
463 for i in 0..rows {
464 let mut min_val = array[[i, 0]];
465 for j in 1..cols {
466 if array[[i, j]] < min_val {
467 min_val = array[[i, j]];
468 }
469 }
470 result[i] = min_val;
471 }
472
473 Ok(result)
474 }
475 _ => Err("Axis index out of bounds for 2D array"),
476 }
477 }
478 None => {
479 // Global min
480 let mut min_val = array[[0, 0]];
481
482 for &val in array {
483 if val < min_val {
484 min_val = val;
485 }
486 }
487
488 Ok(Array::from_elem(1, min_val))
489 }
490 }
491}
492
493/// Calculate the maximum value(s) of array elements (2D arrays)
494///
495/// # Errors
496///
497/// Returns an error if the array is empty.
498///
499/// # Arguments
500///
501/// * `array` - The input 2D array
502/// * `axis` - Optional axis along which to compute the maximum (None for global maximum)
503///
504/// # Returns
505///
506/// The maximum value(s) of the array elements
507#[allow(dead_code)]
508pub fn max_2d<T>(
509 array: &ArrayView<T, Ix2>,
510 axis: Option<Axis>,
511) -> Result<Array<T, Ix1>, &'static str>
512where
513 T: Clone + Float,
514{
515 if array.is_empty() {
516 return Err("Cannot compute maximum of an empty array");
517 }
518
519 match axis {
520 Some(ax) => {
521 let (rows, cols) = (array.shape()[0], array.shape()[1]);
522
523 match ax.index() {
524 0 => {
525 // Max along axis 0 (columns)
526 let mut result = Array::<T, Ix1>::zeros(cols);
527
528 for j in 0..cols {
529 let mut max_val = array[[0, j]];
530 for i in 1..rows {
531 if array[[i, j]] > max_val {
532 max_val = array[[i, j]];
533 }
534 }
535 result[j] = max_val;
536 }
537
538 Ok(result)
539 }
540 1 => {
541 // Max along axis 1 (rows)
542 let mut result = Array::<T, Ix1>::zeros(rows);
543
544 for i in 0..rows {
545 let mut max_val = array[[i, 0]];
546 for j in 1..cols {
547 if array[[i, j]] > max_val {
548 max_val = array[[i, j]];
549 }
550 }
551 result[i] = max_val;
552 }
553
554 Ok(result)
555 }
556 _ => Err("Axis index out of bounds for 2D array"),
557 }
558 }
559 None => {
560 // Global max
561 let mut max_val = array[[0, 0]];
562
563 for &val in array {
564 if val > max_val {
565 max_val = val;
566 }
567 }
568
569 Ok(Array::from_elem(1, max_val))
570 }
571 }
572}
573
574/// Calculate the sum of array elements (2D arrays)
575///
576/// # Errors
577///
578/// Returns an error if the array is empty.
579///
580/// # Arguments
581///
582/// * `array` - The input 2D array
583/// * `axis` - Optional axis along which to compute the sum (None for global sum)
584///
585/// # Returns
586///
587/// The sum of the array elements
588#[allow(dead_code)]
589pub fn sum_2d<T>(
590 array: &ArrayView<T, Ix2>,
591 axis: Option<Axis>,
592) -> Result<Array<T, Ix1>, &'static str>
593where
594 T: Clone + Float,
595{
596 if array.is_empty() {
597 return Err("Cannot compute sum of an empty array");
598 }
599
600 match axis {
601 Some(ax) => {
602 let (rows, cols) = (array.shape()[0], array.shape()[1]);
603
604 match ax.index() {
605 0 => {
606 // Sum along axis 0 (columns)
607 let mut result = Array::<T, Ix1>::zeros(cols);
608
609 for j in 0..cols {
610 let mut sum = T::zero();
611 for i in 0..rows {
612 sum = sum + array[[i, j]];
613 }
614 result[j] = sum;
615 }
616
617 Ok(result)
618 }
619 1 => {
620 // Sum along axis 1 (rows)
621 let mut result = Array::<T, Ix1>::zeros(rows);
622
623 for i in 0..rows {
624 let mut sum = T::zero();
625 for j in 0..cols {
626 sum = sum + array[[i, j]];
627 }
628 result[0] = sum;
629 }
630
631 Ok(result)
632 }
633 _ => Err("Axis index out of bounds for 2D array"),
634 }
635 }
636 None => {
637 // Global sum
638 let mut sum = T::zero();
639
640 for &val in array {
641 sum = sum + val;
642 }
643
644 Ok(Array::from_elem(1, sum))
645 }
646 }
647}
648
649/// Calculate the product of array elements (2D arrays)
650///
651/// # Errors
652///
653/// Returns an error if the array is empty.
654///
655/// # Arguments
656///
657/// * `array` - The input 2D array
658/// * `axis` - Optional axis along which to compute the product (None for global product)
659///
660/// # Returns
661///
662/// The product of the array elements
663#[allow(dead_code)]
664pub fn product_2d<T>(
665 array: &ArrayView<T, Ix2>,
666 axis: Option<Axis>,
667) -> Result<Array<T, Ix1>, &'static str>
668where
669 T: Clone + Float,
670{
671 if array.is_empty() {
672 return Err("Cannot compute product of an empty array");
673 }
674
675 match axis {
676 Some(ax) => {
677 let (rows, cols) = (array.shape()[0], array.shape()[1]);
678
679 match ax.index() {
680 0 => {
681 // Product along axis 0 (columns)
682 let mut result = Array::<T, Ix1>::from_elem(cols, T::one());
683
684 for j in 0..cols {
685 for i in 0..rows {
686 result[j] = result[j] * array[[i, j]];
687 }
688 }
689
690 Ok(result)
691 }
692 1 => {
693 // Product along axis 1 (rows)
694 let mut result = Array::<T, Ix1>::from_elem(rows, T::one());
695
696 for i in 0..rows {
697 for j in 0..cols {
698 result[i] = result[i] * array[[i, j]];
699 }
700 }
701
702 Ok(result)
703 }
704 _ => Err("Axis index out of bounds for 2D array"),
705 }
706 }
707 None => {
708 // Global product
709 let mut product = T::one();
710
711 for &val in array {
712 product = product * val;
713 }
714
715 Ok(Array::from_elem(1, product))
716 }
717 }
718}
719
720/// Calculate the percentile of array elements (2D arrays)
721///
722/// # Errors
723///
724/// Returns an error if the array is empty or percentile is invalid.
725///
726/// # Panics
727///
728/// Panics if type conversion or partial comparison fails.
729///
730/// # Arguments
731///
732/// * `array` - The input 2D array
733/// * `q` - The percentile to compute (0 to 100)
734/// * `axis` - Optional axis along which to compute the percentile (None for global percentile)
735///
736/// # Returns
737///
738/// The percentile value(s) of the array elements
739#[allow(dead_code)]
740pub fn percentile_2d<T>(
741 array: &ArrayView<T, Ix2>,
742 q: f64,
743 axis: Option<Axis>,
744) -> Result<Array<T, Ix1>, &'static str>
745where
746 T: Clone + Float + FromPrimitive,
747{
748 if array.is_empty() {
749 return Err("Cannot compute percentile of an empty array");
750 }
751
752 if !(0.0..=100.0).contains(&q) {
753 return Err("Percentile must be between 0 and 100");
754 }
755
756 match axis {
757 Some(ax) => {
758 let (rows, cols) = (array.shape()[0], array.shape()[1]);
759
760 match ax.index() {
761 0 => {
762 // Percentile along axis 0 (columns)
763 let mut result = Array::<T, Ix1>::zeros(cols);
764
765 for j in 0..cols {
766 let mut column_values = Vec::with_capacity(rows);
767 for i in 0..rows {
768 column_values.push(array[[i, j]]);
769 }
770
771 column_values
772 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
773
774 // Linear interpolation
775 let pos = (q / 100.0) * (column_values.len() as f64 - 1.0);
776 let idx_low = pos.floor() as usize;
777 let idx_high = pos.ceil() as usize;
778
779 if idx_low == idx_high {
780 result[j] = column_values[idx_low];
781 } else {
782 let weight_high = pos - (idx_low as f64);
783 let weight_low = 1.0 - weight_high;
784
785 result[j] = column_values[idx_low]
786 * T::from_f64(weight_low).expect("Operation failed")
787 + column_values[idx_high]
788 * T::from_f64(weight_high).expect("Operation failed");
789 }
790 }
791
792 Ok(result)
793 }
794 1 => {
795 // Percentile along axis 1 (rows)
796 let mut result = Array::<T, Ix1>::zeros(rows);
797
798 for i in 0..rows {
799 let mut row_values = Vec::with_capacity(cols);
800 for j in 0..cols {
801 row_values.push(array[[i, j]]);
802 }
803
804 row_values
805 .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
806
807 // Linear interpolation
808 let pos = (q / 100.0) * (row_values.len() as f64 - 1.0);
809 let idx_low = pos.floor() as usize;
810 let idx_high = pos.ceil() as usize;
811
812 if idx_low == idx_high {
813 result[0] = row_values[idx_low];
814 } else {
815 let weight_high = pos - (idx_low as f64);
816 let weight_low = 1.0 - weight_high;
817
818 result[0] = row_values[idx_low]
819 * T::from_f64(weight_low).expect("Operation failed")
820 + row_values[idx_high]
821 * T::from_f64(weight_high).expect("Operation failed");
822 }
823 }
824
825 Ok(result)
826 }
827 _ => Err("Axis index out of bounds for 2D array"),
828 }
829 }
830 None => {
831 // Global percentile
832 let mut values: Vec<_> = array.iter().copied().collect();
833 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
834
835 // Linear interpolation
836 let pos = (q / 100.0) * (values.len() as f64 - 1.0);
837 let idx_low = pos.floor() as usize;
838 let idx_high = pos.ceil() as usize;
839
840 let result = if idx_low == idx_high {
841 values[idx_low]
842 } else {
843 let weight_high = pos - (idx_low as f64);
844 let weight_low = 1.0 - weight_high;
845
846 values[idx_low] * T::from_f64(weight_low).expect("Operation failed")
847 + values[idx_high] * T::from_f64(weight_high).expect("Operation failed")
848 };
849
850 Ok(Array::from_elem(1, result))
851 }
852 }
853}
854
855// Generic implementations for n-dimensional arrays
856
857/// Calculate the mean of array elements
858///
859/// # Errors
860///
861/// Returns an error if the array is empty or if conversion fails.
862///
863/// # Panics
864///
865/// Panics if type conversion from usize fails.
866///
867/// # Arguments
868///
869/// * `array` - The input array
870/// * `axis` - Optional axis along which to compute the mean (None for global mean)
871///
872/// # Returns
873///
874/// The mean of the array elements
875#[allow(dead_code)]
876pub fn mean<T, D>(
877 array: &ArrayView<T, D>,
878 axis: Option<Axis>,
879) -> Result<Array<T, Ix1>, &'static str>
880where
881 T: Clone + Float + FromPrimitive,
882 D: Dimension + crate::ndarray::RemoveAxis,
883{
884 if array.is_empty() {
885 return Err("Cannot compute mean of an empty array");
886 }
887
888 match axis {
889 Some(ax) => {
890 // Axis-specific mean implementation for arbitrary dimensions
891 let ndim = array.ndim();
892 if ax.index() >= ndim {
893 return Err("Axis index out of bounds");
894 }
895
896 // Create output shape by removing the specified axis
897 let mut outputshape = array.shape().to_vec();
898 outputshape.remove(ax.index());
899
900 // Handle case where removing axis results in scalar
901 if outputshape.is_empty() {
902 outputshape.push(1);
903 }
904
905 // Calculate mean along specified axis using ndarray's mean_axis
906 let result = array
907 .mean_axis(ax)
908 .ok_or("Failed to compute mean along axis")?;
909
910 // Convert to 1D array as expected by function signature
911 let flat_result = result
912 .to_shape((result.len(),))
913 .map_err(|_| "Failed to reshape result to 1D")?;
914
915 Ok(flat_result.into_owned())
916 }
917 None => {
918 // Global mean
919 let total_elements = array.len();
920 let mut sum = T::zero();
921
922 for &val in array {
923 sum = sum + val;
924 }
925
926 let count = T::from_usize(total_elements).ok_or("Cannot convert array length to T")?;
927 Ok(Array::from_elem(1, sum / count))
928 }
929 }
930}
931
932/// Calculate the median of array elements
933///
934/// # Arguments
935///
936/// * `array` - The input array
937/// * `axis` - Optional axis along which to compute the median (None for global median)
938///
939/// # Returns
940///
941/// The median of the array elements
942///
943/// # Errors
944///
945/// Returns an error if the array is empty.
946///
947/// # Panics
948///
949/// Panics if type conversion or partial comparison fails.
950#[allow(dead_code)]
951pub fn median<T, D>(
952 array: &ArrayView<T, D>,
953 axis: Option<Axis>,
954) -> Result<Array<T, Ix1>, &'static str>
955where
956 T: Clone + Float + FromPrimitive,
957 D: Dimension + crate::ndarray::RemoveAxis,
958{
959 if array.is_empty() {
960 return Err("Cannot compute median of an empty array");
961 }
962
963 match axis {
964 Some(_) => {
965 // For higher dimensional arrays, we need to implement axis-specific logic
966 // This is a placeholder for now
967 Err("Axis-specific median for arbitrary dimension arrays not yet implemented")
968 }
969 None => {
970 // Global median
971 let mut values: Vec<_> = array.iter().copied().collect();
972 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
973
974 let median_value = if values.len() % 2 == 0 {
975 let mid = values.len() / 2;
976 (values[mid - 1] + values[mid]) / T::from_f64(2.0).expect("Operation failed")
977 } else {
978 values[values.len() / 2]
979 };
980
981 Ok(Array::from_elem(1, median_value))
982 }
983 }
984}
985
986/// Calculate the variance of array elements
987///
988/// # Arguments
989///
990/// * `array` - The input array
991/// * `axis` - Optional axis along which to compute the variance (None for global variance)
992/// * `ddof` - Delta degrees of freedom (default 0)
993///
994/// # Returns
995///
996/// The variance of the array elements
997///
998/// # Errors
999///
1000/// Returns an error if the array is empty or if conversion fails.
1001///
1002/// # Panics
1003///
1004/// Panics if type conversion from usize fails.
1005#[allow(dead_code)]
1006pub fn variance<T, D>(
1007 array: &ArrayView<T, D>,
1008 axis: Option<Axis>,
1009 ddof: usize,
1010) -> Result<Array<T, Ix1>, &'static str>
1011where
1012 T: Clone + Float + FromPrimitive,
1013 D: Dimension + crate::ndarray::RemoveAxis,
1014{
1015 if array.is_empty() {
1016 return Err("Cannot compute variance of an empty array");
1017 }
1018
1019 match axis {
1020 Some(ax) => {
1021 // Axis-specific variance implementation for arbitrary dimensions
1022 let ndim = array.ndim();
1023 if ax.index() >= ndim {
1024 return Err("Axis index out of bounds");
1025 }
1026
1027 // Create output shape by removing the specified axis
1028 let mut outputshape = array.shape().to_vec();
1029 outputshape.remove(ax.index());
1030
1031 // Handle case where removing axis results in scalar
1032 if outputshape.is_empty() {
1033 outputshape.push(1);
1034 }
1035
1036 // Calculate variance along specified axis using ndarray's var_axis
1037 let result = array.var_axis(ax, T::from_usize(ddof).expect("Operation failed"));
1038
1039 // Convert to 1D array as expected by function signature
1040 let flat_result = result
1041 .to_shape((result.len(),))
1042 .map_err(|_| "Failed to reshape variance result to 1D")?;
1043
1044 Ok(flat_result.into_owned())
1045 }
1046 None => {
1047 // Global variance
1048 let total_elements = array.len();
1049
1050 if total_elements <= ddof {
1051 return Err("Not enough data points for variance calculation with given ddof");
1052 }
1053
1054 // Calculate global mean
1055 let global_mean = mean(array, None)?[0];
1056
1057 // Calculate sum of squared differences from the mean
1058 let mut sum_sq_diff = T::zero();
1059 for &val in array {
1060 let diff = val - global_mean;
1061 sum_sq_diff = sum_sq_diff + (diff * diff);
1062 }
1063
1064 let divisor = T::from_usize(total_elements - ddof).expect("Operation failed");
1065
1066 Ok(Array::from_elem(1, sum_sq_diff / divisor))
1067 }
1068 }
1069}
1070
1071/// Calculate the standard deviation of array elements
1072///
1073/// # Arguments
1074///
1075/// * `array` - The input array
1076/// * `axis` - Optional axis along which to compute the std dev (None for global std dev)
1077/// * `ddof` - Delta degrees of freedom (default 0)
1078///
1079/// # Returns
1080///
1081/// The standard deviation of the array elements
1082///
1083/// # Errors
1084///
1085/// Returns an error if the array is empty or variance calculation fails.
1086///
1087/// # Panics
1088///
1089/// Panics if variance calculation panics.
1090#[allow(dead_code)]
1091pub fn std_dev<T, D>(
1092 array: &ArrayView<T, D>,
1093 axis: Option<Axis>,
1094 ddof: usize,
1095) -> Result<Array<T, Ix1>, &'static str>
1096where
1097 T: Clone + Float + FromPrimitive,
1098 D: Dimension + crate::ndarray::RemoveAxis,
1099{
1100 let var_result = variance(array, axis, ddof)?;
1101 Ok(var_result.mapv(|x| x.sqrt()))
1102}
1103
1104/// Calculate the minimum value(s) of array elements
1105///
1106/// # Arguments
1107///
1108/// * `array` - The input array
1109/// * `axis` - Optional axis along which to compute the minimum (None for global minimum)
1110///
1111/// # Returns
1112///
1113/// The minimum value(s) of the array elements
1114///
1115/// # Errors
1116///
1117/// Returns an error if the array is empty.
1118#[allow(dead_code)]
1119pub fn min<T, D>(array: &ArrayView<T, D>, axis: Option<Axis>) -> Result<Array<T, Ix1>, &'static str>
1120where
1121 T: Clone + Float,
1122 D: Dimension + crate::ndarray::RemoveAxis,
1123{
1124 if array.is_empty() {
1125 return Err("Cannot compute minimum of an empty array");
1126 }
1127
1128 match axis {
1129 Some(ax) => {
1130 // Axis-specific minimum implementation for arbitrary dimensions
1131 let ndim = array.ndim();
1132 if ax.index() >= ndim {
1133 return Err("Axis index out of bounds");
1134 }
1135
1136 // Create output shape by removing the specified axis
1137 let mut outputshape = array.shape().to_vec();
1138 outputshape.remove(ax.index());
1139
1140 // Handle case where removing axis results in scalar
1141 if outputshape.is_empty() {
1142 outputshape.push(1);
1143 }
1144
1145 // Use ndarray's fold_axis to compute minimum along specified axis
1146 let result = array.fold_axis(ax, T::infinity(), |&a, &b| if a < b { a } else { b });
1147
1148 // Convert to 1D array as expected by function signature
1149 let flat_result = result
1150 .to_shape((result.len(),))
1151 .map_err(|_| "Failed to reshape minimum result to 1D")?;
1152
1153 Ok(flat_result.into_owned())
1154 }
1155 None => {
1156 // Global minimum
1157 let mut min_val = *array.iter().next().expect("Operation failed");
1158
1159 for &val in array {
1160 if val < min_val {
1161 min_val = val;
1162 }
1163 }
1164
1165 Ok(Array::from_elem(1, min_val))
1166 }
1167 }
1168}
1169
1170/// Calculate the maximum value(s) of array elements
1171///
1172/// # Arguments
1173///
1174/// * `array` - The input array
1175/// * `axis` - Optional axis along which to compute the maximum (None for global maximum)
1176///
1177/// # Returns
1178///
1179/// The maximum value(s) of the array elements
1180///
1181/// # Errors
1182///
1183/// Returns an error if the array is empty.
1184#[allow(dead_code)]
1185pub fn max<T, D>(array: &ArrayView<T, D>, axis: Option<Axis>) -> Result<Array<T, Ix1>, &'static str>
1186where
1187 T: Clone + Float,
1188 D: Dimension + crate::ndarray::RemoveAxis,
1189{
1190 if array.is_empty() {
1191 return Err("Cannot compute maximum of an empty array");
1192 }
1193
1194 match axis {
1195 Some(ax) => {
1196 // Axis-specific maximum implementation for arbitrary dimensions
1197 let ndim = array.ndim();
1198 if ax.index() >= ndim {
1199 return Err("Axis index out of bounds");
1200 }
1201
1202 // Create output shape by removing the specified axis
1203 let mut outputshape = array.shape().to_vec();
1204 outputshape.remove(ax.index());
1205
1206 // Handle case where removing axis results in scalar
1207 if outputshape.is_empty() {
1208 outputshape.push(1);
1209 }
1210
1211 // Use ndarray's fold_axis to compute maximum along specified axis
1212 let result = array.fold_axis(ax, T::neg_infinity(), |&a, &b| if a > b { a } else { b });
1213
1214 // Convert to 1D array as expected by function signature
1215 let flat_result = result
1216 .to_shape((result.len(),))
1217 .map_err(|_| "Failed to reshape maximum result to 1D")?;
1218
1219 Ok(flat_result.into_owned())
1220 }
1221 None => {
1222 // Global maximum
1223 let mut max_val = *array.iter().next().expect("Operation failed");
1224
1225 for &val in array {
1226 if val > max_val {
1227 max_val = val;
1228 }
1229 }
1230
1231 Ok(Array::from_elem(1, max_val))
1232 }
1233 }
1234}
1235
1236/// Calculate the percentile of array elements
1237///
1238/// # Arguments
1239///
1240/// * `array` - The input array
1241/// * `q` - The percentile to compute (0 to 100)
1242/// * `axis` - Optional axis along which to compute the percentile (None for global percentile)
1243///
1244/// # Returns
1245///
1246/// The percentile value(s) of the array elements
1247///
1248/// # Errors
1249///
1250/// Returns an error if the array is empty or percentile is invalid.
1251///
1252/// # Panics
1253///
1254/// Panics if type conversion or partial comparison fails.
1255#[allow(dead_code)]
1256pub fn percentile<T, D>(
1257 array: &ArrayView<T, D>,
1258 q: f64,
1259 axis: Option<Axis>,
1260) -> Result<Array<T, Ix1>, &'static str>
1261where
1262 T: Clone + Float + FromPrimitive,
1263 D: Dimension + crate::ndarray::RemoveAxis,
1264{
1265 if array.is_empty() {
1266 return Err("Cannot compute percentile of an empty array");
1267 }
1268
1269 if !(0.0..=100.0).contains(&q) {
1270 return Err("Percentile must be between 0 and 100");
1271 }
1272
1273 match axis {
1274 Some(_) => {
1275 // For higher dimensional arrays, we need to implement axis-specific logic
1276 // This is a placeholder for now
1277 Err("Axis-specific percentile for arbitrary dimension arrays not yet implemented")
1278 }
1279 None => {
1280 // Global percentile
1281 let mut values: Vec<_> = array.iter().copied().collect();
1282 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1283
1284 // Linear interpolation
1285 let pos = (q / 100.0) * (values.len() as f64 - 1.0);
1286 let idx_low = pos.floor() as usize;
1287 let idx_high = pos.ceil() as usize;
1288
1289 let result = if idx_low == idx_high {
1290 values[idx_low]
1291 } else {
1292 let weight_high = pos - (idx_low as f64);
1293 let weight_low = 1.0 - weight_high;
1294
1295 values[idx_low] * T::from_f64(weight_low).expect("Operation failed")
1296 + values[idx_high] * T::from_f64(weight_high).expect("Operation failed")
1297 };
1298
1299 Ok(Array::from_elem(1, result))
1300 }
1301 }
1302}