Spark:将UDF应用于Dataframe基于DF中的值生成新列

时间:2017-03-07 08:44:31

标签: scala apache-spark spark-dataframe

我在Scala中DataFrame中转换值时出现问题。我的初始DataFrame看起来像这样:

+----+----+----+----+
|col1|col2|col3|col4|
+----+----+----+----+
|   A|   X|   6|null|
|   B|   Z|null|   5|
|   C|   Y|   4|null|
+----+----+----+----+

col1col2类型为Stringcol3col4Int

结果应如下所示:

+----+----+----+----+------+------+------+
|col1|col2|col3|col4|AXcol3|BZcol4|CYcol4|
+----+----+----+----+------+------+------+
|   A|   X|   6|null|     6|  null|  null|
|   B|   Z|null|   5|  null|     5|  null|
|   C|   Y|   4|   4|  null|  null|     4|
+----+----+----+----+------+------+------+

这意味着应该在col1col2和提取值的列之后命名三个新列。提取的值来自列col2col3col5,具体取决于哪个值不是null

那么如何实现呢?我首先想到了这样的UDF

def myFunc (col1:String, col2:String, col3:Long, col4:Long) : (newColumn:String, rowValue:Long) = {
    if col3 == null{
        val rowValue=col4;
        val newColumn=col1+col2+"col4";
    } else{
        val rowValue=col3;
        val newColumn=col1+col2+"col3";
     }
    return (newColumn, rowValue);
}

val udfMyFunc = udf(myFunc _ ) //needed to treat it as partially applied function

但我怎样才能以正确的方式从数据框中调用它?

当然,上面的所有代码都是垃圾,可能有更好的方法。由于我只是在处理第一段代码片段,请告诉我们......将Int值与null进行比较已经无效了。

任何帮助表示赞赏!谢谢!

2 个答案:

答案 0 :(得分:3)

有一种更简单的方法:

val df3 = df2.withColumn("newCol", concat($"col1", $"col2")) //Step 1
          .withColumn("value",when($"col3".isNotNull, $"col3").otherwise($"col4")) //Step 2
          .groupBy($"col1",$"col2",$"col3",$"col4",$"newCol") //Step 3
          .pivot("newCol") // Step 4
          .agg(max($"value")) // Step 5
          .orderBy($"newCol") // Step 6
          .drop($"newCol") // Step 7

      df3.show()

步骤如下:

  1. 添加一个新列,其中包含与col2连接的col1的内容
  2. //添加新列,"值"其中包含col3或col4
  3. 的非null内容
  4. GroupBy您想要的列
  5. 在newCol上进行透视,其中包含现在为列标题的值
  6. 按值的最大值聚合,如果groupBy是每个组的单值,则该值将是值本身;或者.agg(first($"value"))如果值恰好是字符串而不是数字类型 - max函数只能应用于数字类型
  7. 按newCol排序,因此DF按升序排列
  8. 删除此列,因为您不再需要它,或者如果您想要一列没有空值的值,请跳过此步骤
  9. 由于@ user8371915帮助我首先回答了我自己的支点问题。

    结果如下:

    +----+----+----+----+----+----+----+
    |col1|col2|col3|col4|  AX|  BZ|  CY|
    +----+----+----+----+----+----+----+
    |   A|   X|   6|null|   6|null|null|
    |   B|   Z|null|   5|null|   5|null|
    |   C|   Y|   4|   4|null|null|   4|
    +----+----+----+----+----+----+----+
    

    您可能必须使用列标题字符串连接来获得正确的结果。

答案 1 :(得分:1)

好的,我有一个解决方法来实现我想要的。我做了以下事情:

(1)我根据此建议生成了一个包含[newColumnName,rowValue]元组的新列Derive multiple columns from a single column in a Spark DataFrame

case class toTuple(newColumnName: String, rowValue: String)

def createTuple (input1:String, input2:String) : toTuple = {
    //do something fancy here
    var column:String= input1 + input2
    var value:String= input1        
    return toTuple(column, value)
}

val UdfCreateTuple = udf(createTuple _)

(2)将函数应用于DataFrame

dfNew= df.select($"*", UdfCreateTuple($"col1",$"col2").alias("tmpCol")

(3)创建具有不同值newColumnName

的数组
val dfDistinct = dfNew.select($"tmpCol.newColumnName").distinct

(4)创建一个具有不同值的数组

var a = dfDistinct.select($"newCol").rdd.map(r => r(0).asInstanceOf[String])

var arrDistinct = a.map(a => a).collect()

(5)创建键值映射

var seqMapping:Seq[(String,String)]=Seq()
for (i <- arrDistinct){
    seqMapping :+= (i,i)
}

(6)将映射应用于原始数据帧,参见Mapping a value into a specific column based on annother column

val exprsDistinct = seqMapping.map { case (key, target) => 
  when($"tmpCol.newColumnName" === key, $"tmpCol.rowValue").alias(target) }

val dfFinal = dfNew.select($"*" +: exprsDistinct: _*)

嗯,这有点麻烦但是我可以在不知道有多少列的情况下派生出一组新列,同时将值传输到新列中。