我使用 pyarrow 创建和分析包含生物信息的 Parquet 表,我需要存储一些元数据,例如数据来自哪个样本,如何获取和处理。
Parquet 似乎支持文件范围元数据,但我找不到如何通过 pyarrow 写入它。我能找到的最接近的东西是如何编写行组元数据,但这似乎有点矫枉过正,因为我的元数据对于文件中的所有行组都是相同的。
有没有办法用 pyarrow 写入文件范围的 Parquet 元数据?
Pyarrow 将文件范围的元数据映射到表架构中的名为元数据的字段。 遗憾的是,目前还没有这方面的文档。
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'
代表什么。 它似乎以某种我无法识别的方式进行编码,而且我还没有真正使用过它。 我认为它的目的是记录与“pandas”模式类似的东西。
为了回答您的问题,我们需要知道的最后一件事是所有 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 Number
和 Date Obtained
的键/值元数据字段(在文件级别)。
另外,请注意
replace_schema_metadata
和 with_metadata
方法可以接受常规 Python 字符串(如我的示例中所示)。 但是,它会将这些转换为“b 字符串”,因此如果您想访问架构中的字段,则必须使用“b 字符串”。 例如,如果您刚刚阅读了表格并想要获取样本编号,则必须使用 table.schema.metadata[b'Sample Number']
,而 table.schema.metadats['Sample Number']
将为您提供 KeyError
。
当您开始使用它时,您可能会意识到不断地将
Sample Number
来回映射到整数是一件痛苦的事情。 此外,如果您的元数据在应用程序中表示为大型嵌套对象,则将该对象映射到字符串/字符串对的集合可能会很痛苦。 此外,不断记住“b string”键也是一件痛苦的事情。 解决方案是做与 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 类型,并且它们都将被命名为单个键/值对(在本例中名为“记录元数据”) .
此示例演示如何使用 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'
这并不完全是所要求的,但如果您使用 Pandas 来读取和写入镶木地板,最简单的方法是使用鲜为人知的 Dataframe 属性
attrs
。
Pandas 的
DataFrame.to_parquet()
方法会自动将 DataFrame.attrs
编码到 parquet 元数据中。
示例:
>>> import pandas as pd
>>> from datetime import datetime
>>> df = pd.DataFrame(dict(colA=list(range(10)),
... colB=[_*3 for _ in list('abcdefghij')],))
>>> df.attrs
{}
>>> df.attrs['Created'] = str(datetime.now())
>>> df.attrs['Location'] = 'Earth'
>>> df.attrs
{'Created': '2024-11-22 11:00:13.920618', 'Location': 'Earth'}
>>> df.to_parquet('mydata.parquet')
>>> new_df = pd.read_parquet('mydata.parquet')
>>> new_df.attrs
{'Created': '2024-11-22 11:00:13.920618', 'Location': 'Earth'}