1use vortex_error::VortexResult;
5use vortex_error::vortex_bail;
6use vortex_session::registry::CachedId;
7
8use crate::ArrayRef;
9use crate::ExecutionCtx;
10use crate::IntoArray;
11use crate::aggregate_fn::Accumulator;
12use crate::aggregate_fn::AggregateFnId;
13use crate::aggregate_fn::AggregateFnVTable;
14use crate::aggregate_fn::DynAccumulator;
15use crate::aggregate_fn::NumericalAggregateOpts;
16use crate::aggregate_fn::combined::BinaryCombined;
17use crate::aggregate_fn::combined::Combined;
18use crate::aggregate_fn::combined::CombinedOptions;
19use crate::aggregate_fn::combined::PairOptions;
20use crate::aggregate_fn::fns::count::Count;
21use crate::aggregate_fn::fns::sum::Sum;
22use crate::aggregate_fn::fns::sum::sum_decimal_dtype;
23use crate::arrays::ConstantArray;
24use crate::builtins::ArrayBuiltins;
25use crate::dtype::DType;
26use crate::dtype::DecimalDType;
27use crate::dtype::MAX_PRECISION;
28use crate::dtype::MAX_SCALE;
29use crate::dtype::Nullability;
30use crate::dtype::PType;
31use crate::dtype::i256;
32use crate::scalar::DecimalValue;
33use crate::scalar::Scalar;
34use crate::scalar_fn::fns::operators::Operator;
35
36pub fn mean(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Scalar> {
40 let mut acc = Accumulator::try_new(
41 Mean::combined(),
42 PairOptions(
43 NumericalAggregateOpts::default(),
44 NumericalAggregateOpts::default(),
45 ),
46 array.dtype().clone(),
47 )?;
48 acc.accumulate(array, ctx)?;
49 acc.finish()
50}
51
52#[derive(Clone, Debug)]
58pub struct Mean;
59
60impl Mean {
61 pub fn combined() -> Combined<Self> {
62 Combined(Mean)
63 }
64}
65
66impl BinaryCombined for Mean {
67 type Left = Sum;
68 type Right = Count;
69
70 fn id(&self) -> AggregateFnId {
71 static ID: CachedId = CachedId::new("vortex.mean");
72 *ID
73 }
74
75 fn left(&self) -> Sum {
76 Sum
77 }
78
79 fn right(&self) -> Count {
80 Count
81 }
82
83 fn left_name(&self) -> &'static str {
84 "sum"
85 }
86
87 fn right_name(&self) -> &'static str {
88 "count"
89 }
90
91 fn return_dtype(&self, input_dtype: &DType) -> Option<DType> {
92 Some(mean_output_dtype(input_dtype)?.with_nullability(Nullability::Nullable))
93 }
94
95 fn finalize(&self, sum: ArrayRef, count: ArrayRef) -> VortexResult<ArrayRef> {
96 if let DType::Decimal(..) = sum.dtype() {
97 vortex_bail!("grouped mean over decimals is not yet supported");
98 }
99 let target = DType::Primitive(PType::F64, Nullability::Nullable);
100 let sum = sum.cast(target.clone())?;
101 let count = count.cast(target.clone())?;
102
103 let non_zero = count
104 .binary(
105 ConstantArray::new(Scalar::zero_value(&target), count.len()).into_array(),
106 Operator::NotEq,
107 )?
108 .fill_null(false)?;
109 let count = count.mask(non_zero)?;
113
114 sum.binary(count, Operator::Div)
115 }
116
117 fn finalize_scalar(&self, left_scalar: Scalar, right_scalar: Scalar) -> VortexResult<Scalar> {
118 if let DType::Decimal(decimal_dtype, _) = *left_scalar.dtype() {
119 return finalize_decimal_scalar(&left_scalar, &right_scalar, decimal_dtype);
120 }
121
122 let target = DType::Primitive(PType::F64, Nullability::Nullable);
123 let sum_cast = left_scalar.cast(&target)?;
124 let count_cast = right_scalar.cast(&target)?;
125
126 let sum = sum_cast.as_primitive().typed_value::<f64>();
127 let count = count_cast.as_primitive().typed_value::<f64>();
128 let value = match (sum, count) {
129 (None, _) | (_, None) | (_, Some(0.0)) => return Ok(Scalar::null(target)),
131 (Some(s), Some(c)) => s / c,
132 };
133 Ok(Scalar::primitive(value, Nullability::Nullable))
134 }
135
136 fn serialize(&self, _options: &CombinedOptions<Self>) -> VortexResult<Option<Vec<u8>>> {
137 unimplemented!("mean is not yet serializable");
138 }
139
140 fn coerce_args(
141 &self,
142 _options: &PairOptions<
143 <Sum as AggregateFnVTable>::Options,
144 <Count as AggregateFnVTable>::Options,
145 >,
146 input_dtype: &DType,
147 ) -> VortexResult<DType> {
148 Ok(coerced_input_dtype(input_dtype).unwrap_or_else(|| input_dtype.clone()))
151 }
152}
153
154fn coerced_input_dtype(input_dtype: &DType) -> Option<DType> {
161 match input_dtype {
162 DType::Bool(_) => Some(input_dtype.clone()),
163 DType::Primitive(_, n) => Some(DType::Primitive(PType::F64, *n)),
164 DType::Decimal(..) => Some(input_dtype.clone()),
165 _ => None,
166 }
167}
168
169fn mean_output_dtype(input_dtype: &DType) -> Option<DType> {
170 match input_dtype {
171 DType::Bool(_) | DType::Primitive(..) => {
172 Some(DType::Primitive(PType::F64, Nullability::Nullable))
173 }
174 DType::Decimal(decimal_dtype, _) => Some(DType::Decimal(
175 mean_decimal_dtype(&sum_decimal_dtype(decimal_dtype)),
176 Nullability::Nullable,
177 )),
178 _ => None,
179 }
180}
181
182fn mean_decimal_dtype(sum: &DecimalDType) -> DecimalDType {
184 DecimalDType::new(
185 u8::min(MAX_PRECISION, sum.precision().saturating_sub(6)),
186 i8::min(MAX_SCALE, sum.scale() + 4),
187 )
188}
189
190fn finalize_decimal_scalar(
191 sum: &Scalar,
192 count: &Scalar,
193 sum_decimal: DecimalDType,
194) -> VortexResult<Scalar> {
195 let target_decimal_dtype = mean_decimal_dtype(&sum_decimal);
196 let target_dtype = DType::Decimal(target_decimal_dtype, Nullability::Nullable);
197
198 let Some(sum_value) = sum.as_decimal().decimal_value() else {
200 return Ok(Scalar::null(target_dtype));
201 };
202 let count = count.as_primitive().typed_value::<u64>().unwrap_or(0);
204 if count == 0 {
205 return Ok(Scalar::null(target_dtype));
206 }
207
208 let Ok(sum) = DecimalValue::rescale_i256(
209 sum_value.as_i256(),
210 sum_decimal.scale(),
211 target_decimal_dtype.scale(),
212 ) else {
213 return Ok(Scalar::null(target_dtype));
214 };
215 let mean = sum / i256::from_i128(i128::from(count));
216
217 let Ok(mean) = DecimalValue::try_from_i256(mean, target_decimal_dtype) else {
218 return Ok(Scalar::null(target_dtype));
219 };
220 Ok(Scalar::decimal(
221 mean,
222 target_decimal_dtype,
223 Nullability::Nullable,
224 ))
225}
226
227#[cfg(test)]
228mod tests {
229 use vortex_buffer::buffer;
230 use vortex_error::VortexResult;
231
232 use super::*;
233 use crate::VortexSessionExecute;
234 use crate::aggregate_fn::DynGroupedAccumulator;
235 use crate::aggregate_fn::GroupedAccumulator;
236 use crate::array_session;
237 use crate::arrays::BoolArray;
238 use crate::arrays::ChunkedArray;
239 use crate::arrays::DecimalArray;
240 use crate::arrays::FixedSizeListArray;
241 use crate::arrays::PrimitiveArray;
242 use crate::dtype::DecimalDType;
243 use crate::validity::Validity;
244
245 #[test]
246 fn mean_all_valid() -> VortexResult<()> {
247 let array = PrimitiveArray::new(buffer![1.0f64, 2.0, 3.0, 4.0, 5.0], Validity::NonNullable)
248 .into_array();
249 let mut ctx = array_session().create_execution_ctx();
250 let result = mean(&array, &mut ctx)?;
251 assert_eq!(result.as_primitive().as_::<f64>(), Some(3.0));
252 Ok(())
253 }
254
255 #[test]
256 fn mean_with_nulls() -> VortexResult<()> {
257 let array = PrimitiveArray::from_option_iter([Some(2.0f64), None, Some(4.0)]).into_array();
258 let mut ctx = array_session().create_execution_ctx();
259 let result = mean(&array, &mut ctx)?;
260 assert_eq!(result.as_primitive().as_::<f64>(), Some(3.0));
261 Ok(())
262 }
263
264 #[test]
265 fn mean_integers() -> VortexResult<()> {
266 let array = PrimitiveArray::new(buffer![10i32, 20, 30], Validity::NonNullable).into_array();
267 let mut ctx = array_session().create_execution_ctx();
268 let result = mean(&array, &mut ctx)?;
269 assert_eq!(result.as_primitive().as_::<f64>(), Some(20.0));
270 Ok(())
271 }
272
273 #[test]
274 fn mean_bool() -> VortexResult<()> {
275 let array: BoolArray = [true, false, true, true].into_iter().collect();
276 let mut ctx = array_session().create_execution_ctx();
277 let result = mean(&array.into_array(), &mut ctx)?;
278 assert_eq!(result.as_primitive().as_::<f64>(), Some(0.75));
279 Ok(())
280 }
281
282 #[test]
283 fn mean_constant_non_null() -> VortexResult<()> {
284 let array = ConstantArray::new(5.0f64, 4);
285 let mut ctx = array_session().create_execution_ctx();
286 let result = mean(&array.into_array(), &mut ctx)?;
287 assert_eq!(result.as_primitive().as_::<f64>(), Some(5.0));
288 Ok(())
289 }
290
291 #[test]
292 fn mean_chunked() -> VortexResult<()> {
293 let chunk1 = PrimitiveArray::from_option_iter([Some(1.0f64), None, Some(3.0)]);
294 let chunk2 = PrimitiveArray::from_option_iter([Some(5.0f64), None]);
295 let dtype = chunk1.dtype().clone();
296 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
297 let mut ctx = array_session().create_execution_ctx();
298 let result = mean(&chunked.into_array(), &mut ctx)?;
299 assert_eq!(result.as_primitive().as_::<f64>(), Some(3.0));
300 Ok(())
301 }
302
303 #[test]
304 fn mean_skips_nans_by_default() -> VortexResult<()> {
305 let array =
307 PrimitiveArray::new(buffer![1.0f64, f64::NAN, 3.0], Validity::NonNullable).into_array();
308 let mut ctx = array_session().create_execution_ctx();
309 let result = mean(&array, &mut ctx)?;
310 assert_eq!(result.as_primitive().as_::<f64>(), Some(2.0));
311 Ok(())
312 }
313
314 #[test]
315 fn mean_with_nan_not_skipping() -> VortexResult<()> {
316 let array =
317 PrimitiveArray::new(buffer![1.0f64, f64::NAN, 3.0], Validity::NonNullable).into_array();
318 let mut ctx = array_session().create_execution_ctx();
319 let keep_nans = NumericalAggregateOpts::include_nans();
320 let mut acc = Accumulator::try_new(
321 Mean::combined(),
322 PairOptions(keep_nans, keep_nans),
323 array.dtype().clone(),
324 )?;
325 acc.accumulate(&array, &mut ctx)?;
326 let result = acc.finish()?;
327 assert!(result.as_primitive().as_::<f64>().is_some_and(f64::is_nan));
328 Ok(())
329 }
330
331 #[test]
332 fn mean_all_null_returns_null() -> VortexResult<()> {
333 let array = PrimitiveArray::from_option_iter::<f64, _>([None, None, None]).into_array();
334 let mut ctx = array_session().create_execution_ctx();
335 let result = mean(&array, &mut ctx)?;
336 assert_eq!(result.as_primitive().as_::<f64>(), None);
337 Ok(())
338 }
339
340 #[test]
341 fn mean_decimal() -> VortexResult<()> {
342 let dtype = DecimalDType::new(6, 2);
343 let array =
344 DecimalArray::new(buffer![100i32, 200, 300], dtype, Validity::NonNullable).into_array();
345 let mut ctx = array_session().create_execution_ctx();
346 let result = mean(&array, &mut ctx)?;
347 assert_eq!(
348 result.dtype(),
349 &DType::Decimal(DecimalDType::new(10, 6), Nullability::Nullable)
350 );
351 assert_eq!(
353 result.as_decimal().decimal_value(),
354 Some(DecimalValue::I256(i256::from_i128(2_000_000)))
355 );
356 Ok(())
357 }
358
359 #[test]
360 fn mean_decimal_null() -> VortexResult<()> {
361 let dtype = DecimalDType::new(6, 2);
362 let validity = Validity::from_iter([true, false, true]);
363 let array = DecimalArray::new(buffer![150i32, 0, 450], dtype, validity).into_array();
364 let mut ctx = array_session().create_execution_ctx();
365 let result = mean(&array, &mut ctx)?;
366 assert_eq!(
368 result.as_decimal().decimal_value(),
369 Some(DecimalValue::I256(i256::from_i128(3_000_000)))
370 );
371 Ok(())
372 }
373
374 #[test]
375 fn mean_decimal_chunked() -> VortexResult<()> {
376 let dtype = DecimalDType::new(6, 2);
377 let validity = Validity::NonNullable;
378 let chunk1 = DecimalArray::new(buffer![100i32, 200], dtype, validity.clone()).into_array();
379 let chunk2 = DecimalArray::new(buffer![300i32, 400, 500], dtype, validity).into_array();
380 let dtype = chunk1.dtype().clone();
381 let chunked = ChunkedArray::try_new(vec![chunk1, chunk2], dtype)?;
382 let mut ctx = array_session().create_execution_ctx();
383 let result = mean(&chunked.into_array(), &mut ctx)?;
384 assert_eq!(
386 result.as_decimal().decimal_value(),
387 Some(DecimalValue::I256(i256::from_i128(3_000_000)))
388 );
389 Ok(())
390 }
391
392 #[test]
393 fn mean_decimal_33() -> VortexResult<()> {
394 let dtype = DecimalDType::new(6, 2);
395 let buf = buffer![100i32, 0, 0];
396 let array = DecimalArray::new(buf, dtype, Validity::NonNullable).into_array();
397 let mut ctx = array_session().create_execution_ctx();
398 let result = mean(&array, &mut ctx)?;
399 assert_eq!(
401 result.as_decimal().decimal_value(),
402 Some(DecimalValue::I256(i256::from_i128(333_333)))
403 );
404 Ok(())
405 }
406
407 #[test]
408 fn mean_multi_batch() -> VortexResult<()> {
409 let mut ctx = array_session().create_execution_ctx();
410 let dtype = DType::Primitive(PType::F64, Nullability::NonNullable);
411 let mut acc = Accumulator::try_new(
412 Mean::combined(),
413 PairOptions(
414 NumericalAggregateOpts::default(),
415 NumericalAggregateOpts::default(),
416 ),
417 dtype,
418 )?;
419
420 let batch1 =
421 PrimitiveArray::new(buffer![1.0f64, 2.0, 3.0], Validity::NonNullable).into_array();
422 acc.accumulate(&batch1, &mut ctx)?;
423
424 let batch2 = PrimitiveArray::new(buffer![4.0f64, 5.0], Validity::NonNullable).into_array();
425 acc.accumulate(&batch2, &mut ctx)?;
426
427 let result = acc.finish()?;
428 assert_eq!(result.as_primitive().as_::<f64>(), Some(3.0));
429 Ok(())
430 }
431
432 fn mean_nan_null() -> Vec<(Vec<Option<f64>>, Option<f64>)> {
433 vec![
434 (vec![Some(f64::NAN), Some(1.0), None], Some(1.0)),
435 (vec![Some(f64::NAN), Some(1.0), Some(3.0)], Some(2.0)),
436 (vec![None, None, Some(f64::NAN)], None),
437 (vec![None, None, None], None),
438 (vec![Some(1.0), Some(2.0), Some(3.0)], Some(2.0)),
439 ]
440 }
441
442 #[test]
443 fn mean_combined_partials() -> VortexResult<()> {
444 let mut ctx = array_session().create_execution_ctx();
445 for (case, (group, expected)) in mean_nan_null().into_iter().enumerate() {
446 let mut acc = Accumulator::try_new(
447 Mean::combined(),
448 PairOptions(
449 NumericalAggregateOpts::default(),
450 NumericalAggregateOpts::default(),
451 ),
452 DType::Primitive(PType::F64, Nullability::Nullable),
453 )?;
454 let (head, tail) = group.split_at(2);
455 let head = PrimitiveArray::from_option_iter(head.iter().copied()).into_array();
456 let tail = PrimitiveArray::from_option_iter(tail.iter().copied()).into_array();
457 acc.accumulate(&head, &mut ctx)?;
458 acc.accumulate(&tail, &mut ctx)?;
459 let result = acc.finish()?;
460 assert_eq!(result.as_primitive().as_::<f64>(), expected, "case {case}");
461 }
462 Ok(())
463 }
464
465 #[test]
466 fn mean_grouped_finalize() -> VortexResult<()> {
467 let cases = mean_nan_null();
468 let elements = PrimitiveArray::from_option_iter(
469 cases.iter().flat_map(|(group, _)| group.iter().copied()),
470 )
471 .into_array();
472 let groups = FixedSizeListArray::try_new(elements, 3, Validity::NonNullable, cases.len())?;
473
474 let mut acc = GroupedAccumulator::try_new(
475 Mean::combined(),
476 PairOptions(
477 NumericalAggregateOpts::default(),
478 NumericalAggregateOpts::default(),
479 ),
480 DType::Primitive(PType::F64, Nullability::Nullable),
481 )?;
482 let mut ctx = array_session().create_execution_ctx();
483 acc.accumulate_list(&groups.into_array(), &mut ctx)?;
484 let result = acc.finish()?;
485
486 for (case, (_, expected)) in cases.into_iter().enumerate() {
487 let actual = result.execute_scalar(case, &mut ctx)?;
488 assert_eq!(actual.as_primitive().as_::<f64>(), expected, "case {case}");
489 }
490 Ok(())
491 }
492}