我整天都在尝试使用Dataframe,但到目前为止还没有运气。已经使用RDD做到了这一点,但是它并不是真正可读的,因此,在代码可读性方面,这种方法会更好。
使用此初始和结果DF,包括开始DF和执行.groupBy()
之后我想要获得的东西。
case class SampleRow(name:String, surname:String, age:Int, city:String)
case class ResultRow(name: String, surnamesAndAges: Map[String, (Int, String)])
val df = List(
SampleRow("Rick", "Fake", 17, "NY"),
SampleRow("Rick", "Jordan", 18, "NY"),
SampleRow("Sandy", "Sample", 19, "NY")
).toDF()
val resultDf = List(
ResultRow("Rick", Map("Fake" -> (17, "NY"), "Jordan" -> (18, "NY"))),
ResultRow("Sandy", Map("Sample" -> (19, "NY")))
).toDF()
到目前为止,我一直在尝试执行以下.groupBy
...
val resultDf = df
.groupBy(
Name
)
.agg(
functions.map(
selectColumn(Surname),
functions.array(
selectColumn(Age),
selectColumn(City)
)
)
)
但是,控制台中会提示以下内容。
Exception in thread "main" org.apache.spark.sql.AnalysisException: expression '`surname`' is neither present in the group by, nor is it an aggregate function. Add to group by or wrap in first() (or first_value) if you don't care which value you get.;;
但是,这样做将导致每个姓氏只有一个条目,而我想将它们累积在一个Map中,就像您在resultDf
中看到的那样。是否有使用DF实现此目的的简单方法?
答案 0 :(得分:1)
如果您不关心将数据帧类型转换为数据集(在这种情况下,ResultRow
可以这样做
val grouped =df.withColumn("surnameAndAge",struct($"surname",$"age"))
.groupBy($"name")
.agg(collect_list("surnameAndAge").alias("surnamesAndAges"))
然后您可以创建一个类似于以下内容的用户定义函数
import org.apache.spark.sql._
val arrayToMap = udf[Map[String, String], Seq[Row]] {
array => array.map {
case Row(key: String, value: String) => (key, value) }.toMap
}
现在您可以使用.withColumn
并将其称为udf
val finalData = grouped.withColumn("surnamesAndAges",arrayToMap($"surnamesAndAges"))
数据框看起来像这样
finalData: org.apache.spark.sql.DataFrame = [name: string, surnamesAndAges: map<string,string>]
答案 1 :(得分:1)
您可以使用单个UDF来实现,以将数据转换为地图:
val toMap = udf((keys: Seq[String], values1: Seq[String], values2: Seq[String]) => {
keys.zip(values1.zip(values2)).toMap
})
val myResultDF = df.groupBy("name").agg(collect_list("surname") as "surname", collect_list("age") as "age", collect_list("city") as "city").withColumn("surnamesAndAges", toMap($"surname", $"age", $"city")).drop("age", "city", "surname").show(false)
+-----+--------------------------------------+
|name |surnamesAndAges |
+-----+--------------------------------------+
|Sandy|[Sample -> [19, NY]] |
|Rick |[Fake -> [17, NY], Jordan -> [18, NY]]|
+-----+--------------------------------------+
答案 2 :(得分:0)
从 Spark 2.4 开始,您不需要使用 Spark 用户定义的函数:
import org.apache.spark.sql.functions.{col, collect_set, map_from_entries, struct}
df.withColumn("mapEntry", struct(col("surname"), struct(col("age"), col("city"))))
.groupBy("name")
.agg(map_from_entries(collect_set("mapEntry")).as("surnameAndAges"))
您首先从所需的列中添加一个包含 Map 条目的列。 Map 条目只是一个包含两列的 struct
:第一列是键,第二列是值。您可以将另一个 struct
作为值。因此,在这里您的 Map 条目将使用列 surname
作为键,并使用 struct
列 age
和 city
作为值:
struct(col("surname"), struct(col("age"), col("city")))
然后,您使用函数 name
收集按 groupBy 键(即列 collect_set
)分组的所有 Map 条目,并使用函数 map_from_entries
将此 Map 条目列表转换为 Map }