use crate::*;
use sklears_core::types::Float;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FeatureType {
Numerical,
Categorical,
Binary,
Ordinal,
DateTime,
Text,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MissingValueStrategy {
Mean,
Median,
Mode,
Zero,
ForwardFill,
BackwardFill,
Interpolate,
Drop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CategoricalEncoding {
OneHot,
LabelEncoding,
TargetEncoding,
BinaryEncoding,
FrequencyEncoding,
HashEncoding,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TransformType {
FFT,
DCT,
Walsh,
Haar,
DWT,
RFF,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DistanceMetric {
Euclidean,
Manhattan,
Cosine,
Hamming,
Jaccard,
Minkowski(u32),
Mahalanobis,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SamplingStrategy {
Random,
Stratified,
Systematic,
Importance,
Reservoir,
Bootstrap,
Cluster,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AggregationFunction {
Mean,
Median,
Std,
Variance,
Min,
Max,
Sum,
Count,
Skewness,
Kurtosis,
Percentile(u8),
Range,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectionCriterion {
KBest(usize),
Threshold(u32), Percentile(u8),
VarianceThreshold,
MutualInformation,
ChiSquared,
FTest,
}
#[derive(Debug, Clone)]
pub struct EngineeringConfig {
pub random_state: Option<u64>,
pub n_jobs: Option<usize>,
pub verbose: bool,
pub memory_limit: Option<usize>,
pub chunk_size: usize,
}
impl Default for EngineeringConfig {
fn default() -> Self {
Self {
random_state: Some(42),
n_jobs: None,
verbose: false,
memory_limit: None,
chunk_size: 1000,
}
}
}
pub type FeatureImportance = HashMap<String, Float>;
pub type FeatureNames = Vec<String>;
pub type CategoryMapping = HashMap<String, HashMap<String, usize>>;
pub type EngineeringResult<T> = Result<T, SklearsError>;
#[derive(Debug, Clone)]
pub struct FeatureStatistics {
pub name: String,
pub feature_type: FeatureType,
pub count: usize,
pub unique_count: usize,
pub mean: Option<Float>,
pub std: Option<Float>,
pub min: Option<Float>,
pub max: Option<Float>,
pub mode: Option<String>,
pub missing_fraction: Float,
}
impl FeatureStatistics {
pub fn new(name: String, feature_type: FeatureType) -> Self {
Self {
name,
feature_type,
count: 0,
unique_count: 0,
mean: None,
std: None,
min: None,
max: None,
mode: None,
missing_fraction: 0.0,
}
}
pub fn is_categorical(&self) -> bool {
matches!(
self.feature_type,
FeatureType::Categorical | FeatureType::Binary | FeatureType::Ordinal
)
}
pub fn is_numerical(&self) -> bool {
matches!(self.feature_type, FeatureType::Numerical)
}
pub fn is_high_cardinality(&self) -> bool {
if self.count == 0 {
return false;
}
let cardinality_ratio = self.unique_count as Float / self.count as Float;
cardinality_ratio > 0.5 }
}
#[derive(Debug, Clone)]
pub struct TransformationMetadata {
pub input_features: FeatureNames,
pub output_features: FeatureNames,
pub parameters: HashMap<String, serde_json::Value>,
pub input_statistics: Vec<FeatureStatistics>,
pub timestamp: String,
}
impl TransformationMetadata {
pub fn new(input_features: FeatureNames, output_features: FeatureNames) -> Self {
Self {
input_features,
output_features,
parameters: HashMap::new(),
input_statistics: Vec::new(),
timestamp: chrono::Utc::now().to_rfc3339(),
}
}
pub fn add_parameter<T: serde::Serialize>(&mut self, key: String, value: T) {
if let Ok(json_value) = serde_json::to_value(value) {
self.parameters.insert(key, json_value);
}
}
pub fn input_dimension(&self) -> usize {
self.input_features.len()
}
pub fn output_dimension(&self) -> usize {
self.output_features.len()
}
pub fn dimensionality_ratio(&self) -> Float {
if self.input_features.is_empty() {
return 0.0;
}
self.output_features.len() as Float / self.input_features.len() as Float
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_feature_type_properties() {
assert_eq!(FeatureType::Numerical, FeatureType::Numerical);
assert_ne!(FeatureType::Numerical, FeatureType::Categorical);
}
#[test]
fn test_missing_value_strategy() {
let strategy = MissingValueStrategy::Mean;
assert_eq!(strategy, MissingValueStrategy::Mean);
}
#[test]
fn test_feature_statistics() {
let stats = FeatureStatistics::new("test".to_string(), FeatureType::Numerical);
assert!(stats.is_numerical());
assert!(!stats.is_categorical());
assert!(!stats.is_high_cardinality());
}
#[test]
fn test_engineering_config_default() {
let config = EngineeringConfig::default();
assert_eq!(config.random_state, Some(42));
assert_eq!(config.chunk_size, 1000);
assert!(!config.verbose);
}
#[test]
fn test_transformation_metadata() {
let input = vec!["feature1".to_string(), "feature2".to_string()];
let output = vec!["transformed1".to_string(), "transformed2".to_string(), "transformed3".to_string()];
let metadata = TransformationMetadata::new(input, output);
assert_eq!(metadata.input_dimension(), 2);
assert_eq!(metadata.output_dimension(), 3);
assert_eq!(metadata.dimensionality_ratio(), 1.5);
}
#[test]
fn test_distance_metric_equality() {
assert_eq!(DistanceMetric::Euclidean, DistanceMetric::Euclidean);
assert_eq!(DistanceMetric::Minkowski(2), DistanceMetric::Minkowski(2));
assert_ne!(DistanceMetric::Minkowski(1), DistanceMetric::Minkowski(2));
}
#[test]
fn test_selection_criterion() {
let criterion = SelectionCriterion::KBest(10);
match criterion {
SelectionCriterion::KBest(k) => assert_eq!(k, 10),
_ => panic!("Wrong criterion type"),
}
}
}