use crate::types::ColumnType;
pub fn is_missing(s: &str) -> bool {
let t = s.trim();
if t.is_empty() {
return true;
}
t.eq_ignore_ascii_case("na")
|| t.eq_ignore_ascii_case("n/a")
|| t.eq_ignore_ascii_case("null")
|| t.eq_ignore_ascii_case("nan")
|| t.eq_ignore_ascii_case("none")
|| t == "-"
|| t == "?"
}
pub fn infer_column<T: AsRef<str>>(cells: &[T]) -> ColumnType {
let any_non_missing = cells.iter().any(|c| !is_missing(c.as_ref()));
if !any_non_missing {
return ColumnType::Categorical;
}
let all_numeric = cells
.iter()
.all(|c| is_missing(c.as_ref()) || c.as_ref().trim().parse::<f64>().is_ok());
if all_numeric {
ColumnType::Numeric
} else {
ColumnType::Categorical
}
}
pub fn parse_numeric_column<T: AsRef<str>>(cells: &[T]) -> Vec<f64> {
cells
.iter()
.map(|c| {
let c = c.as_ref();
if is_missing(c) {
f64::NAN
} else {
c.trim().parse::<f64>().unwrap_or(f64::NAN)
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_missing_recognizes_all_markers() {
assert!(is_missing(""));
assert!(is_missing("NA"));
assert!(is_missing("N/A"));
assert!(is_missing("null"));
assert!(is_missing("NULL"));
assert!(is_missing("NaN"));
assert!(is_missing("nan"));
assert!(is_missing("None"));
assert!(is_missing("none"));
assert!(is_missing("-"));
assert!(is_missing("?"));
assert!(is_missing(" NA "));
assert!(is_missing(" nan "));
assert!(!is_missing("0"));
assert!(!is_missing("1.5"));
assert!(!is_missing("hello"));
}
#[test]
fn infer_column_all_missing_is_categorical() {
let cells = vec!["NA".to_string(), "null".to_string(), "".to_string()];
assert_eq!(infer_column(&cells), ColumnType::Categorical);
}
#[test]
fn infer_column_mixed_numeric_and_missing_is_numeric() {
let cells = vec!["1.0".to_string(), "NA".to_string(), "2.0".to_string()];
assert_eq!(infer_column(&cells), ColumnType::Numeric);
}
#[test]
fn infer_column_one_non_numeric_is_categorical() {
let cells = vec!["1.0".to_string(), "hello".to_string(), "2.0".to_string()];
assert_eq!(infer_column(&cells), ColumnType::Categorical);
}
#[test]
fn parse_numeric_column_handles_missing() {
let cells = vec!["1.0".to_string(), "NA".to_string(), "2.0".to_string()];
let parsed = parse_numeric_column(&cells);
assert!((parsed[0] - 1.0).abs() < 1e-9);
assert!(parsed[1].is_nan());
assert!((parsed[2] - 2.0).abs() < 1e-9);
}
#[test]
fn parse_numeric_column_non_numeric_becomes_nan() {
let cells = vec!["1.0".to_string(), "hello".to_string()];
let parsed = parse_numeric_column(&cells);
assert!((parsed[0] - 1.0).abs() < 1e-9);
assert!(parsed[1].is_nan());
}
}