【问题标题】:Renaming and Optimisation of Multiple Pivoted columns in SparkSpark中多个透视列的重命名和优化
【发布时间】:2018-08-23 09:00:33
【问题描述】:

我的输入数据中有一组列,我基于多个列在这些列上进行透视。

旋转完成后,我遇到了列标题问题。

输入数据

我的方法生成的输出 -

预期的输出标题:

我需要输出的标题看起来像 -

到目前为止完成的步骤以实现我得到的输出 -

// *Load the data*

scala> val input_data =spark.read.option("header","true").option("inferschema","true").option("delimiter","\t").csv("s3://mybucket/data.tsv")

// *Filter the data where residentFlag column = T*

scala> val filtered_data = input_data.select("numericID","age","salary","gender","residentFlag").filter($"residentFlag".contains("T"))

// *Now we will the pivot the filtered data by each column*

scala> val pivotByAge = filtered_data.groupBy("age","numericID").pivot("age").agg(expr("coalesce(first(numericID),'-')")).drop("age")

// *Pivot the data by the second column named "salary"*

scala> val pivotBySalary = filtered_data.groupBy("salary","numericID").pivot("salary").agg(expr("coalesce(first(numericID),'-')")).drop("salary")

// *Join the above two dataframes based on the numericID*

scala> val intermediateDf = pivotByAge.join(pivotBySalary,"numericID")

// *Now pivot the filtered data on Step 2 on the third column named Gender*

scala> val pivotByGender = filtered_data.groupBy("gender","numericID").pivot("gender").agg(expr("coalesce(first(numericID),'-')")).drop("gender")

// *Join the above dataframe with the intermediateDf*

scala> val outputDF= pivotByGender.join(intermediateDf ,"numericID")

如何重命名旋转后生成的列?

对于基于多列(近 300 列)透视数据集,我可以采取其他方法吗?

任何优化/提高性能的建议?

【问题讨论】:

  • 当您使用 scala 时,是否有理由将其标记为 pyspark?
  • 这是因为可能有人在使用 pySpark 时遇到了类似的问题。这是火花问题,而不是特定于语言的问题。此外,关于优化问题还有第二部分,因此该问题在所有 Spark 执行环境中都变得通用。
  • 好的,你试过df.withColumnRenamed吗?
  • 如您所见,目前最终输出大约有 10 列,因此 withColumnRenamed 可以在这里工作。但是,它不起作用有两个原因 1. 我不想通过查看生成的标题来手动重命名列的额外步骤 2. 实际上,输入文件将有 300 列,因此会发生旋转,所以它不会使用 withColumnRenamed 是可行的,因为我不会提前知道标题。寻找一种方法,我可以使用输入列名称进行透视,然后以某种方式将其附加到从该列生成的标题中

标签: scala hadoop apache-spark pyspark


【解决方案1】:

可以考虑使用foldLeft遍历to-pivot列的列表,依次创建pivot dataframe,将生成的pivot列重命名,然后进行累积连接:

val data = Seq(
  (1, 30, 50000, "M"),
  (1, 25, 70000, "F"),
  (1, 40, 70000, "M"),
  (1, 30, 80000, "M"),
  (2, 30, 80000, "M"),
  (2, 40, 50000, "F"),
  (2, 25, 70000, "F")
).toDF("numericID", "age", "salary", "gender")

// Create list pivotCols which consists columns to pivot
val id = data.columns.head
val pivotCols = data.columns.filter(_ != "numericID")

// Create the first pivot dataframe from the first column in list pivotCols and
// rename each of the generated pivot columns
val c1 = pivotCols.head
val df1 = data.groupBy(c1, id).pivot(c1).agg(expr(s"coalesce(first($id),'-')")).drop(c1)
val df1Renamed = df1.columns.tail.foldLeft( df1 )( (acc, x) =>
      acc.withColumnRenamed(x, c1 + "_" + x)
    )

