【问题标题】:PySpark to_json loses column name of struct inside arrayPySpark to_json 丢失数组内结构的列名
【发布时间】:2020-12-28 04:37:21
【问题描述】:

我正在尝试从嵌套的 pyspark DataFrame 生成 json 字符串,但正在丢失键值。 我的初始数据集类似于以下内容:

data = [
    {"foo": [1, 2], "bar": [4, 5], "buzz": [7, 8]},
    {"foo": [1], "bar": [4], "buzz": [7]},
    {"foo": [1, 2, 3], "bar": [4, 5, 6], "buzz": [7, 8, 9]},
]
df = spark.read.json(sc.parallelize(data))
df.show()
## +---------+---------+---------+
## |      bar|     buzz|      foo|
## +---------+---------+---------+
## |   [4, 5]|   [7, 8]|   [1, 2]|
## |      [4]|      [7]|      [1]|
## |[4, 5, 6]|[7, 8, 9]|[1, 2, 3]|
## +---------+---------+---------+

然后我使用 arrays_zip 将每一列压缩在一起:

df_zipped = (
    df
    .withColumn(
        "zipped",
        F.arrays_zip(
            F.col("foo"),
            F.col("bar"),
            F.col("buzz"),
        )
    )
)
df_zipped.printSchema()
root
 |-- bar: array (nullable = true)
 |    |-- element: long (containsNull = true)
 |-- buzz: array (nullable = true)
 |    |-- element: long (containsNull = true)
 |-- foo: array (nullable = true)
 |    |-- element: long (containsNull = true)
 |-- zipped: array (nullable = true)
 |    |-- element: struct (containsNull = false)
 |    |    |-- foo: long (nullable = true)
 |    |    |-- bar: long (nullable = true)
 |    |    |-- buzz: long (nullable = true)

问题是在压缩数组上使用 to_json。它会丢失 foo、bar 和 Buzz 键值,而是将键保存为元素索引

(
    df_zipped
    .withColumn("zipped", F.to_json("zipped"))
    .select("zipped")
    .show(truncate=False)
)
+-------------------------------------------------------------+
|zipped                                                       |
+-------------------------------------------------------------+
|[{"0":1,"1":4,"2":7},{"0":2,"1":5,"2":8}]                    |
|[{"0":1,"1":4,"2":7}]                                        |
|[{"0":1,"1":4,"2":7},{"0":2,"1":5,"2":8},{"0":3,"1":6,"2":9}]|
+-------------------------------------------------------------+

如何保留“bar”、“buzz”和“foo”而不是 0、1、2?

【问题讨论】:

  • 不漂亮,但您可以使用transform 手动构建字符串:类似于F.expr("""transform(zipped, x -> concat('{"foo":', x['foo'], '"bar":', x['bar'], '"buzz":', x['buzz'], '}'))""")

标签: python dataframe apache-spark pyspark apache-spark-sql


【解决方案1】:

手动指定架构也可以: 对于 foo、bar 和 Buzz 字段,元素顶部的数组必须已命名,而不是实际数据字段本身

data = [
    {"foo": [1, 2], "bar": [4, 5], "buzz": [7, 8]},
    {"foo": [1], "bar": [4], "buzz": [7]},
    {"foo": [1, 2, 3], "bar": [4, 5, 6], "buzz": [7, 8, 9]},
]
df = spark.read.json(sc.parallelize(data))
df.show()
+---------+---------+---------+
|      bar|     buzz|      foo|
+---------+---------+---------+
|   [4, 5]|   [7, 8]|   [1, 2]|
|      [4]|      [7]|      [1]|
|[4, 5, 6]|[7, 8, 9]|[1, 2, 3]|
+---------+---------+---------+

然后手动定义并转换为模式:

schema = StructType([
    StructField("foo", IntegerType()),
    StructField("bar", IntegerType()),
    StructField("buzz", IntegerType()),
])

df_zipped = (
    df_test
    .select(
        F.arrays_zip(
            F.col("foo"), 
            F.col("bar"), 
            F.col("buzz"),
                )
        .alias("zipped")
            )
    .filter(F.col("zipped").isNotNull())
    .select(F.col("zipped").cast(ArrayType(schema)))
)

这会产生所需的解决方案:

(
    df_zipped
    .withColumn("zipped", F.to_json("zipped"))
    .select("zipped")
    .show(truncate=False)
)
+----------------------------------------------------------------------------------+
|zipped                                                                            |
+----------------------------------------------------------------------------------+
|[{"foo":1,"bar":4,"buzz":7},{"foo":2,"bar":5,"buzz":8}]                           |
|[{"foo":1,"bar":4,"buzz":7}]                                                      |
|[{"foo":1,"bar":4,"buzz":7},{"foo":2,"bar":5,"buzz":8},{"foo":3,"bar":6,"buzz":9}]|
+----------------------------------------------------------------------------------+

注意:在架构中转换为 LongType 不起作用

【讨论】:

    【解决方案2】:

    这不是一个超级漂亮的答案(因为您必须明确指定键),但比what I put in the comments 更好。

    transformmap 一起使用:

    df_zipped.withColumn(
        "zipped", 
        F.to_json(
            F.expr(
                """transform(zipped, x -> map('foo', x['foo'], 'bar', x['bar'], 'buzz', x['buzz']))"""
            )
        )
    ).select('zipped').show(truncate=False)
    #+----------------------------------------------------------------------------------+
    #|zipped                                                                            |
    #+----------------------------------------------------------------------------------+
    #|[{"foo":1,"bar":4,"buzz":7},{"foo":2,"bar":5,"buzz":8}]                           |
    #|[{"foo":1,"bar":4,"buzz":7}]                                                      |
    #|[{"foo":1,"bar":4,"buzz":7},{"foo":2,"bar":5,"buzz":8},{"foo":3,"bar":6,"buzz":9}]|
    #+----------------------------------------------------------------------------------+
    

    【讨论】:

    • 是的,谢谢你的回答。它很好用。我希望有一种方法,您不必手动定义键/模式,但这可能是不可能的
    • 您可以通过查询模式来动态构建表达式以获取键的名称。这很复杂,不确定是否值得付出努力。在任何情况下,您都可以在 arrays_zip 中指定键
    • 我遇到了同样的问题,但我不能执行 create_map,因为我的列的类型不一样。有什么想法吗?
    猜你喜欢
    • 1970-01-01
    • 2022-08-10
    • 1970-01-01
    • 2018-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多