Parquet 匯出
將標註匯出為 Apache Parquet 格式,用於高效的大規模資料處理。
v2.3.0 新增
Apache Parquet 是一種面向分析工作負載最佳化的列式儲存格式。對於大型標註資料集,它相比 JSON 和 CSV 具有顯著優勢:更小的檔案大小(通常 5-10 倍壓縮)、列子集查詢更快的讀取速度,以及幾乎所有資料科學工具(pandas、DuckDB、PyArrow、Spark、Polars、Hugging Face Datasets)的原生支援。
Potato 可以直接將標註匯出為 Parquet 格式,生成三個結構化檔案,覆蓋所有標註類型。
啟用 Parquet 匯出
作為主輸出格式
yaml
output_annotation_dir: "output/"
output_annotation_format: "parquet"作為輔助匯出(保留 JSON 為主格式)
yaml
output_annotation_dir: "output/"
output_annotation_format: "jsonl"
parquet_export:
enabled: true
output_dir: "output/parquet/"
auto_export: true # export after each annotation session通過 CLI 按需匯出
bash
python -m potato.export parquet --config config.yaml --output ./parquet_output/輸出檔案
Parquet 匯出生成三個檔案,分別代表標註資料的不同層級。
1. annotations.parquet
主輸出檔案。每行對應一個(實例、標註者、方案)組合。
| 列名 | 類型 | 描述 |
|---|---|---|
instance_id | string | 實例識別符號 |
annotator | string | 標註者使用者名稱 |
schema_name | string | 標註方案名稱 |
value | string | 標註值(複雜類型使用 JSON 編碼) |
timestamp | timestamp | 標註建立時間 |
duration_ms | int64 | 此實例所花時間(毫秒) |
session_id | string | 標註會話識別符號 |
對於簡單標註類型(radio、likert、text),value 包含原始值。對於複雜類型(multiselect、spans、events),value 包含 JSON 字串。
2. spans.parquet
用於基於 span 的標註類型(span、span_link、event_annotation、coreference)。每行對應一個標註的 span。
| 列名 | 類型 | 描述 |
|---|---|---|
instance_id | string | 實例識別符號 |
annotator | string | 標註者使用者名稱 |
schema_name | string | 標註方案名稱 |
span_id | string | 唯一 span 識別符號 |
text | string | Span 文本內容 |
start_offset | int32 | 字元起始偏移量 |
end_offset | int32 | 字元結束偏移量 |
label | string | Span 標籤 |
field | string | 源欄位(用於多欄位 span 標註) |
links | string | JSON 編碼的連結資料(用於 span_link) |
attributes | string | JSON 編碼的附加屬性 |
3. items.parquet
資料集中每個實例的後設資料。每行對應一個實例。
| 列名 | 類型 | 描述 |
|---|---|---|
instance_id | string | 實例識別符號 |
text | string | 主要文本內容 |
annotation_count | int32 | 已收到的標註數量 |
annotators | string | 標註者使用者名稱的 JSON 列表 |
status | string | 實例狀態(pending、in_progress、complete) |
metadata | string | JSON 編碼的實例後設資料 |
壓縮選項
yaml
parquet_export:
enabled: true
output_dir: "output/parquet/"
compression: snappy # snappy (default), gzip, zstd, lz4, brotli, none
row_group_size: 50000 # rows per row group (affects read performance)
use_dictionary: true # dictionary encoding for string columns
write_statistics: true # column statistics for query optimization壓縮對比
| 演算法 | 壓縮比 | 寫入速度 | 讀取速度 | 最適用場景 |
|---|---|---|---|---|
snappy | 中等 | 快 | 快 | 通用(預設) |
gzip | 高 | 慢 | 中等 | 歸檔、小檔案 |
zstd | 高 | 快 | 快 | 大小和速度的最佳平衡 |
lz4 | 低 | 非常快 | 非常快 | 速度敏感的工作負載 |
brotli | 非常高 | 非常慢 | 中等 | 最大壓縮 |
none | 無 | 最快 | 最快 | 除錯 |
對於大多數標註項目,預設的 snappy 壓縮是不錯的選擇。對於檔案大小重要的大型資料集,使用 zstd。
載入 Parquet 資料
pandas
python
import pandas as pd
annotations = pd.read_parquet("output/parquet/annotations.parquet")
spans = pd.read_parquet("output/parquet/spans.parquet")
items = pd.read_parquet("output/parquet/items.parquet")
# Filter to a specific schema
sentiment = annotations[annotations["schema_name"] == "sentiment"]
# Compute inter-annotator agreement
from sklearn.metrics import cohen_kappa_score
pivot = sentiment.pivot(index="instance_id", columns="annotator", values="value")
kappa = cohen_kappa_score(pivot.iloc[:, 0], pivot.iloc[:, 1])DuckDB
sql
-- Direct query without loading into memory
SELECT instance_id, value, COUNT(*) as annotator_count
FROM 'output/parquet/annotations.parquet'
WHERE schema_name = 'sentiment'
GROUP BY instance_id, value
ORDER BY annotator_count DESC;
-- Join annotations with items
SELECT a.instance_id, i.text, a.value, a.annotator
FROM 'output/parquet/annotations.parquet' a
JOIN 'output/parquet/items.parquet' i
ON a.instance_id = i.instance_id
WHERE a.schema_name = 'sentiment';PyArrow
python
import pyarrow.parquet as pq
# Read specific columns only (fast for wide tables)
table = pq.read_table(
"output/parquet/annotations.parquet",
columns=["instance_id", "value", "annotator"]
)
# Convert to pandas
df = table.to_pandas()
# Read with row group filtering
parquet_file = pq.ParquetFile("output/parquet/annotations.parquet")
print(f"Row groups: {parquet_file.metadata.num_row_groups}")
print(f"Total rows: {parquet_file.metadata.num_rows}")Hugging Face Datasets
python
from datasets import load_dataset
# Load directly from Parquet files
dataset = load_dataset("parquet", data_files={
"annotations": "output/parquet/annotations.parquet",
"spans": "output/parquet/spans.parquet",
"items": "output/parquet/items.parquet",
})
# Access as a regular HF dataset
print(dataset["annotations"][0])
# Push to Hugging Face Hub
dataset["annotations"].push_to_hub("my-org/my-annotations", split="train")Polars
python
import polars as pl
annotations = pl.read_parquet("output/parquet/annotations.parquet")
# Fast aggregation
label_counts = (
annotations
.filter(pl.col("schema_name") == "sentiment")
.group_by("value")
.agg(pl.count().alias("count"))
.sort("count", descending=True)
)
print(label_counts)增量匯出
對於長期執行的標註項目,啟用增量匯出以避免每次重新匯出整個資料集:
yaml
parquet_export:
enabled: true
output_dir: "output/parquet/"
incremental: true
partition_by: date # date, annotator, or none使用 partition_by: date 時,Parquet 檔案被組織到按日期分割槽的目錄中:
text
output/parquet/
annotations/
date=2026-03-01/part-0.parquet
date=2026-03-02/part-0.parquet
date=2026-03-03/part-0.parquet
spans/
date=2026-03-01/part-0.parquet
items/
part-0.parquet
分割槽資料集可以被所有主要工具作為單個邏輯表讀取:
python
# pandas reads partitioned directories automatically
df = pd.read_parquet("output/parquet/annotations/")
# DuckDB handles partitions natively
# SELECT * FROM 'output/parquet/annotations/**/*.parquet'配置參考
yaml
parquet_export:
enabled: true
output_dir: "output/parquet/"
# When to export
auto_export: true # export after each session (default: false)
export_on_shutdown: true # export when server stops (default: true)
# File settings
compression: snappy
row_group_size: 50000
use_dictionary: true
write_statistics: true
# Incremental settings
incremental: false
partition_by: none # none, date, annotator
# Schema-specific options
flatten_complex_types: false # flatten JSON values into columns
include_raw_json: true # include raw JSON alongside flattened columns
# Span export
export_spans: true # generate spans.parquet
export_items: true # generate items.parquet完整示例
yaml
task_name: "NER Annotation Project"
task_dir: "."
data_files:
- "data/documents.jsonl"
item_properties:
id_key: doc_id
text_key: text
annotation_schemes:
- annotation_type: span
name: entities
labels:
- name: PERSON
color: "#3b82f6"
- name: ORGANIZATION
color: "#22c55e"
- name: LOCATION
color: "#f59e0b"
output_annotation_dir: "output/"
output_annotation_format: "jsonl"
parquet_export:
enabled: true
output_dir: "output/parquet/"
compression: zstd
auto_export: true
export_spans: true
export_items: true標註完成後,載入並分析:
python
import pandas as pd
spans = pd.read_parquet("output/parquet/spans.parquet")
# Entity type distribution
print(spans["label"].value_counts())
# Average span length by type
spans["length"] = spans["end_offset"] - spans["start_offset"]
print(spans.groupby("label")["length"].mean())延伸閱讀
有關實現詳情,請參閱源文件。