Skip to content

Export Parquet

Exportez les annotations Potato vers Apache Parquet — un format en colonnes pensé pour les chaînes d'apprentissage automatique à grande échelle, avec intégration Spark, DuckDB, Pandas et HuggingFace Datasets.

Nouveau dans la v2.3.0

Apache Parquet est un format de stockage en colonnes conçu pour les charges de travail analytiques. Sur de gros jeux de données d'annotation, il apporte plusieurs avantages par rapport à JSON et CSV : des fichiers plus petits (compression de l'ordre de 5 à 10x), des lectures plus rapides quand la requête ne porte que sur quelques colonnes, et une prise en charge native dans à peu près tous les outils de science des données (pandas, DuckDB, PyArrow, Spark, Polars, Hugging Face Datasets).

Potato exporte les annotations directement au format Parquet, sous la forme de trois fichiers structurés qui couvrent tous les types d'annotation.

Activer l'export Parquet

Comme format de sortie principal

yaml
output_annotation_dir: "output/"
output_annotation_format: "parquet"

Comme export secondaire (en gardant JSON comme format principal)

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

À la demande, via la CLI

bash
python -m potato.export parquet --config config.yaml --output ./parquet_output/

Fichiers produits

L'export Parquet produit trois fichiers, chacun correspondant à un niveau différent des données d'annotation.

1. annotations.parquet

Le fichier de sortie principal. Une ligne par combinaison (instance, annotateur, schéma).

ColonneTypeDescription
instance_idstringIdentifiant de l'instance
annotatorstringNom d'utilisateur de l'annotateur
schema_namestringNom du schéma d'annotation
valuestringValeur de l'annotation (encodée en JSON pour les types complexes)
timestamptimestampDate de création de l'annotation
duration_msint64Temps passé sur cette instance (en millisecondes)
session_idstringIdentifiant de la session d'annotation

Pour les types d'annotation simples (radio, likert, text), value contient la valeur brute. Pour les types complexes (multiselect, empans, événements), value contient une chaîne JSON.

2. spans.parquet

Pour les types d'annotation par empans (span, span_link, event_annotation, coreference). Une ligne par empan annoté.

ColonneTypeDescription
instance_idstringIdentifiant de l'instance
annotatorstringNom d'utilisateur de l'annotateur
schema_namestringNom du schéma d'annotation
span_idstringIdentifiant unique de l'empan
textstringContenu textuel de l'empan
start_offsetint32Décalage du caractère de début
end_offsetint32Décalage du caractère de fin
labelstringÉtiquette de l'empan
fieldstringChamp source (pour l'annotation d'empans sur plusieurs champs)
linksstringDonnées de lien encodées en JSON (pour span_link)
attributesstringAttributs supplémentaires encodés en JSON

3. items.parquet

Les métadonnées de chaque instance du jeu de données. Une ligne par instance.

ColonneTypeDescription
instance_idstringIdentifiant de l'instance
textstringContenu textuel principal
annotation_countint32Nombre d'annotations reçues
annotatorsstringListe JSON des noms d'utilisateur des annotateurs
statusstringÉtat de l'instance (pending, in_progress, complete)
metadatastringMétadonnées de l'instance encodées en JSON

Options de compression

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

Comparaison des algorithmes de compression

AlgorithmeTaux de compressionVitesse d'écritureVitesse de lectureConvient à
snappyMoyenRapideRapideUsage courant (par défaut)
gzipÉlevéLentMoyenArchivage, petits fichiers
zstdÉlevéRapideRapideMeilleur compromis taille/vitesse
lz4FaibleTrès rapideTrès rapideCharges où la vitesse prime
brotliTrès élevéTrès lentMoyenCompression maximale
noneAucuneLa plus rapideLa plus rapideDébogage

Pour la plupart des projets d'annotation, la compression snappy par défaut convient. Sur de gros jeux de données où la taille des fichiers compte, préférez zstd.

Charger des données 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)

Export incrémental

Sur les projets d'annotation au long cours, activez l'export incrémental pour ne pas réexporter tout le jeu de données à chaque fois :

yaml
parquet_export:
  enabled: true
  output_dir: "output/parquet/"
  incremental: true
  partition_by: date             # date, annotator, or none

Avec partition_by: date, les fichiers Parquet sont rangés dans des répertoires partitionnés par date :

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

Tous les grands outils savent lire un jeu de données partitionné comme une seule table logique :

python
# pandas reads partitioned directories automatically
df = pd.read_parquet("output/parquet/annotations/")
 
# DuckDB handles partitions natively
# SELECT * FROM 'output/parquet/annotations/**/*.parquet'

Référence de configuration

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

Exemple complet

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

Une fois l'annotation terminée, chargez les données et analysez-les :

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

Pour aller plus loin

Pour les détails d'implémentation, consultez la documentation source.