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

Scala 将UDF函数应用于Spark窗口,其中输入参数是范围内所有列值的列表,scala,apache-spark,Scala,Apache Spark,我想在窗口中的每一行上建立一个移动平均线。比方说-10排。但是如果可用行少于10行,我想在结果行->新建列中插入一个0。 因此,我将尝试在聚合窗口中使用UDF,该窗口具有输入参数List()(或任何超类),其中包含所有行的值 下面是一个不起作用的代码示例: val w = Window.partitionBy("id").rowsBetween(-10, +0) dfRetail2.withColumn("test", udftestf(dfRetail2("salesMth")).over(w

我想在窗口中的每一行上建立一个移动平均线。比方说-10排。但是如果可用行少于10行,我想在结果行->新建列中插入一个0。 因此,我将尝试在聚合窗口中使用UDF,该窗口具有输入参数List()(或任何超类),其中包含所有行的值

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

val w = Window.partitionBy("id").rowsBetween(-10, +0)
dfRetail2.withColumn("test", udftestf(dfRetail2("salesMth")).over(w))
预期输出:如果没有更多行可用,则列出(1,2,3,4)
,并将其作为udf函数的输入参数。udf函数应返回计算值,如果可用行数少于10行,则返回0


上述代码终止:
表达式“UDF(salesMth#152L)”在窗口函数中不受支持

您可以在不需要UDF/UDAF的情况下,根据您的具体情况使用Spark的内置窗口功能以及
when/other
。为简单起见,在以下使用虚拟数据的示例中,滑动窗口大小减小为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|
// +---+------+------------+----------+
import org.apache.spark.sql.functions_
导入org.apache.spark.sql.expressions.Window
导入spark.implicits_
val df=(1到2).flatMap(i=>序列表(8)(j=>(i,i*10.0+j)))。
toDF(“id”、“金额”)
val slidingWin=4
val winSpec=Window.partitionBy($“id”).rowsBetween(-(slidingWin-1),0)
df。
withColumn(“滑动计数”,计数($“金额”).over(winSpec))。
withColumn(“slidingAvg”,当($“slidingCount”
根据评论部分的评论,我将通过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
defmovingavg(n:Int)=udf{(ls:Seq[Double])=>
val(平均,计数)=ls.takeRight(n.foldLeft((0.0,1)){
案例((a,i,next)=>(a+(next-a)/i,i+1)
}

if(count您可以针对您的特定情况使用Spark的内置窗口函数以及
when/other
,而无需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|
// +---+------+------------+----------+
import org.apache.spark.sql.functions_
导入org.apache.spark.sql.expressions.Window
导入spark.implicits_
val df=(1到2).flatMap(i=>序列表(8)(j=>(i,i*10.0+j)))。
toDF(“id”、“金额”)
val slidingWin=4
val winSpec=Window.partitionBy($“id”).rowsBetween(-(slidingWin-1),0)
df。
withColumn(“滑动计数”,计数($“金额”).over(winSpec))。
withColumn(“slidingAvg”,当($“slidingCount”
根据评论部分的评论,我将通过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
defmovingavg(n:Int)=udf{(ls:Seq[Double])=>
val(平均,计数)=ls.takeRight(n.foldLeft((0.0,1)){
案例((a,i,next)=>(a+(next-a)/i,i+1)
}

if(count)在浏览网络以获取答案时,我发现这是:“用户定义的聚合函数UDAF”,而不是UDF。可能就是这样。在浏览网络以获取答案时,我发现这是:“用户定义的聚合函数UDAF”而不是UDF。这可能是它。清晰明了的scala代码。成功了。谢谢!一个问题。我想它也适用于UDAF,但需要更多的代码。这个解决方案是否意味着我可以只使用UDF,而不需要使用UDAF?@Sven,实际上没有必要使用UDF或UDAF。请查看我的简化解决方案。T非常感谢。我必须承认,我的0示例太简单了(因为它超出了所问问题的范围)。我的评估有点复杂,所以我宁愿使用udf解决方案。我可以重新措辞吗?因此,使用udf解决方案,我是否可以使用UDAF。UDAF更复杂,因为您必须创建一个新的(案例)每个函数类。还是有一个我需要它们的用例?只是好奇而已。@Sven,请看我的扩展答案。清晰明了的scala代码。成功了。非常感谢!一个问题。我想它也适用于UDAF,但需要更多的代码。这个解决方案是否意味着我可以只使用UDF,而不需要费心使用它UDAF?@Sven,实际上不需要UDF或UDAF。请查看我的简化解决方案。非常感谢。我不得不承认我的0示例太简单了(因为它超出了所问问题的范围)。我的评估有点m