// Using the first pivot dataframe as the initial dataframe, process each of the
// remaining columns in list pivotCols similar to how the first column is processed,
// and cumulatively join each of them with the previously joined dataframe
pivotCols.tail.foldLeft( df1Renamed )(
  (accDF, c) => {
    val df = data.groupBy(c, id).pivot(c).agg(expr(s"coalesce(first($id),'-')")).drop(c)
    val dfRenamed = df.columns.tail.foldLeft( df )( (acc, x) =>
      acc.withColumnRenamed(x, c + "_" + x)
    )
    dfRenamed.join(accDF, Seq(id))
  }
)

// +---------+--------+--------+------------+------------+------------+------+------+------+
// |numericID|gender_F|gender_M|salary_50000|salary_70000|salary_80000|age_25|age_30|age_40|
// +---------+--------+--------+------------+------------+------------+------+------+------+
// |2        |2       |-       |2           |-           |-           |-     |2     |-     |
// |2        |2       |-       |2           |-           |-           |2     |-     |-     |
// |2        |2       |-       |2           |-           |-           |-     |-     |2     |
// |2        |2       |-       |-           |2           |-           |-     |2     |-     |
// |2        |2       |-       |-           |2           |-           |2     |-     |-     |
// |2        |2       |-       |-           |2           |-           |-     |-     |2     |
// |2        |2       |-       |-           |-           |2           |-     |2     |-     |
// |2        |2       |-       |-           |-           |2           |2     |-     |-     |
// |2        |2       |-       |-           |-           |2           |-     |-     |2     |
// |2        |-       |2       |2           |-           |-           |-     |2     |-     |
// |2        |-       |2       |2           |-           |-           |2     |-     |-     |
// |2        |-       |2       |2           |-           |-           |-     |-     |2     |
// |2        |-       |2       |-           |2           |-           |-     |2     |-     |
// |2        |-       |2       |-           |2           |-           |2     |-     |-     |
// |2        |-       |2       |-           |2           |-           |-     |-     |2     |
// |2        |-       |2       |-           |-           |2           |-     |2     |-     |
// |2        |-       |2       |-           |-           |2           |2     |-     |-     |
// |2        |-       |2       |-           |-           |2           |-     |-     |2     |
// |1        |-       |1       |-           |1           |-           |1     |-     |-     |
// |1        |-       |1       |-           |1           |-           |-     |-     |1     |
// ...

【讨论】:

  • 你能帮忙理解你到底在做什么吗 - pivotCols.tail.foldLeft( df1Renamed )( (accDF, c) => { val df = data.groupBy(c, id).pivot (c).agg(expr(s"coalesce(first($id),'-')")).drop(c) val dfRenamed = df.columns.tail.foldLeft( df )( (acc, x) = > acc.withColumnRenamed(x, c + "_" + x) ) dfRenamed.join(accDF, Seq(id)) } ) 这适用于任意数量的列吗? (在实际场景中,我有大约 300 列)如果在这个场景中我们还有 2 列(例如,Country 和 City)会发生什么?
  • 请参阅更新答案中的 cmets。只要 groupBy/pivot/agg 结构保持不变,相同的代码将处理列表pivotCols 中组装的任意数量的列。请记住,尽管枢轴数据帧的累积连接会呈指数级增长。
【解决方案2】:

你可以这样做并使用正则表达式来简化

var outputDF= pivotByGender.join(intermediateDf ,"numericID")

val cols: Array[String] = outputDF.columns

cols
  .foreach{
    cl => cl match {
        case "F" => outputDF = outputDF.withColumnRenamed(cl,s"gender_${cl}")
        case "M" => outputDF = outputDF.withColumnRenamed(cl,s"gender_${cl}")
        case cl.matches("""\\d{2}""") => outputDF = outputDF.withColumnRenamed(cl,s"age_${cl}")

      }
  }

【讨论】:

  • 在这种方法中,我需要为每个透视列的所有可能结果编写所有案例。
猜你喜欢
  • 2016-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多