Skip to main content

Audit a labeled dataset before you trust it

Project description

raterkit

Audit a labeled dataset before you trust it.

ci

Every benchmark score, every fine-tune, every "our judge agrees with humans 92% of the time" bottoms out in a pile of human labels — produced by raters who may have been careless, biased, tired by the end of the queue, or graded against gold that was quietly wrong. None of that is visible in accuracy numbers computed on top. raterkit reads the raw ratings and measures each of these failure modes directly, with a confidence interval on every number and a flag only when the evidence clears a stated bar.

The audit in one table

Eight synthetic annotation projects, seven implanted defects. Bold values are flags; each lands on the row where its defect was implanted, and the clean row carries none.

project implanted defect alpha rogue biased drift gap leaks stale gold flags
clean nothing 0.80 0 0 +0.00 0 0.0% none
careless-crowd every rater at 55% accuracy 0.18 0 0 -0.02 0 0.7% reliability
one-spammer one rater answers uniformly at random 0.72 1 0 +0.05 0 0.0% rogue raters
leaning-rater one rater always takes the same side of ambiguous items 0.65 0 1 +0.05 0 - rater bias
tiring-crowd competence decays 0.97 to 0.73 over the queue 0.64 0 0 +0.25 0 0.0% drift
leaky-split 12 test items duplicate a train item's text 0.80 0 0 +0.00 12 0.0% split leakage
stale-gold 8% of gold labels are wrong 0.80 0 0 +0.00 0 6.7% stale gold
easy-but-skewed 94% of items are one class; raters 90% accurate 0.34 0 0 +0.12 0 0.0% reliability

The row to sit with is easy-but-skewed: raters are 90% accurate and agree with each other 81% of the time, yet alpha is 0.34 — far below the 0.667 floor. With 94% of items in one class, raters agree that often by accident; the skew, not the raters, is doing the agreeing. Raw agreement percentages cannot be compared across datasets with different label distributions, which is why every agreement number here is chance-corrected.

rater forest

depth curve

make demo regenerates the table, the full report and the figures from fixed seeds; CI rebuilds them from pinned dependencies and fails if a committed number differs from what the code produces.

211,225 real ratings, audited

examples/goemotions/ audits the raw crowd ratings behind GoEmotions — 211,225 ratings by 82 raters over 58,011 Reddit comments, 28 binary emotion labels each. Committed results: examples/goemotions/results/goemotions.md.

  • 27 of 28 emotions sit below the 0.667 reliability floor — the whole 95% CI, not just the point estimate. Only gratitude (alpha 0.72) passes; the median emotion manages 0.23, and neutral itself scores 0.26.
  • Rater quality spans 0.11 to 0.36 (rater-vs-rest alpha across the 67 raters with 300+ ratings) — the noisiest raters agree with their co-raters at a third of the rate the best ones do, and 20 of 67 sit below 0.20.
  • The rater count was never enough for the task. By Spearman-Brown, the observed reliability would need ~13 raters per item to yield 0.8-reliable aggregated labels; the dataset averages 3.6, which projects to 0.52.
  • 86% of multi-rated comments are contested on at least one emotion, yet raters marked only 1.6% of ratings "very unclear" — disagreement without awareness of disagreement.

None of this says GoEmotions is a scandal — fine-grained emotion is genuinely hard, and the released splits aggregate and filter these raw ratings. It says the raw layer under a famous benchmark fails the standard reliability bar almost everywhere, measurably and reproducibly.

Install

pip install git+https://github.com/mohammadi-hadi/raterkit

Python 3.11+. Runtime dependencies: numpy, pydantic, matplotlib.

Quickstart

Log one JSON object per rating — who labeled what, and optionally when, in which split, and the item text:

{"item_id": "c41", "rater_id": "r07", "label": "toxic", "t": 1718040000, "split": "train", "text": "..."}
from raterkit import load_ratings, load_gold, run_audit

audit = run_audit(load_ratings("ratings.jsonl"), gold=load_gold("gold.jsonl"))
for flag in audit.flags:
    print(flag.name, "--", flag.detail)

Or from the shell, with an exit code CI can gate on:

raterkit report ratings.jsonl --gold gold.jsonl --out audit --fail-on-flags

An optional config sets the measurement level (nominal, ordinal, interval), the label order, and the target reliability. Alongside report.md the audit writes report.json for pipelines that gate on specific numbers. Any check that could not run is listed with the exact fields to log to enable it — a skip describes the log file, not the raters.

An LLM judge is just another rater: give its verdicts a rater_id and the same audit reports how it agrees with the humans, whether its label distribution diverges, and whether it drifts — no special casing needed.

What it checks

check question it answers needs
reliability is agreement above the chance-corrected floor? 2+ ratings on some items
rogue raters does any rater agree with the rest credibly less than the pool? 30+ pairs per rater
rater bias does any rater use the labels differently (marginal shift)? 30+ ratings per rater
drift did agreement change between early and late work? t per rating
split leakage does any duplicated text span train/test? text + split
stale gold which gold labels does a unanimous crowd contradict? a gold file
ambiguity how many items are genuinely contested? 2+ ratings on some items
prevalence is raw agreement inflated by class skew? always runs
rating depth how many raters per item does your target reliability need? always runs

The last three are descriptive and never flag: they explain the numbers above them.

