在DataFrame

时间:2018-05-29 21:09:34

标签: scala apache-spark apache-spark-sql

我正在开发一个项目,我正在处理一些带有复杂架构/数据结构的嵌套JSON日期。基本上我想要做的是在数据帧中过滤掉其中一列,这样我就可以选择数组中的最后一个元素。我完全不知道如何做到这一点。我希望这是有道理的。

下面是我想要完成的一个例子:

val singersDF = Seq(
  ("beatles", "help,hey,jude"),
  ("romeo", "eres,mia"),
  ("elvis", "this,is,an,example")
).toDF("name", "hit_songs")

val actualDF = singersDF.withColumn(
  "hit_songs",
  split(col("hit_songs"), "\\,")
)

actualDF.show(false)
actualDF.printSchema() 

+-------+-----------------------+
|name   |hit_songs              |
+-------+-----------------------+
|beatles|[help, hey, jude]      |
|romeo  |[eres, mia]            |
|elvis  |[this, is, an, example]|
+-------+-----------------------+
root
 |-- name: string (nullable = true)
 |-- hit_songs: array (nullable = true)
 |    |-- element: string (containsNull = true)

输出的最终目标如下,选择hit_songs数组中的最后一个“字符串”。

我并不担心之后架构会是什么样子。

+-------+---------+
|name   |hit_songs|
+-------+---------+
|beatles|jude     |
|romeo  |mia      |
|elvis  |example  |
+-------+---------+

5 个答案:

答案 0 :(得分:6)

您可以使用size函数计算数组中所需项目的索引,然后将其作为Column.apply的参数(显式或隐式)传递:

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

actualDF.withColumn("hit_songs", $"hit_songs".apply(size($"hit_songs").minus(1)))

或者:

actualDF.withColumn("hit_songs", $"hit_songs"(size($"hit_songs").minus(1)))

答案 1 :(得分:4)

这是一种方法:

val actualDF = Seq(
  ("beatles", Seq("help", "hey", "jude")),
  ("romeo", Seq("eres", "mia")),
  ("elvis", Seq("this", "is", "an", "example"))
).toDF("name", "hit_songs")

import org.apache.spark.sql.functions._

actualDF.withColumn("total_songs", size($"hit_songs")).
  select($"name", $"hit_songs"($"total_songs" - 1).as("last_song"))
// +-------+---------+
// |   name|last_song|
// +-------+---------+
// |beatles|     jude|
// |  romeo|      mia|
// |  elvis|  example|
// +-------+---------+

答案 2 :(得分:2)

spark 2.4 + 起,您就可以使用支持否定索引的element_at。如您在本文档中所引用的:

element_at(array,index)-返回给定(从1开始)索引处的数组元素。如果index <0,则从最后到第一个访问元素。如果索引超过数组的长度,则返回NULL。

有了这个,下面是获取最后一个元素的方法:

import org.apache.spark.sql.functions.element_at
actualDF.withColumn("hit_songs", element_at($"hit_songs", -1))

可复制的示例:

首先让我们准备一个带有数组列的示例数据框:

val columns = Seq("col1")
val data = Seq((Array(1,2,3)))
val rdd = spark.sparkContext.parallelize(data)
val df = rdd.toDF(columns:_*)

如下所示:

scala> df.show()
+---------+
|     col1|
+---------+
|[1, 2, 3]|
+---------+

然后,应用element_at来获取最后一个元素,如下所示:

scala> df.withColumn("last_value", element_at($"col1", -1)).show()
+---------+----------+
|     col1|last_value|
+---------+----------+
|[1, 2, 3]|         3|
+---------+----------+

答案 3 :(得分:1)

您正在寻找SparkSQL函数slice。或这个PySpark Source

您在Scala slice($"hit_songs", -1, 1)(0)中的实现,其中-1是起始位置(最后一个索引),1是长度,(0)从得到的数组中提取第一个字符串恰好1个元素。

完整示例:

import org.apache.spark.sql.functions._

val singersDF = Seq(
  ("beatles", "help,hey,jude"),
  ("romeo", "eres,mia"),
  ("elvis", "this,is,an,example")
).toDF("name", "hit_songs")

val actualDF = singersDF.withColumn(
  "hit_songs",
  split(col("hit_songs"), "\\,")
)

val newDF = actualDF.withColumn("last_song", slice($"hit_songs", -1, 1)(0))

display(newDF)

输出:

+---------+------------------------------+-------------+
|  name   |          hit_songs           |  last_song  |
+---------+------------------------------+-------------+
| beatles | ["help","hey","jude"]        | jude        |
| romeo   | ["eres","mia"]               | mia         |
| elvis   | ["this","is","an","example"] | example     |
+---------+------------------------------+-------------+

答案 4 :(得分:0)

您还可以使用如下的UDF:

val lastElementUDF = udf((array: Seq[String]) => array.lastOption)

actualDF.withColumn("hit_songs", lastElementUDF($"hit_songs"))

array.lastOption将返回NoneSome,如果数组为空,则array.last将引发异常。