datafusion_functions_aggregate/
approx_percentile_cont_with_weight.rs1use std::fmt::Debug;
19use std::hash::Hash;
20use std::mem::size_of_val;
21use std::sync::Arc;
22
23use arrow::compute::{and, filter, is_not_null};
24use arrow::datatypes::FieldRef;
25use arrow::{array::ArrayRef, datatypes::DataType};
26use datafusion_common::ScalarValue;
27use datafusion_common::types::{NativeType, logical_float64};
28use datafusion_common::{Result, not_impl_err, plan_err};
29use datafusion_expr::expr::{AggregateFunction, Sort};
30use datafusion_expr::function::{AccumulatorArgs, StateFieldsArgs};
31use datafusion_expr::{
32 Accumulator, AggregateUDFImpl, Coercion, Documentation, Expr, Signature,
33 TypeSignature, TypeSignatureClass, Volatility,
34};
35use datafusion_functions_aggregate_common::tdigest::{Centroid, TDigest};
36use datafusion_macros::user_doc;
37
38use crate::approx_percentile_cont::{ApproxPercentileAccumulator, ApproxPercentileCont};
39
40create_func!(
41 ApproxPercentileContWithWeight,
42 approx_percentile_cont_with_weight_udaf
43);
44
45pub fn approx_percentile_cont_with_weight(
47 order_by: Sort,
48 weight: Expr,
49 percentile: Expr,
50 centroids: Option<Expr>,
51) -> Expr {
52 let expr = order_by.expr.clone();
53
54 let args = if let Some(centroids) = centroids {
55 vec![expr, weight, percentile, centroids]
56 } else {
57 vec![expr, weight, percentile]
58 };
59
60 Expr::AggregateFunction(AggregateFunction::new_udf(
61 approx_percentile_cont_with_weight_udaf(),
62 args,
63 false,
64 None,
65 vec![order_by],
66 None,
67 ))
68}
69
70#[user_doc(
72 doc_section(label = "Approximate Functions"),
73 description = "Returns the weighted approximate percentile of input values using the t-digest algorithm.",
74 syntax_example = "approx_percentile_cont_with_weight(weight, percentile [, centroids]) WITHIN GROUP (ORDER BY expression)",
75 sql_example = r#"```sql
76> SELECT approx_percentile_cont_with_weight(weight_column, 0.90) WITHIN GROUP (ORDER BY column_name) FROM table_name;
77+---------------------------------------------------------------------------------------------+
78| approx_percentile_cont_with_weight(weight_column, 0.90) WITHIN GROUP (ORDER BY column_name) |
79+---------------------------------------------------------------------------------------------+
80| 78.5 |
81+---------------------------------------------------------------------------------------------+
82> SELECT approx_percentile_cont_with_weight(weight_column, 0.90, 100) WITHIN GROUP (ORDER BY column_name) FROM table_name;
83+--------------------------------------------------------------------------------------------------+
84| approx_percentile_cont_with_weight(weight_column, 0.90, 100) WITHIN GROUP (ORDER BY column_name) |
85+--------------------------------------------------------------------------------------------------+
86| 78.5 |
87+--------------------------------------------------------------------------------------------------+
88```
89An alternative syntax is also supported:
90
91```sql
92> SELECT approx_percentile_cont_with_weight(column_name, weight_column, 0.90) FROM table_name;
93+--------------------------------------------------+
94| approx_percentile_cont_with_weight(column_name, weight_column, 0.90) |
95+--------------------------------------------------+
96| 78.5 |
97+--------------------------------------------------+
98```"#,
99 standard_argument(name = "expression", prefix = "The"),
100 argument(
101 name = "weight",
102 description = "Expression to use as weight. Can be a constant, column, or function, and any combination of arithmetic operators."
103 ),
104 argument(
105 name = "percentile",
106 description = "Percentile to compute. Must be a float value between 0 and 1 (inclusive)."
107 ),
108 argument(
109 name = "centroids",
110 description = "Number of centroids to use in the t-digest algorithm. _Default is 100_. A higher number results in more accurate approximation but requires more memory."
111 )
112)]
113#[derive(PartialEq, Eq, Hash, Debug)]
114pub struct ApproxPercentileContWithWeight {
115 signature: Signature,
116 approx_percentile_cont: ApproxPercentileCont,
117}
118
119impl Default for ApproxPercentileContWithWeight {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl ApproxPercentileContWithWeight {
126 pub fn new() -> Self {
128 let signature = Signature::one_of(
129 vec![
130 TypeSignature::Coercible(vec![
132 Coercion::new_implicit(
133 TypeSignatureClass::Float,
134 vec![TypeSignatureClass::Numeric],
135 NativeType::Float64,
136 ),
137 Coercion::new_implicit(
138 TypeSignatureClass::Float,
139 vec![TypeSignatureClass::Numeric],
140 NativeType::Float64,
141 ),
142 Coercion::new_implicit(
143 TypeSignatureClass::Native(logical_float64()),
144 vec![TypeSignatureClass::Numeric],
145 NativeType::Float64,
146 ),
147 ]),
148 TypeSignature::Coercible(vec![
150 Coercion::new_implicit(
151 TypeSignatureClass::Float,
152 vec![TypeSignatureClass::Numeric],
153 NativeType::Float64,
154 ),
155 Coercion::new_implicit(
156 TypeSignatureClass::Float,
157 vec![TypeSignatureClass::Numeric],
158 NativeType::Float64,
159 ),
160 Coercion::new_implicit(
161 TypeSignatureClass::Native(logical_float64()),
162 vec![TypeSignatureClass::Numeric],
163 NativeType::Float64,
164 ),
165 Coercion::new_implicit(
166 TypeSignatureClass::Integer,
167 vec![TypeSignatureClass::Numeric],
168 NativeType::Int64,
169 ),
170 ]),
171 ],
172 Volatility::Immutable,
173 );
174 Self {
175 signature,
176 approx_percentile_cont: ApproxPercentileCont::new(),
177 }
178 }
179}
180
181impl AggregateUDFImpl for ApproxPercentileContWithWeight {
182 fn name(&self) -> &str {
183 "approx_percentile_cont_with_weight"
184 }
185
186 fn signature(&self) -> &Signature {
187 &self.signature
188 }
189
190 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
191 if !arg_types[0].is_numeric() {
192 return plan_err!(
193 "approx_percentile_cont_with_weight requires numeric input types"
194 );
195 }
196 if !arg_types[1].is_numeric() {
197 return plan_err!(
198 "approx_percentile_cont_with_weight requires numeric weight input types"
199 );
200 }
201 if arg_types[2] != DataType::Float64 {
202 return plan_err!(
203 "approx_percentile_cont_with_weight requires float64 percentile input types"
204 );
205 }
206 if arg_types.len() == 4 && !arg_types[3].is_integer() {
207 return plan_err!(
208 "approx_percentile_cont_with_weight requires integer centroids input types"
209 );
210 }
211 Ok(arg_types[0].clone())
212 }
213
214 fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
215 if acc_args.is_distinct {
216 return not_impl_err!(
217 "approx_percentile_cont_with_weight(DISTINCT) aggregations are not available"
218 );
219 }
220
221 if acc_args.exprs.len() != 3 && acc_args.exprs.len() != 4 {
222 return plan_err!(
223 "approx_percentile_cont_with_weight requires three or four arguments: value, weight, percentile[, centroids]"
224 );
225 }
226
227 let sub_args = AccumulatorArgs {
228 exprs: if acc_args.exprs.len() == 4 {
229 &[
230 Arc::clone(&acc_args.exprs[0]), Arc::clone(&acc_args.exprs[2]), Arc::clone(&acc_args.exprs[3]), ]
234 } else {
235 &[
236 Arc::clone(&acc_args.exprs[0]), Arc::clone(&acc_args.exprs[2]), ]
239 },
240 expr_fields: if acc_args.exprs.len() == 4 {
241 &[
242 Arc::clone(&acc_args.expr_fields[0]), Arc::clone(&acc_args.expr_fields[2]), Arc::clone(&acc_args.expr_fields[3]), ]
246 } else {
247 &[
248 Arc::clone(&acc_args.expr_fields[0]), Arc::clone(&acc_args.expr_fields[2]), ]
251 },
252 return_field: acc_args.return_field,
256 schema: acc_args.schema,
257 ignore_nulls: acc_args.ignore_nulls,
258 order_bys: acc_args.order_bys,
259 is_reversed: acc_args.is_reversed,
260 name: acc_args.name,
261 is_distinct: acc_args.is_distinct,
262 };
263 let approx_percentile_cont_accumulator =
264 self.approx_percentile_cont.create_accumulator(&sub_args)?;
265 let accumulator = ApproxPercentileWithWeightAccumulator::new(
266 approx_percentile_cont_accumulator,
267 );
268 Ok(Box::new(accumulator))
269 }
270
271 fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> {
274 self.approx_percentile_cont.state_fields(args)
275 }
276
277 fn supports_within_group_clause(&self) -> bool {
278 true
279 }
280
281 fn documentation(&self) -> Option<&Documentation> {
282 self.doc()
283 }
284}
285
286#[derive(Debug)]
287pub struct ApproxPercentileWithWeightAccumulator {
288 approx_percentile_cont_accumulator: ApproxPercentileAccumulator,
289}
290
291impl ApproxPercentileWithWeightAccumulator {
292 pub fn new(approx_percentile_cont_accumulator: ApproxPercentileAccumulator) -> Self {
293 Self {
294 approx_percentile_cont_accumulator,
295 }
296 }
297}
298
299impl Accumulator for ApproxPercentileWithWeightAccumulator {
300 fn state(&mut self) -> Result<Vec<ScalarValue>> {
301 self.approx_percentile_cont_accumulator.state()
302 }
303
304 fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
305 let mut means = Arc::clone(&values[0]);
306 let mut weights = Arc::clone(&values[1]);
307 match (means.null_count() > 0, weights.null_count() > 0) {
309 (true, true) => {
311 let predicate = and(&is_not_null(&means)?, &is_not_null(&weights)?)?;
312 means = filter(&means, &predicate)?;
313 weights = filter(&weights, &predicate)?;
314 }
315 (false, true) => {
317 let predicate = &is_not_null(&weights)?;
318 means = filter(&means, predicate)?;
319 weights = filter(&weights, predicate)?;
320 }
321 (true, false) => {
322 let predicate = &is_not_null(&means)?;
323 means = filter(&means, predicate)?;
324 weights = filter(&weights, predicate)?;
325 }
326 (false, false) => {}
328 }
329 debug_assert_eq!(
330 means.len(),
331 weights.len(),
332 "invalid number of values in means and weights"
333 );
334 let means_f64 = ApproxPercentileAccumulator::convert_to_float(&means)?;
335 let weights_f64 = ApproxPercentileAccumulator::convert_to_float(&weights)?;
336 let mut digests: Vec<TDigest> = vec![];
337 for (mean, weight) in means_f64.iter().zip(weights_f64.iter()) {
338 digests.push(TDigest::new_with_centroid(
339 self.approx_percentile_cont_accumulator.max_size(),
340 Centroid::new(*mean, *weight),
341 ))
342 }
343 self.approx_percentile_cont_accumulator
344 .merge_digests(&digests);
345 Ok(())
346 }
347
348 fn evaluate(&mut self) -> Result<ScalarValue> {
349 self.approx_percentile_cont_accumulator.evaluate()
350 }
351
352 fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
353 self.approx_percentile_cont_accumulator
354 .merge_batch(states)?;
355
356 Ok(())
357 }
358
359 fn size(&self) -> usize {
360 size_of_val(self) - size_of_val(&self.approx_percentile_cont_accumulator)
361 + self.approx_percentile_cont_accumulator.size()
362 }
363}