use itertools::Itertools;
use smartcore::linalg::basic::arrays::{Array, Array2};
use smartcore::linalg::basic::matrix::DenseMatrix;
use smartcore::numbers::floatnum::FloatNumber;
use smartcore::numbers::realnum::RealNumber;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use crate::utils::math::elementwise_multiply;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FeatureError {
MatrixCreationFailed,
InvalidInputDimensions,
}
impl Display for FeatureError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::MatrixCreationFailed => write!(f, "cannot create matrix"),
Self::InvalidInputDimensions => write!(f, "input matrix has invalid dimensions"),
}
}
}
impl Error for FeatureError {}
pub fn interaction_features<INPUT, InputArray>(
mut x: InputArray,
) -> Result<InputArray, FeatureError>
where
INPUT: RealNumber + FloatNumber,
InputArray: Clone + Array<INPUT, (usize, usize)> + Array2<INPUT>,
{
let (height, width) = x.shape();
if height == 0 || width == 0 {
return Err(FeatureError::InvalidInputDimensions);
}
for column_1 in 0..width {
for column_2 in (column_1 + 1)..width {
let col1: Vec<INPUT> = (0..height)
.map(|idx| *x.get_col(column_1).get(idx))
.collect();
let col2: Vec<INPUT> = (0..height)
.map(|idx| *x.get_col(column_2).get(idx))
.collect();
let feature = elementwise_multiply(&col1, &col2);
let new_column = DenseMatrix::from_2d_vec(&vec![feature; 1])
.map_err(|_| FeatureError::MatrixCreationFailed)?
.transpose();
x = x.h_stack(&new_column);
}
}
Ok(x)
}
pub fn polynomial_features<INPUT, InputArray>(
mut x: InputArray,
order: usize,
) -> Result<InputArray, FeatureError>
where
INPUT: RealNumber + FloatNumber,
InputArray: Clone + Array<INPUT, (usize, usize)> + Array2<INPUT>,
{
let (height, width) = x.shape();
if height == 0 || width == 0 {
return Err(FeatureError::InvalidInputDimensions);
}
for n in 2..=order {
let combinations = (0..width).combinations_with_replacement(n);
for combo in combinations {
let mut feature: Vec<INPUT> = vec![INPUT::one(); height];
for column in combo {
let col: Vec<INPUT> = (0..height).map(|idx| *x.get_col(column).get(idx)).collect();
feature = elementwise_multiply(&col, &feature);
}
let new_column = DenseMatrix::from_2d_vec(&vec![feature; 1])
.map_err(|_| FeatureError::MatrixCreationFailed)?
.transpose();
x = x.h_stack(&new_column);
}
}
Ok(x)
}