Skip to content

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가 필요합니다.

bash
pip install "pyarrow>=12.0.0"

내보내기 실행

Parquet은 Potato에 등록된 내보내기 포맷 중 하나이므로 내보내기 CLI가 직접 작성합니다.

bash
python -m potato.export -c config.yaml -f parquet -o ./parquet_export/

Potato는 설정의 output_annotation_dir 아래에 기록된 주석을 읽고, 지정한 출력 디렉터리에 Parquet 테이블을 작성합니다.

주기적 내보내기

장기간 진행되는 연구에서는 보통 누군가 CLI 실행을 기억하는 데 의존하지 않고 Parquet 스냅샷을 얻고 싶어 합니다. export_annotation_format을 설정하면 Potato가 주석 작업이 진행되는 동안 주기적으로 내보내기를 작성합니다.

yaml
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으로 읽고 경고를 기록하므로 이름을 바꾸십시오.

출력 파일

내보내기는 최대 네 개의 테이블을 생성하며, 각 테이블은 데이터의 서로 다른 수준을 다룹니다.

annotations.parquet

(인스턴스, 주석자) 쌍마다 한 행이며, 스키마는 컬럼으로 펼쳐집니다.

컬럼타입설명
instance_idstring주석이 달린 항목의 ID
user_idstring주석자의 ID
<schema_name>다양함주석 스키마마다 한 컬럼

컬럼 타입은 주석 유형을 따릅니다. radio와 select는 선택된 레이블을 담은 문자열이 되고, likert, slider, number는 float64가 되며(이름 있는 레이블이 있는 척도라면 문자열), multiselect는 문자열 리스트가 되고, text는 문자열이 됩니다.

여러 응답을 한 번에 저장하는 스키마는 하위 응답마다 <schema>.<key>라는 이름의 컬럼을 하나씩 받습니다. handling이라는 이름의 multirate 스키마는 handling.Urgency와 handling.Customer tone을 만들고, constant_sum과 soft_label도 같은 방식으로 나뉩니다. 단일 선택은 한 컬럼에 그대로 남습니다. 데이터프레임에서 쓸 수 있는 형태는 severity가 "Serious"를 담고 있는 것이기 때문입니다.

모든 컬럼이 모든 행에 나타나며, 주석자가 응답하지 않은 곳은 null로 채워집니다. 따라서 일부 주석자만 도달한 스키마도 파일에 그대로 남습니다.

spans.parquet

스팬 주석마다 한 행이며, 연구에 스팬 스키마가 있을 때 작성됩니다.

컬럼타입설명
instance_idstring주석이 달린 항목의 ID
user_idstring주석자의 ID
schema_namestring스팬 스키마 이름
startint스팬이 시작하는 문자 오프셋
endint스팬이 끝나는 문자 오프셋
labelstring스팬의 레이블
textstring스팬의 텍스트 내용
target_fieldstring스팬이 그려진 표시 필드, 필드가 하나인 페이지에서는 ""
titlestring레이블의 표시 제목
span_idstring저장된 스팬의 id, schema_label_start_end
format_coordsstringJSON. PDF 스팬은 페이지와 상자, 스프레드시트 스팬은 행과 열
additional_partsstringJSON. 불연속 스팬의 나머지 범위
kb_id, kb_source, kb_labelstring연결된 스팬이 가리키는 엔티티

연구에 스팬 대상이 둘 이상이면 항상 target_field를 읽으십시오. 하나의 스팬 스키마가 페이지의 모든 스팬 대상 필드에 기록하므로 필드는 스키마 안에서도 달라지며, span_id로는 이를 복원할 수 없습니다. 같은 단어를 두 필드에서 표시하면 그 밖의 값은 모두 같은 행이 만들어집니다.

text는 저장되지 않고 내보내기 시점에 오프셋에서 도출되므로 오프셋과 어긋날 수 없습니다.

items.parquet

원본 항목마다 한 행이며, 원본 데이터를 담습니다.

컬럼타입설명
item_idstring항목의 ID
<field_name>다양함원본 데이터의 필드마다 한 컬럼(중첩된 딕셔너리와 리스트는 JSON으로 직렬화됨)

phase_responses.parquet

연구가 동의서나 연구 후 설문 같은 단계 데이터를 수집했고 export_include_phase_data가 true일 때 작성됩니다. 이 키의 기본값은 false이므로, 동의와 연구 후 설문을 수집한 연구라도 이 키를 켜기 전까지는 phase_responses.parquet이 생기지 않으며, 내보내기는 제외된 응답을 경고로 나열합니다.

내보내기 옵션

포맷 옵션은 --option key=value로 전달하며, 옵션마다 플래그를 반복합니다.

bash
python -m potato.export -c config.yaml -f parquet -o ./parquet_export/ \
    --option compression=zstd --option include_items=false
옵션기본값설명
compressionsnappy압축 코덱: snappy, gzip, zstd, lz4 또는 none
include_itemstrue원본 데이터가 담긴 items.parquet 작성
include_spanstrue스팬 주석이 있을 때 spans.parquet 작성
row_group_sizePyArrow 기본값annotations.parquet의 행 그룹 크기

대부분의 프로젝트에는 snappy가 알맞은 기본값입니다. 압축 해제가 충분히 빨라 읽기가 대화형 속도로 유지되기 때문입니다. 파일 크기가 중요할 만큼 데이터셋이 크다면 zstd를 선택하십시오. 비슷한 속도에서 압축률이 훨씬 높습니다.

Parquet 데이터 불러오기

pandas

python
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

sql
-- 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

python
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

python
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

python
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을 함께 작성하는 스팬 주석 프로젝트입니다.

yaml
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

주석 작업이 끝나면 엔티티 오프셋을 담고 있는 것은 스팬 테이블입니다.

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

더 읽어보기

구현 세부 정보는 소스 문서를 참조하십시오.