我可以存储一个 Parquet 文件,其中的字典列的值具有混合类型吗?

Can I store a Parquet file with a dictionary column having mixed types in their values?

我正在尝试将 Python Pandas DataFrame 存储为 Parquet 文件,但我遇到了一些问题。我的 Pandas DF 的其中一列包含这样的字典:

import pandas as pandas

df = pd.DataFrame({
    "ColA": [1, 2, 3],
    "ColB": ["X", "Y", "Z"],
    "ColC": [
        { "Field": "Value" },
        { "Field": "Value2" },
        { "Field": "Value3" }
    ]
})

df.to_parquet("test.parquet")

现在,这工作得很好,问题是当字典的一个嵌套值的类型与其余值不同时。例如:

import pandas as pandas

df = pd.DataFrame({
    "ColA": [1, 2, 3],
    "ColB": ["X", "Y", "Z"],
    "ColC": [
        { "Field": "Value" },
        { "Field": "Value2" },
        { "Field": ["Value3"] }
    ]
})

df.to_parquet("test.parquet")

这会引发以下错误:

ArrowInvalid: ('cannot mix list and non-list, non-null values', 'Conversion failed for column ColC with type object')

请注意,对于 DF 的最后一行,ColC 字典的 Field 属性 是一个列表而不是字符串。

是否有任何解决方法可以将此 DF 存储为 Parquet 文件?

ColC 是一种 UDT(用户定义类型),其中一个字段名为 Field,类型为 Union of String, List of String

理论上arrow是支持的,但实际上很难弄清楚ColC的类型是什么。即使您明确提供了数据框的模式,它也不会起作用,因为尚不支持这种类型的转换(将联合从 pandas 转换为 arrow/parquet)。

union_type = pa.union(
    [pa.field("0",pa.string()), pa.field("1", pa.list_(pa.string()))],
    'dense'
)
col_c_type = pa.struct(
    [
        pa.field('Field', union_type)
    ]
)

schema=pa.schema(
    [
        pa.field('ColA', pa.int32()),
        pa.field('ColB', pa.string()),
        pa.field('ColC', col_c_type),
    ]
)

df = pd.DataFrame({
    "ColA": [1, 2, 3],
    "ColB": ["X", "Y", "Z"],
    "ColC": [
        { "Field": "Value" },
        { "Field": "Value2" },
        { "Field": ["Value3"] }
    ]
})

pa.Table.from_pandas(df, schema)

这给你这个错误:

('Sequence converter for type union[dense]<0: string=0, 1: list<item: string>=1> not implemented', 'Conversion failed for column ColC with type object'

即使您手动创建箭头 table,它也无法将其转换为镶木地板(同样,不支持并集)。

import io
import pyarrow.parquet as pq

col_a = pa.array([1, 2, 3], pa.int32())
col_b = pa.array(["X", "Y", "Z"], pa.string())

xs = pa.array(["Value", "Value2", None], type=pa.string())
ys = pa.array([None, None, ["value3"]], type=pa.list_(pa.string()))
types = pa.array([0, 0, 1], type=pa.int8())

col_c = pa.UnionArray.from_sparse(types, [xs, ys])

table = pa.Table.from_arrays(
    [col_a, col_b, col_c],
    schema=pa.schema([
        pa.field('ColA', col_a.type),
        pa.field('ColB', col_b.type),
        pa.field('ColC', col_c.type),
    ])
)

with io.BytesIO() as buffer:
    pq.write_table(table, buffer)
Unhandled type for Arrow to Parquet schema conversion: sparse_union<0: string=0, 1: list<item: string>=1>

我认为你现在唯一的选择是使用一个结构,其中字段的字符串值和字符串值列表具有不同的名称。

df = pd.DataFrame({
    "ColA": [1, 2, 3],
    "ColB": ["X", "Y", "Z"],
    "ColC": [
        { "Field1": "Value" },
        { "Field1": "Value2" },
        { "Field2": ["Value3"] }
    ]
})

df.to_parquet('/tmp/hello')

我刚遇到同样的问题,通过将 ColC 转换为字符串来解决:

df['ColC'] = df['ColC'].astype(str)

我不确定这会不会在未来造成问题,不要引用我的话。