use crate::array::Array;
use crate::error::{NumRs2Error, Result};
use num_traits::{Float, NumCast, One, Zero};
use std::ops::{Add, Div, Mul, Sub};
pub fn nansum<T>(array: &Array<T>, axis: Option<isize>) -> Result<Array<T>>
where
T: Float + Clone + Add<Output = T> + Zero,
{
if let Some(ax) = axis {
let ax = if ax < 0 {
(array.ndim() as isize + ax) as usize
} else {
ax as usize
};
let shape = array.shape();
let mut new_shape = shape.clone();
new_shape.remove(ax);
if new_shape.is_empty() {
new_shape = vec![1];
}
let axis_len = shape[ax];
let mut result = Array::zeros(&new_shape);
let mut strides = vec![1; shape.len()];
for i in (0..shape.len() - 1).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
let result_size: usize = new_shape.iter().product();
for res_idx in 0..result_size {
let mut sum = T::zero();
let mut res_indices = vec![0; new_shape.len()];
let mut temp = res_idx;
for i in (0..new_shape.len()).rev() {
res_indices[i] = temp % new_shape[i];
temp /= new_shape[i];
}
for ax_idx in 0..axis_len {
let mut full_indices = vec![0; shape.len()];
let mut res_idx_ptr = 0;
for i in 0..shape.len() {
if i == ax {
full_indices[i] = ax_idx;
} else {
full_indices[i] = res_indices[res_idx_ptr];
res_idx_ptr += 1;
}
}
let value = array.get(&full_indices)?;
if !value.is_nan() {
sum = sum + value;
}
}
result.set(&res_indices, sum)?;
}
Ok(result)
} else {
let array_vec = array.to_vec();
let sum = array_vec
.iter()
.filter(|x| !x.is_nan())
.fold(T::zero(), |acc, x| acc + *x);
Ok(Array::from_vec(vec![sum]))
}
}
pub fn nanmean<T>(array: &Array<T>, axis: Option<isize>) -> Result<Array<T>>
where
T: Float + Clone + Add<Output = T> + Div<Output = T> + Zero,
{
if let Some(ax) = axis {
let ax = if ax < 0 {
(array.ndim() as isize + ax) as usize
} else {
ax as usize
};
let sums = nansum(array, Some(ax as isize))?;
let shape = array.shape();
let mut new_shape = shape.clone();
new_shape.remove(ax);
if new_shape.is_empty() {
new_shape = vec![1];
}
let axis_len = shape[ax];
let mut counts = Array::zeros(&new_shape);
let mut strides = vec![1; shape.len()];
for i in (0..shape.len() - 1).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
let result_size: usize = new_shape.iter().product();
for res_idx in 0..result_size {
let mut count = T::zero();
let mut res_indices = vec![0; new_shape.len()];
let mut temp = res_idx;
for i in (0..new_shape.len()).rev() {
res_indices[i] = temp % new_shape[i];
temp /= new_shape[i];
}
for ax_idx in 0..axis_len {
let mut full_indices = vec![0; shape.len()];
let mut res_idx_ptr = 0;
for i in 0..shape.len() {
if i == ax {
full_indices[i] = ax_idx;
} else {
full_indices[i] = res_indices[res_idx_ptr];
res_idx_ptr += 1;
}
}
let value = array.get(&full_indices)?;
if !value.is_nan() {
count = count + T::one();
}
}
counts.set(&res_indices, count)?;
}
Ok(sums.zip_with(
&counts,
|s, c| {
if c == T::zero() {
T::nan()
} else {
s / c
}
},
)?)
} else {
let mut sum = T::zero();
let mut count = 0;
let array_vec = array.to_vec();
for value in array_vec.iter() {
if !value.is_nan() {
sum = sum + *value;
count += 1;
}
}
if count == 0 {
Ok(Array::from_vec(vec![T::nan()]))
} else {
Ok(Array::from_vec(vec![
sum / T::from(count).expect("count should be representable"),
]))
}
}
}
pub fn nanstd<T>(array: &Array<T>, axis: Option<isize>, ddof: usize) -> Result<Array<T>>
where
T: Float + Clone + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Zero,
{
let variance = nanvar(array, axis, ddof)?;
Ok(variance.map(|x| x.sqrt()))
}
pub fn nanvar<T>(array: &Array<T>, axis: Option<isize>, ddof: usize) -> Result<Array<T>>
where
T: Float + Clone + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Zero,
{
let mean = nanmean(array, axis)?;
if let Some(axis_val) = axis {
let ax = if axis_val < 0 {
(array.ndim() as isize + axis_val) as usize
} else {
axis_val as usize
};
let mut mean_shape = vec![1; array.ndim()];
mean_shape[ax] = array.shape()[ax];
let mean_expanded = mean.reshape(&mean_shape);
let diff = array.zip_with(&mean_expanded, |a, m| a - m)?;
let diff_sq = diff.map(|x| if x.is_nan() { T::zero() } else { x * x });
let shape = array.shape();
let mut new_shape = shape.clone();
new_shape.remove(ax);
if new_shape.is_empty() {
new_shape = vec![1];
}
let axis_len = shape[ax];
let mut counts = Array::zeros(&new_shape);
let result_size: usize = new_shape.iter().product();
for res_idx in 0..result_size {
let mut count = T::zero();
let mut res_indices = vec![0; new_shape.len()];
let mut temp = res_idx;
for i in (0..new_shape.len()).rev() {
res_indices[i] = temp % new_shape[i];
temp /= new_shape[i];
}
for ax_idx in 0..axis_len {
let mut full_indices = vec![0; shape.len()];
let mut res_idx_ptr = 0;
for i in 0..shape.len() {
if i == ax {
full_indices[i] = ax_idx;
} else {
full_indices[i] = res_indices[res_idx_ptr];
res_idx_ptr += 1;
}
}
let value = array.get(&full_indices)?;
if !value.is_nan() {
count = count + T::one();
}
}
counts.set(&res_indices, count)?;
}
let sum_sq = nansum(&diff_sq, Some(ax as isize))?;
Ok(sum_sq.zip_with(&counts, |s, c| {
let adjusted_count = c - T::from(ddof).expect("ddof should be representable");
if adjusted_count <= T::zero() {
T::nan()
} else {
s / adjusted_count
}
})?)
} else {
let mean_val = mean.to_vec()[0];
let mut sum_sq = T::zero();
let mut count = 0;
let array_vec = array.to_vec();
for value in array_vec.iter() {
if !value.is_nan() {
let diff = *value - mean_val;
sum_sq = sum_sq + diff * diff;
count += 1;
}
}
if count <= ddof {
Ok(Array::from_vec(vec![T::nan()]))
} else {
Ok(Array::from_vec(vec![
sum_sq / T::from(count - ddof).expect("count-ddof should be representable"),
]))
}
}
}
pub fn nanmin<T>(array: &Array<T>, axis: Option<isize>) -> Result<Array<T>>
where
T: Float + Clone + PartialOrd,
{
if let Some(ax) = axis {
let ax = if ax < 0 {
(array.ndim() as isize + ax) as usize
} else {
ax as usize
};
let shape = array.shape();
let mut new_shape = shape.clone();
new_shape.remove(ax);
if new_shape.is_empty() {
new_shape = vec![1];
}
let axis_len = shape[ax];
let mut result = Array::full(&new_shape, T::nan());
let result_size: usize = new_shape.iter().product();
for res_idx in 0..result_size {
let mut min_val = T::nan();
let mut res_indices = vec![0; new_shape.len()];
let mut temp = res_idx;
for i in (0..new_shape.len()).rev() {
res_indices[i] = temp % new_shape[i];
temp /= new_shape[i];
}
for ax_idx in 0..axis_len {
let mut full_indices = vec![0; shape.len()];
let mut res_idx_ptr = 0;
for i in 0..shape.len() {
if i == ax {
full_indices[i] = ax_idx;
} else {
full_indices[i] = res_indices[res_idx_ptr];
res_idx_ptr += 1;
}
}
let value = array.get(&full_indices)?;
if !value.is_nan() && (min_val.is_nan() || value < min_val) {
min_val = value;
}
}
result.set(&res_indices, min_val)?;
}
Ok(result)
} else {
let array_vec = array.to_vec();
let min = array_vec
.iter()
.filter(|x| !x.is_nan())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.cloned()
.unwrap_or(T::nan());
Ok(Array::from_vec(vec![min]))
}
}
pub fn nanmax<T>(array: &Array<T>, axis: Option<isize>) -> Result<Array<T>>
where
T: Float + Clone + PartialOrd,
{
if let Some(ax) = axis {
let ax = if ax < 0 {
(array.ndim() as isize + ax) as usize
} else {
ax as usize
};
let shape = array.shape();
let mut new_shape = shape.clone();
new_shape.remove(ax);
if new_shape.is_empty() {
new_shape = vec![1];
}
let axis_len = shape[ax];
let mut result = Array::full(&new_shape, T::nan());
let result_size: usize = new_shape.iter().product();
for res_idx in 0..result_size {
let mut max_val = T::nan();
let mut res_indices = vec![0; new_shape.len()];
let mut temp = res_idx;
for i in (0..new_shape.len()).rev() {
res_indices[i] = temp % new_shape[i];
temp /= new_shape[i];
}
for ax_idx in 0..axis_len {
let mut full_indices = vec![0; shape.len()];
let mut res_idx_ptr = 0;
for i in 0..shape.len() {
if i == ax {
full_indices[i] = ax_idx;
} else {
full_indices[i] = res_indices[res_idx_ptr];
res_idx_ptr += 1;
}
}
let value = array.get(&full_indices)?;
if !value.is_nan() && (max_val.is_nan() || value > max_val) {
max_val = value;
}
}
result.set(&res_indices, max_val)?;
}
Ok(result)
} else {
let array_vec = array.to_vec();
let max = array_vec
.iter()
.filter(|x| !x.is_nan())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.cloned()
.unwrap_or(T::nan());
Ok(Array::from_vec(vec![max]))
}
}
pub fn nancumsum<T>(array: &Array<T>, axis: Option<isize>) -> Result<Array<T>>
where
T: Float + Clone + Add<Output = T> + Zero,
{
if let Some(ax) = axis {
let ax = if ax < 0 {
(array.ndim() as isize + ax) as usize
} else {
ax as usize
};
if ax >= array.ndim() {
return Err(NumRs2Error::DimensionMismatch(format!(
"axis {} is out of bounds for array of dimension {}",
ax,
array.ndim()
)));
}
let shape = array.shape();
let mut result = Array::zeros(&shape);
let axis_len = shape[ax];
let mut strides = vec![1; shape.len()];
for i in (0..shape.len() - 1).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
let total_elems: usize = shape.iter().product();
let axis_stride = strides[ax];
let group_size = axis_stride * axis_len;
for group_start in (0..total_elems).step_by(group_size) {
for offset in 0..axis_stride {
let mut cumsum = T::zero();
for i in 0..axis_len {
let idx = group_start + i * axis_stride + offset;
let flat_idx = idx;
let mut indices = vec![0; shape.len()];
let mut temp = flat_idx;
for j in 0..shape.len() {
indices[j] = temp / strides[j];
temp %= strides[j];
}
let value = array.get(&indices)?;
if !value.is_nan() {
cumsum = cumsum + value;
}
result.set(&indices, cumsum)?;
}
}
}
Ok(result)
} else {
let flat = array.to_vec();
let mut result = Vec::with_capacity(flat.len());
let mut cumsum = T::zero();
for value in flat {
if !value.is_nan() {
cumsum = cumsum + value;
}
result.push(cumsum);
}
Ok(Array::from_vec(result).reshape(&array.shape()))
}
}
pub fn nanprod<T>(array: &Array<T>, axis: Option<isize>) -> Result<Array<T>>
where
T: Float + Clone + Mul<Output = T> + One,
{
if let Some(ax) = axis {
let ax = if ax < 0 {
(array.ndim() as isize + ax) as usize
} else {
ax as usize
};
let shape = array.shape();
let mut new_shape = shape.clone();
new_shape.remove(ax);
if new_shape.is_empty() {
new_shape = vec![1];
}
let axis_len = shape[ax];
let mut result = Array::full(&new_shape, T::one());
let result_size: usize = new_shape.iter().product();
for res_idx in 0..result_size {
let mut prod = T::one();
let mut res_indices = vec![0; new_shape.len()];
let mut temp = res_idx;
for i in (0..new_shape.len()).rev() {
res_indices[i] = temp % new_shape[i];
temp /= new_shape[i];
}
for ax_idx in 0..axis_len {
let mut full_indices = vec![0; shape.len()];
let mut res_idx_ptr = 0;
for i in 0..shape.len() {
if i == ax {
full_indices[i] = ax_idx;
} else {
full_indices[i] = res_indices[res_idx_ptr];
res_idx_ptr += 1;
}
}
let value = array.get(&full_indices)?;
if !value.is_nan() {
prod = prod * value;
}
}
result.set(&res_indices, prod)?;
}
Ok(result)
} else {
let array_vec = array.to_vec();
let prod = array_vec
.iter()
.filter(|x| !x.is_nan())
.fold(T::one(), |acc, &x| acc * x);
Ok(Array::from_vec(vec![prod]))
}
}
pub fn nanpercentile<T>(
array: &Array<T>,
q: T,
axis: Option<isize>,
method: Option<&str>,
) -> Result<Array<T>>
where
T: Float + Clone + NumCast + std::fmt::Display,
{
let q_arr = Array::from_vec(vec![
q / T::from(100.0).expect("100.0 should be representable"),
]);
nanquantile(array, &q_arr, axis, method)
}
pub fn nanquantile<T>(
array: &Array<T>,
q: &Array<T>,
axis: Option<isize>,
method: Option<&str>,
) -> Result<Array<T>>
where
T: Float + Clone + NumCast + std::fmt::Display,
{
let method_str = method.unwrap_or("linear");
if let Some(ax) = axis {
let ax = if ax < 0 {
(array.ndim() as isize + ax) as usize
} else {
ax as usize
};
if ax >= array.ndim() {
return Err(NumRs2Error::DimensionMismatch(format!(
"axis {} is out of bounds for array of dimension {}",
ax,
array.ndim()
)));
}
let shape = array.shape();
let mut new_shape = shape.clone();
new_shape.remove(ax);
if new_shape.is_empty() {
new_shape = vec![1];
}
let axis_len = shape[ax];
let q_vec = q.to_vec();
let n_quantiles = q_vec.len();
let mut result_shape = new_shape.clone();
result_shape.push(n_quantiles);
let mut result = Array::zeros(&result_shape);
let result_size: usize = new_shape.iter().product();
for res_idx in 0..result_size {
let mut res_indices = vec![0; new_shape.len()];
let mut temp = res_idx;
for i in (0..new_shape.len()).rev() {
res_indices[i] = temp % new_shape[i];
temp /= new_shape[i];
}
let mut values = Vec::new();
for ax_idx in 0..axis_len {
let mut full_indices = vec![0; shape.len()];
let mut res_idx_ptr = 0;
for i in 0..shape.len() {
if i == ax {
full_indices[i] = ax_idx;
} else {
full_indices[i] = res_indices[res_idx_ptr];
res_idx_ptr += 1;
}
}
let value = array.get(&full_indices)?;
if !value.is_nan() {
values.push(value);
}
}
if values.is_empty() {
for (q_idx, _) in q_vec.iter().enumerate() {
let mut result_indices = res_indices.clone();
result_indices.push(q_idx);
result.set(&result_indices, T::nan())?;
}
} else {
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = values.len();
for (q_idx, &q_val) in q_vec.iter().enumerate() {
if q_val < T::zero() || q_val > T::one() {
return Err(NumRs2Error::InvalidOperation(format!(
"Quantile value {} out of bounds [0, 1]",
q_val
)));
}
let idx_float = q_val * T::from(n - 1).expect("n-1 should be representable");
let idx_lower = idx_float.floor();
let idx_upper = idx_float.ceil();
let idx_lower_usize = idx_lower
.to_usize()
.expect("lower index should be convertible");
let idx_upper_usize = idx_upper
.to_usize()
.expect("upper index should be convertible");
let quantile = match method_str {
"linear" => {
if idx_lower == idx_upper {
values[idx_lower_usize]
} else {
let fraction = idx_float - idx_lower;
values[idx_lower_usize]
+ fraction * (values[idx_upper_usize] - values[idx_lower_usize])
}
}
"lower" => values[idx_lower_usize],
"higher" => values[idx_upper_usize],
"nearest" => {
if idx_float - idx_lower < idx_upper - idx_float {
values[idx_lower_usize]
} else {
values[idx_upper_usize]
}
}
"midpoint" => {
if idx_lower == idx_upper {
values[idx_lower_usize]
} else {
(values[idx_lower_usize] + values[idx_upper_usize])
/ T::from(2.0).expect("2.0 should be representable")
}
}
_ => {
return Err(NumRs2Error::InvalidOperation(format!(
"Invalid method '{}'. Must be one of 'linear', 'lower', 'higher', 'nearest', 'midpoint'",
method_str
)))
}
};
let mut result_indices = res_indices.clone();
result_indices.push(q_idx);
result.set(&result_indices, quantile)?;
}
}
}
Ok(result)
} else {
let array_vec = array.to_vec();
let mut values: Vec<T> = array_vec.into_iter().filter(|x| !x.is_nan()).collect();
if values.is_empty() {
let q_vec = q.to_vec();
let result = vec![T::nan(); q_vec.len()];
return Ok(Array::from_vec(result));
}
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = values.len();
let q_vec = q.to_vec();
let mut result = Vec::with_capacity(q_vec.len());
for &q_val in &q_vec {
if q_val < T::zero() || q_val > T::one() {
return Err(NumRs2Error::InvalidOperation(format!(
"Quantile value {} out of bounds [0, 1]",
q_val
)));
}
let idx_float = q_val * T::from(n - 1).expect("n-1 should be representable");
let idx_lower = idx_float.floor();
let idx_upper = idx_float.ceil();
let idx_lower_usize = idx_lower
.to_usize()
.expect("lower index should be convertible");
let idx_upper_usize = idx_upper
.to_usize()
.expect("upper index should be convertible");
let quantile = match method_str {
"linear" => {
if idx_lower == idx_upper {
values[idx_lower_usize]
} else {
let fraction = idx_float - idx_lower;
values[idx_lower_usize]
+ fraction * (values[idx_upper_usize] - values[idx_lower_usize])
}
}
"lower" => values[idx_lower_usize],
"higher" => values[idx_upper_usize],
"nearest" => {
if idx_float - idx_lower < idx_upper - idx_float {
values[idx_lower_usize]
} else {
values[idx_upper_usize]
}
}
"midpoint" => {
if idx_lower == idx_upper {
values[idx_lower_usize]
} else {
(values[idx_lower_usize] + values[idx_upper_usize])
/ T::from(2.0).expect("2.0 should be representable")
}
}
_ => {
return Err(NumRs2Error::InvalidOperation(format!(
"Invalid method '{}'. Must be one of 'linear', 'lower', 'higher', 'nearest', 'midpoint'",
method_str
)))
}
};
result.push(quantile);
}
Ok(Array::from_vec(result))
}
}