【问题标题】:How to query a dictionary format column in Pyspark dataframe如何在 Pyspark 数据框中查询字典格式列
【发布时间】:2020-01-07 15:13:18
【问题描述】:

有如下数据框:

  >>> df.printSchema()
  root
   |-- I: string (nullable = true)
   |-- F: string (nullable = true)
   |-- D: string (nullable = true)
   |-- T: string (nullable = true)
   |-- S: string (nullable = true)
   |-- P: string (nullable = true)

F 列是字典格式:

   {"P1":"1:0.01","P2":"3:0.03,4:0.04","P3":"3:0.03,4:0.04",...}

我需要阅读下面的 F 列并创建两个新列 P 和 N

   P1 => "1:0.01"
   P2 => "3:0.03,4:0.04"
   and so on

 +--------+--------+-----------------+-----+------+--------+----+
 | I      |  P     | N               |  D  | T    | S      | P  |
 +--------+--------+---------------- +------------+--------+----+
 | i1     |  p1    | 1:0.01          |  d1 | t1   | s1     | p1 |
 |--------|--------|-----------------|-----|------|--------|----|
 | i1     |  p2    | 3:0.03,4:0.04   |  d1 | t1   | s1     | p1 |
 |--------|--------|-----------------|-----|------|--------|----|
 | i1     |  p3    | 3:0.03,4:0.04   |  d1 | t1   | s1     | p1 |
 |--------|--------|-----------------|-----|------|--------|----|
 | i2     |  ...   | ....            |  d2 | t2   | s2     | p2 |
 +--------+--------+-----------------+-----+------+--------+----+

Pyspark 有什么建议吗?

【问题讨论】:

  • 感谢您的评论。在提供的链接中,字典只有两个键。这里的字典有很多键。
  • 对于这个答案,在链接中提供了架构,这是事先已知的。但就我而言,无法提供架构。这里它只有两个键,key1 和 key2。我的可能有 key1, key2, ...., key128 而不是固定数字。您如何提供灵活的架构?
  • 另外,我不想为每个键添加新列,而是添加新行。它更像是一次“爆炸”。

标签: json dataframe dictionary pyspark


【解决方案1】:

这就是我最后解决这个问题的方法:

 #This method replaces "," with ";" to 
 #distinguish between other camas in the string to split it
 def _comma_replacement(val):
    if (val):
        val = val.replace('","', '";"').replace('{','').replace('}', '')
    return val

replacing = UserDefinedFunction(lambda x: _comma_replacement(x))
new_df = df.withColumn("F", replacing(col("F")))
new_df = new_df.withColumn("F",split(col("F"),";").cast(ArrayType(StringType())))
exploded_df = new_df.withColumn("F", explode("F"))
df_sep = exploded_df.withColumn("F",split(col("F"),'":"').cast(ArrayType(StringType())))
dff = df_sep.withColumn("P", df_sep["F"].getItem(0))
dff_new = dff.withColumn("N", dff["F"].getItem(1))
dff_new = dff_new.drop('F')

使用另一个 UDF,我删除了字符串操作期间剩余的额外字符。

上面的解决方案也使用了同样的方法。关键思想是区分不同组件之间和内部的逗号。为此,我建议在 UDF 中调用 _comma_replacement(val) 方法。上面的解决方案也使用了相同的方法,但使用了可以更优化的 regxp_replace。

【讨论】:

    【解决方案2】:

    试试这个:

    1. 你拥有的DataFrame
    from pyspark.sql import functions as F
    
    df = spark.createDataFrame([('id01', '{"P1":"1:0.01","P2":"3:0.03,4:0.04","P3":"3:0.03,4:0.04"}')], ['I', 'F'])
    df.printSchema()
    df.show(truncate=False)
    

    您可以在帖子中看到架构和数据相同。

    root
     |-- I: string (nullable = true)
     |-- F: string (nullable = true)
    
    +----+---------------------------------------------------------+
    |I   |F                                                        |
    +----+---------------------------------------------------------+
    |id01|{"P1":"1:0.01","P2":"3:0.03,4:0.04","P3":"3:0.03,4:0.04"}|
    +----+---------------------------------------------------------+
    
    
    1. 处理字符串以区分子字典
    # remove '{' and '}'
    df = df.withColumn('array', F.regexp_replace('F', r'\{', ''))
    df = df.withColumn('array', F.regexp_replace('array', r'\}', ''))
    
    # replace the comma with '#' between each sub-dict so we can split on them
    df = df.withColumn('array', F.regexp_replace('array', '","', '"#"' ))
    df = df.withColumn('array', F.split('array', '#'))
    df.show(truncate=False)
    

    这是中间结果

    +----+---------------------------------------------------------+-----------------------------------------------------------+
    |I   |F                                                        |array                                                      |
    +----+---------------------------------------------------------+-----------------------------------------------------------+
    |id01|{"P1":"1:0.01","P2":"3:0.03,4:0.04","P3":"3:0.03,4:0.04"}|["P1":"1:0.01", "P2":"3:0.03,4:0.04", "P3":"3:0.03,4:0.04"]|
    +----+---------------------------------------------------------+-----------------------------------------------------------+
    
    
    1. 现在为每个子字典生成一行
    # generate one row for each element int he array
    df = df.withColumn('exploded', F.explode(df['array']))
    
    # Need to distinguish ':' in the dict and in the value
    df = df.withColumn('exploded', F.regexp_replace('exploded', '":"', '"#"' ))
    df = df.withColumn('exploded', F.split('exploded', '#'))
    
    # extract the name and value
    df = df.withColumn('P', F.col('exploded')[0])
    df = df.withColumn('N', F.col('exploded')[1])
    df.select('I', 'exploded', 'P', 'N').show(truncate=False)
    

    最终输出:

    +----+-----------------------+----+---------------+
    |I   |exploded               |P   |N              |
    +----+-----------------------+----+---------------+
    |id01|["P1", "1:0.01"]       |"P1"|"1:0.01"       |
    |id01|["P2", "3:0.03,4:0.04"]|"P2"|"3:0.03,4:0.04"|
    |id01|["P3", "3:0.03,4:0.04"]|"P3"|"3:0.03,4:0.04"|
    +----+-----------------------+----+---------------+
    

    【讨论】:

    • 感谢您的回答。这与我已经提出的答案非常相似。
    • 它不使用udf,一般来说应该更快。
    • 如果 F.col('exploded') 为 NULL 并且 F.col('exploded')[0] 和 F.col('exploded')[1] 无效怎么办?跨度>
    • 这种特殊情况可以用regexp_replace等处理。udf也需要做类似的事情。
    • 如果数组中有 NULL,explode 会将 NULL 与“P3”放在一起。所以:["P1":"1:0.01", "P2":"3:0.03,4:0.04", "P3":"3:0.03,4:0.04", null] 变为 "P3"|"3:0.03,4:0.04", null。你必须在那里处理额外的空值。
    猜你喜欢
    • 1970-01-01
    • 2022-08-21
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 2021-06-08
    • 1970-01-01
    • 2021-05-21
    • 2022-01-17
    相关资源
    最近更新 更多