antecedent_data/
selection.rs1use std::sync::Arc;
6
7use antecedent_core::{
8 CausalSchemaBuilder, MeasurementSpec, RoleHint, SmallRoleSet, ValueType, VariableId,
9};
10
11use crate::column::{ColumnView, Float64Column, OwnedColumn, ValidityBitmap};
12use crate::dataset::TabularData;
13use crate::error::DataError;
14use crate::storage::OwnedColumnarStorage;
15use crate::table::TableView;
16
17impl TabularData {
18 pub fn complete_case_mask(&self, ids: &[VariableId]) -> Result<Vec<bool>, DataError> {
24 let n = self.row_count();
25 let mut keep = vec![true; n];
26 if let Some(mask) = self.storage().analysis_mask() {
27 for (i, slot) in keep.iter_mut().enumerate() {
28 *slot = mask.is_valid(i);
29 }
30 }
31 for &id in ids {
32 let validity = self.column(id)?.validity();
33 for (i, slot) in keep.iter_mut().enumerate() {
34 if *slot && !validity.is_valid(i) {
35 *slot = false;
36 }
37 }
38 }
39 if !keep.iter().any(|k| *k) {
40 return Err(DataError::EmptySelection {
41 context: "complete-case mask after validity/analysis filtering",
42 });
43 }
44 Ok(keep)
45 }
46
47 pub fn float64_masked(&self, id: VariableId, keep: &[bool]) -> Result<Vec<f64>, DataError> {
53 if keep.len() != self.row_count() {
54 return Err(DataError::LengthMismatch {
55 expected: self.row_count(),
56 actual: keep.len(),
57 context: "complete-case keep mask",
58 });
59 }
60 let ColumnView::Float64(c) = self.column(id)? else {
61 return Err(DataError::TypeMismatch { id, expected: "float64" });
62 };
63 let mut out = Vec::with_capacity(keep.iter().filter(|k| **k).count());
64 for (i, &k) in keep.iter().enumerate() {
65 if k {
66 out.push(c.values[i]);
67 }
68 }
69 Ok(out)
70 }
71
72 pub fn with_replaced_float(
80 &self,
81 id: VariableId,
82 values: Arc<[f64]>,
83 ) -> Result<Self, DataError> {
84 let n = self.row_count();
85 if values.len() != n {
86 return Err(DataError::LengthMismatch {
87 expected: n,
88 actual: values.len(),
89 context: "replacement float column",
90 });
91 }
92 let storage = self.storage();
93 let mut cols: Vec<OwnedColumn> = storage.columns().to_vec();
94 let idx = id.as_usize();
95 if idx >= cols.len() {
96 return Err(DataError::UnknownVariable { id });
97 }
98 if !matches!(cols[idx], OwnedColumn::Float64(_)) {
99 return Err(DataError::TypeMismatch { id, expected: "float64" });
100 }
101 cols[idx] =
102 OwnedColumn::Float64(Float64Column::new(id, values, ValidityBitmap::all_valid(n))?);
103 let storage = OwnedColumnarStorage::try_new(
104 storage.schema().clone(),
105 cols,
106 storage.analysis_mask().cloned(),
107 storage.weights().map(Arc::from),
108 )?;
109 Ok(Self::new(storage))
110 }
111
112 pub fn with_analysis_mask(&self, mask: ValidityBitmap) -> Result<Self, DataError> {
119 let storage = self.storage();
120 let n = storage.row_count();
121 if mask.len() != n {
122 return Err(DataError::LengthMismatch {
123 expected: n,
124 actual: mask.len(),
125 context: "analysis mask",
126 });
127 }
128 let combined = match storage.analysis_mask() {
129 Some(existing) => {
130 let mut bytes = vec![0u8; n.div_ceil(8)];
131 for i in 0..n {
132 if existing.is_valid(i) && mask.is_valid(i) {
133 bytes[i / 8] |= 1 << (i % 8);
134 }
135 }
136 ValidityBitmap::from_bytes(bytes, n)?
137 }
138 None => mask,
139 };
140 let new_storage = OwnedColumnarStorage::try_new(
141 storage.schema().clone(),
142 storage.columns().to_vec(),
143 Some(combined),
144 storage.weights().map(Arc::from),
145 )?;
146 Ok(Self::new(new_storage))
147 }
148
149 pub fn with_appended_float(
155 &self,
156 name: &str,
157 values: Arc<[f64]>,
158 ) -> Result<(Self, VariableId), DataError> {
159 let n = self.row_count();
160 if values.len() != n {
161 return Err(DataError::LengthMismatch {
162 expected: n,
163 actual: values.len(),
164 context: "appended float column",
165 });
166 }
167 let storage = self.storage();
168 let mut builder = CausalSchemaBuilder::new();
169 for v in storage.schema().variables() {
170 builder
171 .add_variable(
172 Arc::clone(&v.name),
173 v.value_type.clone(),
174 v.role_hints,
175 v.unit.clone(),
176 v.category_domain,
177 v.measurement.clone(),
178 )
179 .map_err(|e| DataError::Schema(e.to_string()))?;
180 }
181 builder
182 .add_variable(
183 name,
184 ValueType::Continuous,
185 SmallRoleSet::from_hint(RoleHint::Context),
186 None,
187 None,
188 MeasurementSpec::default(),
189 )
190 .map_err(|e| DataError::Schema(e.to_string()))?;
191 let schema = builder.build().map_err(|e| DataError::Schema(e.to_string()))?;
192 let new_id = VariableId::from_raw(u32::try_from(schema.len() - 1).map_err(|_| {
193 DataError::InvalidArgument { message: "schema exceeds VariableId range".into() }
194 })?);
195 let mut cols: Vec<OwnedColumn> = storage.columns().to_vec();
196 cols.push(OwnedColumn::Float64(Float64Column::new(
197 new_id,
198 values,
199 ValidityBitmap::all_valid(n),
200 )?));
201 let storage = OwnedColumnarStorage::try_new(
202 schema,
203 cols,
204 storage.analysis_mask().cloned(),
205 storage.weights().map(Arc::from),
206 )?;
207 Ok((Self::new(storage), new_id))
208 }
209}