use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Axis, Data, Ix1, Ix2};
use scirs2_core::numeric::{Float, NumCast};
use crate::error::{Result, TransformError};
pub const EPSILON: f64 = 1e-10;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum NormalizationMethod {
MinMax,
MinMaxCustom(f64, f64),
ZScore,
MaxAbs,
L1,
L2,
Robust,
}
#[allow(dead_code)]
pub fn normalize_array<S>(
array: &ArrayBase<S, Ix2>,
method: NormalizationMethod,
axis: usize,
) -> Result<Array2<f64>>
where
S: Data,
S::Elem: Float + NumCast,
{
let array_f64 = array.mapv(|x| NumCast::from(x).unwrap_or(0.0));
if !array_f64.is_standard_layout() {
return Err(TransformError::InvalidInput(
"Input array must be in standard memory layout".to_string(),
));
}
if array_f64.ndim() != 2 {
return Err(TransformError::InvalidInput(
"Only 2D arrays are supported".to_string(),
));
}
if axis >= array_f64.ndim() {
return Err(TransformError::InvalidInput(format!(
"Invalid axis {} for array with {} dimensions",
axis,
array_f64.ndim()
)));
}
let shape = array_f64.shape();
let mut normalized = Array2::zeros((shape[0], shape[1]));
match method {
NormalizationMethod::MinMax => {
let min = array_f64.map_axis(Axis(axis), |view| {
view.fold(f64::INFINITY, |acc, &x| acc.min(x))
});
let max = array_f64.map_axis(Axis(axis), |view| {
view.fold(f64::NEG_INFINITY, |acc, &x| acc.max(x))
});
let range = &max - &min;
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if axis == 0 { j } else { i };
if range[idx].abs() > EPSILON {
normalized[[i, j]] = (value - min[idx]) / range[idx];
} else {
normalized[[i, j]] = 0.5; }
}
}
}
NormalizationMethod::MinMaxCustom(new_min, new_max) => {
let min = array_f64.map_axis(Axis(axis), |view| {
view.fold(f64::INFINITY, |acc, &x| acc.min(x))
});
let max = array_f64.map_axis(Axis(axis), |view| {
view.fold(f64::NEG_INFINITY, |acc, &x| acc.max(x))
});
let range = &max - &min;
let new_range = new_max - new_min;
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if axis == 0 { j } else { i };
if range[idx].abs() > EPSILON {
normalized[[i, j]] = (value - min[idx]) / range[idx] * new_range + new_min;
} else {
normalized[[i, j]] = (new_min + new_max) / 2.0; }
}
}
}
NormalizationMethod::ZScore => {
let mean = array_f64.map_axis(Axis(axis), |view| {
view.iter().sum::<f64>() / view.len() as f64
});
let std_dev = array_f64.map_axis(Axis(axis), |view| {
let m = view.iter().sum::<f64>() / view.len() as f64;
let variance =
view.iter().map(|&x| (x - m).powi(2)).sum::<f64>() / view.len() as f64;
variance.sqrt()
});
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if axis == 0 { j } else { i };
if std_dev[idx] > EPSILON {
normalized[[i, j]] = (value - mean[idx]) / std_dev[idx];
} else {
normalized[[i, j]] = 0.0; }
}
}
}
NormalizationMethod::MaxAbs => {
let max_abs = array_f64.map_axis(Axis(axis), |view| {
view.fold(0.0, |acc, &x| acc.max(x.abs()))
});
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if axis == 0 { j } else { i };
if max_abs[idx] > EPSILON {
normalized[[i, j]] = value / max_abs[idx];
} else {
normalized[[i, j]] = 0.0; }
}
}
}
NormalizationMethod::L1 => {
let l1_norm =
array_f64.map_axis(Axis(axis), |view| view.fold(0.0, |acc, &x| acc + x.abs()));
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if axis == 0 { j } else { i };
if l1_norm[idx] > EPSILON {
normalized[[i, j]] = value / l1_norm[idx];
} else {
normalized[[i, j]] = 0.0; }
}
}
}
NormalizationMethod::L2 => {
let l2_norm = array_f64.map_axis(Axis(axis), |view| {
let sum_squares = view.iter().fold(0.0, |acc, &x| acc + x * x);
sum_squares.sqrt()
});
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if axis == 0 { j } else { i };
if l2_norm[idx] > EPSILON {
normalized[[i, j]] = value / l2_norm[idx];
} else {
normalized[[i, j]] = 0.0; }
}
}
}
NormalizationMethod::Robust => {
let median = array_f64.map_axis(Axis(axis), |view| {
let mut data = view.to_vec();
data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = data.len();
if n % 2 == 0 {
(data[n / 2 - 1] + data[n / 2]) / 2.0
} else {
data[n / 2]
}
});
let iqr = array_f64.map_axis(Axis(axis), |view| {
let mut data = view.to_vec();
data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = data.len();
let q1_pos = 0.25 * (n - 1) as f64;
let q1_idx_low = q1_pos.floor() as usize;
let q1_idx_high = q1_pos.ceil() as usize;
let q1 = if q1_idx_low == q1_idx_high {
data[q1_idx_low]
} else {
let weight = q1_pos - q1_idx_low as f64;
data[q1_idx_low] * (1.0 - weight) + data[q1_idx_high] * weight
};
let q3_pos = 0.75 * (n - 1) as f64;
let q3_idx_low = q3_pos.floor() as usize;
let q3_idx_high = q3_pos.ceil() as usize;
let q3 = if q3_idx_low == q3_idx_high {
data[q3_idx_low]
} else {
let weight = q3_pos - q3_idx_low as f64;
data[q3_idx_low] * (1.0 - weight) + data[q3_idx_high] * weight
};
q3 - q1
});
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if axis == 0 { j } else { i };
if iqr[idx] > EPSILON {
normalized[[i, j]] = (value - median[idx]) / iqr[idx];
} else {
normalized[[i, j]] = 0.0; }
}
}
}
}
Ok(normalized)
}
#[allow(dead_code)]
pub fn normalize_vector<S>(
array: &ArrayBase<S, Ix1>,
method: NormalizationMethod,
) -> Result<Array1<f64>>
where
S: Data,
S::Elem: Float + NumCast,
{
let array_f64 = array.mapv(|x| NumCast::from(x).unwrap_or(0.0));
if array_f64.is_empty() {
return Err(TransformError::InvalidInput(
"Input array is empty".to_string(),
));
}
let mut normalized = Array1::zeros(array_f64.len());
match method {
NormalizationMethod::MinMax => {
let min = array_f64.fold(f64::INFINITY, |acc, &x| acc.min(x));
let max = array_f64.fold(f64::NEG_INFINITY, |acc, &x| acc.max(x));
let range = max - min;
if range.abs() > EPSILON {
for (i, &value) in array_f64.iter().enumerate() {
normalized[i] = (value - min) / range;
}
} else {
normalized.fill(0.5); }
}
NormalizationMethod::MinMaxCustom(new_min, new_max) => {
let min = array_f64.fold(f64::INFINITY, |acc, &x| acc.min(x));
let max = array_f64.fold(f64::NEG_INFINITY, |acc, &x| acc.max(x));
let range = max - min;
let new_range = new_max - new_min;
if range.abs() > EPSILON {
for (i, &value) in array_f64.iter().enumerate() {
normalized[i] = (value - min) / range * new_range + new_min;
}
} else {
normalized.fill((new_min + new_max) / 2.0); }
}
NormalizationMethod::ZScore => {
let mean = array_f64.iter().sum::<f64>() / array_f64.len() as f64;
let variance =
array_f64.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / array_f64.len() as f64;
let std_dev = variance.sqrt();
if std_dev > EPSILON {
for (i, &value) in array_f64.iter().enumerate() {
normalized[i] = (value - mean) / std_dev;
}
} else {
normalized.fill(0.0); }
}
NormalizationMethod::MaxAbs => {
let max_abs = array_f64.fold(0.0, |acc, &x| acc.max(x.abs()));
if max_abs > EPSILON {
for (i, &value) in array_f64.iter().enumerate() {
normalized[i] = value / max_abs;
}
} else {
normalized.fill(0.0); }
}
NormalizationMethod::L1 => {
let l1_norm = array_f64.fold(0.0, |acc, &x| acc + x.abs());
if l1_norm > EPSILON {
for (i, &value) in array_f64.iter().enumerate() {
normalized[i] = value / l1_norm;
}
} else {
normalized.fill(0.0); }
}
NormalizationMethod::L2 => {
let sum_squares = array_f64.iter().fold(0.0, |acc, &x| acc + x * x);
let l2_norm = sum_squares.sqrt();
if l2_norm > EPSILON {
for (i, &value) in array_f64.iter().enumerate() {
normalized[i] = value / l2_norm;
}
} else {
normalized.fill(0.0); }
}
NormalizationMethod::Robust => {
let mut data = array_f64.to_vec();
data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = data.len();
let median = if n.is_multiple_of(2) {
(data[n / 2 - 1] + data[n / 2]) / 2.0
} else {
data[n / 2]
};
let q1_pos = 0.25 * (n - 1) as f64;
let q1_idx_low = q1_pos.floor() as usize;
let q1_idx_high = q1_pos.ceil() as usize;
let q1 = if q1_idx_low == q1_idx_high {
data[q1_idx_low]
} else {
let weight = q1_pos - q1_idx_low as f64;
data[q1_idx_low] * (1.0 - weight) + data[q1_idx_high] * weight
};
let q3_pos = 0.75 * (n - 1) as f64;
let q3_idx_low = q3_pos.floor() as usize;
let q3_idx_high = q3_pos.ceil() as usize;
let q3 = if q3_idx_low == q3_idx_high {
data[q3_idx_low]
} else {
let weight = q3_pos - q3_idx_low as f64;
data[q3_idx_low] * (1.0 - weight) + data[q3_idx_high] * weight
};
let iqr = q3 - q1;
if iqr > EPSILON {
for (i, &value) in array_f64.iter().enumerate() {
normalized[i] = (value - median) / iqr;
}
} else {
normalized.fill(0.0); }
}
}
Ok(normalized)
}
#[derive(Clone)]
pub struct Normalizer {
axis: usize,
params: NormalizerParams,
}
#[derive(Clone)]
enum NormalizerParams {
MinMax {
min: Array1<f64>,
max: Array1<f64>,
new_min: f64,
new_max: f64,
},
ZScore {
mean: Array1<f64>,
std_dev: Array1<f64>,
},
MaxAbs { max_abs: Array1<f64> },
L1 { l1_norm: Array1<f64> },
L2 { l2_norm: Array1<f64> },
Robust {
median: Array1<f64>,
iqr: Array1<f64>,
},
}
impl Normalizer {
pub fn new(method: NormalizationMethod, axis: usize) -> Self {
let params = match method {
NormalizationMethod::MinMax => NormalizerParams::MinMax {
min: Array1::zeros(0),
max: Array1::zeros(0),
new_min: 0.0,
new_max: 1.0,
},
NormalizationMethod::MinMaxCustom(min, max) => NormalizerParams::MinMax {
min: Array1::zeros(0),
max: Array1::zeros(0),
new_min: min,
new_max: max,
},
NormalizationMethod::ZScore => NormalizerParams::ZScore {
mean: Array1::zeros(0),
std_dev: Array1::zeros(0),
},
NormalizationMethod::MaxAbs => NormalizerParams::MaxAbs {
max_abs: Array1::zeros(0),
},
NormalizationMethod::L1 => NormalizerParams::L1 {
l1_norm: Array1::zeros(0),
},
NormalizationMethod::L2 => NormalizerParams::L2 {
l2_norm: Array1::zeros(0),
},
NormalizationMethod::Robust => NormalizerParams::Robust {
median: Array1::zeros(0),
iqr: Array1::zeros(0),
},
};
Normalizer { axis, params }
}
pub fn fit<S>(&mut self, array: &ArrayBase<S, Ix2>) -> Result<()>
where
S: Data,
S::Elem: Float + NumCast,
{
let array_f64 = array.mapv(|x| NumCast::from(x).unwrap_or(0.0));
if !array_f64.is_standard_layout() {
return Err(TransformError::InvalidInput(
"Input array must be in standard memory layout".to_string(),
));
}
if array_f64.ndim() != 2 {
return Err(TransformError::InvalidInput(
"Only 2D arrays are supported".to_string(),
));
}
if self.axis >= array_f64.ndim() {
return Err(TransformError::InvalidInput(format!(
"Invalid axis {} for array with {} dimensions",
self.axis,
array_f64.ndim()
)));
}
match &mut self.params {
NormalizerParams::MinMax {
min,
max,
new_min: _,
new_max: _,
} => {
*min = array_f64.map_axis(Axis(self.axis), |view| {
view.fold(f64::INFINITY, |acc, &x| acc.min(x))
});
*max = array_f64.map_axis(Axis(self.axis), |view| {
view.fold(f64::NEG_INFINITY, |acc, &x| acc.max(x))
});
}
NormalizerParams::ZScore { mean, std_dev } => {
*mean = array_f64.map_axis(Axis(self.axis), |view| {
view.iter().sum::<f64>() / view.len() as f64
});
*std_dev = array_f64.map_axis(Axis(self.axis), |view| {
let m = view.iter().sum::<f64>() / view.len() as f64;
let variance =
view.iter().map(|&x| (x - m).powi(2)).sum::<f64>() / view.len() as f64;
variance.sqrt()
});
}
NormalizerParams::MaxAbs { max_abs } => {
*max_abs = array_f64.map_axis(Axis(self.axis), |view| {
view.fold(0.0, |acc, &x| acc.max(x.abs()))
});
}
NormalizerParams::L1 { l1_norm } => {
*l1_norm = array_f64.map_axis(Axis(self.axis), |view| {
view.fold(0.0, |acc, &x| acc + x.abs())
});
}
NormalizerParams::L2 { l2_norm } => {
*l2_norm = array_f64.map_axis(Axis(self.axis), |view| {
let sum_squares = view.iter().fold(0.0, |acc, &x| acc + x * x);
sum_squares.sqrt()
});
}
NormalizerParams::Robust { median, iqr } => {
*median = array_f64.map_axis(Axis(self.axis), |view| {
let mut data = view.to_vec();
data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = data.len();
if n % 2 == 0 {
(data[n / 2 - 1] + data[n / 2]) / 2.0
} else {
data[n / 2]
}
});
*iqr = array_f64.map_axis(Axis(self.axis), |view| {
let mut data = view.to_vec();
data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = data.len();
let q1_pos = 0.25 * (n - 1) as f64;
let q1_idx_low = q1_pos.floor() as usize;
let q1_idx_high = q1_pos.ceil() as usize;
let q1 = if q1_idx_low == q1_idx_high {
data[q1_idx_low]
} else {
let weight = q1_pos - q1_idx_low as f64;
data[q1_idx_low] * (1.0 - weight) + data[q1_idx_high] * weight
};
let q3_pos = 0.75 * (n - 1) as f64;
let q3_idx_low = q3_pos.floor() as usize;
let q3_idx_high = q3_pos.ceil() as usize;
let q3 = if q3_idx_low == q3_idx_high {
data[q3_idx_low]
} else {
let weight = q3_pos - q3_idx_low as f64;
data[q3_idx_low] * (1.0 - weight) + data[q3_idx_high] * weight
};
q3 - q1
});
}
}
Ok(())
}
pub fn transform<S>(&self, array: &ArrayBase<S, Ix2>) -> Result<Array2<f64>>
where
S: Data,
S::Elem: Float + NumCast,
{
let array_f64 = array.mapv(|x| NumCast::from(x).unwrap_or(0.0));
if !array_f64.is_standard_layout() {
return Err(TransformError::InvalidInput(
"Input array must be in standard memory layout".to_string(),
));
}
if array_f64.ndim() != 2 {
return Err(TransformError::InvalidInput(
"Only 2D arrays are supported".to_string(),
));
}
let expected_size = match &self.params {
NormalizerParams::MinMax { min, .. } => min.len(),
NormalizerParams::ZScore { mean, .. } => mean.len(),
NormalizerParams::MaxAbs { max_abs } => max_abs.len(),
NormalizerParams::L1 { l1_norm } => l1_norm.len(),
NormalizerParams::L2 { l2_norm } => l2_norm.len(),
NormalizerParams::Robust { median, .. } => median.len(),
};
let actual_size = if self.axis == 0 {
array_f64.shape()[1]
} else {
array_f64.shape()[0]
};
if expected_size != actual_size {
return Err(TransformError::InvalidInput(format!(
"Expected {expected_size} features, got {actual_size}"
)));
}
let shape = array_f64.shape();
let mut transformed = Array2::zeros((shape[0], shape[1]));
match &self.params {
NormalizerParams::MinMax {
min,
max,
new_min,
new_max,
} => {
let range = max - min;
let new_range = new_max - new_min;
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if self.axis == 0 { j } else { i };
if range[idx].abs() > EPSILON {
transformed[[i, j]] =
(value - min[idx]) / range[idx] * new_range + new_min;
} else {
transformed[[i, j]] = (new_min + new_max) / 2.0; }
}
}
}
NormalizerParams::ZScore { mean, std_dev } => {
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if self.axis == 0 { j } else { i };
if std_dev[idx] > EPSILON {
transformed[[i, j]] = (value - mean[idx]) / std_dev[idx];
} else {
transformed[[i, j]] = 0.0; }
}
}
}
NormalizerParams::MaxAbs { max_abs } => {
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if self.axis == 0 { j } else { i };
if max_abs[idx] > EPSILON {
transformed[[i, j]] = value / max_abs[idx];
} else {
transformed[[i, j]] = 0.0; }
}
}
}
NormalizerParams::L1 { l1_norm } => {
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if self.axis == 0 { j } else { i };
if l1_norm[idx] > EPSILON {
transformed[[i, j]] = value / l1_norm[idx];
} else {
transformed[[i, j]] = 0.0; }
}
}
}
NormalizerParams::L2 { l2_norm } => {
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if self.axis == 0 { j } else { i };
if l2_norm[idx] > EPSILON {
transformed[[i, j]] = value / l2_norm[idx];
} else {
transformed[[i, j]] = 0.0; }
}
}
}
NormalizerParams::Robust { median, iqr } => {
for i in 0..shape[0] {
for j in 0..shape[1] {
let value = array_f64[[i, j]];
let idx = if self.axis == 0 { j } else { i };
if iqr[idx] > EPSILON {
transformed[[i, j]] = (value - median[idx]) / iqr[idx];
} else {
transformed[[i, j]] = 0.0; }
}
}
}
}
Ok(transformed)
}
pub fn fit_transform<S>(&mut self, array: &ArrayBase<S, Ix2>) -> Result<Array2<f64>>
where
S: Data,
S::Elem: Float + NumCast,
{
self.fit(array)?;
self.transform(array)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::Array;
#[test]
fn test_normalize_vector_minmax() {
let data = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let normalized =
normalize_vector(&data, NormalizationMethod::MinMax).expect("Operation failed");
let expected = Array::from_vec(vec![0.0, 0.25, 0.5, 0.75, 1.0]);
for (a, b) in normalized.iter().zip(expected.iter()) {
assert_abs_diff_eq!(a, b, epsilon = 1e-10);
}
}
#[test]
fn test_normalize_vector_zscore() {
let data = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let normalized =
normalize_vector(&data, NormalizationMethod::ZScore).expect("Operation failed");
let mean = 3.0;
let std_dev = (10.0 / 5.0_f64).sqrt();
let expected = data.mapv(|x| (x - mean) / std_dev);
for (a, b) in normalized.iter().zip(expected.iter()) {
assert_abs_diff_eq!(a, b, epsilon = 1e-10);
}
}
#[test]
fn test_normalize_array_minmax() {
let data = Array::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
.expect("Operation failed");
let normalized =
normalize_array(&data, NormalizationMethod::MinMax, 0).expect("Operation failed");
let expected =
Array::from_shape_vec((3, 3), vec![0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0])
.expect("Operation failed");
for i in 0..3 {
for j in 0..3 {
assert_abs_diff_eq!(normalized[[i, j]], expected[[i, j]], epsilon = 1e-10);
}
}
let normalized =
normalize_array(&data, NormalizationMethod::MinMax, 1).expect("Operation failed");
let expected =
Array::from_shape_vec((3, 3), vec![0.0, 0.5, 1.0, 0.0, 0.5, 1.0, 0.0, 0.5, 1.0])
.expect("Operation failed");
for i in 0..3 {
for j in 0..3 {
assert_abs_diff_eq!(normalized[[i, j]], expected[[i, j]], epsilon = 1e-10);
}
}
}
#[test]
fn test_normalizer_fit_transform() {
let data = Array::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
.expect("Operation failed");
let mut normalizer = Normalizer::new(NormalizationMethod::MinMax, 0);
let transformed = normalizer.fit_transform(&data).expect("Operation failed");
let expected =
Array::from_shape_vec((3, 3), vec![0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0])
.expect("Operation failed");
for i in 0..3 {
for j in 0..3 {
assert_abs_diff_eq!(transformed[[i, j]], expected[[i, j]], epsilon = 1e-10);
}
}
let data2 = Array::from_shape_vec((2, 3), vec![2.0, 3.0, 4.0, 5.0, 6.0, 7.0])
.expect("Operation failed");
let transformed2 = normalizer.transform(&data2).expect("Operation failed");
let expected2 = Array::from_shape_vec(
(2, 3),
vec![
1.0 / 6.0,
1.0 / 6.0,
1.0 / 6.0,
2.0 / 3.0,
2.0 / 3.0,
2.0 / 3.0,
],
)
.expect("Operation failed");
for i in 0..2 {
for j in 0..3 {
assert_abs_diff_eq!(transformed2[[i, j]], expected2[[i, j]], epsilon = 1e-10);
}
}
}
#[test]
fn test_normalize_vector_robust() {
let data = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 100.0]); let normalized =
normalize_vector(&data, NormalizationMethod::Robust).expect("Operation failed");
let expected = Array::from_vec(vec![
(1.0 - 3.0) / 2.0, (2.0 - 3.0) / 2.0, (3.0 - 3.0) / 2.0, (4.0 - 3.0) / 2.0, (100.0 - 3.0) / 2.0, ]);
for (a, b) in normalized.iter().zip(expected.iter()) {
assert_abs_diff_eq!(a, b, epsilon = 1e-10);
}
}
#[test]
fn test_normalize_array_robust() {
let data = Array::from_shape_vec((3, 2), vec![1.0, 10.0, 2.0, 20.0, 3.0, 30.0])
.expect("Operation failed");
let normalized =
normalize_array(&data, NormalizationMethod::Robust, 0).expect("Operation failed");
let expected = Array::from_shape_vec(
(3, 2),
vec![
(1.0 - 2.0) / 1.0, (10.0 - 20.0) / 10.0, (2.0 - 2.0) / 1.0, (20.0 - 20.0) / 10.0, (3.0 - 2.0) / 1.0, (30.0 - 20.0) / 10.0, ],
)
.expect("Operation failed");
for i in 0..3 {
for j in 0..2 {
assert_abs_diff_eq!(normalized[[i, j]], expected[[i, j]], epsilon = 1e-10);
}
}
}
#[test]
fn test_robust_normalizer() {
let data =
Array::from_shape_vec((4, 2), vec![1.0, 100.0, 2.0, 200.0, 3.0, 300.0, 4.0, 400.0])
.expect("Operation failed");
let mut normalizer = Normalizer::new(NormalizationMethod::Robust, 0);
let transformed = normalizer.fit_transform(&data).expect("Operation failed");
let expected = Array::from_shape_vec(
(4, 2),
vec![
(1.0 - 2.5) / 1.5, (100.0 - 250.0) / 150.0, (2.0 - 2.5) / 1.5, (200.0 - 250.0) / 150.0, (3.0 - 2.5) / 1.5, (300.0 - 250.0) / 150.0, (4.0 - 2.5) / 1.5, (400.0 - 250.0) / 150.0, ],
)
.expect("Operation failed");
for i in 0..4 {
for j in 0..2 {
assert_abs_diff_eq!(transformed[[i, j]], expected[[i, j]], epsilon = 1e-10);
}
}
}
}