zsfm_tabfm/ensemble/
cat_encoder.rs1use std::collections::HashMap;
6
7use serde_json::Value;
8
9pub struct CategoricalOrdinalEncoder {
10 index: HashMap<String, i64>,
11}
12
13impl CategoricalOrdinalEncoder {
14 pub fn fit(column: &[Value]) -> Self {
15 let mut index = HashMap::new();
16 let mut next = 0i64;
17 for v in column {
18 if is_missing(v) {
19 continue;
20 }
21 let key = value_key(v);
22 index.entry(key).or_insert_with(|| {
23 let code = next;
24 next += 1;
25 code
26 });
27 }
28 CategoricalOrdinalEncoder { index }
29 }
30
31 pub fn transform(&self, column: &[Value]) -> Vec<f64> {
32 column
33 .iter()
34 .map(|v| {
35 if is_missing(v) {
36 return -1.0;
37 }
38 *self.index.get(&value_key(v)).unwrap_or(&-1) as f64
39 })
40 .collect()
41 }
42}
43
44pub struct LabelEncoder {
47 classes: Vec<String>, }
49
50impl LabelEncoder {
51 pub fn fit(y: &[Value]) -> Self {
52 let mut uniq: Vec<String> = y.iter().filter(|v| !is_missing(v)).map(value_key).collect();
53 uniq.sort();
54 uniq.dedup();
55 LabelEncoder { classes: uniq }
56 }
57
58 pub fn n_classes(&self) -> usize {
59 self.classes.len()
60 }
61
62 pub fn transform(&self, y: &[Value]) -> Vec<f64> {
63 y.iter()
64 .map(|v| {
65 if is_missing(v) {
66 return -1.0;
67 }
68 let key = value_key(v);
69 self.classes
70 .iter()
71 .position(|c| c == &key)
72 .map(|i| i as f64)
73 .unwrap_or(-1.0)
74 })
75 .collect()
76 }
77
78 pub fn decode(&self, code: usize) -> &str {
79 &self.classes[code]
80 }
81}
82
83fn is_missing(v: &Value) -> bool {
84 match v {
85 Value::Null => true,
86 Value::String(s) => s.is_empty() || s.eq_ignore_ascii_case("nan"),
87 Value::Number(n) => n.as_f64().map(f64::is_nan).unwrap_or(false),
88 _ => false,
89 }
90}
91
92fn value_key(v: &Value) -> String {
93 match v {
94 Value::String(s) => s.clone(),
95 Value::Number(n) => n.to_string(),
96 Value::Bool(b) => b.to_string(),
97 other => other.to_string(),
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_appearance_order() {
107 let col = vec![
108 Value::String("blue".into()),
109 Value::String("red".into()),
110 Value::String("blue".into()),
111 Value::String("green".into()),
112 ];
113 let enc = CategoricalOrdinalEncoder::fit(&col);
114 assert_eq!(enc.transform(&col), vec![0.0, 1.0, 0.0, 2.0]);
115 }
116
117 #[test]
118 fn test_unknown_at_transform_time() {
119 let train = vec![Value::String("a".into()), Value::String("b".into())];
120 let enc = CategoricalOrdinalEncoder::fit(&train);
121 let test = vec![Value::String("a".into()), Value::String("c".into())];
122 assert_eq!(enc.transform(&test), vec![0.0, -1.0]);
123 }
124
125 #[test]
126 fn test_missing_values() {
127 let col = vec![
128 Value::String("a".into()),
129 Value::Null,
130 Value::String("a".into()),
131 ];
132 let enc = CategoricalOrdinalEncoder::fit(&col);
133 assert_eq!(enc.transform(&col), vec![0.0, -1.0, 0.0]);
134 }
135}