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

عند الطلب عبر سطر الأوامر

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_link، event_annotation، coreference). صف واحد لكل نطاق معلّق.

العمودالنوعالوصف
instance_idstringمعرّف الحالة
annotatorstringاسم مستخدم المعلّق
schema_namestringاسم مخطط التعليق التوضيحي
span_idstringمعرّف النطاق الفريد
textstringمحتوى نص النطاق
start_offsetint32إزاحة بداية الحرف
end_offsetint32إزاحة نهاية الحرف
labelstringتسمية النطاق
fieldstringالحقل المصدر (للتعليق التوضيحي متعدد الحقول)
linksstringبيانات الروابط المشفّرة بـ JSON (لـ span_link)
attributesstringالسمات الإضافية المشفّرة بـ JSON

3. items.parquet

البيانات الوصفية عن كل حالة في مجموعة البيانات. صف واحد لكل حالة.

العمودالنوعالوصف
instance_idstringمعرّف الحالة
textstringالمحتوى النصي الأساسي
annotation_countint32عدد التعليقات التوضيحية المستلمة
annotatorsstringقائمة JSON بأسماء مستخدمي المعلّقين
statusstringحالة الحالة (pending، in_progress، complete)
metadatastringالبيانات الوصفية للحالة مشفّرة بـ 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())

قراءات إضافية

للاطلاع على تفاصيل التنفيذ، راجع الوثائق المصدرية.