تصدير Parquet
تصدير التعليقات التوضيحية إلى صيغة Apache Parquet لمعالجة البيانات الكبيرة بكفاءة.
تصدير Parquet
جديد في الإصدار v2.3.0
Apache Parquet هو تنسيق تخزين عمودي محسّن لأحمال العمل التحليلية. يقدم مزايا كبيرة مقارنة بـ JSON وCSV لمجموعات بيانات التعليق التوضيحي الكبيرة: أحجام ملفات أصغر (ضغط 5-10 أضعاف عادةً)، وقراءات أسرع لاستعلامات مجموعة فرعية من الأعمدة، ودعم أصلي في كل أداة علوم بيانات تقريباً (pandas، DuckDB، PyArrow، Spark، Polars، Hugging Face Datasets).
يمكن لـ Potato تصدير التعليقات التوضيحية مباشرة إلى تنسيق Parquet، مما يُنتج ثلاثة ملفات منظّمة تغطي جميع أنواع التعليق التوضيحي.
تفعيل تصدير Parquet
كتنسيق إخراج أساسي
output_annotation_dir: "output/"
output_annotation_format: "parquet"كتصدير ثانوي (الإبقاء على JSON أساسياً)
output_annotation_dir: "output/"
output_annotation_format: "jsonl"
parquet_export:
enabled: true
output_dir: "output/parquet/"
auto_export: true # export after each annotation sessionعند الطلب عبر سطر الأوامر
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_link، event_annotation، coreference). صف واحد لكل نطاق معلّق.
| العمود | النوع | الوصف |
|---|---|---|
instance_id | string | معرّف الحالة |
annotator | string | اسم مستخدم المعلّق |
schema_name | string | اسم مخطط التعليق التوضيحي |
span_id | string | معرّف النطاق الفريد |
text | string | محتوى نص النطاق |
start_offset | int32 | إزاحة بداية الحرف |
end_offset | int32 | إزاحة نهاية الحرف |
label | string | تسمية النطاق |
field | string | الحقل المصدر (للتعليق التوضيحي متعدد الحقول) |
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 |
خيارات الضغط
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
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
-- 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
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
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
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)التصدير التدريجي
لمشاريع التعليق التوضيحي طويلة الأمد، فعّل التصدير التدريجي لتجنب إعادة تصدير مجموعة البيانات بالكامل في كل مرة:
parquet_export:
enabled: true
output_dir: "output/parquet/"
incremental: true
partition_by: date # date, annotator, or noneمع partition_by: date، تُنظّم ملفات Parquet في مجلدات مقسّمة حسب التاريخ:
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
يمكن قراءة مجموعات البيانات المقسّمة كجدول منطقي واحد بواسطة جميع الأدوات الرئيسية:
# pandas reads partitioned directories automatically
df = pd.read_parquet("output/parquet/annotations/")
# DuckDB handles partitions natively
# SELECT * FROM 'output/parquet/annotations/**/*.parquet'مرجع التهيئة
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مثال كامل
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بعد التعليق التوضيحي، حمّل وحلّل:
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())قراءات إضافية
- تنسيقات التصدير -- تنسيقات COCO، YOLO، CoNLL، وغيرها
- مصادر البيانات البعيدة -- تحميل البيانات من التخزين السحابي
- لوحة تحكم المشرف -- مراقبة حالة التصدير
للاطلاع على تفاصيل التنفيذ، راجع الوثائق المصدرية.