Skip to content

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_idstring實例識別符號
annotatorstring標註者使用者名稱
schema_namestring標註方案名稱
valuestring標註值(複雜類型使用 JSON 編碼)
timestamptimestamp標註建立時間
duration_msint64此實例所花時間(毫秒)
session_idstring標註會話識別符號

對於簡單標註類型(radio、likert、text),value 包含原始值。對於複雜類型(multiselect、spans、events),value 包含 JSON 字串。

2. spans.parquet

用於基於 span 的標註類型(span、span_link、event_annotation、coreference)。每行對應一個標註的 span。

列名類型描述
instance_idstring實例識別符號
annotatorstring標註者使用者名稱
schema_namestring標註方案名稱
span_idstring唯一 span 識別符號
textstringSpan 文本內容
start_offsetint32字元起始偏移量
end_offsetint32字元結束偏移量
labelstringSpan 標籤
fieldstring源欄位(用於多欄位 span 標註)
linksstringJSON 編碼的連結資料(用於 span_link)
attributesstringJSON 編碼的附加屬性

3. items.parquet

資料集中每個實例的後設資料。每行對應一個實例。

列名類型描述
instance_idstring實例識別符號
textstring主要文本內容
annotation_countint32已收到的標註數量
annotatorsstring標註者使用者名稱的 JSON 列表
statusstring實例狀態(pending、in_progress、complete)
metadatastringJSON 編碼的實例後設資料

壓縮選項

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())

延伸閱讀

有關實現詳情,請參閱源文件