You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@spark.apache.org by "ASF GitHub Bot (Jira)" <ji...@apache.org> on 2025/12/09 00:36:00 UTC

[jira] [Updated] (SPARK-54647) Support User-Defined Aggregate Functions (UDAF) in PySpark

     [ https://issues.apache.org/jira/browse/SPARK-54647?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

ASF GitHub Bot updated SPARK-54647:
-----------------------------------
    Labels: pull-request-available  (was: )

> Support User-Defined Aggregate Functions (UDAF) in PySpark
> ----------------------------------------------------------
>
>                 Key: SPARK-54647
>                 URL: https://issues.apache.org/jira/browse/SPARK-54647
>             Project: Spark
>          Issue Type: New Feature
>          Components: PySpark
>    Affects Versions: 4.2.0
>            Reporter: Yicong Huang
>            Priority: Major
>              Labels: pull-request-available
>
> Currently PySpark supports User-Defined Functions (UDF) and User-Defined Table Functions (UDTF), but lacks support for User-Defined Aggregate Functions (UDAF). Users need to write custom aggregation logic in Scala/Java or use less efficient workarounds. This limits the ability to express complex aggregation logic directly in Python.
> This change adds UDAF support in PySpark using a two-stage aggregation pattern with mapInArrow and applyInArrow. The basic idea is to implement aggregation (and partial aggregation) by:
> {code}
> df.selectExpr("rand() as key").mapInArrow(func1).groupBy(key).applyInArrow(func2)
> {code}
> Where func1 calls Aggregator.reduce() for partial aggregation within each partition, and func2 calls Aggregator.merge() to combine partial results, then Aggregator.finish() for final results.
> The implementation provides a Python Aggregator base class that users can subclass:
> {code:python}
> class Aggregator:
>     def zero(self) -> Any:
>         """Return zero value for aggregation buffer"""
>         raise NotImplementedError
>     
>     def reduce(self, buffer: Any, value: Any) -> Any:
>         """Combine input value into buffer"""
>         raise NotImplementedError
>     
>     def merge(self, buffer1: Any, buffer2: Any) -> Any:
>         """Merge two intermediate buffers"""
>         raise NotImplementedError
>     
>     def finish(self, reduction: Any) -> Any:
>         """Produce final result from buffer"""
>         raise NotImplementedError
> {code}
> Users can create UDAF instances using the udaf() function and use them with DataFrame.agg():
> {code:python}
> sum_udaf = udaf(MySum(), "bigint")
> df.agg(sum_udaf(df.value))
> df.groupBy("group").agg(sum_udaf(df.value))
> {code}
> The implementation uses iterator-based mapInArrow and applyInArrow APIs to reduce memory footprint, serializes Aggregator instances using cloudpickle for distribution to worker nodes, and handles both grouped and non-grouped aggregations. The random key range is determined by spark.sql.shuffle.partitions or DataFrame partition count to ensure proper data distribution.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@spark.apache.org
For additional commands, e-mail: issues-help@spark.apache.org