From 90d743356722a8d5ca7e39053229654778bacf46 Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Thu, 11 Jun 2026 16:14:52 +0100 Subject: [PATCH] Return NaN instead of null for mean of all-null input Mean::finalize_scalar returned null when count was zero, while the array finalize path computes sum/count = 0/0 = NaN for the same input. The result of a mean over an all-null group therefore depended on which reduce path it took. Nulls are skipped during accumulation, so an all-null input is an empty mean: let the division produce NaN in both paths. Signed-off-by: Dimitar Dimitrov --- vortex-array/src/aggregate_fn/fns/mean/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vortex-array/src/aggregate_fn/fns/mean/mod.rs b/vortex-array/src/aggregate_fn/fns/mean/mod.rs index f7d471023ee..17fb2616d44 100644 --- a/vortex-array/src/aggregate_fn/fns/mean/mod.rs +++ b/vortex-array/src/aggregate_fn/fns/mean/mod.rs @@ -104,7 +104,9 @@ impl BinaryCombined for Mean { let sum = sum_cast.as_primitive().typed_value::(); let count = count_cast.as_primitive().typed_value::(); let value = match (sum, count) { - (None, _) | (_, None) | (_, Some(0.0)) => return Ok(Scalar::null(target)), // Sum overflowed + (None, _) | (_, None) => return Ok(Scalar::null(target)), // Sum overflowed + // A count of zero yields 0/0 = NaN, matching the array `finalize` path: nulls are + // skipped during accumulation, so an all-null input is an empty mean, not null. (Some(s), Some(c)) => s / c, }; Ok(Scalar::primitive(value, Nullability::Nullable)) @@ -230,11 +232,11 @@ mod tests { } #[test] - fn mean_all_null_returns_null() -> VortexResult<()> { + fn mean_all_null_returns_nan() -> VortexResult<()> { let array = PrimitiveArray::from_option_iter::([None, None, None]).into_array(); let mut ctx = LEGACY_SESSION.create_execution_ctx(); let result = mean(&array, &mut ctx)?; - assert_eq!(result.as_primitive().as_::(), None); + assert!(result.as_primitive().as_::().is_some_and(f64::is_nan)); Ok(()) }