1use antecedent_core::{CausalSchema, VariableId};
6
7use crate::column::ColumnView;
8use crate::error::DataError;
9
10fn analysis_f64_from_i64(v: i64) -> f64 {
12 #[allow(clippy::cast_precision_loss)]
13 {
14 v as f64
15 }
16}
17
18pub trait TableView {
20 fn schema(&self) -> &CausalSchema;
22
23 fn row_count(&self) -> usize;
25
26 fn column(&self, id: VariableId) -> Result<ColumnView<'_>, DataError>;
32
33 fn float64_slice(&self, id: VariableId) -> Result<&[f64], DataError> {
44 match self.column(id)? {
45 ColumnView::Float64(c) => Ok(c.values.as_slice()),
46 _ => Err(DataError::TypeMismatch { id, expected: "native float64 (borrowed slice)" }),
47 }
48 }
49
50 fn float64_cow(&self, id: VariableId) -> Result<std::borrow::Cow<'_, [f64]>, DataError> {
60 match self.float64_slice(id) {
61 Ok(s) => Ok(std::borrow::Cow::Borrowed(s)),
62 Err(_) => self.float64_values(id).map(std::borrow::Cow::Owned),
63 }
64 }
65
66 fn float64_values(&self, id: VariableId) -> Result<Vec<f64>, DataError> {
76 match self.column(id)? {
77 ColumnView::Float64(c) => Ok(c.values.to_vec()),
78 ColumnView::Int64(c) => {
79 let mut out = Vec::with_capacity(c.values.len());
80 for (i, &v) in c.values.iter().enumerate() {
81 out.push(if c.validity.is_valid(i) {
82 analysis_f64_from_i64(v)
83 } else {
84 f64::NAN
85 });
86 }
87 Ok(out)
88 }
89 ColumnView::Boolean(c) => {
90 let mut out = Vec::with_capacity(c.values.len());
91 for (i, &v) in c.values.iter().enumerate() {
92 out.push(if c.validity.is_valid(i) { f64::from(v) } else { f64::NAN });
93 }
94 Ok(out)
95 }
96 _ => Err(DataError::TypeMismatch {
97 id,
98 expected: "float64 (or coercible int64/boolean)",
99 }),
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use std::sync::Arc;
107
108 use antecedent_core::{
109 CausalSchemaBuilder, MeasurementSpec, RoleHint, SmallRoleSet, ValueType, VariableId,
110 };
111
112 use super::*;
113 use crate::column::{BooleanColumn, Float64Column, Int64Column, OwnedColumn, ValidityBitmap};
114 use crate::dataset::TabularData;
115 use crate::storage::OwnedColumnarStorage;
116
117 fn schema_n(n: usize) -> antecedent_core::CausalSchema {
118 let mut b = CausalSchemaBuilder::new();
119 for i in 0..n {
120 b.add_variable(
121 format!("v{i}"),
122 ValueType::Continuous,
123 SmallRoleSet::from_hint(RoleHint::Context),
124 None,
125 None,
126 MeasurementSpec::default(),
127 )
128 .unwrap();
129 }
130 b.build().unwrap()
131 }
132
133 #[test]
134 fn float64_values_coerces_int64_and_boolean() {
135 let schema = schema_n(3);
136 let cols = vec![
137 OwnedColumn::Float64(
138 Float64Column::new(
139 VariableId::from_raw(0),
140 Arc::from([1.5_f64, 2.5]),
141 ValidityBitmap::all_valid(2),
142 )
143 .unwrap(),
144 ),
145 OwnedColumn::Int64(
146 Int64Column::new(
147 VariableId::from_raw(1),
148 Arc::<[i64]>::from([3_i64, 4]),
149 ValidityBitmap::all_valid(2),
150 )
151 .unwrap(),
152 ),
153 OwnedColumn::Boolean(
154 BooleanColumn::new(
155 VariableId::from_raw(2),
156 Arc::<[u8]>::from([1_u8, 0]),
157 ValidityBitmap::all_valid(2),
158 )
159 .unwrap(),
160 ),
161 ];
162 let storage = OwnedColumnarStorage::try_new(schema, cols, None, None).unwrap();
163 let data = TabularData::new(storage);
164 assert_eq!(data.float64_values(VariableId::from_raw(0)).unwrap(), vec![1.5, 2.5]);
165 assert_eq!(data.float64_values(VariableId::from_raw(1)).unwrap(), vec![3.0, 4.0]);
166 assert_eq!(data.float64_values(VariableId::from_raw(2)).unwrap(), vec![1.0, 0.0]);
167 }
168}