将UDF函数应用于Spark窗口,其中输入参数是范围内所有列值的列表

时间:2019-03-27 14:10:25

标签: scala apache-spark

我想在窗口的每一行上建立移动平均线。假设有-10行。但是如果少于10行可用,我想在结果行->新列中插入0。 因此,我想实现的目标是在具有输入参数List()(或任何超类)的聚合窗口中使用UDF,该参数具有所有可用行的值。

这是一个不起作用的代码示例:

val w = Window.partitionBy("id").rowsBetween(-10, +0)
dfRetail2.withColumn("test", udftestf(dfRetail2("salesMth")).over(w))

Expected output: List( 1,2,3,4)(如果没有更多行可用),并将其作为udf函数的输入参数。 udf函数应返回一个计算值,如果可用行少于10行,则应返回0。

以上代码终止:Expression 'UDF(salesMth#152L)' not supported within a window function.;;

1 个答案:

答案 0 :(得分:0)

您可以将Spark的内置Window函数与when/otherwise一起用于特定条件,而无需使用UDF / UDAF。为简单起见,在下面的示例中,使用伪数据将滑动窗口的大小减小为4:

import org.apache.spark.sql.functions._
import org.apache.spark.sql.expressions.Window
import spark.implicits._

val df = (1 to 2).flatMap(i => Seq.tabulate(8)(j => (i, i * 10.0 + j))).
  toDF("id", "amount")

val slidingWin = 4

val winSpec = Window.partitionBy($"id").rowsBetween(-(slidingWin - 1), 0)

df.
  withColumn("slidingCount", count($"amount").over(winSpec)).
  withColumn("slidingAvg", when($"slidingCount" < slidingWin, 0.0).
    otherwise(avg($"amount").over(winSpec))
  ).show
// +---+------+------------+----------+
// | id|amount|slidingCount|slidingAvg|
// +---+------+------------+----------+
// |  1|  10.0|           1|       0.0|
// |  1|  11.0|           2|       0.0|
// |  1|  12.0|           3|       0.0|
// |  1|  13.0|           4|      11.5|
// |  1|  14.0|           4|      12.5|
// |  1|  15.0|           4|      13.5|
// |  1|  16.0|           4|      14.5|
// |  1|  17.0|           4|      15.5|
// |  2|  20.0|           1|       0.0|
// |  2|  21.0|           2|       0.0|
// |  2|  22.0|           3|       0.0|
// |  2|  23.0|           4|      21.5|
// |  2|  24.0|           4|      22.5|
// |  2|  25.0|           4|      23.5|
// |  2|  26.0|           4|      24.5|
// |  2|  27.0|           4|      25.5|
// +---+------+------------+----------+

在评论部分的每句话中,我包括以下通过UDF提供的解决方案:

def movingAvg(n: Int) = udf{ (ls: Seq[Double]) =>
  val (avg, count) = ls.takeRight(n).foldLeft((0.0, 1)){
    case ((a, i), next) => (a + (next-a)/i, i + 1) 
  }
  if (count <= n) 0.0 else avg  // Expand/Modify this for specific requirement
}

// To apply the UDF:
df.
  withColumn("average", movingAvg(slidingWin)(collect_list($"amount").over(winSpec))).
  show

请注意,与sumcount不同的是,collect_list会忽略rowsBetween()并生成分区的数据,这些数据可能很大,要传递给UDF(因此需要takeRight())。如果计算出的窗口sumcount足以满足您的特定要求,请考虑将它们传递给UDF。


通常,特别是如果手头的数据已经是DataFrame格式,则使用内置的DataFrame API来利用Spark的执行引擎优化要比使用用户定义的UDF / UDAF更好,并且其性能和扩展性更好。您可能对阅读article re:DataFrame / Dataset API相对于UDF / UDAF的优势感兴趣。