Uh oh!
There was an error while loading. Please reload this page.
[SPARK-22274][PYTHON][SQL] User-defined aggregation functions with pandas udf (full shuffle) - #19872
[SPARK-22274][PYTHON][SQL] User-defined aggregation functions with pandas udf (full shuffle)#19872icexelloss wants to merge 35 commits into
Conversation
icexelloss
commented
Dec 4, 2017
cc @HyukjinKwon@holdenk@ueshin Passing some basic tests. I will work on this more next week to clean up and add more testing. |
SparkQA
commented
Dec 4, 2017
Test build #84414 has finished for PR 19872 at commit
|
There was a problem hiding this comment.
FlatMapGroupsInPandas and AggregateInPandasExec can be...
SparkQA
commented
Dec 4, 2017
Test build #84415 has finished for PR 19872 at commit
|
There was a problem hiding this comment.
If exprs[n] (n > 0) is a UDFColumn? I think we should make sure if any column is a UDFColumn, all columns should be UDFColumn.
There was a problem hiding this comment.
This code is removed.
There was a problem hiding this comment.
A informative error message should be better.
There was a problem hiding this comment.
So I'm a little worried about this change, if other folks have wrapped Java UDAFs (which is reasonable since there aren't other ways to make UDAFs in PySpark before this), this seems like they won't be able to mix them. I'd suggest maybe doing what @viirya suggested bellow but instead of a failure just a warning until Spark 3.
What do y'all think?
There was a problem hiding this comment.
I am still trying to figure out the best way to dispatch this, but either way I think we won't be able to fix Java UDAF with pandas UDF.
@holdenk I am not sure what kind of warning message do you have in mind. Can you please explain?
There was a problem hiding this comment.
Ah so what your saying is you don't support mixed Python & Java UDAFs? That's certainly something which needs to be communicated in both the documentation and the error message.
Is there a reason why we don't support this?
holdenk
left a comment
There was a problem hiding this comment.
Thanks for working on this. I'm off for a flight to Strata but a few quick questions. I'll read this more over the coming week :)
There was a problem hiding this comment.
Why did we add this new sub-class?
There was a problem hiding this comment.
So I'm worried that it isn't clear to the user that this will result in a full-shuffle with no-partial aggregation. Is there maybe a place we can document this warning?
There was a problem hiding this comment.
Added in docstring of pandas_udf and groupby().agg()
HyukjinKwon
left a comment
There was a problem hiding this comment.
I thought @ueshin is working on this BTW.
There was a problem hiding this comment.
nit:
child: LogicalPlan) extends UnaryNode {
There was a problem hiding this comment.
BTW, what do you think about adding an attribute instead in __call__ like a flag?
icexelloss
commented
Dec 4, 2017
Oh, I certainly don't want to duplicate @ueshin 's work. I am under the impression that @ueshin is working on two-stage PySpark UDAF with pandas_udf, but I cannot really find the Jira for it... @ueshin can you point me to what you are working on so I don't overstep? |
a1058b8 to
c1dc543CompareSparkQA
commented
Dec 4, 2017
Test build #84446 has finished for PR 19872 at commit
|
SparkQA
commented
Dec 8, 2017
Test build #84628 has finished for PR 19872 at commit
|
I end up removing The code works and three tests (test_basic, test_alias, test_multiple) passes now but the code is kind of messy. I am going on vacation next week but I will clean up the code and move this PR forward when I get back (Dec 16). Thanks all. |
icexelloss
commented
Dec 8, 2017
And to @holdenk 's question. Pandas group_agg udf fundamentally uses different physical plan than the existing java/scala udf and therefore it's hard to combine them together. I don't know a good way to do this, the closest is maybe to compute java/scala and python aggregation separately and join them together. |
3352050 to
184b37fCompareSparkQA
commented
Dec 8, 2017
Test build #84630 has finished for PR 19872 at commit
|
SparkQA
commented
Dec 8, 2017
Test build #84631 has finished for PR 19872 at commit
|
SparkQA
commented
Dec 8, 2017
Test build #84632 has finished for PR 19872 at commit
|
ueshin
commented
Dec 8, 2017
@icexelloss I'm sorry for the late response. |
There was a problem hiding this comment.
Whoops, my bad, adding back
There was a problem hiding this comment.
nit: how about making this the common method?
There was a problem hiding this comment.
nit: columnarBatchIter.flatMap(_.rowIterator)?
nit: style, add a space between map and { outputRow =>.
There was a problem hiding this comment.
columnarBatchIter.flatMap(_.rowIterator)
Doesn't work because rowIterator is a java iterator not a scala iterator, we can convert it, but I am not sure it's better though. @ueshin if you prefer the flatMap one I can change it.
There was a problem hiding this comment.
Sorry, I meant columnarBatchIter.flatMap(_.rowIterator.asScala). I'd prefer this one.
There was a problem hiding this comment.
We don't need _ @ here.
nit: remove extra space after SQL_PANDAS_GROUP_AGG_UDF.
There was a problem hiding this comment.
nit: style, we need indent for this block.
There was a problem hiding this comment.
Do we need to move package to catalyst?
There was a problem hiding this comment.
We do. This is similar to https://github.com/apache/spark/blob/master/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/ScalaUDF.scala
The reason is we need to access the class PythonUDF in analyzer.
SparkQA
commented
Dec 19, 2017
Test build #85136 has finished for PR 19872 at commit
|
SparkQA
commented
Dec 20, 2017
Test build #85137 has finished for PR 19872 at commit
|
SparkQA
commented
Dec 20, 2017
Test build #85138 has finished for PR 19872 at commit
|
ueshin
commented
Dec 20, 2017
@ramacode2014 Hi, I'm not sure why you received notifications from this PR, but I guess you can unsubscribe by the "Unsubscribe" button in the right column of this page. Sorry for the inconvenience. Thanks! |
There was a problem hiding this comment.
I am not sure. But I added copy in ExtractGroupAggPandasUDFFromAggregate similar to existing rules.
There was a problem hiding this comment.
Do we need to drop the grouping expressions?
If we need, we can drop them only if conf.dataFrameRetainGroupColumns == true, otherwise aggregateExpressions doesn't contain groupingExpressions?
There was a problem hiding this comment.
This is fixed. Added test_retain_grouping_columns test
There was a problem hiding this comment.
I guess we don't need to append groupingExpressions. Seems like they are dropped later.
There was a problem hiding this comment.
We need to handle resultExpressions for the following cases:
deftest_result_expressions(self):
importnumpyasnpfrompyspark.sql.functionsimportmean, pandas_udf, PandasUDFTypedf=self.data@pandas_udf('double', PandasUDFType.GROUP_AGG)defmean_udf(v, w):
returnnp.average(v, weights=w)
result1= (df.groupby('id')
.agg(mean_udf(df.v, lit(1.0)) +1)
.sort('id')
.toPandas())
expected1= (df.groupby('id')
.agg(mean(df.v) +1)
.sort('id')
.toPandas())
self.assertPandasEqual(expected1, result1)There was a problem hiding this comment.
Thanks @ueshin for reminding me of this. Just want to clarify the semantics:
Does
.agg(mean(df.v) + 1)
mean "compute mean of df.v and plus the mean by one as output", i.e, same as
.agg(mean(df.v).alias('mean'))
.withColumn('mean', col('mean') + 1)
?
There was a problem hiding this comment.
Yes, I think so about the behavior. I guess the plan could be different, though.
We can compare the behavior with non-udf aggregation and let's follow the behavior.
There was a problem hiding this comment.
I added ExtractGroupAggPandasUDFFromAggregate rule to deal with this
SparkQA
commented
Dec 20, 2017
Test build #85152 has finished for PR 19872 at commit
|
99367a6 to
66a31f9Compareicexelloss
commented
Dec 27, 2017
@ueshin I pushed some more change to address your comments. There is one regression in existing test |
SparkQA
commented
Dec 28, 2017
Test build #85442 has finished for PR 19872 at commit
|
SparkQA
commented
Dec 28, 2017
Test build #85446 has finished for PR 19872 at commit
|
a94b146 to
17fad5cCompareSparkQA
commented
Jan 18, 2018
Test build #86345 has finished for PR 19872 at commit
|
SparkQA
commented
Jan 18, 2018
Test build #86344 has finished for PR 19872 at commit
|
SparkQA
commented
Jan 18, 2018
Test build #86346 has finished for PR 19872 at commit
|
SparkQA
commented
Jan 18, 2018
Test build #86350 has finished for PR 19872 at commit
|
icexelloss
commented
Jan 19, 2018
@ueshin I think all comments are addressed. Can you take a final look? Thanks! |
ueshin
left a comment
There was a problem hiding this comment.
We also need to add PythonEvalType.SQL_PANDAS_GROUP_AGG_UDF to udf.py#L40-L41 to pass require_minimum_pyarrow_version().
LGTM except for the comments.
Btw, I'm afraid I guess we shouldn't merge this into branch-2.3 since we are already close to release 2.3.
WDYT? @HyukjinKwon@cloud-fan
| 3. GROUP_AGG | ||
| A group aggregate UDF defines a transformation: One or more `pandas.Series` -> A scalar | ||
| The `returnType` should be a primitive data type, e.g, :class:`DoubleType`. |
There was a problem hiding this comment.
very small nit: e.g. instead of e.g.
HyukjinKwon
commented
Jan 22, 2018
+1 for master-only. We can cherry-pick and backport if we should even after this gets merged anyway. For a reminder, we should complete the doc #19575 too. |
icexelloss
commented
Jan 22, 2018
Addressed latest comments. Yeah I think master only is fine. |
SparkQA
commented
Jan 22, 2018
Test build #86487 has finished for PR 19872 at commit
|
SparkQA
commented
Jan 22, 2018
Test build #86492 has finished for PR 19872 at commit
|
ueshin
commented
Jan 23, 2018
Thanks! merging to master. |
icexelloss
commented
Jan 23, 2018
Thanks all for review! |
| // groupingExpressions, aggregateExpressions, resultExpressions, child | ||
| type ReturnType = | ||
| (Seq[NamedExpression], Seq[AggregateExpression], Seq[NamedExpression], LogicalPlan) | ||
| (Seq[NamedExpression], Seq[Expression], Seq[NamedExpression], LogicalPlan) |
There was a problem hiding this comment.
@icexelloss Thank you for this contribution! I just came across the change in this file. I am not sure if changing the type at here is the best option. The reason is that whenever we use this PhysicalAggregation rule, we have to check the instance type of those aggregate expressions and do casting. To me, it seems better to leave this rule untouched and create a new rule just for Python UDAF. What do you think?
(maybe you and reviewers already discussed it. If so, can you point me to the discussion?)
Thank you!
There was a problem hiding this comment.
Hi @yhuai,
You bring up a good point. I agree with you ideally we should avoid doing. When I was making the change, I found the solution implemented results in least amount of duplicate code, because a lot of logic is shared between AggregateExpression and Python UDF, but the downside is exactly what you mentioned.
One alternative is to create new rules for Python UDAF, my concern is that could result in quite a bit of code duplication. Maybe there is a way to avoid code duplication and keep the type safety, I am happy to explore the option. (Maybe create a parent class for AggregateExpression and Python UDAF)?
There was a problem hiding this comment.
I prefer that we try out using a new rule. We can create utility function to reuse code. Will you have a chance to try it out?
There was a problem hiding this comment.
@yhuai Yeah I can certainly try it out. Created https://issues.apache.org/jira/browse/SPARK-23302 to track.
I assume this is not urgent?
There was a problem hiding this comment.
It will be good to try it out soon. But it is not urgent.
| from pyspark.sql.functions import pandas_udf, PandasUDFType | ||
| with QuietTest(self.sc): | ||
| with self.assertRaisesRegex(NotImplementedError, 'not supported'): |
There was a problem hiding this comment.
@icexelloss This line does not compile ( we need assertRaisesRegexp). Can you file a pr to fix it? Thanks! Meanwhile, we will look into jenkins setup and see why the test was not exercised.
There was a problem hiding this comment.
I'll file the follow-up pr to fix it soon.
There was a problem hiding this comment.
@yhuai, if you meant not running tests in Python 2, this link might be helpful. Let me leave it just in case - #19884 (comment).
There was a problem hiding this comment.
@ueshin Thanks for fixing this. (I am late to the party)
… of `assertRaisesRegex`. ## What changes were proposed in this pull request? This is a follow-up pr of apache#19872 which uses `assertRaisesRegex` but it doesn't exist in Python 2, so some tests fail when running tests in Python 2 environment. Unfortunately, we missed it because currently Python 2 environment of the pr builder doesn't have proper versions of pandas or pyarrow, so the tests were skipped. This pr modifies to use `assertRaisesRegexp` instead of `assertRaisesRegex`. ## How was this patch tested? Tested manually in my local environment. Author: Takuya UESHIN <ueshin@databricks.com> Closesapache#20467 from ueshin/issues/SPARK-22274/fup1.
What changes were proposed in this pull request?
Add support for using pandas UDFs with groupby().agg().
This PR introduces a new type of pandas UDF - group aggregate pandas UDF. This type of UDF defines a transformation of multiple pandas Series -> a scalar value. Group aggregate pandas UDFs can be used with groupby().agg(). Note group aggregate pandas UDF doesn't support partial aggregation, i.e., a full shuffle is required.
This PR doesn't support group aggregate pandas UDFs that return ArrayType, StructType or MapType. Support for these types is left for future PR.
How was this patch tested?
GroupbyAggPandasUDFTests