1use super::*;
4
5impl Column {
6 fn combined_nulls(&self, other: &Column) -> Validity {
9 let a = self.validity().cloned().unwrap_or_default();
10 let b = other.validity().cloned().unwrap_or_default();
11 a.and(&b)
12 }
13
14 pub fn require_numeric(&self) -> Result<()> {
20 if self.dtype().is_numeric() || matches!(self, Column::Bool(..)) {
21 Ok(())
22 } else {
23 Err(VolasError::DType(format!(
24 "a numeric operation is not supported on a {} column",
25 self.dtype()
26 )))
27 }
28 }
29
30 pub fn binary(&self, other: &Column, op: BinOp) -> Result<Column> {
36 self.require_numeric()?;
37 other.require_numeric()?;
38 if let (Column::Bool(a, av), Column::Bool(b, bv)) = (self, other) {
39 let nulls = av.and(bv);
40 return match op {
41 BinOp::Add => Ok(Column::bool_with(
42 a.iter().zip(b.iter()).map(|(&x, &y)| x || y).collect(),
43 nulls,
44 )),
45 BinOp::Mul => Ok(Column::bool_with(
46 a.iter().zip(b.iter()).map(|(&x, &y)| x && y).collect(),
47 nulls,
48 )),
49 BinOp::Sub => Err(VolasError::DType(
50 "the `-` operator is not supported for bool columns (use `^`)".into(),
51 )),
52 };
53 }
54 match binary_supertype(self.dtype(), other.dtype()) {
55 DType::I64 => Ok(Column::i64_with(
56 binary_kernel(&self.as_i64_vec()?, &other.as_i64_vec()?, op),
57 self.combined_nulls(other),
58 )),
59 DType::I32 => Ok(Column::i32_with(
60 binary_kernel(&self.as_i32_vec()?, &other.as_i32_vec()?, op),
61 self.combined_nulls(other),
62 )),
63 DType::F32 => Ok(Column::f32(binary_kernel(
64 &self.to_f32_vec(),
65 &other.to_f32_vec(),
66 op,
67 ))),
68 _ => Ok(Column::f64(binary_kernel(
69 &self.to_f64_vec(),
70 &other.to_f64_vec(),
71 op,
72 ))),
73 }
74 }
75
76 pub fn div(&self, other: &Column) -> Result<Column> {
79 self.require_numeric()?;
80 other.require_numeric()?;
81 if matches!((self, other), (Column::Bool(_, _), Column::Bool(_, _))) {
82 return Err(VolasError::DType(
83 "division is not supported between two bool columns".into(),
84 ));
85 }
86 let (a, b) = (self.to_f64_vec(), other.to_f64_vec());
87 Ok(Column::f64(
88 a.iter().zip(&b).map(|(&x, &y)| x / y).collect(),
89 ))
90 }
91
92 pub fn floordiv(&self, other: &Column) -> Result<Column> {
99 self.require_numeric()?;
100 other.require_numeric()?;
101 if matches!((self, other), (Column::Bool(_, _), Column::Bool(_, _))) {
102 return Err(VolasError::DType(
103 "floor division is not supported between two bool columns".into(),
104 ));
105 }
106 let n = self.len();
107 let st = binary_supertype(self.dtype(), other.dtype());
108 let int_path = matches!(st, DType::I64 | DType::I32)
110 && !(0..n).any(|i| other.is_valid(i) && other.get_f64(i) == 0.0);
111 if int_path {
112 let (a, b) = (self.as_i64_vec()?, other.as_i64_vec()?);
113 let present = |i: usize| self.is_valid(i) && other.is_valid(i);
114 let out: Vec<i64> = (0..n)
117 .map(|i| if present(i) { ifloordiv(a[i], b[i]) } else { 0 })
118 .collect();
119 let val = Validity::from_valid_iter(n, (0..n).map(present));
120 Ok(if st == DType::I32 {
122 Column::i32_with(out.iter().map(|&x| x as i32).collect(), val)
123 } else {
124 Column::i64_with(out, val)
125 })
126 } else {
127 let (a, b) = (self.to_f64_vec(), other.to_f64_vec());
128 Ok(Column::f64(
129 a.iter().zip(&b).map(|(&x, &y)| (x / y).floor()).collect(),
130 ))
131 }
132 }
133
134 pub fn compare(&self, other: &Column, op: CmpOp) -> Result<Column> {
142 let n = self.len();
143 let numeric_like = |c: &Column| c.dtype().is_numeric() || matches!(c, Column::Bool(..));
144 let out = match (self, other) {
145 (Column::Str(a, av), Column::Str(b, bv)) => cmp_typed(n, op, |i| {
146 (av.is_valid(i) && bv.is_valid(i))
148 .then(|| unsafe { a.get_unchecked(i).cmp(b.get_unchecked(i)) })
149 }),
150 (Column::Datetime(a), Column::Datetime(b)) => cmp_typed(n, op, |i| {
151 (a[i] != i64::MIN && b[i] != i64::MIN).then(|| a[i].cmp(&b[i]))
152 }),
153 (Column::Bool(a, av), Column::Bool(b, bv)) => cmp_typed(n, op, |i| {
154 (av.is_valid(i) && bv.is_valid(i)).then(|| a[i].cmp(&b[i]))
155 }),
156 (a, b) if numeric_like(a) && numeric_like(b) => {
159 let (x, y) = (a.to_f64_vec(), b.to_f64_vec());
160 (0..n).map(|i| op.matches_f64(x[i], y[i])).collect()
161 }
162 _ => {
163 return Err(VolasError::DType(format!(
164 "cannot compare a {} column with a {} column",
165 self.dtype(),
166 other.dtype()
167 )))
168 }
169 };
170 Ok(Column::bool(out))
171 }
172
173 pub fn logical(&self, other: &Column, op: BoolOp) -> Column {
178 let n = self.len();
179 from_option_bools(
180 n,
181 (0..n).map(|i| {
182 let (a, ap) = self.bool_at(i);
183 let (b, bp) = other.bool_at(i);
184 match op {
185 BoolOp::And => {
186 if (ap && !a) || (bp && !b) {
187 Some(false)
188 } else if ap && bp {
189 Some(true)
190 } else {
191 None
192 }
193 }
194 BoolOp::Or => {
195 if (ap && a) || (bp && b) {
196 Some(true)
197 } else if ap && bp {
198 Some(false)
199 } else {
200 None
201 }
202 }
203 BoolOp::Xor => (ap && bp).then_some(a ^ b),
204 }
205 }),
206 )
207 }
208
209 pub fn not(&self) -> Column {
212 let n = self.len();
213 from_option_bools(
214 n,
215 (0..n).map(|i| {
216 let (a, ap) = self.bool_at(i);
217 ap.then_some(!a)
218 }),
219 )
220 }
221
222 fn bool_at(&self, i: usize) -> (bool, bool) {
226 match self {
227 Column::Bool(v, val) => (v[i], val.is_valid(i)),
228 _ => (self.get_f64(i) != 0.0, true),
229 }
230 }
231
232 pub fn sum(&self) -> Scalar {
235 match self {
236 Column::F64(v) => Scalar::F64(stats::sum(v.as_slice())),
237 Column::F32(v) => Scalar::F32(stats::sum(v.as_slice())),
238 Column::I64(v, val) => Scalar::I64(sum_valid(v, val)),
239 Column::I32(v, val) => Scalar::I64(sum_valid(&widen_i64(v), val)),
241 Column::Bool(v, val) => Scalar::I64(sum_valid(&widen_i64(v), val)),
242 other => Scalar::F64(stats::sum(&other.to_f64_vec())),
243 }
244 }
245
246 pub fn prod(&self) -> Scalar {
248 match self {
249 Column::F64(v) => Scalar::F64(stats::prod(v.as_slice())),
250 Column::F32(v) => Scalar::F32(stats::prod(v.as_slice())),
251 Column::I64(v, val) => Scalar::I64(prod_valid(v, val)),
252 Column::I32(v, val) => Scalar::I64(prod_valid(&widen_i64(v), val)),
253 Column::Bool(v, val) => Scalar::I64(prod_valid(&widen_i64(v), val)),
254 other => Scalar::F64(stats::prod(&other.to_f64_vec())),
255 }
256 }
257
258 pub fn extreme(&self, want_max: bool) -> Scalar {
261 match self {
262 Column::I64(v, val) => match extreme_valid(v, val, want_max) {
263 Some(x) => Scalar::I64(x),
264 None => Scalar::F64(f64::NAN),
265 },
266 Column::I32(v, val) => match extreme_valid(v, val, want_max) {
267 Some(x) => Scalar::I32(x),
268 None => Scalar::F64(f64::NAN),
269 },
270 Column::F32(v) => {
271 Scalar::F32(stats::extreme(v.as_slice(), want_max).unwrap_or(f32::NAN))
272 }
273 Column::Bool(v, val) => {
274 let present = |i: usize| val.is_valid(i);
276 if !(0..v.len()).any(present) {
277 Scalar::F64(f64::NAN)
278 } else if want_max {
279 Scalar::Bool((0..v.len()).any(|i| present(i) && v[i]))
280 } else {
281 Scalar::Bool((0..v.len()).all(|i| !present(i) || v[i]))
282 }
283 }
284 Column::F64(v) => {
285 Scalar::F64(stats::extreme(v.as_slice(), want_max).unwrap_or(f64::NAN))
286 }
287 other => Scalar::F64(stats::extreme(&other.to_f64_vec(), want_max).unwrap_or(f64::NAN)),
288 }
289 }
290}