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}