如何用 pyarrow 编写 Parquet 元数据?

How to write Parquet metadata with pyarrow?

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

Parquet 似乎支持 file-wide metadata, but I cannot find how the write it via pyarrow. The closest thing I could find is how to write row-group metadata,但这似乎有点矫枉过正,因为我的元数据对于文件中的所有行组都是相同的。

有什么方法可以用 pyarrow 写入文件范围的 Parquet 元数据吗?

Pyarrow 将文件级元数据映射到 field in the table's schema 命名元数据。遗憾的是(目前)还没有这方面的文档。

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

中的 "b strings"(字节)对象

Pyarrow 目前在元数据字段中存储了一些它自己的信息。它有一个内置键 b'ARROW:schema' 和另一个内置键 b'pandas'。在 pandas 的情况下,值是一个用 UTF-8 编码的 JSON 对象。这允许命名空间。 "pandas" 模式可以根据需要拥有任意数量的字段,并且它们都在 "pandas" 下命名空间。 Pyarrow 使用 "pandas" 模式来存储有关 table 具有何种索引以及列使用何种编码类型的信息(当有多个可能的 pandas 编码时给定的数据类型)。我不确定 b'ARROW:schema' 代表什么。它似乎是以某种我不认识的方式编码的,我也没有真正玩过它。我假设它旨在记录与 "pandas" 模式类似的内容。

要回答您的问题,我们最后需要知道的是所有 pyarrow 对象都是 immutable。所以没有办法简单地向模式添加字段。 Pyarrow 确实有模式实用方法 with_metadata,它 returns 是模式对象的克隆,但具有您自己的元数据,但这会替换现有的元数据并且不会附加到它。还有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)

将此 table 另存为镶木地板文件后,它将包含 Sample NumberDate Obtained 的 key/value 元数据字段(在文件级别)。

另外,请注意 replace_schema_metadatawith_metadata 方法可以接受常规的 python 字符串(就像我的例子)。但是,它会将这些转换为 "b strings",因此如果您想访问架构中的字段,则必须使用 "b string"。例如,如果您刚刚读入 table 并想要获取样本编号,则必须使用 table.schema.metadata[b'Sample Number'] 并且 table.schema.metadats['Sample Number'] 将为您提供 KeyError.

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

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 类型,并且它们都将被命名为单个 key/value 对(在本例中名为 "Record Metadata")。

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

假设您有以下 CSV 数据:

movie,release_year
three idiots,2009
her,2013

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

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 创建一个新的 table 并将其写为 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'