我最初有一个DataFrame,如下所示:
Key Emails PassportNum Age
0001 [Alan@gmail,Alan@hotmail] passport1 23
0002 [Ben@gmail,Ben@hotmail] passport2 28
我需要在每个电子邮件上应用一个函数,例如最后添加“_2”的虚拟函数,操作无关紧要。所以我会像这样爆炸这个专栏:
val dfExplode = df.withColumn("Email",explode($"Emails")).drop("Emails")
现在我将有一个这样的数据框:
Key Email PassportNum Age
0001 Alan@gmail passport1 23
0001 Alan@hotmail passport1 23
0002 Ben@gmail passport2 28
0002 Ben@hotmail passport2 28
我对护照进行任何更改,然后我想要的是:
Key Emails PassportNum Age
0001 [Alan_2@gmail,Alan_2@hotmail] passport1 23
0002 [Ben_2@gmail,Ben_2@hotmail] passport2 28
我正在考虑的选项是:
dfOriginal = dfExploded.groupBy("Key","PassportNum","Age").agg(collect_set("Email").alias("Emails"))
在这种情况下,它可能不是一个糟糕的方法。但在我的实际案例中,我在单个列上执行爆炸,我有另外20个列,如PassportNum,Age ...这些将被复制。
这意味着我需要在groupBy中添加大约20列,当我真的可以通过一个列执行该组时,例如Key是唯一的。
我正在考虑将这些列添加到agg中,如下所示:
dfOriginal = dfExploded.groupBy("Key").agg(collect_set("Email").alias("Emails"),collect_set("PassportNum"),collect_set("Age"))
但我不希望它们在单个元素数组中。
是否可以在没有任何collect_*
的情况下制作聚合?是否有更简单的方法来撤消explode
?
答案 0 :(得分:3)
假设您希望保留在DataFrame世界中,那么定义一个操纵输入数组的UDF可能是值得的。将Seq作为输入并返回修改后的内容的东西。 e.g。
def myUdf = udf[Seq[String], Seq[String]] {
inputSeq => inputSeq.map(elem => elem + "_2")
}
df.withColumn("Emails", myUdf($"Emails"))
更好的是,您可以将确切的逻辑作为参数传递:
def myUdf(myFunc: String => String) = udf[Seq[String], Seq[String]] {
inputSeq => inputSeq.map(myFunc)
}
df.withColumn("Emails", myUdf((email: String) => email + "_XYZ")($"Emails"))
答案 1 :(得分:1)
除了所有常见字段上的groupby之外的另一个选项是在单独的临时数据框上进行爆炸,然后从原始数据集中删除展开的列并按
重新加入分组然而,编写一个直接操作数组而不会爆炸并收集的UDF可能更简单
def handleEmail(emails: mutable.WrappedArray[String]) = {
emails.map(dosomething)
}
context.udf.register("handleEmailsm"m (em:mutabe.WrappedArray[String]) => handleEmail(em))
答案 2 :(得分:1)
这意味着我需要在groupBy中添加大约20列,当我真的可以通过一个列执行该组时,例如Key是唯一的。
您可以通过一个简单的技巧 跳过编写每个列名,如下所示使用所有列名(或选中)除了爆炸列名称
之外,import org.apache.spark.sql.functions._
val dfExploded = df.withColumn("Emails", explode($"Emails"))
val groupColumns = dfExploded.columns.filterNot(_.equalsIgnoreCase("Emails"))
val dfOriginal = dfExploded.groupBy(groupColumns.map(col): _*).agg(collect_set("Emails").alias("Emails"))
创建结构列
您可以使用struct inbuilt function 创建单个列,使用groupBy 中的单个列
val groupColumns = df.columns.filterNot(_.equalsIgnoreCase("Emails"))
import org.apache.spark.sql.functions._
val dfExploded = df.select(struct(groupColumns.map(col): _*).as("groupedKey"), col("Emails"))
.withColumn("Emails", explode($"Emails"))
会给你
+-------------------+------------+
|groupedKey |Emails |
+-------------------+------------+
|[0001,passport1,23]|Alan@gmail |
|[0001,passport1,23]|Alan@hotmail|
|[0002,passport2,28]|Ben@gmail |
|[0002,passport2,28]|Ben@hotmail |
+-------------------+------------+
然后在groupBy中使用groupedKey并再次在select 中分隔它们
val dfOriginal = dfExploded.groupBy("groupedKey").agg(collect_set("Emails").alias("Emails"))
.select($"groupedKey.*", $"Emails")