1use std::collections::HashMap;
19use std::fmt::Debug;
20use std::mem::{size_of, size_of_val};
21use std::sync::Arc;
22
23use arrow::array::{
24 ArrowNumericType, BooleanArray, ListArray, PrimitiveArray, PrimitiveBuilder,
25};
26use arrow::buffer::{OffsetBuffer, ScalarBuffer};
27use arrow::{
28 array::{Array, ArrayRef, AsArray},
29 datatypes::{DataType, Field, FieldRef, Float16Type, Float32Type, Float64Type},
30};
31
32use num_traits::AsPrimitive;
33
34use arrow::array::ArrowNativeTypeOp;
35use datafusion_common::internal_err;
36use datafusion_common::types::{NativeType, logical_float64};
37use datafusion_functions_aggregate_common::noop_accumulator::NoopAccumulator;
38
39use crate::min_max::{max_udaf, min_udaf};
40use datafusion_common::{
41 Result, ScalarValue, internal_datafusion_err, utils::take_function_args,
42};
43use datafusion_expr::utils::format_state_name;
44use datafusion_expr::{
45 Accumulator, AggregateUDFImpl, Coercion, Documentation, Expr, Signature,
46 TypeSignatureClass, Volatility,
47};
48use datafusion_expr::{EmitTo, GroupsAccumulator};
49use datafusion_expr::{
50 expr::{AggregateFunction, Sort},
51 function::{AccumulatorArgs, AggregateFunctionSimplification, StateFieldsArgs},
52 simplify::SimplifyContext,
53};
54use datafusion_functions_aggregate_common::aggregate::groups_accumulator::accumulate::accumulate;
55use datafusion_functions_aggregate_common::aggregate::groups_accumulator::nulls::filtered_null_mask;
56use datafusion_functions_aggregate_common::utils::{GenericDistinctBuffer, Hashable};
57use datafusion_macros::user_doc;
58
59use crate::utils::validate_percentile_expr;
60
61const INTERPOLATION_PRECISION: f64 = 1_000_000.0;
75
76create_func!(PercentileCont, percentile_cont_udaf);
77
78pub fn percentile_cont(order_by: Sort, percentile: Expr) -> Expr {
80 let expr = order_by.expr.clone();
81 let args = vec![expr, percentile];
82
83 Expr::AggregateFunction(AggregateFunction::new_udf(
84 percentile_cont_udaf(),
85 args,
86 false,
87 None,
88 vec![order_by],
89 None,
90 ))
91}
92
93#[user_doc(
94 doc_section(label = "General Functions"),
95 description = "Returns the exact percentile of input values, interpolating between values if needed.",
96 syntax_example = "percentile_cont(percentile) WITHIN GROUP (ORDER BY expression)",
97 sql_example = r#"```sql
98> SELECT percentile_cont(0.75) WITHIN GROUP (ORDER BY column_name) FROM table_name;
99+----------------------------------------------------------+
100| percentile_cont(0.75) WITHIN GROUP (ORDER BY column_name) |
101+----------------------------------------------------------+
102| 45.5 |
103+----------------------------------------------------------+
104```
105
106An alternate syntax is also supported:
107```sql
108> SELECT percentile_cont(column_name, 0.75) FROM table_name;
109+---------------------------------------+
110| percentile_cont(column_name, 0.75) |
111+---------------------------------------+
112| 45.5 |
113+---------------------------------------+
114```"#,
115 standard_argument(name = "expression", prefix = "The"),
116 argument(
117 name = "percentile",
118 description = "Percentile to compute. Must be a float value between 0 and 1 (inclusive)."
119 )
120)]
121#[derive(PartialEq, Eq, Hash, Debug)]
129pub struct PercentileCont {
130 signature: Signature,
131 aliases: Vec<String>,
132}
133
134impl Default for PercentileCont {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140impl PercentileCont {
141 pub fn new() -> Self {
142 Self {
143 signature: Signature::coercible(
144 vec![
145 Coercion::new_implicit(
146 TypeSignatureClass::Float,
147 vec![TypeSignatureClass::Numeric],
148 NativeType::Float64,
149 ),
150 Coercion::new_implicit(
151 TypeSignatureClass::Native(logical_float64()),
152 vec![TypeSignatureClass::Numeric],
153 NativeType::Float64,
154 ),
155 ],
156 Volatility::Immutable,
157 )
158 .with_parameter_names(vec!["expr", "percentile"])
159 .unwrap(),
160 aliases: vec![String::from("quantile_cont")],
161 }
162 }
163}
164
165impl AggregateUDFImpl for PercentileCont {
166 fn name(&self) -> &str {
167 "percentile_cont"
168 }
169
170 fn aliases(&self) -> &[String] {
171 &self.aliases
172 }
173
174 fn signature(&self) -> &Signature {
175 &self.signature
176 }
177
178 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
179 match &arg_types[0] {
180 DataType::Null => Ok(DataType::Float64),
181 dt => Ok(dt.clone()),
182 }
183 }
184
185 fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
186 let input_type = args.input_fields[0].data_type().clone();
187 if input_type.is_null() {
188 return Ok(vec![
189 Field::new(
190 format_state_name(args.name, self.name()),
191 DataType::Null,
192 true,
193 )
194 .into(),
195 ]);
196 }
197
198 let field = Field::new_list_field(input_type, true);
199 let state_name = if args.is_distinct {
200 "distinct_percentile_cont"
201 } else {
202 "percentile_cont"
203 };
204
205 Ok(vec![
206 Field::new(
207 format_state_name(args.name, state_name),
208 DataType::List(Arc::new(field)),
209 true,
210 )
211 .into(),
212 ])
213 }
214
215 fn accumulator(&self, args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
216 let percentile = get_percentile(&args)?;
217
218 let input_dt = args.expr_fields[0].data_type();
219 if input_dt.is_null() {
220 return Ok(Box::new(NoopAccumulator::new(ScalarValue::Float64(None))));
221 }
222
223 if args.is_distinct {
224 match input_dt {
225 DataType::Float16 => Ok(Box::new(DistinctPercentileContAccumulator::<
226 Float16Type,
227 >::new(percentile))),
228 DataType::Float32 => Ok(Box::new(DistinctPercentileContAccumulator::<
229 Float32Type,
230 >::new(percentile))),
231 DataType::Float64 => Ok(Box::new(DistinctPercentileContAccumulator::<
232 Float64Type,
233 >::new(percentile))),
234 dt => internal_err!("Unsupported datatype for percentile cont: {dt}"),
235 }
236 } else {
237 match input_dt {
238 DataType::Float16 => Ok(Box::new(
239 PercentileContAccumulator::<Float16Type>::new(percentile),
240 )),
241 DataType::Float32 => Ok(Box::new(
242 PercentileContAccumulator::<Float32Type>::new(percentile),
243 )),
244 DataType::Float64 => Ok(Box::new(
245 PercentileContAccumulator::<Float64Type>::new(percentile),
246 )),
247 dt => internal_err!("Unsupported datatype for percentile cont: {dt}"),
248 }
249 }
250 }
251
252 fn groups_accumulator_supported(&self, args: AccumulatorArgs) -> bool {
253 !args.is_distinct && !args.expr_fields[0].data_type().is_null()
254 }
255
256 fn create_groups_accumulator(
257 &self,
258 args: AccumulatorArgs,
259 ) -> Result<Box<dyn GroupsAccumulator>> {
260 let percentile = get_percentile(&args)?;
261
262 let input_dt = args.expr_fields[0].data_type();
263 match input_dt {
264 DataType::Float16 => Ok(Box::new(PercentileContGroupsAccumulator::<
265 Float16Type,
266 >::new(percentile))),
267 DataType::Float32 => Ok(Box::new(PercentileContGroupsAccumulator::<
268 Float32Type,
269 >::new(percentile))),
270 DataType::Float64 => Ok(Box::new(PercentileContGroupsAccumulator::<
271 Float64Type,
272 >::new(percentile))),
273 dt => internal_err!("Unsupported datatype for percentile cont: {dt}"),
274 }
275 }
276
277 fn simplify(&self) -> Option<AggregateFunctionSimplification> {
278 Some(Box::new(|aggregate_function, info| {
279 simplify_percentile_cont_aggregate(aggregate_function, info)
280 }))
281 }
282
283 fn supports_within_group_clause(&self) -> bool {
284 true
285 }
286
287 fn documentation(&self) -> Option<&Documentation> {
288 self.doc()
289 }
290}
291
292fn get_percentile(args: &AccumulatorArgs) -> Result<f64> {
293 let percentile = validate_percentile_expr(&args.exprs[1], "PERCENTILE_CONT")?;
294
295 let is_descending = args
296 .order_bys
297 .first()
298 .map(|sort_expr| sort_expr.options.descending)
299 .unwrap_or(false);
300
301 let percentile = if is_descending {
302 1.0 - percentile
303 } else {
304 percentile
305 };
306
307 Ok(percentile)
308}
309
310fn simplify_percentile_cont_aggregate(
311 aggregate_function: AggregateFunction,
312 info: &SimplifyContext,
313) -> Result<Expr> {
314 enum PercentileRewriteTarget {
315 Min,
316 Max,
317 }
318
319 let params = &aggregate_function.params;
320 let [value, percentile] = take_function_args("percentile_cont", ¶ms.args)?;
321 let input_type = info.get_data_type(value)?;
325 if input_type.is_null() {
326 return Ok(Expr::AggregateFunction(aggregate_function));
327 }
328
329 let is_descending = params
330 .order_by
331 .first()
332 .map(|sort| !sort.asc)
333 .unwrap_or(false);
334
335 let rewrite_target = match percentile {
336 Expr::Literal(ScalarValue::Float64(Some(0.0)), _) => {
337 if is_descending {
338 PercentileRewriteTarget::Max
339 } else {
340 PercentileRewriteTarget::Min
341 }
342 }
343 Expr::Literal(ScalarValue::Float64(Some(1.0)), _) => {
344 if is_descending {
345 PercentileRewriteTarget::Min
346 } else {
347 PercentileRewriteTarget::Max
348 }
349 }
350 _ => return Ok(Expr::AggregateFunction(aggregate_function)),
351 };
352
353 let udaf = match rewrite_target {
354 PercentileRewriteTarget::Min => min_udaf(),
355 PercentileRewriteTarget::Max => max_udaf(),
356 };
357
358 let rewritten = Expr::AggregateFunction(AggregateFunction::new_udf(
359 udaf,
360 vec![value.clone()],
361 params.distinct,
362 params.filter.clone(),
363 vec![],
364 params.null_treatment,
365 ));
366 Ok(rewritten)
367}
368
369#[derive(Debug)]
377struct PercentileContAccumulator<T: ArrowNumericType + Debug> {
378 all_values: Vec<T::Native>,
379 percentile: f64,
380}
381
382impl<T: ArrowNumericType + Debug> PercentileContAccumulator<T> {
383 fn new(percentile: f64) -> Self {
384 Self {
385 all_values: vec![],
386 percentile,
387 }
388 }
389}
390
391impl<T> Accumulator for PercentileContAccumulator<T>
392where
393 T: ArrowNumericType + Debug,
394 T::Native: Copy + AsPrimitive<f64>,
395 f64: AsPrimitive<T::Native>,
396{
397 fn state(&mut self) -> Result<Vec<ScalarValue>> {
398 let offsets =
402 OffsetBuffer::new(ScalarBuffer::from(vec![0, self.all_values.len() as i32]));
403
404 let values_array = PrimitiveArray::<T>::new(
406 ScalarBuffer::from(std::mem::take(&mut self.all_values)),
407 None,
408 );
409
410 let list_array = ListArray::new(
412 Arc::new(Field::new_list_field(T::DATA_TYPE, true)),
413 offsets,
414 Arc::new(values_array),
415 None,
416 );
417
418 Ok(vec![ScalarValue::List(Arc::new(list_array))])
419 }
420
421 fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
422 let values = values[0].as_primitive::<T>();
423 self.all_values.reserve(values.len() - values.null_count());
424 self.all_values.extend(values.iter().flatten());
425 Ok(())
426 }
427
428 fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
429 let array = states[0].as_list::<i32>();
430 self.update_batch(&[array.value(0)])?;
431 Ok(())
432 }
433
434 fn evaluate(&mut self) -> Result<ScalarValue> {
435 let value = calculate_percentile::<T>(&mut self.all_values, self.percentile);
436 ScalarValue::new_primitive::<T>(value, &T::DATA_TYPE)
437 }
438
439 fn size(&self) -> usize {
440 size_of_val(self) + self.all_values.capacity() * size_of::<T::Native>()
441 }
442
443 fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
444 let mut to_remove: HashMap<Hashable<T::Native>, usize> = HashMap::new();
445
446 let arr = values[0].as_primitive::<T>();
447 for value in arr.iter().flatten() {
448 *to_remove.entry(Hashable(value)).or_default() += 1;
449 }
450
451 let mut i = 0;
452 while i < self.all_values.len() {
453 let k = Hashable(self.all_values[i]);
454 if let Some(count) = to_remove.get_mut(&k)
455 && *count > 0
456 {
457 self.all_values.swap_remove(i);
458 *count -= 1;
459 if *count == 0 {
460 to_remove.remove(&k);
461 if to_remove.is_empty() {
462 break;
463 }
464 }
465 } else {
466 i += 1;
467 }
468 }
469 Ok(())
470 }
471
472 fn supports_retract_batch(&self) -> bool {
473 true
474 }
475}
476
477#[derive(Debug)]
484struct PercentileContGroupsAccumulator<T: ArrowNumericType + Send> {
485 group_values: Vec<Vec<T::Native>>,
486 percentile: f64,
487}
488
489impl<T: ArrowNumericType + Send> PercentileContGroupsAccumulator<T> {
490 fn new(percentile: f64) -> Self {
491 Self {
492 group_values: vec![],
493 percentile,
494 }
495 }
496}
497
498impl<T> GroupsAccumulator for PercentileContGroupsAccumulator<T>
499where
500 T: ArrowNumericType + Send,
501 T::Native: Copy + AsPrimitive<f64>,
502 f64: AsPrimitive<T::Native>,
503{
504 fn update_batch(
505 &mut self,
506 values: &[ArrayRef],
507 group_indices: &[usize],
508 opt_filter: Option<&BooleanArray>,
509 total_num_groups: usize,
510 ) -> Result<()> {
511 let values = values[0].as_primitive::<T>();
515
516 self.group_values.resize(total_num_groups, Vec::new());
518 accumulate(
519 group_indices,
520 values,
521 opt_filter,
522 |group_index, new_value| {
523 self.group_values[group_index].push(new_value);
524 },
525 );
526
527 Ok(())
528 }
529
530 fn merge_batch(
531 &mut self,
532 values: &[ArrayRef],
533 group_indices: &[usize],
534 _opt_filter: Option<&BooleanArray>,
536 total_num_groups: usize,
537 ) -> Result<()> {
538 assert_eq!(values.len(), 1, "one argument to merge_batch");
539
540 let input_group_values = values[0].as_list::<i32>();
541
542 self.group_values.resize(total_num_groups, Vec::new());
544
545 group_indices
547 .iter()
548 .zip(input_group_values.iter())
549 .for_each(|(&group_index, values_opt)| {
550 if let Some(values) = values_opt {
551 let values = values.as_primitive::<T>();
552 self.group_values[group_index].extend(values.values().iter());
553 }
554 });
555
556 Ok(())
557 }
558
559 fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
560 let emit_group_values = emit_to.take_needed(&mut self.group_values);
562
563 let mut offsets = Vec::with_capacity(self.group_values.len() + 1);
565 offsets.push(0);
566 let mut cur_len = 0_i32;
567 for group_value in &emit_group_values {
568 cur_len += group_value.len() as i32;
569 offsets.push(cur_len);
570 }
571 let offsets = OffsetBuffer::new(ScalarBuffer::from(offsets));
572
573 let flatten_group_values =
575 emit_group_values.into_iter().flatten().collect::<Vec<_>>();
576 let group_values_array =
577 PrimitiveArray::<T>::new(ScalarBuffer::from(flatten_group_values), None);
578
579 let result_list_array = ListArray::new(
581 Arc::new(Field::new_list_field(T::DATA_TYPE, true)),
582 offsets,
583 Arc::new(group_values_array),
584 None,
585 );
586
587 Ok(vec![Arc::new(result_list_array)])
588 }
589
590 fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
591 let mut emit_group_values = emit_to.take_needed(&mut self.group_values);
593
594 let mut evaluate_result_builder =
596 PrimitiveBuilder::<T>::with_capacity(emit_group_values.len());
597 for values in &mut emit_group_values {
598 let value = calculate_percentile::<T>(values.as_mut_slice(), self.percentile);
599 evaluate_result_builder.append_option(value);
600 }
601
602 Ok(Arc::new(evaluate_result_builder.finish()))
603 }
604
605 fn convert_to_state(
606 &self,
607 values: &[ArrayRef],
608 opt_filter: Option<&BooleanArray>,
609 ) -> Result<Vec<ArrayRef>> {
610 assert_eq!(values.len(), 1, "one argument to merge_batch");
611
612 let input_array = values[0].as_primitive::<T>();
613
614 let values = PrimitiveArray::<T>::new(input_array.values().clone(), None);
623
624 let offset_end = i32::try_from(input_array.len()).map_err(|e| {
626 internal_datafusion_err!(
627 "cast array_len to i32 failed in convert_to_state of group percentile_cont, err:{e:?}"
628 )
629 })?;
630 let offsets = (0..=offset_end).collect::<Vec<_>>();
631 let offsets = unsafe { OffsetBuffer::new_unchecked(ScalarBuffer::from(offsets)) };
638
639 let nulls = filtered_null_mask(opt_filter, input_array);
641
642 let converted_list_array = ListArray::new(
643 Arc::new(Field::new_list_field(T::DATA_TYPE, true)),
644 offsets,
645 Arc::new(values),
646 nulls,
647 );
648
649 Ok(vec![Arc::new(converted_list_array)])
650 }
651
652 fn supports_convert_to_state(&self) -> bool {
653 true
654 }
655
656 fn size(&self) -> usize {
657 self.group_values
658 .iter()
659 .map(|values| values.capacity() * size_of::<T::Native>())
660 .sum::<usize>()
661 + self.group_values.capacity() * size_of::<Vec<T::Native>>()
663 }
664}
665
666#[derive(Debug)]
667struct DistinctPercentileContAccumulator<T: ArrowNumericType> {
668 distinct_values: GenericDistinctBuffer<T>,
669 percentile: f64,
670}
671
672impl<T: ArrowNumericType + Debug> DistinctPercentileContAccumulator<T> {
673 fn new(percentile: f64) -> Self {
674 Self {
675 distinct_values: GenericDistinctBuffer::new(T::DATA_TYPE),
676 percentile,
677 }
678 }
679}
680
681impl<T> Accumulator for DistinctPercentileContAccumulator<T>
682where
683 T: ArrowNumericType + Debug,
684 T::Native: Copy + AsPrimitive<f64>,
685 f64: AsPrimitive<T::Native>,
686{
687 fn state(&mut self) -> Result<Vec<ScalarValue>> {
688 self.distinct_values.state()
689 }
690
691 fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
692 self.distinct_values.update_batch(values)
693 }
694
695 fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
696 self.distinct_values.merge_batch(states)
697 }
698
699 fn evaluate(&mut self) -> Result<ScalarValue> {
700 let mut values: Vec<T::Native> =
701 self.distinct_values.values.iter().map(|v| v.0).collect();
702 let value = calculate_percentile::<T>(&mut values, self.percentile);
703 ScalarValue::new_primitive::<T>(value, &T::DATA_TYPE)
704 }
705
706 fn size(&self) -> usize {
707 size_of_val(self) + self.distinct_values.size()
708 }
709
710 fn retract_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
711 if values.is_empty() {
712 return Ok(());
713 }
714
715 let arr = values[0].as_primitive::<T>();
716 for value in arr.iter().flatten() {
717 self.distinct_values.values.remove(&Hashable(value));
718 }
719 Ok(())
720 }
721
722 fn supports_retract_batch(&self) -> bool {
723 true
724 }
725}
726
727fn calculate_percentile<T: ArrowNumericType>(
739 values: &mut [T::Native],
740 percentile: f64,
741) -> Option<T::Native>
742where
743 T::Native: Copy + AsPrimitive<f64>,
744 f64: AsPrimitive<T::Native>,
745{
746 let cmp = |x: &T::Native, y: &T::Native| x.compare(*y);
747
748 let len = values.len();
749 if len == 0 {
750 None
751 } else if len == 1 {
752 Some(values[0])
753 } else if percentile == 0.0 {
754 Some(
756 *values
757 .iter()
758 .min_by(|a, b| cmp(a, b))
759 .expect("we checked for len > 0 a few lines above"),
760 )
761 } else if percentile == 1.0 {
762 Some(
764 *values
765 .iter()
766 .max_by(|a, b| cmp(a, b))
767 .expect("we checked for len > 0 a few lines above"),
768 )
769 } else {
770 let index = percentile * ((len - 1) as f64);
772 let lower_index = index.floor() as usize;
773 let upper_index = index.ceil() as usize;
774
775 if lower_index == upper_index {
776 let (_, value, _) = values.select_nth_unstable_by(lower_index, cmp);
778 Some(*value)
779 } else {
780 let (_, lower_value, _) = values.select_nth_unstable_by(lower_index, cmp);
783 let lower_value = *lower_value;
784
785 let (_, upper_value, _) = values.select_nth_unstable_by(upper_index, cmp);
787 let upper_value = *upper_value;
788
789 let fraction = index - (lower_index as f64);
796 let scaled = (fraction * INTERPOLATION_PRECISION) as usize;
797 let weight = scaled as f64 / INTERPOLATION_PRECISION;
798
799 let lower_f: f64 = lower_value.as_();
800 let upper_f: f64 = upper_value.as_();
801 let interpolated_f = lower_f + (upper_f - lower_f) * weight;
802 Some(interpolated_f.as_())
803 }
804 }
805}
806
807#[cfg(test)]
808mod tests {
809 use super::calculate_percentile;
810 use half::f16;
811
812 #[test]
813 fn f16_interpolation_does_not_overflow_to_nan() {
814 let mut values = vec![f16::from_f32(0.0), f16::from_f32(65504.0)];
818 let result =
819 calculate_percentile::<arrow::datatypes::Float16Type>(&mut values, 0.5)
820 .expect("non-empty input");
821 let result_f = result.to_f32();
822 assert!(
823 !result_f.is_nan(),
824 "expected non-NaN result, got {result_f}"
825 );
826 assert!(
828 (result_f - 32752.0).abs() < 1.0,
829 "unexpected result {result_f}"
830 );
831 }
832}