Skip to content

Exportação para Parquet

Exporte anotações do Potato para o Apache Parquet — um formato colunar otimizado para pipelines de ML em larga escala, com integração para Spark, DuckDB, Pandas e HuggingFace Datasets.

Novidade na v2.3.0

O Apache Parquet é um formato de armazenamento colunar otimizado para cargas de trabalho analíticas. Ele oferece vantagens consideráveis sobre JSON e CSV para grandes conjuntos de anotações: arquivos menores (compressão típica de 5 a 10x), leituras mais rápidas para consultas a um subconjunto de colunas e suporte nativo em praticamente todas as ferramentas de ciência de dados (pandas, DuckDB, PyArrow, Spark, Polars, Hugging Face Datasets).

O Potato pode exportar anotações diretamente para o formato Parquet, gerando três arquivos estruturados que cobrem todos os tipos de anotação.

Habilitando a exportação para Parquet

Como formato de saída principal

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

Como exportação secundária (mantendo o JSON como 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

Sob demanda via CLI

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

Arquivos de saída

A exportação para Parquet gera três arquivos, cada um representando um nível diferente dos dados de anotação.

1. annotations.parquet

O arquivo de saída principal. Uma linha por combinação (instância, anotador, esquema).

ColunaTipoDescrição
instance_idstringIdentificador da instância
annotatorstringNome de usuário do anotador
schema_namestringNome do esquema de anotação
valuestringValor da anotação (codificado em JSON para tipos complexos)
timestamptimestampQuando a anotação foi criada
duration_msint64Tempo gasto nesta instância (milissegundos)
session_idstringIdentificador da sessão de anotação

Para tipos de anotação simples (radio, likert, text), value contém o valor bruto. Para tipos complexos (multiselect, spans, events), value contém uma string JSON.

2. spans.parquet

Para tipos de anotação baseados em spans (span, span_link, event_annotation, coreference). Uma linha por span anotado.

ColunaTipoDescrição
instance_idstringIdentificador da instância
annotatorstringNome de usuário do anotador
schema_namestringNome do esquema de anotação
span_idstringIdentificador único do span
textstringConteúdo textual do span
start_offsetint32Deslocamento inicial em caracteres
end_offsetint32Deslocamento final em caracteres
labelstringRótulo do span
fieldstringCampo de origem (para anotação de spans em múltiplos campos)
linksstringDados de vínculo codificados em JSON (para span_link)
attributesstringAtributos adicionais codificados em JSON

3. items.parquet

Metadados sobre cada instância no conjunto de dados. Uma linha por instância.

ColunaTipoDescrição
instance_idstringIdentificador da instância
textstringConteúdo textual principal
annotation_countint32Número de anotações recebidas
annotatorsstringLista JSON dos nomes de usuário dos anotadores
statusstringStatus da instância (pending, in_progress, complete)
metadatastringMetadados da instância codificados em JSON

Opções de compressão

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

Comparação de compressão

AlgoritmoTaxa de compressãoVelocidade de escritaVelocidade de leituraIndicado para
snappyModeradaRápidaRápidaUso geral (padrão)
gzipAltaLentaModeradaArquivamento, arquivos pequenos
zstdAltaRápidaRápidaMelhor equilíbrio entre tamanho e velocidade
lz4BaixaMuito rápidaMuito rápidaCargas de trabalho críticas em velocidade
brotliMuito altaMuito lentaModeradaCompressão máxima
noneNenhumaA mais rápidaA mais rápidaDepuração

Para a maioria dos projetos de anotação, a compressão snappy padrão é uma boa escolha. Para conjuntos de dados grandes em que o tamanho do arquivo importa, use zstd.

Carregando dados em 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)

Exportação incremental

Para projetos de anotação de longa duração, habilite a exportação incremental para evitar reexportar todo o conjunto de dados a cada vez:

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

Com partition_by: date, os arquivos Parquet são organizados em diretórios particionados por data:

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

Conjuntos de dados particionados podem ser lidos como uma única tabela lógica por todas as principais ferramentas:

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

Referência de configuração

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

Exemplo completo

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

Após a anotação, carregue e analise:

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

Leitura adicional

Para detalhes de implementação, consulte a documentação de origem.