How the numbers are defended

  • Cross-checked implementations. Krippendorff's alpha (nominal, ordinal and interval, with missing data) matches the reference krippendorff package to 1e-10; Cohen's and Fleiss' kappa match statsmodels; the chi-square survival function matches scipy to 1e-10; Benjamini-Hochberg reproduces statsmodels decisions exactly. The library itself depends on none of them.
  • Validation by implantation. The synthetic projects have defect dials — a spammer, a leaning rater, a competence drift, a gold error rate — and the tests require each probe to recover its dial, stay monotone in it, and stay silent on the clean project.
  • Uncertainty on every number. Band probes trigger only when the whole 95% bootstrap CI leaves the innocent band; the bootstrap resamples items (never single ratings), so dependence within an item is respected.
  • Drift-checked results. The demo table, report and README numbers are regenerated by CI from pinned dependencies and diffed against the committed copy.

Design notes

  • Chance correction is not optional. The easy-but-skewed demo row and the kappa-paradox literature both show raw agreement rewarding class skew. All agreement numbers are Krippendorff's alpha, which handles missing ratings, any number of raters, and nominal/ordinal/interval labels in one framework — and reduces to the familiar coefficients where they overlap.
  • Innocent bands, not point nulls. With enough data any rater's marginal "significantly" differs from the pool's while meaning nothing. Band probes state the interval a healthy project could occupy (bias within 0.10 total variation, drift within ±0.10 alpha) and flag only when the whole CI leaves it. Exact procedures use conventions: the 0.667 floor, zero tolerance for cross-split duplicates. All constants live in one place (probes.py) and are easy to disagree with.
  • Raters are compared to the pool, not to a standard. A rater is rogue when their CI sits below the pool's CI — a claim about this project, not about an absolute bar their domain may not share.
  • Ambiguity is measured, not punished. Contested items are reported descriptively: persistent disagreement can be signal about the task, not noise from the raters, and deserves a number rather than a flag.
  • Deterministic to the digit. Seeded PCG64 everywhere, no BLAS in any statistic's path: the same ratings file gives the same report on any machine.

Limitations

  • An audit is bounded by what was logged. No timestamps, no drift check; no text or splits, no leakage check; no gold, no gold check.
  • The innocent bands and the 0.667 floor are conventions, not derivations; fields differ, and the constants are one file away.
  • Spearman-Brown projections assume added raters behave like existing ones — an optimistic bound, stated as such in the report.
  • The rogue-rater comparison needs overlap; raters who never co-rate an item with others cannot be tested and are counted as untested, not innocent.
  • Implantation shows the probes detect the mechanisms simulated; real annotation projects can fail in ways not simulated here.
  • The GoEmotions audit measures the raw rating layer; the released benchmark splits aggregate and filter it, so per-label alpha here is not a direct statement about scores computed on those splits.

Related work

The failure modes measured here are the standard ones from the agreement and annotation-quality literature: agreement coefficients and their proper use (Artstein & Poesio, 2008; Krippendorff, Content Analysis), interpretation benchmarks (Landis & Koch, 1977), the kappa paradox under skewed prevalence (Feinstein & Cicchetti, 1990), crowd label quality (Snow et al., 2008), learning rater competence (Raykar et al., 2010), annotation error detection (Klie et al., 2023), and human label variation as signal (Plank, 2022). The case study audits the GoEmotions raw ratings (Demszky et al., 2020). raterkit's contribution is packaging the checks as one auditable tool with uncertainty on every number and validation by implantation.

Companion projects: judgekit audits LLM judges the way this audits human raters; judgepanel estimates how accurate a panel of judges is when no gold labels exist at all; abkit applies the same audit-before-you-trust pattern to A/B-test readouts; trajectory-judge measures what outcome-only judges miss on agent trajectories.

Citation

If raterkit is useful in your work, please cite it (see CITATION.cff):

@software{mohammadi_raterkit,
  author  = {Mohammadi, Hadi},
  title   = {raterkit: audit a labeled dataset before you trust it},
  url     = {https://github.com/mohammadi-hadi/raterkit},
  version = {0.1.0},
  year    = {2026}
}

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

raterkit-0.1.0.tar.gz (208.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

raterkit-0.1.0-py3-none-any.whl (27.8 kB view details)

Uploaded Python 3

File details

Details for the file raterkit-0.1.0.tar.gz.

File metadata

  • Download URL: raterkit-0.1.0.tar.gz
  • Upload date:
  • Size: 208.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for raterkit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 32a41f75fdef64f1827811f45ab6d4cc781029546b049b2819ef20d420d4f2d2
MD5 1b36197d827f306bd1ceaf18affeb440
BLAKE2b-256 7d5f00862c00343d743ef0cbdf7b00c2bee56d4772b877f277ff34ac35b4da98

See more details on using hashes here.

Provenance

The following attestation bundles were made for raterkit-0.1.0.tar.gz:

Publisher: release.yml on mohammadi-hadi/raterkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file raterkit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: raterkit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 27.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for raterkit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fd91d2160c63b8845bc4318c9affd713cafebbab75e82d0e299739b13cbb4377
MD5 f468a33a0a2c30c6c66250e47df1026c
BLAKE2b-256 105b1a431ac437cc8d38351e75ef50dc73b3fb1b75c642380de8ef672cf861fa

See more details on using hashes here.

Provenance

The following attestation bundles were made for raterkit-0.1.0-py3-none-any.whl:

Publisher: release.yml on mohammadi-hadi/raterkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page