# PyArrow

[Arrow](https://github.com/apache/arrow) 是一种列式格式，也是用于快速数据交换和内存分析的工具箱。由于 PyArrow 支持使用 [fsspec](https://filesystem-spec.readthedocs.io) 来读写远程数据，因此您可以使用 Hugging Face 路径（[`hf://`](/docs/huggingface_hub/guides/hf_file_system#integrations)）来读写 Hub 上的数据。这对于 [Parquet](https://parquet.apache.org/) 数据尤其有用，因为 Parquet 是 Hugging Face 上最常见的文件格式。事实上，得益于其结构、类型、元数据和压缩特性，Parquet 的效率非常高。

## 加载表格

您可以从本地文件或 Hugging Face Datasets 等远程存储中加载数据。PyArrow 支持多种格式，包括 CSV、JSON，以及更重要的 Parquet：

```python
>>> import pyarrow.parquet as pq
>>> table = pq.read_table("path/to/data.parquet")
```

要从 Hugging Face 加载文件，路径必须以 `hf://` 开头。例如，[stanfordnlp/imdb](https://huggingface.co/datasets/stanfordnlp/imdb) 数据集仓库的路径为 `hf://datasets/stanfordnlp/imdb`。Hugging Face 上的该数据集包含多个 Parquet 文件。Parquet 文件格式旨在高效地读写数据帧，并便于在不同数据分析语言之间共享数据。以下是如何将文件 `plain_text/train-00000-of-00001.parquet` 加载为 pyarrow Table（需要 `pyarrow>=21.0`）：

```python
>>> import pyarrow.parquet as pq
>>> table = pq.read_table("hf://datasets/stanfordnlp/imdb/plain_text/train-00000-of-00001.parquet")
>>> table
pyarrow.Table
text: string
label: int64
----
text: [["I rented I AM CURIOUS-YELLOW from my video store because of all the controversy that surrounded it (... 1542 chars omitted)", ...],...,[..., "The story centers around Barry McKenzie who must go to England if he wishes to claim his inheritan (... 221 chars omitted)"]]
label: [[0,0,0,0,0,...,0,0,0,0,0],...,[1,1,1,1,1,...,1,1,1,1,1]]
```

如果不想加载完整的 Parquet 数据，可以获取 Parquet 元数据，或者按行组逐个加载：

```python
>>> import pyarrow.parquet as pq
>>> pf = pq.ParquetFile("hf://datasets/stanfordnlp/imdb/plain_text/train-00000-of-00001.parquet")
>>> pf.metadata
<pyarrow._parquet.FileMetaData object at 0x1171b4090>
  created_by: parquet-cpp-arrow version 12.0.0
  num_columns: 2
  num_rows: 25000
  num_row_groups: 25
  format_version: 2.6
  serialized_size: 62036
>>> for i in pf.num_row_groups:
...     table = pf.read_row_group(i)
...     ...
```

有关 Hugging Face 路径及其实现方式的更多信息，请参阅 [HfFileSystem 的客户端库文档](/docs/huggingface_hub/guides/hf_file_system)。

## 保存表格

您可以使用 `pyarrow.parquet.write_table` 将 pyarrow Table 保存到本地文件或 Hugging Face。

要在 Hugging Face 上保存 Table，您首先需要[使用您的 Hugging Face 账户登录](/docs/huggingface_hub/quick-start#login)，例如使用：

```
hf auth login
```

然后你可以[创建数据集仓库](/docs/huggingface_hub/quick-start#create-a-repository)，例如使用：

```python
from huggingface_hub import HfApi

HfApi().create_repo(repo_id="username/my_dataset", repo_type="dataset")
```

最后，你可以在 PyArrow 中使用 [Hugging Face 路径](/docs/huggingface_hub/guides/hf_file_system#integrations)：

```python
import pyarrow.parquet as pq

pq.write_table(table, "hf://datasets/username/my_dataset/imdb.parquet", use_content_defined_chunking=True)

# or write in separate files if the dataset has train/validation/test splits
pq.write_table(table_train, "hf://datasets/username/my_dataset/train.parquet", use_content_defined_chunking=True)
pq.write_table(table_valid, "hf://datasets/username/my_dataset/validation.parquet", use_content_defined_chunking=True)
pq.write_table(table_test , "hf://datasets/username/my_dataset/test.parquet", use_content_defined_chunking=True)
```

请注意，Hugging Face 上的 Parquet 文件经过优化，以提升存储效率、加快下载和上传速度，并支持高效的数据集流式处理和编辑：

* [Parquet 内容定义分块](https://huggingface.co/blog/parquet-cdc)针对 Hugging Face 的存储后端 [Xet](https://huggingface.co/docs/hub/en/xet/index) 优化了 Parquet。它通过基于分块的重复数据删除加速了上传和下载，并支持高效的文件编辑
* Page index 在流式传输时加速过滤，并支持高效的随机访问，例如在 [Dataset Viewer](https://huggingface.co/docs/dataset-viewer) 中。

PyArrow 需要额外的参数来写入优化后的 Parquet 文件：

```python
import pyarrow.parquet as pq

pq.write_table(
    table,
    "hf://datasets/username/my_dataset/imdb.parquet",
    # Optimize for Xet
    use_content_defined_chunking=True,
    write_page_index=True,
)
```

* `use_content_defined_chunking=True` 以启用 Parquet Content Defined Chunking，用于[去重](https://huggingface.co/blog/parquet-cdc)和[编辑](./datasets-editing)（需要`pyarrow>=21.0`）
* `write_page_index=True` 以在 Parquet 元数据中包含页面索引，用于[流式处理和随机访问](./datasets-streaming)

> [!TIP]
> 内容定义分块（CDC）使 Parquet 写入器以特定方式对数据页进行分块，从而确保重复数据被分块并以相同方式压缩。如果没有 CDC，数据页会被任意分块，因此由于压缩的存在，无法检测到重复数据。得益于 CDC，从 Hugging Face 上传和下载 Parquet 文件的速度更快，因为重复数据只需上传或下载一次。

有关 Xet 的更多信息，请参见[此处](https://huggingface.co/join/xet)。

## 利用 Xet 去重功能处理 Parquet

经过优化的 Parquet 文件采用内容定义分块（Content Defined Chunking）进行写入，从而实现去重。由于 Hugging Face 上已存在的数据块无需重复上传，这加快了上传速度，并大幅节省了 I/O 开销。

例如，此代码上传 `table` 的内容，然后对于 `edited_table`，由于只上传了发生更改的块，因此上传速度更快：

```python
import pyarrow.parquet as pq

pq.write_table(
    table,
    "hf://datasets/username/my_dataset/imdb.parquet",
    # Optimize for Xet
    use_content_defined_chunking=True,
    write_page_index=True,
)

edited_table = ...  # e.g. with added/modified/removed rows or columns

pq.write_table(
    edited_table,
    "hf://datasets/username/my_dataset/imdb.parquet",
    # Optimize for Xet
    use_content_defined_chunking=True,
    write_page_index=True,
)
```

数据块大小约为 64kB，而 Parquet 按列存储数据，因此实际编辑优化后的 Parquet 文件时，会发生以下情况：

* 添加新列 -> 仅上传新列的块
* 添加/编辑/删除一行 -> 每列上传一个数据块

此外，包含元数据的 Parquet 文件尾块也会被上传。

## 使用图像

您可以加载一个包含元数据文件的文件夹，该文件中有一个字段用于存储图像的名称或路径，其结构如下：

```
Example 1:            Example 2:
folder/               folder/
├── metadata.parquet  ├── metadata.parquet
├── img000.png        └── images
├── img001.png            ├── img000.png
...                       ...
└── imgNNN.png            └── imgNNN.png
```

您可以按以下方式迭代图像路径：

```python
from pathlib import Path
import pyarrow as pq

folder_path = Path("path/to/folder")
table = pq.read_table(folder_path + "metadata.parquet")
for file_name in table["file_name"].to_pylist():
    image_path = folder_path / file_name
    ...
```

由于数据集采用[受支持的结构](https://huggingface.co/docs/hub/en/datasets-image#additional-columns)（一个包含`file_name`字段的`metadata.parquet`文件），您可以将该数据集保存到 Hugging Face，Dataset Viewer 会同时显示元数据和图像。

```python
from huggingface_hub import HfApi
api = HfApi()

api.upload_folder(
    folder_path=folder_path,
    repo_id="username/my_image_dataset",
    repo_type="dataset",
)
```

### 在 Parquet 中嵌入图像

PyArrow 提供了一种二进制类型，允许在 Arrow 表中存储图像的字节数据。因此，它支持将数据集保存为单个 Parquet 文件，其中同时包含图像（字节和路径）以及样本元数据：

```python
import pyarrow as pa
import pyarrow.parquet as pq

# Embed the image bytes in Arrow
image_array = pa.array([
    {
        "bytes": (folder_path / file_name).read_bytes(),
        "path": file_name,
    }
    for file_name in table["file_name"].to_pylist()
])
table.append_column("image", image_array)

# (Optional) Set the HF Image type for the Dataset Viewer and the `datasets` library
features = {"image": {"_type": "Image"}}  # or using datasets.Features(...).to_dict()
schema_metadata = {"huggingface": {"dataset_info": {"features": features}}}
table = table.replace_schema_metadata(schema_metadata)

# Save to Parquet
# (Optional) with use_content_defined_chunking for faster uploads and downloads
pq.write_table(table, "data.parquet", use_content_defined_chunking=True)
```

在 Arrow 架构元数据中设置图像类型，可以让其他库和 Hugging Face 数据集查看器知道“image”字段包含的是图像，而不仅仅是二进制数据。

## 使用 Audios

您可以加载一个包含元数据文件的文件夹，该文件中有一个字段用于存储音频的名称或路径，其结构如下：

```
Example 1:            Example 2:
folder/               folder/
├── metadata.parquet  ├── metadata.parquet
├── rec000.wav        └── audios
├── rec001.wav            ├── rec000.wav
...                       ...
└── recNNN.wav            └── recNNN.wav
```

您可以像这样迭代音频路径：

```python
from pathlib import Path
import pyarrow as pq

folder_path = Path("path/to/folder")
table = pq.read_table(folder_path + "metadata.parquet")
for file_name in table["file_name"].to_pylist():
    audio_path = folder_path / file_name
    ...
```

由于数据集采用[受支持的结构](https://huggingface.co/docs/hub/en/datasets-audio#additional-columns)（一个包含`file_name`字段的`metadata.parquet`文件），您可以将其保存到 Hugging Face，Hub 数据集查看器将同时显示元数据和音频。

```python
from huggingface_hub import HfApi
api = HfApi()

api.upload_folder(
    folder_path=folder_path,
    repo_id="username/my_audio_dataset",
    repo_type="dataset",
)
```

### 在 Parquet 中嵌入音频

PyArrow 提供了一种二进制类型，允许在 Arrow 表中存储音频字节。因此，它支持将数据集保存为单个 Parquet 文件，其中同时包含音频（字节和路径）以及样本元数据：

```python
import pyarrow as pa
import pyarrow.parquet as pq

# Embed the audio bytes in Arrow
audio_array = pa.array([
    {
        "bytes": (folder_path / file_name).read_bytes(),
        "path": file_name,
    }
    for file_name in table["file_name"].to_pylist()
])
table.append_column("audio", audio_array)

# (Optional) Set the HF Audio type for the Dataset Viewer and the `datasets` library
features = {"audio": {"_type": "Audio"}}  # or using datasets.Features(...).to_dict()
schema_metadata = {"huggingface": {"dataset_info": {"features": features}}}
table = table.replace_schema_metadata(schema_metadata)

# Save to Parquet
# (Optional) with use_content_defined_chunking for faster uploads and downloads
pq.write_table(table, "data.parquet", use_content_defined_chunking=True)
```

在 Arrow 架构元数据中设置 Audio 类型，可让其他库和 Hugging Face Dataset Viewer 识别出 "audio" 包含的是音频数据，而不仅仅是二进制数据。

