scala数据帧中的合并映射

scala数据帧中的合并映射,scala,dataframe,apache-spark,user-defined-functions,scala-collections,Scala,Dataframe,Apache Spark,User Defined Functions,Scala Collections,我有一个列为col1、col2、col3的数据帧。 col1,col2是字符串。col3是下面定义的映射[String,String] |-- col3: map (nullable = true) | |-- key: string | |-- value: string (valueContainsNull = true) 我已按col1、col2分组,并使用collect_list进行聚合,以获得一个映射数组并存储在col4中 df.groupBy($"col1",

我有一个列为col1、col2、col3的数据帧。 col1,col2是字符串。col3是下面定义的映射[String,String]

 |-- col3: map (nullable = true)
 |    |-- key: string
 |    |-- value: string (valueContainsNull = true)
我已按col1、col2分组,并使用collect_list进行聚合,以获得一个映射数组并存储在col4中

 df.groupBy($"col1", $"col2").agg(collect_list($"col3").as("col4"))

 |-- col4: array (nullable = true)
 |    |-- element: map (containsNull = true)
 |    |    |-- key: string
 |    |    |-- value: string (valueContainsNull = true)
但是,我希望将col4作为一个单独的映射,并将所有映射合并在一起。 目前我有:

[[a->a1,b->b1],[c->c1]]
预期产量

[a->a1,b->b1,c->c1]
使用udf是否理想

感谢您的帮助。 谢谢。

您可以使用和:

import org.apache.spark.sql.functions.{expr,collect\u list}
val df=Seq(
(1,地图(“k1”->“v1”,“k2”->“v3”),
(1,地图(“k3”->“v3”),
(2,地图(“k4”->“v4”),
(2,地图(“k6”->“v6”,“k5”->“v5”))
).toDF(“id”、“数据”)
val mergexpr=expr(“聚合(数据,映射(),(acc,i)->映射(acc,i)))
df.groupBy(“id”).agg(收集列表(“数据”).as(“数据”))
。选择($“id”,合并表达式为(“合并的数据”))
.show(假)
// +---+------------------------------+
//| id |合并|数据|
// +---+------------------------------+
//| 1 |[k1->v1,k2->v3,k3->v3]|
//| 2 |[k4->v4,k6->v6,k5->v5]|
// +---+------------------------------+
使用
map\u concat
我们通过
aggregate
内置函数连接数据列的所有
map
项,该函数允许我们将聚合应用于列表对

注意:Spark 2.4.5上map_concat的当前实现允许相同密钥共存。这很可能是一个bug,因为根据这位官员的说法,这不是预期的行为。请注意这一点

如果您想避免这种情况,也可以选择自定义项:

import org.apache.spark.sql.functions.{collect\u list,udf}
val mergeMapUDF=udf((数据:Seq[Map[String,String]])=>data.reduce(u++)
df.groupBy(“id”).agg(收集列表(“数据”).as(“数据”))
。选择($“id”,合并MAPUDF($“数据”)。作为(“合并的数据”))
.show(假)

无需自定义项即可实现。 让我们创建您的数据帧:

val df = Seq(Seq(Map("a" -> "a1", "b" -> "b1"), Map("c" -> "c1", "d" -> "d1"))).toDF()
df.show(false)
df.printSchema()
输出:

+----------------------------------------+
|value                                   |
+----------------------------------------+
|[[a -> a1, b -> b1], [c -> c1, d -> d1]]|
+----------------------------------------+

root
 |-- value: array (nullable = true)
 |    |-- element: map (containsNull = true)
 |    |    |-- key: string
 |    |    |-- value: string (valueContainsNull = true)
+------------------------------------+
|value                               |
+------------------------------------+
|[a -> a1, b -> b1, c -> c1, d -> d1]|
+------------------------------------+

root
 |-- value: map (nullable = true)
 |    |-- key: string
 |    |-- value: string (valueContainsNull = true)
如果数组包含2个元素,只需使用
map\u concat

df.select(map_concat('value.getItem(0), 'value.getItem(1))).show(false)
或者这个(我不知道如何动态地从0循环到'value数组类型列大小,这可能是最短的解决方案)


在这种情况下,您可以创建用户定义的聚合函数:很好的答案+1我们如何使“聚合”方法适用于不同类型的映射?基本上,我们如何初始化(int,int)或(float,float)的空映射,因为默认情况下空映射是(string,string)
  val df2 = df.map(s => {
    val list = s.getList[Map[String, String]](0)
    var map = Map[String, String]()
    for (i <- 0 to list.size() - 1) {
      map = map ++ list.get(i)
    }
    map
  })

  df2.show(false)
  df2.printSchema()
+------------------------------------+
|value                               |
+------------------------------------+
|[a -> a1, b -> b1, c -> c1, d -> d1]|
+------------------------------------+

root
 |-- value: map (nullable = true)
 |    |-- key: string
 |    |-- value: string (valueContainsNull = true)