在Spark UDF中操作数据帧

时间:2018-02-20 19:40:34

标签: apache-spark dataframe apache-spark-sql spark-dataframe

我有一个UDF,用于过滤和选择数据帧中的值,但它会遇到“object not serializable”错误。详情如下。

假设我有一个数据帧df1,其中包含带有名称的列(“ID”,“Y1”,“Y2”,“Y3”,“Y4”,“Y5”,“Y6”,“Y7”,“Y8” ,“Y9”,“Y10”)。我希望根据来自另一个数据帧df2的匹配“ID”和“值”求和“Y”列的子集。我尝试了以下方法:

val y_list = ("Y1", "Y2", "Y3", "Y4", "Y5", "Y6", "Y7", "Y8", "Y9", "Y10").map(c => col(c))

def udf_test(ID: String, value: Int): Double = {
  df1.filter($"ID" === ID).select(y_list:_*).first.toSeq.toList.take(value).foldLeft(0.0)(_+_)
}
sqlContext.udf.register("udf_test", udf_test _)

val df_result = df2.withColumn("Result", callUDF("udf_test", $"ID", $"Value"))

这给了我错误的表格:

java.io.NotSerializableException: org.apache.spark.sql.Column
Serialization stack:
- object not serializable (class: org.apache.spark.sql.Column, value: Y1)

我查了一下,发现Spark Column不可序列化。我在想:

1)有没有办法在UDF中操作数据帧?

2)如果没有,那么实现上述操作类型的最佳方法是什么?我的真实案例比这更复杂。它要求我根据大数据帧中的某些列从多个小数据帧中选择值,并将值计算回大数据帧。

我正在使用Spark 1.6.3。谢谢!

3 个答案:

答案 0 :(得分:1)

您无法在UDF中使用数据集操作。 UDF只能在现有列上进行操作并生成一个结果列。它无法过滤数据集或进行聚合,但可以在过滤器内使用。 UDAF也可以汇总价值。

相反,您可以使用.as[SomeCaseClass]从DataFrame制作数据集,并在filter,map,reduce中使用普通的强类型函数。

编辑:如果你想将你的bigDF加入smallDFs List中的每个小DF,你可以这样做:

import org.apache.spark.sql.functions._
val bigDF = // some processing
val smallDFs = Seq(someSmallDF1, someSmallDF2)
val joined = smallDFs.foldLeft(bigDF)((acc, df) => acc.join(broadcast(df), "join_column"))

broadcast是一个将广播提示添加到小型DF的功能,因此小型DF将使用更高效的广播连接而不是排序合并连接

答案 1 :(得分:0)

1)不,你只能在UDF中使用普通的scala代码

2)如果您正确地解释了代码,您可以通过以下方式实现目标:

df2
  .join(
    df1.select($"ID",y_list.foldLeft(lit(0))(_ + _).as("Result")),Seq("ID")
  )

答案 2 :(得分:0)

import org.apache.spark.sql.functions._
val events = Seq (
(1,1,2,3,4),
(2,1,2,3,4),
(3,1,2,3,4),
(4,1,2,3,4),
(5,1,2,3,4)).toDF("ID","amt1","amt2","amt3","amt4")

var prev_amt5=0
var i=1
def getamt5value(ID:Int,amt1:Int,amt2:Int,amt3:Int,amt4:Int) : Int = {  
  if(i==1){
    i=i+1
    prev_amt5=0
  }else{
    i=i+1
  }
  if (ID == 0)
  {
    if(amt1==0)
    {
      val cur_amt5= 1
      prev_amt5=cur_amt5
      cur_amt5
    }else{
      val cur_amt5=1*(amt2+amt3)
      prev_amt5=cur_amt5
      cur_amt5
    }
  }else if (amt4==0 || (prev_amt5==0 & amt1==0)){
    val cur_amt5=0
    prev_amt5=cur_amt5
    cur_amt5
  }else{
    val cur_amt5=prev_amt5 +  amt2 + amt3 + amt4
    prev_amt5=cur_amt5
    cur_amt5
  }
}

val getamt5 = udf {(ID:Int,amt1:Int,amt2:Int,amt3:Int,amt4:Int) =>            
   getamt5value(ID,amt1,amt2,amt3,amt4)    
}
myDF.withColumn("amnt5", getamt5(myDF.col("ID"),myDF.col("amt1"),myDF.col("amt2"),myDF.col("amt3"),myDF.col("amt4"))).show()