Skip to main content

napparent_tabular/
table.rs

1//! Columnar chunk representation (Arrow-derived).
2
3use arrow::array::ArrayRef;
4use ndarray::Array1;
5
6/// Feature matrix columns (no target column), in schema order.
7#[derive(Clone, Debug)]
8pub struct ChunkTable {
9    pub names: Vec<String>,
10    pub cols: Vec<ColumnVec>,
11}
12
13#[derive(Clone, Debug)]
14pub enum ColumnVec {
15    F32(Vec<f32>),
16    /// Owned ndarray column for zero-copy Arrow export via ndarrow.
17    F32Array(Array1<f32>),
18    Utf8(Vec<String>),
19}
20
21impl ChunkTable {
22    pub fn nrows(&self) -> usize {
23        self.cols.first().map(|c| c.len()).unwrap_or(0)
24    }
25
26    pub fn validate(&self) -> Result<(), String> {
27        let n = self.nrows();
28        for (i, c) in self.cols.iter().enumerate() {
29            if c.len() != n {
30                return Err(format!(
31                    "column length mismatch: column {} len {} vs {}",
32                    i,
33                    c.len(),
34                    n
35                ));
36            }
37        }
38        Ok(())
39    }
40}
41
42impl ColumnVec {
43    pub fn len(&self) -> usize {
44        match self {
45            ColumnVec::F32(v) => v.len(),
46            ColumnVec::F32Array(a) => a.len(),
47            ColumnVec::Utf8(v) => v.len(),
48        }
49    }
50
51    pub fn is_empty(&self) -> bool {
52        self.len() == 0
53    }
54}
55
56/// Feature columns from one batch — Arrow buffers shared via `ArrayRef` where possible.
57#[derive(Clone, Debug)]
58pub struct BatchChunk {
59    pub names: Vec<String>,
60    pub cols: Vec<BatchColumn>,
61}
62
63/// Column storage for one batch pass.
64#[derive(Clone, Debug)]
65pub enum BatchColumn {
66    F32(ArrayRef),
67    F64(ArrayRef),
68    Utf8(ArrayRef),
69    Owned(ColumnVec),
70}
71
72impl BatchChunk {
73    pub fn nrows(&self) -> usize {
74        self.cols.first().map(|c| c.len()).unwrap_or(0)
75    }
76
77    pub fn validate(&self) -> Result<(), String> {
78        let n = self.nrows();
79        for (i, c) in self.cols.iter().enumerate() {
80            if c.len() != n {
81                return Err(format!(
82                    "column length mismatch: column {} len {} vs {}",
83                    i,
84                    c.len(),
85                    n
86                ));
87            }
88        }
89        Ok(())
90    }
91}
92
93impl BatchColumn {
94    pub fn len(&self) -> usize {
95        match self {
96            BatchColumn::F32(a) | BatchColumn::F64(a) | BatchColumn::Utf8(a) => a.len(),
97            BatchColumn::Owned(c) => c.len(),
98        }
99    }
100
101    pub fn is_empty(&self) -> bool {
102        self.len() == 0
103    }
104
105    pub fn is_numeric(&self) -> bool {
106        matches!(
107            self,
108            BatchColumn::F32(_)
109                | BatchColumn::F64(_)
110                | BatchColumn::Owned(ColumnVec::F32(_))
111                | BatchColumn::Owned(ColumnVec::F32Array(_))
112        )
113    }
114
115    pub fn array_ref(&self) -> Option<&ArrayRef> {
116        match self {
117            BatchColumn::F32(a) | BatchColumn::F64(a) | BatchColumn::Utf8(a) => Some(a),
118            BatchColumn::Owned(_) => None,
119        }
120    }
121}
122
123/// Target column — shared Float32 buffer or owned fallback.
124#[derive(Clone, Debug)]
125pub enum TargetColumn {
126    F32(ArrayRef),
127    Owned(Vec<f32>),
128}
129
130impl TargetColumn {
131    pub fn len(&self) -> usize {
132        match self {
133            TargetColumn::F32(a) => a.len(),
134            TargetColumn::Owned(v) => v.len(),
135        }
136    }
137
138    pub fn is_empty(&self) -> bool {
139        self.len() == 0
140    }
141}
142
143/// Maps column index in `ChunkTable` -> original name; which indices are dropped from preprocessing.
144#[derive(Clone, Debug, Default, PartialEq, Eq)]
145pub struct ColGraph {
146    pub names: Vec<String>,
147    pub dropped: std::collections::HashSet<usize>,
148}
149
150impl ColGraph {
151    /// Indices of columns that participate in binning / pair interactions.
152    pub fn active_indices(&self) -> Vec<usize> {
153        (0..self.names.len())
154            .filter(|i| !self.dropped.contains(i))
155            .collect()
156    }
157}
158
159/// Outcome vector aligned with table rows.
160#[derive(Clone, Debug)]
161pub enum OutcomeSource {
162    FromTarget(Vec<f32>),
163    External(Vec<f32>),
164}
165
166impl OutcomeSource {
167    pub fn as_slice(&self) -> &[f32] {
168        match self {
169            OutcomeSource::FromTarget(v) | OutcomeSource::External(v) => v.as_slice(),
170        }
171    }
172}