Learn how to compare workforce labels from several reviewers without treating majority vote as truth.
The outside provider review kept unlike records separate. The Riverton Workforce Lab now checks whether the people assigning labels make comparable decisions. Three fictional paid reviewers apply the same codebook independently to 11 local sentences. Nine rows match; two do not.
Crowdsourcing distributes tasks to a group of people, often through an online marketplace. Access to more reviewers can increase coverage, but a marketplace does not create expertise, fair working conditions, or valid labels. The team must design those conditions.
Note
The reviewers, annotations, and research team are fictional. The disagreements were designed for this lesson and do not evaluate real workers.
TipWhat you will learn
By the end of this lesson, you will be able to:
keep reviewer labels separate;
calculate pairwise agreement with foundryR;
distinguish observed agreement from chance-corrected measures;
test how much one item moves an agreement statistic;
find items that need adjudication and record the decision; and
explain why majority vote is not automatic truth.
Preserve each person’s answer
The annotation file stores one column per reviewer. The next chunk loads file, table, reshaping, repeated-work, agreement, and reliability helpers, then reads the CSV files with declared column types. No vote has been combined or overwritten.
Nine rows have complete agreement. Sentence s010 has one different answer. Sentence s013 has three different answers. Keeping the columns separate makes those disagreements visible, and the sample includes all five labels.
Measure all reviewer comparisons
Simple agreement is the share of answers that match. Cohen’s kappa adjusts for the agreement expected from the reviewers’ label frequencies. Krippendorff’s alpha is another chance-corrected measure that can support more coders and missing labels under suitable settings.
foundryR calculates several measures together. Here we retain observed agreement and Cohen’s kappa for every reviewer pair. Directional classification measures such as precision and recall are not shown because no reviewer is treated as the truth.
reviewer_pairs <-tibble(pair =c("A and B", "A and C", "B and C"),first =c("reviewer_a","reviewer_a","reviewer_b" ),second =c("reviewer_b","reviewer_c","reviewer_c" ))pairwise_agreement <-pmap( reviewer_pairs, \(pair, first, second) { measures <- foundryR::foundry_agreement( crowd_labels,estimate = second,truth = first )tibble(pair = pair,observed_agreement =filter(measures, metric =="accuracy")$value,cohen_kappa =filter(measures, metric =="cohen_kappa")$value ) }) |>list_rbind()knitr::kable( pairwise_agreement |>mutate(observed_agreement =round( observed_agreement,2 ),cohen_kappa =round(cohen_kappa, 2) ),col.names =c("Reviewer pair","Observed agreement","Cohen's kappa" ),caption ="Pairwise agreement across all three reviewers",row.names =FALSE)
Pairwise agreement across all three reviewers
Reviewer pair
Observed agreement
Cohen’s kappa
A and B
0.82
0.76
A and C
0.91
0.88
B and C
0.82
0.76
The B-and-C comparison is weaker than A and C. Reporting only one pair would hide that difference.
The table shows two decimals on purpose. With 11 items, one reviewer changing one answer moves observed agreement by about nine points, so a third decimal describes arithmetic rather than measurement. Publishing 0.818 invites a reader to compare it with 0.812 from another study as though the difference meant something.
Krippendorff’s alpha summarizes all three reviewers at once. pivot_longer() puts the reviewer columns into one label column, then pivot_wider() builds the matrix that irr needs. The calculation converts label names to category numbers; the numbers carry no order.
# A tibble: 2 × 2
measure value
<chr> <dbl>
1 Krippendorff's alpha 0.8
2 Unanimous share 0.82
The 11-item example is too small for a precise population estimate. These statistics describe this annotation exercise and help locate problems; they do not certify the codebook or any reviewer.
Ask how much one item is worth
“Too small for a precise estimate” is a phrase readers skim past. The way to make it concrete is to remove one item at a time and recompute, which for 11 items means 11 recalculations and no new data.
alpha_without_item <-map_dbl(seq_len(nrow(rating_matrix)), \(dropped) { irr::kripp.alpha(t(rating_matrix[-dropped, , drop =FALSE]),method ="nominal" )$value })sensitivity <-tibble(sentence_id = numeric_ratings$sentence_id,alpha_without_this_item = alpha_without_item) |>arrange(desc(alpha_without_this_item))alpha_range <-range(alpha_without_item)knitr::kable( sensitivity |>mutate(alpha_without_this_item =round( alpha_without_this_item,2 ) ),col.names =c("Item removed", "Alpha for the other ten"),caption ="Krippendorff's alpha after dropping each item in turn",row.names =FALSE)
Krippendorff’s alpha after dropping each item in turn
Item removed
Alpha for the other ten
s013
0.91
s010
0.87
s002
0.78
s005
0.78
s017
0.78
s001
0.78
s006
0.78
s016
0.78
s011
0.77
s003
0.76
s004
0.76
Alpha is 0.80 on all 11 items. Drop one item and it lands anywhere between 0.76 and 0.91. The high end is not a coincidence: removing s013, the item all three reviewers read differently, is what produces it.
This is not a confidence interval, and it should not be reported as one. It is a cruder statement and a sufficient one here: a summary this sensitive to a single item cannot support a claim that one codebook, task, or reviewer pool is more reliable than another. Reporting the range beside the estimate is honest; reporting 0.797 alone is not.
A larger sample can reduce single-item sensitivity, but sample size alone does not determine how much alpha moves. Label prevalence, clustered documents, which item is removed, and the pattern of disagreement all matter. For a larger study, resample whole source documents rather than individual rows and report the resulting bootstrap distribution; this lesson’s 11 items cannot predict what its width would be for 500.
Find the disagreements
An item needs review when the three labels are not identical. left_join() adds the sentence text by matching the shared sentence identifier.
The first sentence says experience is preferred; one reviewer chose other. The second mentions a certificate and receives three different labels. Those choices place the same notices under different questions for a person comparing options. The codebook needs to explain the boundary rather than hide it in a vote.
Calculate a majority label carefully
A majority label can organize the next review step. It should not erase the individual answers or replace adjudication.
The unanimous column prevents an easy majority from looking like complete agreement. The tie check stays because teams may change the reviewer count; any tie among top labels goes to adjudicate. Sentence s013 returns adjudicate because three different answers do not form a majority. The team can review the sentence, codebook, and source labels before choosing a reference label.
Finish the adjudication
An adjudication queue that stays a queue is not a quality process. Both flagged items were decided by a fourth reader who had not labeled them, working from the codebook text rather than from the vote counts.
adjudication <-tibble(sentence_id =c("s010", "s013"),question =c("Is a preferred condition an entry condition or something else?","Does naming a credential make the unit a training offer?" ),rule_applied =c("requirement: the main claim states or prefers an entry condition","training: a credential required or preferred is not training" ),decision =c("requirement", "requirement"),decided_by ="fourth reader, not one of the three reviewers")adjudication_outcome <- adjudication |>left_join( sentence_text |>select(sentence_id, text, reference_label),by =join_by(sentence_id) ) |>left_join( crowd_labels |>select(sentence_id, all_of(reviewer_columns)),by =join_by(sentence_id) ) |>mutate(matches_reference_file = decision == reference_label,reviewers_still_disagree = reviewer_a != reviewer_b | reviewer_b != reviewer_c )knitr::kable( adjudication_outcome |>select( sentence_id, text, reviewer_a, reviewer_b, reviewer_c, decision ),col.names =c("Sentence ID","Text","Reviewer A","Reviewer B","Reviewer C","Adjudicated label" ),caption ="Both flagged items resolved, with the reviewer answers kept",row.names =FALSE)
Both flagged items resolved, with the reviewer answers kept
Sentence ID
Text
Reviewer A
Reviewer B
Reviewer C
Adjudicated label
s010
Six months of experience is preferred.
requirement
other
requirement
requirement
s013
A medical records certificate is preferred.
requirement
training
other
requirement
Both items resolve to requirement, and both decisions rest on a clause the adjudicator can point to in the codebook. The reviewer columns are still in the table with their disagreements intact, which is why the check above can confirm that the reviewers still differ after the item was settled.
Notice what the adjudication of s013 did not do. It did not make the item easy. Three trained readers reached three answers, and the sensitivity analysis showed that this one item carries most of the distance between an alpha of 0.80 and one of 0.91. A decision closes the item; it does not retire the question, which belongs in the next codebook revision as a worked example.
Agreement does not prove validity
Reviewers can agree because:
the codebook is clear;
the item is easy;
they share the same misunderstanding;
the task excludes a relevant perspective; or
the platform trained everyone toward the same shortcut.
Validity requires evidence that the labels measure the intended idea. Compare annotations with the research question, consult people with relevant knowledge, and test how conclusions change under reasonable label choices.
Treat crowd work as work
A responsible plan states:
payment for training, annotation, and adjudication time;
expected task length and rejection rules;
privacy and data-handling requirements;
access to support and the ability to skip harmful material;
the expertise and language knowledge required; and
an appeal path for disputed quality decisions.
Hidden attention checks and automatic rejection can shift measurement errors onto workers. Quality control should diagnose the task and instructions as well as individual answers.
What to remember
Keep individual reviewer labels before combining them.
Agreement measures summarize consistency, not truth.
Report agreement to a precision the sample size can support.
Show how far a statistic moves when one item is removed.
Inspect disagreement items and revise the codebook when needed.
Finish the adjudication and record which rule decided it.
Preserve whether a majority label was unanimous.
Fair pay, privacy, expertise, and appeal processes are part of data quality.
The comparison leaves nothing in the queue: two boundary cases were adjudicated against named rules, and the reviewer answers that produced them are still on the page. The following augmentation exercise keeps that annotation record intact and creates fictional variants. Each is marked as synthetic and checked against the same codebook before it can join training data.