ECHO Data Package 1.0.0
A common layout for every dataset ECHO distributes: the original files, unchanged, plus the same data converted to a small set of standard tables with documented columns, in open formats, with machine-readable metadata, licence and credit.
This page describes version 1.0.0 of the standard (draft).
Standards used
| File | Standard | Role |
|---|---|---|
datapackage.json |
Frictionless Data Package 2.0 | Package descriptor: resources, Table Schema for every table, licences, sources and contributors. |
croissant.json |
MLCommons Croissant 1.1 | Machine-learning metadata read by dataset search engines and ML platforms; generated from datapackage.json. |
CITATION.cff |
Citation File Format 1.2.0 | Citation metadata; BibTeX in CITATION.bib carries the same entries. |
LICENSE.md |
SPDX License List | Licences are SPDX identifiers or expressions; terms with no SPDX identifier use a LicenseRef- with the full text. |
manifest-sha256.txt |
BagIt manifest RFC 8493 | A SHA-256 checksum for every file in the package. |
data/*.parquet |
Apache Parquet | The canonical tables: typed, compressed (zstd), columnar, and queryable over HTTP. |
csv/*.csv.gz |
CSV RFC 4180 | Copies of the tables for any tool: UTF-8, header row, gzip. |
jsonl/*.jsonl.gz |
JSON Lines | Copies of text and nested tables, one record per line, gzip. |
graph/graph.graphml.gz |
GraphML | Networks in a format graph tools open directly. |
Layout
A package lives at /download/<slug>/. /download//download/<slug>/v<MAJOR.MINOR.PATCH>/, containing:
| Path | Required | Contents |
|---|---|---|
README.md |
yes | The human entry point, written by ECHO for this dataset. |
datapackage.json |
yes | Frictionless descriptor: every table and column, licence, sources, contributors. |
croissant.json |
yes | Croissant metadata for ML tools, generated from datapackage.json. |
CITATION.cff |
yes | How to cite: ECHO's entry first, then the dataset's. |
CITATION.bib |
yes | The same citations in BibTeX. |
LICENSE.md |
yes | The SPDX licence, the upstream licence text verbatim, and ECHO's redistribution note. |
CHANGELOG.md |
yes | What changed in each version, and why. |
manifest-sha256.txt |
yes | SHA-256 of every file in the version folder. |
data/ |
yes | The canonical tables in Parquet. This is the contract; every other format is generated from it. |
csv/ |
where applicable | The tables as gzip CSV, where policy.yaml gives a table a CSV copy. |
jsonl/ |
where applicable | Text and nested tables as gzip JSON Lines, where policy.yaml gives a table a JSONL copy. |
graph/ |
where applicable | Networks only: edges.tsv.gz and nodes.tsv.gz, plus GraphML where policy.yaml allows. |
docs/upstream/ |
where applicable | The upstream README, codebook and licence files, verbatim, under their original names. |
original/ |
where applicable | The upstream files exactly as published, where the licence allows redistribution. |
Rules:
- A published version folder never changes. Corrections are a new version with a CHANGELOG entry. One exception: while ECHO's datasets are first being converted to this format, every package is released as 1.0.0, and a correction during that period rebuilds 1.0.0 in its place (its manifest-sha256.txt changes with it). Once the conversion is complete, this exception ends.
- data/ is the contract. csv/, jsonl/ and graph/ are generated from it and never edited by hand.
- Every table can be downloaded, or queried over HTTP, on its own.
- original/ holds the upstream files byte for byte; nothing in it is ever modified.
- Operating-system junk (__MACOSX, .DS_Store, ._*, Zone.Identifier, Thumbs.db, .ipynb_checkpoints) and saved error pages are never packaged.
- Every package records the recipe that built it: datapackage.json gives the recipe's commit in ECHO's recipe repository and the SHA-256 of its text, so a package can be rebuilt and checked.
- A partitioned table's parts may be the same files as the tables of other packages: a year of a monthly series, for example, is made of the months' files. They carry the same checksums; nothing is copied or changed.
- Licence: the package carries the licence the original publisher states. Where the publisher states none, the licence is NOASSERTION and LICENSE.md says so: ECHO assigns no licence and redistributes the publicly released files for research and teaching, with credit. Terms that are not a standard licence (for example research use only) are quoted verbatim under a LicenseRef identifier. A licence that a re-uploader declared on content they did not create is reported in the note but not applied. A lossless change of format is a technical modification, not an adaptation, so NoDerivatives licences are respected.
Conventions
Names. Tables and columns are snake_case. A platform-specific table or column keeps its meaning under an x_ prefix (x_score, x_gilded, x_votes), so it can never collide with a standard one. x_ stands for extension; it has nothing to do with the platform X (Twitter).
Identifiers.
- Every ID is a string, exactly as upstream wrote it. IDs are never converted to numbers, because large integers lose digits as floating-point numbers.
- Where upstream gives no ID, ECHO mints one as echo:
: ; the column's schema marks it minted, and minted IDs are stable only within one version. - Where two kinds of node share a numbering (bipartite and heterogeneous graphs), node IDs are prefixed with their type: user:17, item:17.
- IDs that were damaged upstream (rounded by a spreadsheet, written in scientific notation) are kept as found and flagged in the column's quality note. They are never repaired, because a repaired ID would point to a different record.
Time.
- Absolute times are ISO 8601 timestamps in UTC (created_at), kept to the microsecond. The upstream time zone or offset, where known, is recorded in the column's schema; a source time finer than a microsecond is kept as a string.
- Where upstream gives no time zone and the data give no evidence of one, the time is kept as text exactly as written, not presented as UTC; the column note says so.
- Where upstream recorded only a date, the column is a date, not a timestamp at midnight.
- Relative or ordinal times (seconds since the start of a study, "26 days ago", survey waves) go in a column t with a declared time_unit and time_origin. They are never presented as dates.
Text.
- Text is UTF-8, kept exactly as the source wrote it: no Unicode normalisation, lower-casing, stripping, HTML unescaping or spelling correction. Text from different sources may therefore use different Unicode forms for the same characters; normalise (for example to NFC) before comparing or matching text.
- Where upstream text is mis-encoded (mojibake), it is kept as found and the column carries a quality note; the original bytes remain in original/.
- Where upstream text is not valid UTF-8, the column holds the text decoded with replacement characters and an x_
_bytes column holds the original bytes (as hexadecimal text) for exactly those rows, so nothing is lost. - Where upstream text is not valid UTF-8 and the package also holds a clean copy of the same records (for example a Kaggle copy of a CrowdFlower file), the text column holds the clean copy's text for every record that could be matched to exactly one record of the clean copy on fields other than the text itself. The upstream text is kept as found in x_
for every row, with its original bytes in x _bytes, and x _source names the file each value of the text column came from. The README notes give the matching fields and say how many rows take their text from which file; a record that cannot be matched without doubt keeps the upstream text.
Values.
- Booleans are true booleans, never the strings "True"/"False" or 0/1 codes.
- Missing values are null. Upstream sentinels (-1, NaN, "", "N/A") become null only where upstream documents them as missing; otherwise they are kept and noted.
- Lists, vectors and nested records are JSON text columns (type json) in EDP 1.0; their structure is described in the column note.
- Numbers keep their upstream precision; counts are integers.
- Placeholder values: a value that stands in for a missing, hidden or anonymous entity rather than naming a real one (an author written as None, as 1234567? or as [deleted]) is kept exactly as found, never turned into null, because upstream does not document it as missing. The recipe declares such values for a string column, and the builder counts them in the built table and writes them to the column's field metadata in datapackage.json, as echo:placeholders: a list of entries {value or pattern, count, distinct, about}, where value is an exact string, pattern is a regular expression (RE2 syntax) that must match the whole value, count is the number of rows of this package that hold such a value, distinct the number of different values among them, and about says what the value stands for. A consumer that wants real entities only (users, authors) excludes these values; a column without echo:placeholders declares none.
Lossless conversion.
- Every conversion is checked against the source: row counts per table, and a value-by-value comparison of a sample of rows. A conversion that loses or changes information fails the build.
- Where a source value cannot be represented in its standard column without loss, it is kept as a string in an x_ column and noted.
Standard tables
A package contains the tables its data supports. Columns marked required are present in every copy of that table; the others appear where the data has them. A column specific to one platform keeps its meaning under an x_ prefix. Types: string, integer, number, boolean, timestamp (UTC), date, list and json.
posts
Pieces of content people wrote: posts, comments, replies, tweets, messages, reviews.
Primary key: post_id.
| Column | Type | Required | Meaning |
|---|---|---|---|
post_id |
string | yes | The post's ID as upstream wrote it. |
author_id |
string | Who wrote it; joins to users.user_id. | |
created_at |
timestamp | When it was posted (UTC). | |
t |
number | Relative time, where the source has no absolute time; see time_unit and time_origin. | |
title |
string | ||
text |
string | The content, as written. Null where the package does not include text. | |
parent_id |
string | The post this one replies to. | |
thread_id |
string | The conversation it belongs to; joins to threads.thread_id. | |
community_id |
string | Where it was posted (subreddit, board, group, channel); joins to communities.community_id. | |
lang |
string | Language, as a BCP 47 tag, where upstream recorded it. | |
url |
string | ||
item_id |
string | The item it is about (video, product, article); joins to items.item_id. | |
snapshot_id |
string | Which crawl or snapshot it comes from, for repeated collections. | |
collection_member |
string | Which member dataset it belongs to, in a collection package. |
threads
Conversations or discussion threads.
Primary key: thread_id.
| Column | Type | Required | Meaning |
|---|---|---|---|
thread_id |
string | yes | |
community_id |
string | ||
title |
string | ||
created_at |
timestamp | ||
root_post_id |
string |
users
Accounts, users or authors.
Primary key: user_id.
| Column | Type | Required | Meaning |
|---|---|---|---|
user_id |
string | yes | |
handle |
string | Screen name, where the licence and privacy review allow it. | |
name |
string | Display name, where the licence and privacy review allow it. | |
created_at |
timestamp | ||
is_bot |
boolean | Only where upstream labelled it. |
communities
Places where content is posted: subreddits, boards, groups, channels, instances.
Primary key: community_id.
| Column | Type | Required | Meaning |
|---|---|---|---|
community_id |
string | yes | |
name |
string | ||
platform |
string | ||
description |
string |
memberships
Who belongs to which group.
| Column | Type | Required | Meaning |
|---|---|---|---|
user_id |
string | yes | |
group_id |
string | yes | |
role |
string | ||
joined_at |
timestamp |
nodes
Nodes of a network, with their attributes.
Primary key: node_id.
| Column | Type | Required | Meaning |
|---|---|---|---|
node_id |
string | yes | |
node_type |
string | For networks with more than one kind of node. | |
label |
string | ||
layer |
string | For packages with several networks. | |
snapshot_id |
string | For attributes recorded per crawl: the snapshot this row describes. |
edges
Links of a network. One row per link as upstream recorded it: repeated links and events are never merged.
| Column | Type | Required | Meaning |
|---|---|---|---|
src |
string | yes | |
dst |
string | yes | |
weight |
number | ||
sign |
integer | +1 or -1 in signed networks. | |
created_at |
timestamp | ||
t |
number | Relative time; see time_unit and time_origin. | |
edge_type |
string | ||
layer |
string | Which network the link belongs to, in multilayer packages. | |
snapshot_id |
string | ||
rank |
integer |
interactions
A user acting on an item: ratings, likes, check-ins, plays, bookmarks.
| Column | Type | Required | Meaning |
|---|---|---|---|
user_id |
string | yes | |
item_id |
string | yes | |
kind |
string | ||
value |
number | The rating or count, where there is one. | |
created_at |
timestamp |
items
Things users act on or post about: videos, products, images, articles, venues.
Primary key: item_id.
| Column | Type | Required | Meaning |
|---|---|---|---|
item_id |
string | yes | |
kind |
string | ||
title |
string | ||
url |
string | ||
snapshot_id |
string | For records taken from repeated crawls: the snapshot this row describes. |
annotations
Labels attached to posts, users, threads, links or text spans, by people, models or rules.
| Column | Type | Required | Meaning |
|---|---|---|---|
target_id |
string | yes | |
target_type |
string | yes | post |
task |
string | What is being labelled (toxicity, stance, check-worthiness…). | |
label |
string | The label as upstream coded it. | |
label_text |
string | What the code means, from upstream documentation. | |
span_start |
integer | ||
span_end |
integer | ||
annotator |
string | ||
annotator_kind |
string | human | |
confidence |
number | ||
split |
string | train |
media
Images, video or audio attached to posts.
Primary key: media_id.
| Column | Type | Required | Meaning |
|---|---|---|---|
media_id |
string | yes | |
post_id |
string | ||
kind |
string | ||
path |
string | Path of the file inside original/. | |
url |
string | ||
mime |
string |
ids
Identifiers only, for collections whose terms allow sharing IDs but not content.
| Column | Type | Required | Meaning |
|---|---|---|---|
id |
string | yes | |
id_type |
string | yes | tweet |
collection |
string | ||
collected_at |
timestamp |
snapshots
The crawls or snapshots in a repeated collection.
Primary key: snapshot_id.
| Column | Type | Required | Meaning |
|---|---|---|---|
snapshot_id |
string | yes | |
taken_at |
timestamp | ||
description |
string |
splits
Alternative or k-fold splits.
| Column | Type | Required | Meaning |
|---|---|---|---|
target_id |
string | yes | |
split |
string | yes | |
fold |
integer | ||
scheme |
string |
timeseries
Aggregate measurements over time, in long format.
| Column | Type | Required | Meaning |
|---|---|---|---|
entity_id |
string | yes | |
period |
string | yes | ISO 8601 date or period. |
metric |
string | yes | |
value |
number |
features
Precomputed features or embeddings, with a feature dictionary in the schema.
| Column | Type | Required | Meaning |
|---|---|---|---|
target_id |
string | yes | |
vector |
list |
Structure properties
Each package is tagged with the structural properties of its data. They are computed from the tables themselves, and can be used to find datasets on ECHO (Browse → Structure).
| Property | Meaning |
|---|---|
| Directed network | Edges declared directed |
| Weighted network | Edges.weight present and not constant |
| Signed network | Edges.sign present |
| Bipartite network | Two node types, every edge joins different types |
| Multigraph (repeated links) | Some (src, dst[, layer]) pair occurs more than once |
| Temporal (timestamped) | Edges or posts carry created_at or t |
| Repeated crawls / snapshots | Snapshots table has 2 or more rows |
| Multilayer (several networks) | Edges.layer has 2 or more values |
| Node attributes | Nodes or users have columns beyond the ID |
| Labelled data | Annotations table present |
| Text | Posts.text present |
| Threaded conversations | Posts.parent_id or threads present |
| Images or media | Media table present |
| Identifiers only | Ids table present and no content tables |
| Collection | Package has member views |
Collections, views and derived packages
- view of: This package is a filtered view of a collection package: {package, version, filter}.
- derived from: This package is a slice or an enrichment of another package: {package, version, how}.
- members: This package is a collection; its member packages are listed with their filters.
Metadata
Contributors. Everyone credited, with their role: the creators of the data, the original publisher, every redistributor in between (for example SNAP → KONECT → Network Repository), and ECHO as packager. Taken from upstream documentation, never guessed. Roles: creator, contributor, publisher, distributor, packager.
Privacy review. What personal information the package contains, what was withheld and why. A withheld column stays in the schema, marked withheld, so users can see that it exists.
Quality notes. Per-column notes on known upstream problems (damaged IDs, mis-encoded text, sentinel values), so no user mistakes them for ECHO's conversion.
Fields for software and AI tools. Fields that let APIs, ML pipelines and language-model tools use a package without reading its README.
text_columns: The columns holding natural-language text, with their language where known.id_columns: The columns holding identifiers, and what they join to.row_license: Where the licence varies by row, the column that holds it.embedding_columns: Vector columns, with the model that produced them.languages: The package-levellanguageslist in datapackage.json: the languages of the dataset's text as recorded in ECHO's catalog when the package metadata was written, as lower-case ISO 639 codes such as en, de or fil, sorted. It describes the dataset as a whole, not individual columns or rows, and is absent where the catalog records no language. Packages published before this property was added do not carry it; the catalog is the source for them.placeholders: Theecho:placeholderslist of a column's schema field: placeholder values (see Values above) with their counts, so that a tool can tell them from real entities without reading the README.
The README
Every package's README.md has these sections, in this order:
: One paragraph describing the dataset, written for it. - At a glance: Platform, kind of data, period covered, size, version, licence, DOI.
- Contents: Every file, its role and format.
- Tables and columns: Generated from datapackage.json: every column with type, meaning and quality notes.
- Quick start: Loading the main table in Python (pandas), DuckDB and R.
- How this package was made: The source files, what was converted, what was left out and why, known issues.
- Credit: Creators, publisher and redistributors, with links to the upstream documentation.
- Licence and terms: The licence and what it permits.
- Citation: ECHO's entry first, then the dataset's.
- Versions: This version, the changelog, and how to cite a specific version.
Versions
Versions are numbered MAJOR.MINOR.PATCH.
- MAJOR: A table or column is removed or changes meaning, or rows are removed.
- MINOR: Rows, tables or columns are added, or upstream published new data.
- PATCH: Documentation or metadata only; the data is unchanged.
datapackage.json records the recipe revision and the SHA-256 of every original file used, so any version can be rebuilt exactly.
Checks before publication
A package version is published only when all of these checks pass:
- datapackage.json is valid; every table matches its Table Schema, primary keys are unique and declared foreign keys resolve. Where a table has snapshot_id or layer columns, the key is unique within each snapshot and layer.
- Each table's row count matches the source and the previous version, within the tolerance in policy.yaml; a larger change needs an admin's approval.
- A sample of rows is compared value by value with the source files.
- No original is an HTML or error page, zero bytes, operating-system junk, or a file whose content contradicts its extension; no pickle reaches data/.
- Text columns are valid UTF-8 with no NUL bytes; mojibake and ID columns in scientific notation are flagged.
- Text and user columns are scanned for e-mail addresses, phone numbers and street addresses; findings go to the privacy review (they do not block on their own).
- An SPDX licence expression is present, with the upstream text for any LicenseRef.
- An original already published in another package is declared as a view, a derivative or a shared original.
