Skip to content

Parquet 导出

将标注导出为 Apache Parquet 格式,用于高效的大规模数据处理。

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

延伸阅读

有关实现详情,请参阅源文档