Skip to main content

datafusion_functions_aggregate/
median.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::cmp::Ordering;
19use std::fmt::{Debug, Formatter};
20use std::mem::{size_of, size_of_val};
21use std::sync::Arc;
22
23use arrow::array::{
24    ArrowNumericType, BooleanArray, ListArray, PrimitiveArray, PrimitiveBuilder,
25    downcast_integer,
26};
27use arrow::buffer::{OffsetBuffer, ScalarBuffer};
28use arrow::{
29    array::{ArrayRef, AsArray},
30    datatypes::{
31        DataType, Decimal128Type, Decimal256Type, Field, Float16Type, Float32Type,
32        Float64Type,
33    },
34};
35
36use arrow::array::Array;
37use arrow::array::ArrowNativeTypeOp;
38use arrow::datatypes::{
39    ArrowNativeType, ArrowPrimitiveType, Decimal32Type, Decimal64Type, FieldRef,
40};
41
42use datafusion_common::types::{NativeType, logical_float64};
43use datafusion_common::{
44    DataFusionError, Result, ScalarValue, assert_eq_or_internal_err,
45    internal_datafusion_err,
46};
47use datafusion_expr::function::StateFieldsArgs;
48use datafusion_expr::{
49    Accumulator, AggregateUDFImpl, Coercion, Documentation, Signature, TypeSignature,
50    TypeSignatureClass, Volatility, function::AccumulatorArgs, utils::format_state_name,
51};
52use datafusion_expr::{EmitTo, GroupsAccumulator};
53use datafusion_functions_aggregate_common::aggregate::groups_accumulator::accumulate::accumulate;
54use datafusion_functions_aggregate_common::aggregate::groups_accumulator::nulls::filtered_null_mask;
55use datafusion_functions_aggregate_common::utils::{GenericDistinctBuffer, Hashable};
56use datafusion_macros::user_doc;
57use std::collections::HashMap;
58
59make_udaf_expr_and_func!(
60    Median,
61    median,
62    expression,
63    "Computes the median of a set of numbers",
64    median_udaf
65);
66
67#[user_doc(
68    doc_section(label = "General Functions"),
69    description = "Returns the median value in the specified column.",
70    syntax_example = "median(expression)",
71    sql_example = r#"```sql
72> SELECT median(column_name) FROM table_name;
73+----------------------+
74| median(column_name)   |
75+----------------------+
76| 45.5                 |
77+----------------------+
78```"#,
79    standard_argument(name = "expression", prefix = "The")
80)]
81/// MEDIAN aggregate expression. If using the non-distinct variation, then this uses a
82/// lot of memory because all values need to be stored in memory before a result can be
83/// computed. If an approximation is sufficient then APPROX_MEDIAN provides a much more
84/// efficient solution.
85///
86/// If using the distinct variation, the memory usage will be similarly high if the
87/// cardinality is high as it stores all distinct values in memory before computing the
88/// result, but if cardinality is low then memory usage will also be lower.
89#[derive(PartialEq, Eq, Hash, Debug)]
90pub struct Median {
91    signature: Signature,
92}
93
94impl Default for Median {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100impl Median {
101    pub fn new() -> Self {
102        Self {
103            // Integer inputs are coerced to Float64 so the average of the two
104            // middle values is not truncated. This matches DuckDB / PostgreSQL / Spark.
105            // Float and Decimal inputs preserve their type.
106            signature: Signature::one_of(
107                vec![
108                    TypeSignature::Coercible(vec![Coercion::new_exact(
109                        TypeSignatureClass::Decimal,
110                    )]),
111                    TypeSignature::Coercible(vec![Coercion::new_exact(
112                        TypeSignatureClass::Float,
113                    )]),
114                    TypeSignature::Coercible(vec![Coercion::new_implicit(
115                        TypeSignatureClass::Native(logical_float64()),
116                        vec![TypeSignatureClass::Integer],
117                        NativeType::Float64,
118                    )]),
119                ],
120                Volatility::Immutable,
121            ),
122        }
123    }
124}
125
126impl AggregateUDFImpl for Median {
127    fn name(&self) -> &str {
128        "median"
129    }
130
131    fn signature(&self) -> &Signature {
132        &self.signature
133    }
134
135    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
136        Ok(arg_types[0].clone())
137    }
138
139    fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
140        //Intermediate state is a list of the elements we have collected so far
141        let field = Field::new_list_field(args.input_fields[0].data_type().clone(), true);
142        let state_name = if args.is_distinct {
143            "distinct_median"
144        } else {
145            "median"
146        };
147
148        Ok(vec![
149            Field::new(
150                format_state_name(args.name, state_name),
151                DataType::List(Arc::new(field)),
152                true,
153            )
154            .into(),
155        ])
156    }
157
158    fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
159        macro_rules! helper {
160            ($t:ty, $dt:expr) => {
161                if acc_args.is_distinct {
162                    Ok(Box::new(DistinctMedianAccumulator::<$t> {
163                        data_type: $dt.clone(),
164                        distinct_values: GenericDistinctBuffer::new($dt),
165                    }))
166                } else {
167                    Ok(Box::new(MedianAccumulator::<$t> {
168                        data_type: $dt.clone(),
169                        all_values: vec![],
170                    }))
171                }
172            };
173        }
174
175        let dt = acc_args.expr_fields[0].data_type().clone();
176        downcast_integer! {
177            dt => (helper, dt),
178            DataType::Float16 => helper!(Float16Type, dt),
179            DataType::Float32 => helper!(Float32Type, dt),
180            DataType::Float64 => helper!(Float64Type, dt),
181            DataType::Decimal32(_, _) => helper!(Decimal32Type, dt),
182            DataType::Decimal64(_, _) => helper!(Decimal64Type, dt),
183            DataType::Decimal128(_, _) => helper!(Decimal128Type, dt),
184            DataType::Decimal256(_, _) => helper!(Decimal256Type, dt),
185            _ => Err(DataFusionError::NotImplemented(format!(
186                "MedianAccumulator not supported for {} with {}",
187                acc_args.name,
188                dt,
189            ))),
190        }
191    }
192
193    fn groups_accumulator_supported(&self, args: AccumulatorArgs) -> bool {
194        !args.is_distinct
195    }
196
197    fn create_groups_accumulator(
198        &self,
199        args: AccumulatorArgs,
200    ) -> Result<Box<dyn GroupsAccumulator>> {
201        let num_args = args.exprs.len();
202        assert_eq_or_internal_err!(
203            num_args,
204            1,
205            "median should only have 1 arg, but found num args:{}",
206            num_args
207        );
208
209        let dt = args.expr_fields[0].data_type().clone();
210
211        macro_rules! helper {
212            ($t:ty, $dt:expr) => {
213                Ok(Box::new(MedianGroupsAccumulator::<$t>::new($dt)))
214            };
215        }
216
217        downcast_integer! {
218            dt => (helper, dt),
219            DataType::Float16 => helper!(Float16Type, dt),
220            DataType::Float32 => helper!(Float32Type, dt),
221            DataType::Float64 => helper!(Float64Type, dt),
222            DataType::Decimal32(_, _) => helper!(Decimal32Type, dt),
223            DataType::Decimal64(_, _) => helper!(Decimal64Type, dt),
224            DataType::Decimal128(_, _) => helper!(Decimal128Type, dt),
225            DataType::Decimal256(_, _) => helper!(Decimal256Type, dt),
226            _ => Err(DataFusionError::NotImplemented(format!(
227                "MedianGroupsAccumulator not supported for {} with {}",
228                args.name,
229                dt,
230            ))),
231        }
232    }
233
234    fn documentation(&self) -> Option<&Documentation> {
235        self.doc()
236    }
237}
238
239/// The median accumulator accumulates the raw input values
240/// as `ScalarValue`s
241///
242/// The intermediate state is represented as a List of scalar values updated by
243/// `merge_batch` and a `Vec` of `ArrayRef` that are converted to scalar values
244/// in the final evaluation step so that we avoid expensive conversions and
245/// allocations during `update_batch`.
246struct MedianAccumulator<T: ArrowNumericType> {
247    data_type: DataType,
248    all_values: Vec<T::Native>,
249}
250
251impl<T: ArrowNumericType> Debug for MedianAccumulator<T> {
252    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
253        write!(f, "MedianAccumulator({})", self.data_type)
254    }
255}
256
257impl<T: ArrowNumericType> Accumulator for MedianAccumulator<T> {
258    fn state(&mut self) -> Result<Vec<ScalarValue>> {
259        // Convert `all_values` to `ListArray` and return a single List ScalarValue
260
261        // Build offsets
262        let offsets =
263            OffsetBuffer::new(ScalarBuffer::from(vec![0, self.all_values.len() as i32]));
264
265        // Build inner array
266        let values_array = PrimitiveArray::<T>::new(
267            ScalarBuffer::from(std::mem::take(&mut self.all_values)),
268            None,
269        )
270        .with_data_type(self.data_type.clone());
271
272        // Build the result list array
273        let list_array = ListArray::new(
274            Arc::new(Field::new_list_field(self.data_type.clone(), true)),
275            offsets,
276            Arc::new(values_array),
277            None,
278        );
279
280        Ok(vec![ScalarValue::List(Arc::new(list_array))])
281    }
282
283    fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
284        let values = values[0].as_primitive::<T>();
285        self.all_values.reserve(values.len() - values.null_count());
286        self.all_values.extend(values.iter().flatten());
287        Ok(())
288    }
289
290    fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
291        let array = states[0].as_list::<i32>();
292        for v in array.iter().flatten() {
293            self.update_batch(&[v])?
294        }
295        Ok(())
296    }
297
298    fn evaluate(&mut self) -> Result<ScalarValue> {
299        let median = calculate_median::<T>(&mut self.all_values);
300        ScalarValue::new_primitive::<T>(median, &self.data_type)
301    }
302
303    fn size(&self) -> usize {
304        size_of_val(self) + self.all_values.capacity() * size_of::<T::Native>()
305    }
306
307    fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
308        let mut to_remove: HashMap<Hashable<T::Native>, usize> = HashMap::new();
309
310        let arr = values[0].as_primitive::<T>();
311        for value in arr.iter().flatten() {
312            *to_remove.entry(Hashable(value)).or_default() += 1;
313        }
314
315        let mut i = 0;
316        while i < self.all_values.len() {
317            let k = Hashable(self.all_values[i]);
318            if let Some(count) = to_remove.get_mut(&k)
319                && *count > 0
320            {
321                self.all_values.swap_remove(i);
322                *count -= 1;
323                if *count == 0 {
324                    to_remove.remove(&k);
325                    if to_remove.is_empty() {
326                        break;
327                    }
328                }
329            } else {
330                i += 1;
331            }
332        }
333        Ok(())
334    }
335
336    fn supports_retract_batch(&self) -> bool {
337        true
338    }
339}
340
341/// The median groups accumulator accumulates the raw input values
342///
343/// For calculating the accurate medians of groups, we need to store all values
344/// of groups before final evaluation.
345/// So values in each group will be stored in a `Vec<T>`, and the total group values
346/// will be actually organized as a `Vec<Vec<T>>`.
347#[derive(Debug)]
348struct MedianGroupsAccumulator<T: ArrowNumericType + Send> {
349    data_type: DataType,
350    group_values: Vec<Vec<T::Native>>,
351}
352
353impl<T: ArrowNumericType + Send> MedianGroupsAccumulator<T> {
354    pub fn new(data_type: DataType) -> Self {
355        Self {
356            data_type,
357            group_values: Vec::new(),
358        }
359    }
360}
361
362impl<T: ArrowNumericType + Send> GroupsAccumulator for MedianGroupsAccumulator<T> {
363    fn update_batch(
364        &mut self,
365        values: &[ArrayRef],
366        group_indices: &[usize],
367        opt_filter: Option<&BooleanArray>,
368        total_num_groups: usize,
369    ) -> Result<()> {
370        assert_eq!(values.len(), 1, "single argument to update_batch");
371        let values = values[0].as_primitive::<T>();
372
373        // Push the `not nulls + not filtered` row into its group
374        self.group_values.resize(total_num_groups, Vec::new());
375        accumulate(
376            group_indices,
377            values,
378            opt_filter,
379            |group_index, new_value| {
380                self.group_values[group_index].push(new_value);
381            },
382        );
383
384        Ok(())
385    }
386
387    fn merge_batch(
388        &mut self,
389        values: &[ArrayRef],
390        group_indices: &[usize],
391        // Since aggregate filter should be applied in partial stage, in final stage there should be no filter
392        _opt_filter: Option<&BooleanArray>,
393        total_num_groups: usize,
394    ) -> Result<()> {
395        assert_eq!(values.len(), 1, "one argument to merge_batch");
396
397        // The merged values should be organized like as a `ListArray` which is nullable
398        // (input with nulls usually generated from `convert_to_state`), but `inner array` of
399        // `ListArray`  is `non-nullable`.
400        //
401        // Following is the possible and impossible input `values`:
402        //
403        // # Possible values
404        // ```text
405        //   group 0: [1, 2, 3]
406        //   group 1: null (list array is nullable)
407        //   group 2: [6, 7, 8]
408        //   ...
409        //   group n: [...]
410        // ```
411        //
412        // # Impossible values
413        // ```text
414        //   group x: [1, 2, null] (values in list array is non-nullable)
415        // ```
416        //
417        let input_group_values = values[0].as_list::<i32>();
418
419        // Ensure group values big enough
420        self.group_values.resize(total_num_groups, Vec::new());
421
422        // Extend values to related groups
423        // TODO: avoid using iterator of the `ListArray`, this will lead to
424        // many calls of `slice` of its ``inner array`, and `slice` is not
425        // so efficient(due to the calculation of `null_count` for each `slice`).
426        group_indices
427            .iter()
428            .zip(input_group_values.iter())
429            .for_each(|(&group_index, values_opt)| {
430                if let Some(values) = values_opt {
431                    let values = values.as_primitive::<T>();
432                    self.group_values[group_index].extend(values.values().iter());
433                }
434            });
435
436        Ok(())
437    }
438
439    fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
440        // Emit values
441        let emit_group_values = emit_to.take_needed(&mut self.group_values);
442
443        // Build offsets
444        let mut offsets = Vec::with_capacity(self.group_values.len() + 1);
445        offsets.push(0);
446        let mut cur_len = 0_i32;
447        for group_value in &emit_group_values {
448            cur_len += group_value.len() as i32;
449            offsets.push(cur_len);
450        }
451        // TODO: maybe we can use `OffsetBuffer::new_unchecked` like what in `convert_to_state`,
452        // but safety should be considered more carefully here(and I am not sure if it can get
453        // performance improvement when we introduce checks to keep the safety...).
454        //
455        // Can see more details in:
456        // https://github.com/apache/datafusion/pull/13681#discussion_r1931209791
457        //
458        let offsets = OffsetBuffer::new(ScalarBuffer::from(offsets));
459
460        // Build inner array
461        let flatten_group_values =
462            emit_group_values.into_iter().flatten().collect::<Vec<_>>();
463        let group_values_array =
464            PrimitiveArray::<T>::new(ScalarBuffer::from(flatten_group_values), None)
465                .with_data_type(self.data_type.clone());
466
467        // Build the result list array
468        let result_list_array = ListArray::new(
469            Arc::new(Field::new_list_field(self.data_type.clone(), true)),
470            offsets,
471            Arc::new(group_values_array),
472            None,
473        );
474
475        Ok(vec![Arc::new(result_list_array)])
476    }
477
478    fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
479        // Emit values
480        let emit_group_values = emit_to.take_needed(&mut self.group_values);
481
482        // Calculate median for each group
483        let mut evaluate_result_builder =
484            PrimitiveBuilder::<T>::new().with_data_type(self.data_type.clone());
485        for mut values in emit_group_values {
486            let median = calculate_median::<T>(&mut values);
487            evaluate_result_builder.append_option(median);
488        }
489
490        Ok(Arc::new(evaluate_result_builder.finish()))
491    }
492
493    fn convert_to_state(
494        &self,
495        values: &[ArrayRef],
496        opt_filter: Option<&BooleanArray>,
497    ) -> Result<Vec<ArrayRef>> {
498        assert_eq!(values.len(), 1, "one argument to merge_batch");
499
500        let input_array = values[0].as_primitive::<T>();
501
502        // Directly convert the input array to states, each row will be
503        // seen as a respective group.
504        // For detail, the `input_array` will be converted to a `ListArray`.
505        // And if row is `not null + not filtered`, it will be converted to a list
506        // with only one element; otherwise, this row in `ListArray` will be set
507        // to null.
508
509        // Reuse values buffer in `input_array` to build `values` in `ListArray`
510        let values = PrimitiveArray::<T>::new(input_array.values().clone(), None)
511            .with_data_type(self.data_type.clone());
512
513        // `offsets` in `ListArray`, each row as a list element
514        let offset_end = i32::try_from(input_array.len()).map_err(|e| {
515            internal_datafusion_err!(
516                "cast array_len to i32 failed in convert_to_state of group median, err:{e:?}"
517            )
518        })?;
519        let offsets = (0..=offset_end).collect::<Vec<_>>();
520        // Safety: all checks in `OffsetBuffer::new` are ensured to pass
521        let offsets = unsafe { OffsetBuffer::new_unchecked(ScalarBuffer::from(offsets)) };
522
523        // `nulls` for converted `ListArray`
524        let nulls = filtered_null_mask(opt_filter, input_array);
525
526        let converted_list_array = ListArray::new(
527            Arc::new(Field::new_list_field(self.data_type.clone(), true)),
528            offsets,
529            Arc::new(values),
530            nulls,
531        );
532
533        Ok(vec![Arc::new(converted_list_array)])
534    }
535
536    fn supports_convert_to_state(&self) -> bool {
537        true
538    }
539
540    fn size(&self) -> usize {
541        self.group_values
542            .iter()
543            .map(|values| values.capacity() * size_of::<T>())
544            .sum::<usize>()
545            // account for size of self.grou_values too
546            + self.group_values.capacity() * size_of::<Vec<T>>()
547    }
548}
549
550#[derive(Debug)]
551struct DistinctMedianAccumulator<T: ArrowNumericType> {
552    distinct_values: GenericDistinctBuffer<T>,
553    data_type: DataType,
554}
555
556impl<T: ArrowNumericType + Debug> Accumulator for DistinctMedianAccumulator<T> {
557    fn state(&mut self) -> Result<Vec<ScalarValue>> {
558        self.distinct_values.state()
559    }
560
561    fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
562        self.distinct_values.update_batch(values)
563    }
564
565    fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
566        self.distinct_values.merge_batch(states)
567    }
568
569    fn evaluate(&mut self) -> Result<ScalarValue> {
570        let mut d: Vec<T::Native> =
571            self.distinct_values.values.iter().map(|v| v.0).collect();
572        let median = calculate_median::<T>(&mut d);
573        ScalarValue::new_primitive::<T>(median, &self.data_type)
574    }
575
576    fn size(&self) -> usize {
577        size_of_val(self) + self.distinct_values.size()
578    }
579}
580
581/// Get maximum entry in the slice,
582fn slice_max<T>(array: &[T::Native]) -> T::Native
583where
584    T: ArrowPrimitiveType,
585    T::Native: PartialOrd, // Ensure the type supports PartialOrd for comparison
586{
587    // Make sure that, array is not empty.
588    debug_assert!(!array.is_empty());
589    // `.unwrap()` is safe here as the array is supposed to be non-empty
590    *array
591        .iter()
592        .max_by(|x, y| x.partial_cmp(y).unwrap_or(Ordering::Less))
593        .unwrap()
594}
595
596fn calculate_median<T: ArrowNumericType>(values: &mut [T::Native]) -> Option<T::Native> {
597    let cmp = |x: &T::Native, y: &T::Native| x.compare(*y);
598
599    let len = values.len();
600    if len == 0 {
601        None
602    } else if len % 2 == 0 {
603        let (low, high, _) = values.select_nth_unstable_by(len / 2, cmp);
604        // Get the maximum of the low (left side after bi-partitioning)
605        let left_max = slice_max::<T>(low);
606        // Calculate median as the average of the two middle values.
607        // Use checked arithmetic to detect overflow and fall back to safe formula.
608        let two = T::Native::usize_as(2);
609        let median = match left_max.add_checked(*high) {
610            Ok(sum) => sum.div_wrapping(two),
611            Err(_) => {
612                // Overflow detected - use safe midpoint formula:
613                // a/2 + b/2 + ((a%2 + b%2) / 2)
614                // This avoids overflow by dividing before adding.
615                let half_left = left_max.div_wrapping(two);
616                let half_right = (*high).div_wrapping(two);
617                let rem_left = left_max.mod_wrapping(two);
618                let rem_right = (*high).mod_wrapping(two);
619                // The sum of remainders (0, 1, or 2 for unsigned; -2 to 2 for signed)
620                // divided by 2 gives the correction factor (0 or 1 for unsigned; -1, 0, or 1 for signed)
621                let correction = rem_left.add_wrapping(rem_right).div_wrapping(two);
622                half_left.add_wrapping(half_right).add_wrapping(correction)
623            }
624        };
625        Some(median)
626    } else {
627        let (_, median, _) = values.select_nth_unstable_by(len / 2, cmp);
628        Some(*median)
629    }
630}