【问题标题】:PySpark: Create Dataframe Dynamically from Nested Arrays using Array Values as Column HeadersPySpark:使用数组值作为列标题从嵌套数组动态创建数据框
【发布时间】:2021-11-18 15:35:33
【问题描述】:

我遇到了一个问题,希望有人能提供帮助。

假设我有如下记录的传入数据流:

{ “标题”:[“col_a”,“col_b”,“col_c”,“col_d”],“数据”:[[“0”,“1”,“2”,“3”],[ "0.2","0.1","3","4"],["5","4","3","2"]]}

{ “标题”:[“col_a”,“col_b”,“col_c”,“col_d”],“数据”:[[“0.1”,“1.2”,“2.5”,“3”],[ "0","0","1","0"]]}

...

现在进一步假设数据被清理,使得:

  1. “headers”字段始终包含相同的数组
  2. “数据”数组中的数组始终与标头数组长度相同

在 PySpark 中是否可以将上述记录转换为如下所示的数据框?

col_a col_b col_c col_d
0 1 2 3
0.2 0.1 3 4
5 4 3 2
0.1 1.2 2.5 3
0 0 1 0

非常感谢任何 cmets 和/或工作代码。

【问题讨论】:

    标签: python arrays dataframe pyspark


    【解决方案1】:

    我找到了两种方法

    1. 使用地图
    2. 使用数据透视操作 - 为此,您需要能够将所有数据收集到分组操作的单个分区中。所以它会在大型数据集上失败

    地图解决方案

      df = spark.read.json("map.json")
      
      # collect the headers as a list
      headers = df.select(F.explode("headers").alias("headers")).distinct().collect()
      headers = [r.headers for r in headers]
      
      # explode data arrays so that it has the same dimensions as the header array
      df = df.select(F.explode("data").alias("data"), "headers")
      # zip data and headers together to form a map
      df = df.select(F.arrays_zip("headers", "data").alias("map"))
      df = df.select(F.map_from_entries("map").alias("map"))
      
      #select out your headers from the map to form columns
      df = df.select(*[df.map.getItem(col).alias(col) for col in headers])
      df.show(truncate=False)
      >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
      +-----+-----+-----+-----+
      |col_a|col_b|col_c|col_d|
      +-----+-----+-----+-----+
      |0    |1    |2    |3    |
      |0.2  |0.1  |3    |4    |
      |5    |4    |3    |2    |
      +-----+-----+-----+-----+
    

    枢轴解决方案

      df = spark.read.json("map.json")
    
      # explode data arrays so that it has the same dimensions as the header array
      df = df.select(F.explode("data").alias("data"), "headers")
      # zip data and headers together and explode it into rows
      df = df.select(F.arrays_zip("headers", "data").alias("zipped"))
      df = df.select(F.explode("zipped").alias("exploded_struct"))
      df = df.selectExpr("exploded_struct.*")
      
      # Add a single index so that we can group by it and then pivot our headers into columns. 
      # **NB This groups all of our data into a single partition
      df = df.withColumn("idx", F.lit(1))
      df = df.groupBy("idx").pivot("headers").agg(F.collect_list("data").alias("data")).drop("idx")
      
      # each column now contains it's array of data. in order to explode it we need to zip all of them 
      # together and explode in a single operation
      df = df.withColumn("zipped",F.arrays_zip(*df.columns))
      df = df.select(F.explode("zipped").alias("exploded_struct"))
      
      # Finally we select out our headers from the exploded_struct
      df = df.selectExpr("exploded_struct.*")
      df.show(truncate=False)
    
     >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
       +-----+-----+-----+-----+
       |col_a|col_b|col_c|col_d|
       +-----+-----+-----+-----+
       |0    |1    |2    |3    |
       |0.2  |0.1  |3    |4    |
       |5    |4    |3    |2    |
       +-----+-----+-----+-----+
    

    【讨论】:

    • 非常感谢您及时详细的解决方案。他们非常适合我!
    • @dfzr123 很高兴为您提供帮助。如果您觉得有帮助,请点赞我的回答:)
    • 我愿意,但我是 Stack Overflow 的新手,没有足够的声誉来投票 >_
    • 啊抱歉,我以为你只需要 10 个代表就可以投票,但现在我看到它是 15 个。
    猜你喜欢
    • 2020-01-21
    • 1970-01-01
    • 2011-01-29
    • 1970-01-01
    • 1970-01-01
    • 2018-06-21
    • 2019-04-23
    • 2016-09-04
    • 2014-02-02
    相关资源
    最近更新 更多