如何用 pyarrow 编写 Parquet 元数据?

Posted

技术标签:

【中文标题】如何用 pyarrow 编写 Parquet 元数据?【英文标题】:How to write Parquet metadata with pyarrow? 【发布时间】:2019-02-06 21:54:05 【问题描述】:

我使用 pyarrow 创建和分析包含生物信息的 Parquet 表,我需要存储一些元数据,例如数据来自哪个样本,如何获取和处理。

Parquet 似乎支持file-wide metadata,但我找不到如何通过 pyarrow 编写它。我能找到的最接近的东西是how to write row-group metadata,但这似乎有点过头了,因为我的元数据对于文件中的所有行组都是相同的。

有没有办法用 pyarrow 编写文件范围的 Parquet 元数据?

【问题讨论】:

我也有同样的需求... 【参考方案1】:

此示例展示了如何使用 PyArrow 创建包含文件元数据和列元数据的 Parquet 文件。

假设您有以下 CSV 数据:

movie,release_year
three idiots,2009
her,2013

将 CSV 读入 PyArrow 表并使用列/文件元数据定义自定义架构:

import pyarrow.csv as pv
import pyarrow.parquet as pq
import pyarrow as pa

table = pv.read_csv('movies.csv')

my_schema = pa.schema([
    pa.field("movie", "string", False, metadata="spanish": "pelicula"),
    pa.field("release_year", "int64", True, metadata="portuguese": "ano")],
    metadata="great_music": "reggaeton")

使用my_schema 创建一个新表并将其写为 Parquet 文件:

t2 = table.cast(my_schema)

pq.write_table(t2, 'movies.parquet')

读取 Parquet 文件并获取文件元数据:

s = pq.read_table('movies.parquet').schema

s.metadata # => b'great_music': b'reggaeton'
s.metadata[b'great_music'] # => b'reggaeton'

获取与release_year 列关联的元数据:

parquet_file.schema.field('release_year').metadata[b'portuguese'] # => b'ano'

【讨论】:

【参考方案2】:

Pyarrow 将文件范围的元数据映射到 field in the table's schema 命名的元数据。遗憾的是,还没有这方面的文档。

Parquet 元数据格式和 Pyarrow 元数据格式都将元数据表示为键/值对的集合,其中键和值都必须是字符串。这是不幸的,因为如果它只是一个 UTF-8 编码的 JSON 对象,它会更加灵活。此外,由于这些是 C++ 实现中的 std::string 对象,它们在 Python 中是“b 字符串”(字节)对象。

Pyarrow 目前将一些自己的信息存储在元数据字段中。它有一个内置键b'ARROW:schema' 和另一个内置键b'pandas'。在 pandas 的情况下,该值是一个用 UTF-8 编码的 JSON 对象。这允许命名空间。 “pandas”模式可以有尽可能多的字段,并且它们都在“pandas”下命名。 Pyarrow 使用“pandas”模式来存储有关表具有哪种索引以及列使用哪种编码类型的信息(当给定数据类型有多个可能的 pandas 编码时)。我不确定b'ARROW:schema' 代表什么。它似乎以某种我不认识的方式编码,我还没有真正玩过它。我认为它的目的是记录与“熊猫”模式类似的东西。

要回答您的问题,我们需要知道的最后一件事是所有 pyarrow 对象都是不可变的。所以没有办法简单地将字段添加到模式中。 Pyarrow 确实有模式实用程序方法with_metadata,它返回模式对象的克隆,但使用您自己的元数据,但这会替换现有元数据并且不会附加到它。 Table对象replace_schema_metadata上也有实验方法,但这也替换并且不更新。所以如果你想保留现有的元数据,你必须做更多的工作。综上所述,我们得到...

custom_metadata = 'Sample Number': '12', 'Date Obtained': 'Tuesday'
existing_metadata = table.schema.metadata
merged_metadata =  **custom_metadata, **existing_metadata 
fixed_table = table.replace_schema_metadata(merged_metadata)

将此表保存为 parquet 文件后,它将包含 Sample NumberDate Obtained 的键/值元数据字段(在文件级别)。

另外,请注意replace_schema_metadatawith_metadata 方法可以接受常规的python 字符串(就像在我的示例中一样)。但是,它会将这些转换为“b 字符串”,因此如果您想访问架构中的字段,您必须使用“b 字符串”。例如,如果您刚刚阅读了一个表格并想获得样本编号,您必须使用table.schema.metadata[b'Sample Number'],而table.schema.metadats['Sample Number'] 将为您提供KeyError

当您开始使用它时,您可能会意识到不断地必须将Sample Number 来回映射到整数是很痛苦的。此外,如果您的元数据在您的应用程序中表示为一个大型嵌套对象,那么将这个对象映射到字符串/字符串对的集合可能会很痛苦。此外,不断记住“b 字符串”键是一种痛苦。解决方案是做与 pandas 模式相同的事情。首先将您的元数据转换为 JSON 对象。然后将 JSON 对象转换为“b 字符串”。

custom_metadata_json = 'Sample Number': 12, 'Date Obtained': 'Tuesday'
custom_metadata_bytes = json.dumps(custom_metadata_json).encode('utf8')
existing_metadata = table.schema.metadata
merged_metadata =  **'Record Metadata': custom_metadata_bytes, **existing_metadata 

现在您可以拥有任意数量的元数据字段,以任何您想要的方式嵌套,使用任何标准 JSON 类型,并且它们都将被命名为单个键/值对(在本例中名为“记录元数据")。

【讨论】:

关于b'ARROW:schema'的编码:***.com/questions/61707170/…

以上是关于如何用 pyarrow 编写 Parquet 元数据?的主要内容,如果未能解决你的问题,请参考以下文章

Pyarrow.lib.Schema 与 pyarrow.parquet.Schema

使用谓词过滤 pyarrow.parquet.ParquetDataset 中的行

是否可以用 pyarrow 编写镶木地板统计信息?

带有pyarrow内存的dask read_parquet爆炸

使用 PyArrow + Parquet + Google Cloud Storage 时如何实现谓词下推?

如何使用 pyarrow 和 parquet 保存具有自定义类型的 pandas DataFrame