如何将df单元中的数组转换为单独的列名?

时间:2019-09-11 07:30:27

标签: scala dataframe apache-spark

我无法在DF以下转换:

 | A | B |     C   |     
 | 1 | 2 | t1,t2,t3|     
 | 1 | 3 | t4,t5   |     
 | 2 | 2 | t1,t5   |

到DF以下:

| A | B | t1 | t2 | t3 | t4 | t5 | 
| 1 | 2 | 1  | 1  | 1  | 0  | 0  | 
| 1 | 3 | 0  | 0  | 0  | 1  | 1  | 
| 2 | 2 | 1  | 0  | 0  | 0  | 1  |

在scala火花中。

C中的值以逗号分隔,我希望它使它们成为单独的列。完成此操作后,如果t1t2等存在或不存在,我想在该特定行中标记0和1。

请在这里帮助。会非常感激的。

3 个答案:

答案 0 :(得分:3)

这可以通过使用pivot来完成。但是,首先在“ {”和“ explodeC列上拆分数据。

df.withColumn("C", explode(split($"C", ",")))
  .groupBy("A", "B")
  .pivot("C")
  .count()
  .na.fill(0)

结果:

+---+---+---+---+---+---+---+
|  A|  B| t1| t2| t3| t4| t5|
+---+---+---+---+---+---+---+
|  2|  2|  1|  0|  0|  0|  1|
|  1|  2|  1|  1|  1|  0|  0|
|  1|  3|  0|  0|  0|  1|  1|
+---+---+---+---+---+---+---+

以上解决方案假定所有行在AB中具有唯一的值组合(由于使用groupBy)。如果不是这种情况,则必须采取其他步骤:

添加一个具有唯一ID的新列,该列可以与A中的BgroupBy一起使用,例如:

df.withColumn("D", monotonically_increasing_id)

可以在pivot之后删除此列。

答案 1 :(得分:1)

首先,您需要从列C中获取所有值,然后创建该列C的集合作为值,该值将用作列名,请检查以下过程

  1. 创建数据框:

    val df = spark.sparkContext.parallelize(Seq((1, 2, "t1,t2,t3"), (1, 3, "t4,t5"), (2,2,"t1,t5"))).toDF("A","B","C")
    +---+---+--------+
    |  A|  B|       C|
    +---+---+--------+
    |  1|  2|t1,t2,t3|
    |  1|  3|   t4,t5|
    |  2|  2|   t1,t5|
    +---+---+--------+
    
  2. 读取列值,该值需要数据框的标题:

    val cols = df.select($"C")
    val colnameAsHeader = cols.map(x=>x.getString(0).split(",")).collect.flatten.toSet
    
  3. 为检查条件创建UDF:

    def checkCriteria(inputString: String) = {udf((inputColumn: String) => { if(inputColumn.split(",").contains(inputString)){1}else{0} })}
    
  4. 使用foldLeft设置标题:

    colnameAsHeader.foldLeft(df)((df, name) => df.withColumn(name , lit(checkCriteria(name)($"C")))).drop("C").show
    
    +---+---+---+---+---+---+---+
    |  A|  B| t4| t5| t3| t2| t1|
    +---+---+---+---+---+---+---+
    |  1|  2|  0|  0|  1|  1|  1|
    |  1|  3|  1|  1|  0|  0|  0|
    |  2|  2|  0|  1|  0|  0|  1|
    +---+---+---+---+---+---+---+
    

答案 2 :(得分:0)

另一种可能是利用Spark 2.4的spark sql函数代替udf,如下所示:

收集值列表中的所有值,然后使用array_contains方法测试列表中每个值是否存在于数组列中

val df1  = Seq(
  ("1", Array("t1", "t2")),
  ("2", Array("t1", "t3", "t5"))
).toDF("id", "arr")
import org.apache.spark.sql.functions.{col, lit, array_contains, when}
val values = df1.select("arr").collect().map(x => x.getList[Array[String]](0).toArray)
  .flatMap(x => x)
  .distinct
  .map(_.toString)
val df2 = df1
  .select(df1.columns.map(col(_)) ++
    values.map(x => when(array_contains(col("arr"), x), 1).otherwise(0).alias(x)):_*)
df2.show()

+---+------------+---+---+---+---+
| id|         arr| t1| t2| t3| t5|
+---+------------+---+---+---+---+
|  1|    [t1, t2]|  1|  1|  0|  0|
|  2|[t1, t3, t5]|  1|  0|  1|  1|
+---+------------+---+---+---+---+