Parquetエクスポート
PotatoのアノテーションをApache Parquetにエクスポートします。pandas、DuckDB、Spark、Polars、Hugging Face Datasetsでの大規模分析に向いたカラムナー形式です。
v2.3.0の新機能
Apache Parquetは分析ワークロード向けに設計されたカラムナーストレージ形式です。大規模なアノテーションデータセットではJSONやCSVより圧縮率が高く、ファイルの残りに触れずに一部のカラムだけを読み込めます。また、pandas、DuckDB、PyArrow、Spark、Polars、Hugging Face Datasetsでネイティブに読み込めます。
ParquetエクスポートにはPyArrowが必要です。
pip install "pyarrow>=12.0.0"エクスポートの実行
ParquetはPotatoに登録済みのエクスポート形式の1つなので、エクスポートCLIが直接書き出します。
python -m potato.export -c config.yaml -f parquet -o ./parquet_export/Potatoは設定のoutput_annotation_dirに記録されたアノテーションを読み込み、指定した出力ディレクトリにParquetテーブルを書き出します。
定期的なエクスポート
長期間の調査では、誰かがCLIの実行を忘れないことに頼らずにParquetのスナップショットを取りたいのが普通です。export_annotation_formatを設定すると、Potatoはアノテーションの進行中に定期的にエクスポートを書き出します。
output_annotation_dir: "output/"
# A list is accepted, so Parquet can sit alongside another format
export_annotation_format:
- parquet
- jsonl
# Seconds between automatic exports (default: 60)
auto_export_interval: 300自動エクスポートは<output_annotation_dir>/exports/<format>/に保存されるため、Parquetテーブルはoutput/exports/parquet/の下に作られます。アノテーション自体は、この設定に関係なく常に<output_annotation_dir>/<user>/user_state.jsonとして保存されます。
以前のoutput_annotation_formatキーは非推奨です。Potatoはこれをexport_annotation_formatとして読み込み、警告をログに記録するので、名前を変更してください。
出力ファイル
エクスポートは最大4つのテーブルを生成し、それぞれがデータの異なるレベルを扱います。
annotations.parquet
(インスタンス、アノテーター)のペアごとに1行で、スキーマはカラムに展開されます。
| カラム | 型 | 説明 |
|---|---|---|
instance_id | string | アノテーション対象アイテムのID |
user_id | string | アノテーターのID |
| <schema_name> | 可変 | アノテーションスキーマごとに1カラム |
カラムの型はアノテーションタイプに従います。radioとselectは選択されたラベルを持つ文字列、likert、slider、numberはfloat64(名前付きラベルを持つスケールの場合は文字列)、multiselectは文字列のリスト、textは文字列になります。
複数の回答を同時に保存するスキーマは、サブ回答ごとに<schema>.<key>という名前のカラムを1つずつ持ちます。handlingという名前のmultirateスキーマはhandling.Urgencyとhandling.Customer toneを生成し、constant_sumとsoft_labelも同じように展開されます。単一選択は1カラムのままです。データフレームで扱いやすいのは、severityが"Serious"を持つ形だからです。
すべてのカラムがすべての行に現れ、アノテーターが回答しなかった箇所はnullで埋められます。そのため、一部のアノテーターしか到達しなかったスキーマもファイルに含まれます。
spans.parquet
スパンアノテーションごとに1行で、調査にスパンスキーマがある場合に書き出されます。
| カラム | 型 | 説明 |
|---|---|---|
instance_id | string | アノテーション対象アイテムのID |
user_id | string | アノテーターのID |
schema_name | string | スパンスキーマの名前 |
start | int | スパンが始まる文字オフセット |
end | int | スパンが終わる文字オフセット |
label | string | スパンのラベル |
text | string | スパンのテキスト内容 |
target_field | string | スパンが描かれた表示フィールド。フィールドが1つのページでは"" |
title | string | ラベルの表示タイトル |
span_id | string | 保存されたスパンのid、schema_label_start_end |
format_coords | string | JSON。PDFスパンではページとボックス、スプレッドシートのスパンでは行と列 |
additional_parts | string | JSON。不連続なスパンの残りの範囲 |
kb_id, kb_source, kb_label | string | リンクされたスパンが指すエンティティ |
調査に複数のスパンターゲットがある場合は、必ずtarget_fieldを読んでください。1つのスパンスキーマはページ上のすべてのスパンターゲットフィールドに記録するため、フィールドはスキーマ内で変わります。そしてspan_idからはそれを復元できません。同じ語句を2つのフィールドでマークすると、それ以外はまったく同じ行ができます。
textは保存されず、エクスポート時にオフセットから導出されるため、オフセットと食い違うことはありません。
items.parquet
ソースアイテムごとに1行で、元のデータを保持します。
| カラム | 型 | 説明 |
|---|---|---|
item_id | string | アイテムのID |
| <field_name> | 可変 | 元データのフィールドごとに1カラム(ネストされた辞書やリストはJSONにシリアライズされます) |
phase_responses.parquet
同意フォームや調査後アンケートなどのフェーズデータを調査が収集しており、かつexport_include_phase_dataがtrueの場合に書き出されます。このキーのデフォルトはfalseです。そのため、同意と調査後アンケートを集めた調査でも、この設定を有効にするまでphase_responses.parquetは作られず、エクスポートは除外された回答を警告として一覧表示します。
エクスポートオプション
フォーマットオプションは--option key=valueで渡し、オプションごとにフラグを繰り返します。
python -m potato.export -c config.yaml -f parquet -o ./parquet_export/ \
--option compression=zstd --option include_items=false| オプション | デフォルト | 説明 |
|---|---|---|
compression | snappy | 圧縮コーデック:snappy、gzip、zstd、lz4、またはnone |
include_items | true | ソースデータを含むitems.parquetを書き出す |
include_spans | true | スパンアノテーションがある場合にspans.parquetを書き出す |
row_group_size | PyArrowのデフォルト | annotations.parquetの行グループサイズ |
ほとんどのプロジェクトではsnappyが適切なデフォルトです。解凍が十分に速く、読み込みが対話的な速度に保たれるためです。ファイルサイズが問題になるほどデータセットが大きい場合はzstdを選んでください。同程度の速度でかなり高い圧縮率が得られます。
Parquetデータの読み込み
pandas
import pandas as pd
annotations = pd.read_parquet("parquet_export/annotations.parquet")
spans = pd.read_parquet("parquet_export/spans.parquet")
items = pd.read_parquet("parquet_export/items.parquet")
# One column per schema, so a schema is selected by name
print(annotations["sentiment"].value_counts())
# Compute inter-annotator agreement on the sentiment schema
from sklearn.metrics import cohen_kappa_score
pivot = annotations.pivot(index="instance_id", columns="user_id", values="sentiment")
kappa = cohen_kappa_score(pivot.iloc[:, 0], pivot.iloc[:, 1])DuckDB
-- Direct query without loading into memory
SELECT instance_id, sentiment, COUNT(*) AS annotator_count
FROM 'parquet_export/annotations.parquet'
GROUP BY instance_id, sentiment
ORDER BY annotator_count DESC;
-- Join annotations with the source items
SELECT a.instance_id, i.text, a.sentiment, a.user_id
FROM 'parquet_export/annotations.parquet' a
JOIN 'parquet_export/items.parquet' i
ON a.instance_id = i.item_id;PyArrow
import pyarrow.parquet as pq
# Read specific columns only (fast for wide tables)
table = pq.read_table(
"parquet_export/annotations.parquet",
columns=["instance_id", "user_id", "sentiment"]
)
df = table.to_pandas()
parquet_file = pq.ParquetFile("parquet_export/annotations.parquet")
print(f"Row groups: {parquet_file.metadata.num_row_groups}")
print(f"Total rows: {parquet_file.metadata.num_rows}")Hugging Face Datasets
from datasets import load_dataset
dataset = load_dataset("parquet", data_files={
"annotations": "parquet_export/annotations.parquet",
"spans": "parquet_export/spans.parquet",
"items": "parquet_export/items.parquet",
})
print(dataset["annotations"][0])
dataset["annotations"].push_to_hub("my-org/my-annotations", split="train")Potatoには同じスプリットを直接構築するhuggingfaceエクスポーターも含まれており、Hubが出力先の場合は中間ファイルが不要になります。
Polars
import polars as pl
annotations = pl.read_parquet("parquet_export/annotations.parquet")
label_counts = (
annotations
.group_by("sentiment")
.agg(pl.len().alias("count"))
.sort("count", descending=True)
)
print(label_counts)完全な例
作業用の形式としてJSONLを使い続け、分析用にParquetも並行して書き出すスパンアノテーションプロジェクトです。
annotation_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
description: "Highlight named entities"
labels:
- name: PERSON
- name: ORGANIZATION
- name: LOCATION
output_annotation_dir: "output/"
# Both formats are written periodically to output/exports/<format>/
export_annotation_format:
- jsonl
- parquet
auto_export_interval: 300
user_config:
allow_all_users: trueアノテーション後、エンティティのオフセットを持つのはスパンテーブルです。
import pandas as pd
spans = pd.read_parquet("output/exports/parquet/spans.parquet")
# Entity type distribution
print(spans["label"].value_counts())
# Average span length by type
spans["length"] = spans["end"] - spans["start"]
print(spans.groupby("label")["length"].mean())参考資料
- エクスポート形式:COCO、YOLO、CoNLL、その他の登録済みエクスポーター
- リモートデータソース:クラウドストレージからのデータ読み込み
- 管理ダッシュボード:アノテーションの進捗のモニタリング
実装の詳細については、ソースドキュメントを参照してください。