Data Formats¶
Prepare your input data¶
Upload one or more files containing documents to be annotated in the
data folder.
We support multiple formats of raw data files, including: csv, tsv, json, or jsonl.
Each document needs, at minimum, a unique identifier and the body of the document.
CSV and TSV cells are loaded exactly as written, as text. An id of 007 stays
007, and the text NA or None stays NA or None. CSV uses standard
double-quote quoting, so a cell can contain commas and line breaks. TSV has no
quoting: a " in a TSV cell is part of the text, and a cell cannot contain a
tab or a line break. An empty id, or the same id twice, stops the server with
an error naming the row.
You can find example data files here. We currently support four different document formats:
- Text: body is the document plaintext (example)
- Image, Video, or GIF: body is the filepath (example)
- Dialogue or a list of text: body is a list of comma-seperated documents and potato will automatically display the list of text horizontally. (example)
- Pairs of text displayed in separate boxes: body is a dictionary of documents (example)
- Html files
- Best-Worst Scaling: body is a comma-separated list of documents to order (example)
- Custom Arguments: body is one of the above + extra fields for
whatever custom arguments you want to enter
(example
-- in this
kwargsandother_kwargsare the custom endpoints for a Likert scale) - Annotating Document A in context of Document B: body is document A +
extra
contextfield with the body of document B (example)
You can also use html tags to design the way your text to be displayed. In the match finding example project, html tags are used to create two seperate boxes for the finding pairs.
What the body field can hold¶
The field named by item_properties.text_key is expected to be a string, a
list or a dictionary. A null, a number or a boolean renders as an empty
string or as the bare value, and the server names the offending ids at
startup. Earlier versions raised a TypeError that named nothing, which on a
large export meant bisecting by hand.
Three things happen to a string on the way to the page, and each is worth knowing before you prepare data:
Runs of spaces and tabs collapse to one space. Span offsets have to match
what the browser measures, and this is how they are kept in step. It means the
default rendering path destroys indentation, a diff gutter and any
column-aligned table. If horizontal whitespace carries meaning in your data,
declare instance_display — that path preserves it
exactly, including at type: text. The server warns at startup when an item's
text contains a tab or a run of spaces and no instance_display is declared.
Control characters are stripped, other than newline.
HTML is sanitized, not escaped. Tags on an allowlist (<b>, <i>,
<span> and similar) render as formatting; <script> and anything else is
escaped and inert. This is deliberate: span highlights are injected into the
item as markup, so escaping the whole field would break them. So scraped
markup in your corpus becomes formatting rather than visible text.
Displaying a list or a dictionary of instances¶
Potato can display an instance that is a list or a dictionary rather than a string. Set
list_as_text to True.
For a list of instances, you can also add a prefix to each line
"list_as_text": {
"text_list_prefix_type": 'alphabet'
# whether adding a prefix to each instance in the list
# when creating displayed_text,
# options:
# alphabet: add alphabets before each instance (e.g. A.this is good, B.nice)
# number: add alphabets before each instance (e.g. 1.this is good, 2.nice)
},
the content will displayed vertically by default,
if you want to display the list or dict horizontally, you can set horizontal as True
"list_as_text": {
"horizontal": True
},
if you want to randomize the diplayed content for a dictionary, you can use randomization,
In this case, the order of the displayed content will be shuffled, so that you can avoid potential
biased caused by the ordering effect. You can access the displayed content in the annotated outputs.
"list_as_text": {
"randomization":"value" # whether randomizing the list or dictionary when creating displayed_text,
# options:
# "value": only shuffle the values but keep the order of the keys
# "key": shuffle the order of keys
},
Conversational corpora (ConvoKit)¶
A ConvoKit corpus is converted into a Potato data file with potato convokit
rather than reshaped by hand:
potato convokit conversations-gone-awry-corpus -o data/awry.jsonl
Each item carries the conversation's turns, with every turn's turn_id set to
the real ConvoKit utterance id so annotations can be exported back onto the
corpus:
{
"id": "convo:146743638.12652.12652",
"conversation": [
{"turn_id": "146743638.12652.12652", "speaker": "Sirex98", "text": "...",
"reply_to": null, "timestamp": 1185295934.0, "depth": 0,
"meta": {"toxicity": 0.0}}
],
"conversation_tree": {"id": "146743638.12652.12652", "children": []},
"text": "Sirex98: ...",
"convo_meta": {"page_title": "User talk:2005"},
"_convokit": {"corpus": "conversations-gone-awry-corpus", "unit": "conversation"}
}
The _convokit block is import provenance. It is never rendered — only keys
listed in instance_display.fields are — but it must survive into any data file
you post-process, because the ConvoKit exporter uses it to map annotations back.
See the ConvoKit integration guide for granularity options, metadata handling, and the export path.
File Encoding¶
By default, all input data files are read as UTF-8. If your data uses a different encoding (e.g., latin-1, shift_jis, gb2312), you can specify it per-file using the dict format in data_files:
data_files:
- data/utf8_data.json # string form → defaults to utf-8
- path: data/latin1_data.json # dict form without encoding → defaults to utf-8
encoding: latin-1
- path: data/japanese_data.csv
encoding: shift_jis
The encoding field accepts any encoding name recognized by Python (see Python Standard Encodings).
For directory watching, you can set the encoding for all files in the watched directory:
data_directory: ./data/incoming
data_directory_encoding: latin-1
watch_data_directory: true
Update input data formats on the YAML config file¶
You would pass the input data paths and field names into the YAML config file as follows (please make sure you have "id" and "text" key in your data):
# Pass in a comma-separated list of data files containing documents to be annotated in this task
"data_files": [
"data/toy-example1.json",
"data/toy-example2.json"
],
# Specify the field names containing the document unique identifier (id) and document body (text)
"item_properties": {
"id_key": "id",
"text_key": "text"
},
Update output data preferences on the YAML config file¶
The output file will include each labeled document\'s id and
annotations; the header will consist of the question and answer labels
specified in the
schema.
You need to specify a subdirectory of the annotation_output directory
where files for each annotator should be placed. We support multiple
output formats, including: csv, tsv, json, or jsonl.
# Potato will write the annotation file for all annotations to this
# directory, as well as per-annotator output files and state information
# necessary to restart annotation.
"output_annotation_dir": "annotation_output/folder_name/",
# Write a periodic export of everyone's annotations to
# <output_annotation_dir>/exports/<format>/. Any format the export registry
# knows works here: csv, tsv, jsonl, parquet, huggingface, coco and two dozen
# more. Leave it out and no export runs; the per-annotator JSON is written
# either way.
"export_annotation_format": "jsonl",