vortex_array/aggregate_fn/fns/nan_count/
mod.rs1mod primitive;
5
6use vortex_error::VortexExpect;
7use vortex_error::VortexResult;
8use vortex_error::vortex_bail;
9use vortex_error::vortex_err;
10use vortex_session::VortexSession;
11use vortex_session::registry::CachedId;
12
13use self::primitive::accumulate_primitive;
14use crate::ArrayRef;
15use crate::Canonical;
16use crate::Columnar;
17use crate::ExecutionCtx;
18use crate::aggregate_fn::Accumulator;
19use crate::aggregate_fn::AggregateArgs;
20use crate::aggregate_fn::AggregateDTypes;
21use crate::aggregate_fn::AggregateFnId;
22use crate::aggregate_fn::AggregateFnVTable;
23use crate::aggregate_fn::DynAccumulator;
24use crate::aggregate_fn::EmptyOptions;
25use crate::dtype::DType;
26use crate::dtype::Nullability::NonNullable;
27use crate::dtype::PType;
28use crate::expr::stats::Precision;
29use crate::expr::stats::Stat;
30use crate::expr::stats::StatsProvider;
31use crate::scalar::Scalar;
32
33pub fn nan_count(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<usize> {
39 if let Precision::Exact(nan_count_scalar) = array.statistics().get(Stat::NaNCount) {
41 return usize::try_from(&nan_count_scalar)
42 .map_err(|e| vortex_err!("Failed to convert NaN count stat to usize: {e}"));
43 }
44
45 if NanCount
47 .return_dtype(&EmptyOptions, array.dtype())
48 .is_none()
49 {
50 return Ok(0);
51 }
52
53 if array.is_empty() || array.valid_count(ctx)? == 0 {
55 return Ok(0);
56 }
57
58 let mut acc = Accumulator::try_new(NanCount, EmptyOptions, array.dtype().clone())?;
60 acc.accumulate(array, ctx)?;
61 let result = acc.finish()?;
62
63 let count = result
64 .as_primitive()
65 .typed_value::<u64>()
66 .vortex_expect("nan_count result should not be null");
67 Ok(usize::try_from(count).vortex_expect("Cannot be more nans than usize::MAX"))
69}
70
71pub(crate) fn columnar_nan_count(batch: &Columnar, ctx: &mut ExecutionCtx) -> VortexResult<u64> {
77 if NanCount
79 .return_dtype(&EmptyOptions, batch.dtype())
80 .is_none()
81 {
82 return Ok(0);
83 }
84
85 let dtypes = AggregateDTypes::try_new(&NanCount, &EmptyOptions, batch.dtype().clone())?;
86 let args = dtypes.args(&EmptyOptions);
87 let mut count = NanCount.empty_partial(args)?;
88 NanCount.accumulate(args, &mut count, batch, ctx)?;
89 Ok(count)
90}
91
92#[derive(Clone, Debug)]
97pub struct NanCount;
98
99impl AggregateFnVTable for NanCount {
100 type Options = EmptyOptions;
101 type Partial = u64;
102
103 fn id(&self) -> AggregateFnId {
104 static ID: CachedId = CachedId::new("vortex.nan_count");
105 *ID
106 }
107
108 fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
109 Ok(Some(vec![]))
110 }
111
112 fn deserialize(
113 &self,
114 _metadata: &[u8],
115 _session: &VortexSession,
116 ) -> VortexResult<Self::Options> {
117 Ok(EmptyOptions)
118 }
119
120 fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option<DType> {
121 if let DType::Primitive(ptype, ..) = input_dtype
122 && ptype.is_float()
123 {
124 Some(DType::Primitive(PType::U64, NonNullable))
125 } else {
126 None
127 }
128 }
129
130 fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option<DType> {
131 self.return_dtype(options, input_dtype)
132 }
133
134 fn empty_partial(
135 &self,
136 _args: AggregateArgs<'_, Self::Options>,
137 ) -> VortexResult<Self::Partial> {
138 Ok(0)
139 }
140
141 fn partial_from_scalar(
142 &self,
143 _args: AggregateArgs<'_, Self::Options>,
144 scalar: &Scalar,
145 ) -> VortexResult<Self::Partial> {
146 Ok(scalar
147 .as_primitive()
148 .typed_value::<u64>()
149 .vortex_expect("nan_count partial should not be null"))
150 }
151
152 fn merge_partials(
153 &self,
154 _args: AggregateArgs<'_, Self::Options>,
155 first: Self::Partial,
156 second: Self::Partial,
157 ) -> VortexResult<Self::Partial> {
158 Ok(first + second)
159 }
160
161 fn to_scalar(
162 &self,
163 _args: AggregateArgs<'_, Self::Options>,
164 partial: &Self::Partial,
165 ) -> VortexResult<Scalar> {
166 Ok(Scalar::primitive(*partial, NonNullable))
167 }
168
169 #[inline]
170 fn is_saturated(
171 &self,
172 _args: AggregateArgs<'_, Self::Options>,
173 _partial: &Self::Partial,
174 ) -> bool {
175 false
176 }
177
178 fn accumulate(
179 &self,
180 _args: AggregateArgs<'_, Self::Options>,
181 partial: &mut Self::Partial,
182 batch: &Columnar,
183 ctx: &mut ExecutionCtx,
184 ) -> VortexResult<()> {
185 match batch {
186 Columnar::Constant(c) => {
187 if c.scalar().is_null() {
188 return Ok(());
190 }
191 if c.scalar().as_primitive().is_nan() {
192 *partial += c.len() as u64;
193 }
194 Ok(())
195 }
196 Columnar::Canonical(c) => match c {
197 Canonical::Primitive(p) => accumulate_primitive(partial, p, ctx),
198 _ => vortex_bail!(
199 "Unsupported canonical type for nan_count: {}",
200 batch.dtype()
201 ),
202 },
203 }
204 }
205
206 fn finalize(
207 &self,
208 _args: AggregateArgs<'_, Self::Options>,
209 partials: ArrayRef,
210 ) -> VortexResult<ArrayRef> {
211 Ok(partials)
212 }
213
214 fn finalize_scalar(
215 &self,
216 args: AggregateArgs<'_, Self::Options>,
217 partial: &Self::Partial,
218 ) -> VortexResult<Scalar> {
219 self.to_scalar(args, partial)
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use vortex_buffer::buffer;
226 use vortex_error::VortexResult;
227
228 use crate::IntoArray;
229 use crate::VortexSessionExecute;
230 use crate::aggregate_fn::Accumulator;
231 use crate::aggregate_fn::AggregateDTypes;
232 use crate::aggregate_fn::AggregateFnVTable;
233 use crate::aggregate_fn::DynAccumulator;
234 use crate::aggregate_fn::EmptyOptions;
235 use crate::aggregate_fn::fns::nan_count::NanCount;
236 use crate::aggregate_fn::fns::nan_count::nan_count;
237 use crate::array_session;
238 use crate::arrays::ChunkedArray;
239 use crate::arrays::ConstantArray;
240 use crate::arrays::PrimitiveArray;
241 use crate::dtype::DType;
242 use crate::dtype::Nullability;
243 use crate::dtype::PType;
244 use crate::validity::Validity;
245
246 #[test]
247 fn nan_count_multi_batch() -> VortexResult<()> {
248 let mut ctx = array_session().create_execution_ctx();
249 let dtype = DType::Primitive(PType::F64, Nullability::NonNullable);
250 let mut acc = Accumulator::try_new(NanCount, EmptyOptions, dtype)?;
251
252 let batch1 =
253 PrimitiveArray::new(buffer![f64::NAN, 1.0f64, f64::NAN], Validity::NonNullable)
254 .into_array();
255 acc.accumulate(&batch1, &mut ctx)?;
256
257 let batch2 =
258 PrimitiveArray::new(buffer![2.0f64, f64::NAN], Validity::NonNullable).into_array();
259 acc.accumulate(&batch2, &mut ctx)?;
260
261 let result = acc.finish()?;
262 assert_eq!(result.as_primitive().typed_value::<u64>(), Some(3));
263 Ok(())
264 }
265
266 #[test]
267 fn nan_count_finish_resets_state() -> VortexResult<()> {
268 let mut ctx = array_session().create_execution_ctx();
269 let dtype = DType::Primitive(PType::F64, Nullability::NonNullable);
270 let mut acc = Accumulator::try_new(NanCount, EmptyOptions, dtype)?;
271
272 let batch1 =
273 PrimitiveArray::new(buffer![f64::NAN, 1.0f64], Validity::NonNullable).into_array();
274 acc.accumulate(&batch1, &mut ctx)?;
275 let result1 = acc.finish()?;
276 assert_eq!(result1.as_primitive().typed_value::<u64>(), Some(1));
277
278 let batch2 = PrimitiveArray::new(buffer![f64::NAN, f64::NAN, 2.0], Validity::NonNullable)
279 .into_array();
280 acc.accumulate(&batch2, &mut ctx)?;
281 let result2 = acc.finish()?;
282 assert_eq!(result2.as_primitive().typed_value::<u64>(), Some(2));
283 Ok(())
284 }
285
286 #[test]
287 fn nan_count_state_merge() -> VortexResult<()> {
288 let dtype = DType::Primitive(PType::F64, Nullability::NonNullable);
289 let dtypes = AggregateDTypes::try_new(&NanCount, &EmptyOptions, dtype)?;
290
291 let state = NanCount.merge_partials(dtypes.args(&EmptyOptions), 5, 3)?;
292
293 let result = NanCount.to_scalar(dtypes.args(&EmptyOptions), &state)?;
294 assert_eq!(result.as_primitive().typed_value::<u64>(), Some(8));
295 Ok(())
296 }
297
298 #[test]
299 fn nan_count_constant_nan() -> VortexResult<()> {
300 let array = ConstantArray::new(f64::NAN, 10);
301 let mut ctx = array_session().create_execution_ctx();
302 assert_eq!(nan_count(&array.into_array(), &mut ctx)?, 10);
303 Ok(())
304 }
305
306 #[test]
307 fn nan_count_constant_non_nan() -> VortexResult<()> {
308 let array = ConstantArray::new(1.0f64, 10);
309 let mut ctx = array_session().create_execution_ctx();
310 assert_eq!(nan_count(&array.into_array(), &mut ctx)?, 0);
311 Ok(())
312 }
313
314 #[test]
315 fn nan_count_empty() -> VortexResult<()> {
316 let dtype = DType::Primitive(PType::F64, Nullability::NonNullable);
317 let mut acc = Accumulator::try_new(NanCount, EmptyOptions, dtype)?;
318 let result = acc.finish()?;
319 assert_eq!(result.as_primitive().typed_value::<u64>(), Some(0));
320 Ok(())
321 }
322
323 #[test]
324 fn nan_count_chunked() -> VortexResult<()> {
325 let chunk1 = PrimitiveArray::from_option_iter([Some(f64::NAN), None, Some(1.0)]);
326 let chunk2 = PrimitiveArray::from_option_iter([Some(f64::NAN), Some(f64::NAN), None]);
327 let dtype = chunk1.dtype().clone();
328 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
329 let mut ctx = array_session().create_execution_ctx();
330 assert_eq!(nan_count(&chunked.into_array(), &mut ctx)?, 3);
331 Ok(())
332 }
333
334 #[test]
335 fn nan_count_all_null() -> VortexResult<()> {
336 let p = PrimitiveArray::new(buffer![f64::NAN, f64::NAN, f64::NAN], Validity::AllInvalid);
337 let mut ctx = array_session().create_execution_ctx();
338 assert_eq!(nan_count(&p.into_array(), &mut ctx)?, 0);
339 Ok(())
340 }
341}