cloud-fan commented on code in PR #57952: URL: https://github.com/apache/spark/pull/57952#discussion_r3810016863
########## python/pyspark/sql/aggregator.py: ########## @@ -0,0 +1,211 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" +Incremental user-defined aggregators for PySpark, the Python analog of Scala's +``org.apache.spark.sql.expressions.Aggregator``. +""" + +from abc import ABC, abstractmethod +from typing import Any, Tuple + +from pyspark.errors import PySparkNotImplementedError, PySparkTypeError, PySparkValueError +from pyspark.sql.types import DataType, StructType +from pyspark.util import PythonEvalType + +__all__ = ["Aggregator", "udaf"] + + +class Aggregator(ABC): + """ + Base class for a user-defined *incremental* aggregator, the Python analog of Scala's + :class:`org.apache.spark.sql.expressions.Aggregator`. + + Unlike a grouped-aggregate ``pandas_udf`` (which materializes the whole group and is invoked + once), an :class:`Aggregator` is executed as a genuine two-stage aggregation with map-side + combine: :meth:`reduce` folds input rows into a per-group *buffer* on the map side, the buffers + are shuffled by the grouping key, :meth:`merge` combines the partial buffers of each group, and + :meth:`finish` produces the final output value. + + The buffer is represented as a Python :class:`tuple` whose elements correspond, in order, to the + fields of :attr:`bufferSchema`. An input row is likewise a tuple of the argument values passed + to the aggregator call. :meth:`merge` must be associative and commutative, since the framework + may combine partial buffers in any order. + + .. versionadded:: 4.4.0 + + Examples + -------- + A mean aggregator:: + + from pyspark.sql.aggregator import Aggregator, udaf + from pyspark.sql.types import StructType, StructField, DoubleType, LongType + + class Mean(Aggregator): + @property + def bufferSchema(self): + return StructType([ + StructField("sum", DoubleType()), + StructField("count", LongType()), + ]) + + @property + def outputType(self): + return DoubleType() + + def zero(self): + return (0.0, 0) + + def reduce(self, buffer, value): + (v,) = value + if v is None: # ignore null inputs, like SQL aggregates do + return buffer + return (buffer[0] + v, buffer[1] + 1) + + def merge(self, b1, b2): + return (b1[0] + b2[0], b1[1] + b2[1]) + + def finish(self, buffer): + return buffer[0] / buffer[1] if buffer[1] else None + + mean = udaf(Mean()) + df.groupBy("k").agg(mean(df.v)).show() + """ + + @property + @abstractmethod + def bufferSchema(self) -> StructType: + """The schema of the intermediate buffer that crosses the shuffle.""" + ... + + @property + @abstractmethod + def outputType(self) -> DataType: + """The data type of the aggregator's output value.""" + ... + + @abstractmethod + def zero(self) -> Tuple[Any, ...]: + """The initial (identity) buffer value, as a tuple matching :attr:`bufferSchema`.""" Review Comment: **Non-blocking:** Please spell out the identity law here: `merge(buffer, zero()) == buffer` and `merge(zero(), buffer) == buffer`. The worker creates a fresh zero for each partition or early-flush chunk, so associativity and commutativity of `merge` alone do not guarantee the documented partition-independent result. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
