use crate::error::Error;
use crate::parallel_gates::{cheap_map_f64_parallel_threshold, scan_f64_parallel_min_elems};
use ndarray::{Array1, Array2, ArrayBase, ArrayView1, ArrayView2, ArrayViewMut1, Axis, Data, Ix2};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
pub mod max_abs_scaler;
pub mod min_max_scaler;
pub mod normalizer;
pub mod robust_scaler;
pub mod standard_scaler;
pub use max_abs_scaler::MaxAbsScaler;
pub use min_max_scaler::MinMaxScaler;
pub use normalizer::Normalizer;
pub use robust_scaler::RobustScaler;
pub use standard_scaler::StandardScaler;
const DEGENERATE_SCALE_THRESHOLD: f64 = 10.0 * f64::EPSILON;
#[inline]
fn handle_zero_scale(scale: f64) -> f64 {
if scale.abs() < DEGENERATE_SCALE_THRESHOLD {
1.0
} else {
scale
}
}
fn column_min_max(x: &ArrayView2<f64>) -> Vec<(f64, f64)> {
let fold_lane = |lane: ArrayView1<f64>| {
lane.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), &value| {
(lo.min(value), hi.max(value))
})
};
let lanes: Vec<ArrayView1<f64>> = x.lanes(Axis(0)).into_iter().collect();
if x.len() >= scan_f64_parallel_min_elems() {
lanes.into_par_iter().map(fold_lane).collect()
} else {
lanes.into_iter().map(fold_lane).collect()
}
}
fn column_quantiles(x: &ArrayView2<f64>, quantiles: &[f64]) -> Vec<Vec<f64>> {
let lane_quantiles = |lane: ArrayView1<f64>| {
let mut sorted: Vec<f64> = lane.iter().copied().collect();
sorted.sort_unstable_by(f64::total_cmp);
quantiles
.iter()
.map(|&q| quantile_of_sorted(&sorted, q))
.collect()
};
let lanes: Vec<ArrayView1<f64>> = x.lanes(Axis(0)).into_iter().collect();
if x.len() >= scan_f64_parallel_min_elems() {
lanes.into_par_iter().map(lane_quantiles).collect()
} else {
lanes.into_iter().map(lane_quantiles).collect()
}
}
#[inline]
fn quantile_of_sorted(sorted: &[f64], q: f64) -> f64 {
let position = (sorted.len() - 1) as f64 * q;
let lower = position.floor() as usize;
let fraction = position - lower as f64;
if fraction == 0.0 || lower + 1 >= sorted.len() {
sorted[lower]
} else {
sorted[lower] + fraction * (sorted[lower + 1] - sorted[lower])
}
}
fn for_each_row<F>(x: &mut Array2<f64>, row_op: F)
where
F: Fn(ArrayViewMut1<f64>) + Send + Sync,
{
if x.len() >= cheap_map_f64_parallel_threshold() {
x.axis_iter_mut(Axis(0)).into_par_iter().for_each(row_op);
} else {
x.axis_iter_mut(Axis(0)).for_each(row_op);
}
}
fn validate_matrix<S>(x: &ArrayBase<S, Ix2>) -> Result<(), Error>
where
S: Data<Elem = f64>,
{
if x.nrows() == 0 {
return Err(Error::empty_input("input data"));
}
if x.ncols() == 0 {
return Err(Error::empty_input("features"));
}
if x.iter().any(|value| !value.is_finite()) {
return Err(Error::non_finite("input data"));
}
Ok(())
}
fn validate_transform_matrix<S>(x: &ArrayBase<S, Ix2>, n_features: usize) -> Result<(), Error>
where
S: Data<Elem = f64>,
{
validate_matrix(x)?;
if x.ncols() != n_features {
return Err(Error::dimension_mismatch(n_features, x.ncols()));
}
Ok(())
}
#[inline]
fn fitted<'a>(
stat: &'a Option<Array1<f64>>,
model: &'static str,
) -> Result<&'a Array1<f64>, Error> {
stat.as_ref().ok_or_else(|| Error::not_fitted(model))
}