Understanding Krippendorff's Alpha

Understanding Krippendorff's Alpha

The Problem with Raw Agreement

You're building an LLM evaluation pipeline. Four contractors rate 500 model responses as "Good," "Okay," or "Bad." The dashboard shows 87% pairwise agreement. Ship it?

Not so fast. One rater noticed that 70% of responses are "Good" and started marking everything "Good" to speed through the batch. They still agree with the others most of the time—but they're contributing zero signal.

This is the lazy rater problem, and raw agreement can't detect it. If 70% of items are genuinely "Good," two random guessers would agree about 54% of the time just by chance (0.72+0.152+0.1520.540.7^2 + 0.15^2 + 0.15^2 \approx 0.54). We need a metric that asks: how much better is our agreement than random chance?

The Whole Idea in One Formula

Krippendorff's Alpha measures the reduction in disagreement relative to random labeling:

α=1DoDe\alpha = 1 - \frac{D_o}{D_e}

where DoD_o is the disagreement you actually observed, and DeD_e is the disagreement you'd expect if the same labels had been dealt out at random.

  • α=1\alpha = 1: Perfect agreement—no observed disagreement
  • α=0\alpha = 0: Agreement equals chance—raters might as well flip coins
  • α<0\alpha < 0: Systematic disagreement—raters actively contradict each other

Everything else is careful bookkeeping: how to count observed disagreement fairly, and how to estimate what chance alone would produce. The fastest way to internalize both is to compute them by hand, once, on a dataset small enough to hold in your head.

A Worked Example

Three raters evaluate 5 items using categories A, B, and C:

ItemRater 1Rater 2Rater 3
1AAA
2BBC
3AAA
4BBB
5CCB

We'll follow the same five steps you'll use on real data (and in the practice exercise below).

Step 1: Raw Agreement

First, the simple metric everyone knows—what percentage of rating pairs agree?

For each item with mm raters, there are (m2)=m(m1)2\binom{m}{2} = \frac{m(m-1)}{2} unordered pairs. With 3 raters per item, that's (32)=3\binom{3}{2} = 3 pairs per item. Across 5 items: 5×3=155 \times 3 = 15 pairs total.

ItemRatingsPairsAgreements
1A, A, A(A,A), (A,A), (A,A)3/3
2B, B, C(B,B), (B,C), (B,C)1/3
3A, A, A(A,A), (A,A), (A,A)3/3
4B, B, B(B,B), (B,B), (B,B)3/3
5C, C, B(C,C), (C,B), (C,B)1/3

Raw Agreement = 3+1+3+3+115=1115=73.3%\frac{3+1+3+3+1}{15} = \frac{11}{15} = 73.3\%

Not bad! But is 73% impressive, or would random labeling do nearly as well? Hold that thought.

Step 2: The Coincidence Matrix

The coincidence matrix counts how often each pair of categories co-occurs across all rater pairs. For each item rated by mm raters, we enumerate all m(m1)m(m-1) ordered pairs and weight each by 1m1\frac{1}{m-1}.

With 3 raters per item, each ordered pair gets weight 131=0.5\frac{1}{3-1} = 0.5.

Why the 1/(m-1) weighting?

This ensures each item contributes equally regardless of how many raters labeled it. With mm raters generating m(m1)m(m-1) ordered pairs, the total contribution is m(m1)m1=m\frac{m(m-1)}{m-1} = m—the number of ratings on that item.

Without this, items with more raters would dominate the calculation, biasing Alpha toward those items.

Show item-by-item breakdown

Item 1 (A, A, A): 6 ordered pairs, all (A,A) → contributes 6×0.5=36 \times 0.5 = 3 to cell (A,A)

Item 2 (B, B, C):

  • (B,B): 2 pairs → 2×0.5=12 \times 0.5 = 1
  • (B,C): 2 pairs → 2×0.5=12 \times 0.5 = 1
  • (C,B): 2 pairs → 2×0.5=12 \times 0.5 = 1

Item 3 (A, A, A): Same as Item 1 → adds 3 to (A,A)

Item 4 (B, B, B): All pairs are (B,B) → adds 3 to (B,B)

Item 5 (C, C, B):

  • (C,C): 2 pairs → 2×0.5=12 \times 0.5 = 1
  • (C,B): 2 pairs → 2×0.5=12 \times 0.5 = 1
  • (B,C): 2 pairs → 2×0.5=12 \times 0.5 = 1

The resulting coincidence matrix OO:

ABCRow Total
A6006
B0426
C0213

Matrix total = 15 (equals total ratings across all items: 5 items × 3 raters). The diagonal is where agreement lives; everything off it is disagreement.

Marginal totals ncn_c (row/column sums):

  • nA=6n_A = 6, nB=6n_B = 6, nC=3n_C = 3
  • n=15n = 15 (total pairable values)

Step 3: Observed Disagreement (DoD_o)

For nominal data (unordered categories), the difference function is:

δ(c,k)={0if c=k1if ck\delta(c, k) = \begin{cases} 0 & \text{if } c = k \\ 1 & \text{if } c \neq k \end{cases}

Observed disagreement is the proportion of coincidences that are disagreements (off-diagonal entries):

Do=off-diagonal sumtotal=oAB+oAC+oBA+oBC+oCA+oCBnD_o = \frac{\text{off-diagonal sum}}{\text{total}} = \frac{o_{AB} + o_{AC} + o_{BA} + o_{BC} + o_{CA} + o_{CB}}{n}

From our matrix: Do=0+0+0+2+0+215=415=0.267D_o = \frac{0 + 0 + 0 + 2 + 0 + 2}{15} = \frac{4}{15} = 0.267

This equals 1raw agreement=10.733=0.2671 - \text{raw agreement} = 1 - 0.733 = 0.267. So far, nothing new—we've just reframed agreement as disagreement.

Step 4: Expected Disagreement (DeD_e)

Here's Krippendorff's key insight. If we shuffled all ratings randomly while keeping the same category frequencies, what disagreement would we expect by chance?

The probability that two random draws from our pool are different:

De=1cnc(nc1)n(n1)D_e = 1 - \frac{\sum_c n_c(n_c - 1)}{n(n-1)}

Where ncn_c is the marginal frequency of category cc.

From our data: nA=6n_A = 6, nB=6n_B = 6, nC=3n_C = 3, n=15n = 15

De=16(5)+6(5)+3(2)15(14)=130+30+6210=166210=0.686D_e = 1 - \frac{6(5) + 6(5) + 3(2)}{15(14)} = 1 - \frac{30 + 30 + 6}{210} = 1 - \frac{66}{210} = 0.686

Key insight: Random raters with these category frequencies would disagree 68.6% of the time. Our actual raters disagreed only 26.7% of the time—much better than chance!

Step 5: Krippendorff's Alpha

α=1DoDe=10.2670.686=10.389=0.611\alpha = 1 - \frac{D_o}{D_e} = 1 - \frac{0.267}{0.686} = 1 - 0.389 = 0.611

Interpretation: Our raters captured 61% of the maximum possible agreement beyond chance. The raw 73% sounded good, but after chance correction we land here:

Worked exampleα = 0.61
0
0.67
0.80
1

Moderate reliability—acceptable for exploratory work, but not for high-stakes decisions.

Summary of the Example

MetricValueMeaning
Raw Agreement73.3%How often raters agreed
Observed Disagreement (DoD_o)26.7%How often raters disagreed
Expected Disagreement (DeD_e)68.6%Disagreement expected by chance
Krippendorff's Alpha0.61Reliability beyond chance

Ways to Fool Yourself

Five mistakes account for most misreadings of Alpha:

1. Confusing the coincidence matrix with a confusion matrix. A confusion matrix compares predictions to ground truth. The coincidence matrix compares raters to each other—there's no "correct" answer involved.

2. Forgetting the 1m1\frac{1}{m-1} weighting. Without it, items with more raters dominate the calculation. The weighting ensures each item contributes equally, not each pair.

3. Thinking high raw agreement means high Alpha. If categories are imbalanced (e.g., 90% of items are "Good"), random guessing produces high agreement. Alpha adjusts for this, so you can have 85% agreement but α=0.3\alpha = 0.3.

4. Confusing "agreement" with "accuracy." Alpha measures whether raters agree with each other, not whether they're correct. Three raters who all wrongly label "Bad" responses as "Good" will have perfect Alpha and zero accuracy.

5. Misinterpreting negative Alpha. Negative Alpha doesn't mean "bad raters"—it means raters systematically disagree more than chance. This is rare and usually indicates a coding error, inverted scales, or genuinely adversarial labeling.

Try It Yourself

The playground below simulates raters with controllable accuracy. Each row has a hidden ground truth; drag the sliders to see how rater quality affects Alpha.

Items
12
Rater Skill (P(correct))0% = always wrong | 33% = random | 100% = perfect
Rater 185%
Rater 285%
Rater 385%
Rater 485%
ItemConsensusR1R2R3R4
133333
222221
312131
411111
522222
611111
733333
833333
933333
1022212
1122222
1222222
Categories:123|Matches consensusDiffers
Raw Agreement
85%
Krippendorff's α
0.77
α = 0.77
0
0.67
0.80
1

Experiments to try:

  • "LLM Eval: The Lazy Rater" — One rater at 33% (random guessing for 3 categories). Watch Alpha drop despite decent raw agreement.
  • "Pure Random" — All raters at 33%. They agree by chance about 1/3 of the time, but α0\alpha \approx 0.
  • "Expert vs Novice" — Mixed skill levels. Alpha reflects systematic disagreement.
  • Drag one slider to 0% (always wrong). How does systematic anti-correlation affect Alpha?

Your Turn

The worked example above was mine; this one is yours. Each problem generates fresh ratings in a new setting, and the five steps mirror the walkthrough exactly. If you get stuck, help escalates gently—a nudge first, then the setup with your numbers plugged in, and only then the full answer.

Practice problem

Science Fair

3 judges rated 5 projects: G Gold, S Silver, B Bronze. Work down to α, one step at a time.
ProjectJudge 1Judge 2Judge 3
#1BSB
#2GGB
#3BBB
#4BBB
#5BBG
Step 1 of 50/5

Raw Agreement

Of all rater pairs, what share gave the same label?

%

Choosing the Difference Function

The choice of δ\delta encodes what "disagreement" means for your data type:

Levelδ(c,k)\delta(c,k)ExampleIntuition
Nominal00 if c=kc=k, else 11Categories (Good/Bad/Okay)All disagreements equally bad
Ordinal(i=ckninc+nk2)2\left(\sum_{i=c}^{k} n_i - \frac{n_c + n_k}{2}\right)^2Likert scales (1-5 stars)Disagreeing 1↔5 worse than 1↔2
Interval(ck)2(c-k)^2Temperature, datesDistance matters, ratios don't
Ratio(ckc+k)2\left(\frac{c-k}{c+k}\right)^2Counts, durations2↔4 same as 20↔40

For most LLM evaluation tasks with categorical labels (Good/Okay/Bad), use nominal. For Likert scales or ranked preferences, use ordinal. You can flip between them in the playground above and watch Alpha move.

How Good Is Good Enough?

Krippendorff (2004, p. 241) recommends:

α\alphaInterpretation
0.80\geq 0.80Reliable for most purposes
0.670.670.800.80Acceptable for tentative conclusions
<0.67< 0.67Treat data with caution

For high-stakes LLM evaluations, aim for α0.80\alpha \geq 0.80. Below 0.67, your "signal" is mostly noise.

How Much Should You Trust That Number?

Alpha is a point estimate computed from a sample of items. A small or lopsided sample can produce an impressive Alpha by luck. The practical remedy is a bootstrap confidence interval: resample your items with replacement, compute Alpha for each sample, and take percentiles.

Show Python bootstrapping code
import numpy as np
import krippendorff
 
def bootstrap_alpha(ratings, n_bootstrap=1000):
    """Compute Alpha with 95% confidence interval."""
    n_items = ratings.shape[0]
    alphas = []
 
    for _ in range(n_bootstrap):
        # Resample items with replacement
        indices = np.random.choice(n_items, n_items, replace=True)
        sample = ratings[indices]
        alphas.append(krippendorff.alpha(sample, level_of_measurement='nominal'))
 
    return {
        'alpha': krippendorff.alpha(ratings, level_of_measurement='nominal'),
        'ci_low': np.percentile(alphas, 2.5),
        'ci_high': np.percentile(alphas, 97.5),
        'std': np.std(alphas)
    }
ScenarioAlpha95% CIInterpretation
Tight CI0.75[0.72, 0.78]Reliable estimate; you can trust this
Wide CI0.75[0.55, 0.90]Uncertain; need more data
CI crosses threshold0.75[0.62, 0.85]Might not meet 0.80 standard

A practical rule: if your 95% CI doesn't include your decision threshold (0.67 or 0.80), you can make a confident claim. If it spans the threshold, get more data. Always report CIs when making go/no-go decisions, comparing reliability across conditions, or publishing results others will build on.

Beyond the overall number, examine disagreement item by item. High-disagreement items are diagnostic gold—they show you exactly where your rubric is failing, and they make the best training examples for calibration sessions.

Show Python code for per-item analysis
def item_disagreement(ratings):
    """Compute disagreement for each item."""
    disagreements = []
    for item in ratings:
        valid = item[~np.isnan(item)]
        if len(valid) < 2:
            disagreements.append(np.nan)
            continue
        # Proportion of pairs that disagree
        pairs = [(valid[i], valid[j]) for i in range(len(valid))
                 for j in range(i+1, len(valid))]
        disagree = sum(1 for a, b in pairs if a != b) / len(pairs)
        disagreements.append(disagree)
    return disagreements

When to Use Alpha (and When Not To)

Alpha Is Right For:

  • Annotation quality checks — Are your labelers consistent enough to trust?
  • Codebook validation — Can different people apply your categories reliably?
  • Benchmarking raters — Identifying who needs more training

Alpha Is Wrong For:

1. When ground truth exists. If you have gold labels, measure accuracy, not agreement. Alpha tells you if raters agree with each other, not if they're correct.

2. When you're comparing models. Alpha measures human consistency, not model quality. Use head-to-head preference rates or accuracy instead.

3. When disagreement is the signal. Some tasks have legitimate ambiguity. If experts genuinely disagree, that's valuable information—not measurement failure.

Why Not Just Use Kappa?

Cohen's Kappa addresses chance correction too, but Krippendorff's Alpha is more general:

FeatureCohen's KappaKrippendorff's Alpha
Number of raters2 onlyAny number
Missing dataNot allowedHandles gracefully
Data typesNominal (extensions exist)Nominal, ordinal, interval, ratio
Use caseSimple pairwise agreementComplex annotation projects

For LLM evals with rotating contractor pools and incomplete coverage, Alpha is the practical choice.

Improving Low Alpha

Low reliability isn't failure—it's actionable feedback:

  1. Refine your rubric. Ambiguous categories cause disagreement. "Is this response helpful?" is vague; "Does this response directly answer the user's question?" is testable.

  2. Calibrate with examples. Give raters 20 pre-labeled examples to study before rating. Discuss edge cases explicitly.

  3. Identify problem raters. Calculate Alpha with each rater removed. If one rater's removal dramatically improves Alpha, they need retraining.

  4. Stratify by difficulty. Separate "clear" from "borderline" cases and report Alpha for each.

  5. Add categories. If raters keep disagreeing between "Good" and "Okay," maybe you need a "Good-ish" middle ground.

Code

Python example
import krippendorff
import numpy as np
 
# Ratings matrix: rows = items, columns = raters
# Use np.nan for missing values
ratings = np.array([
    [1, 1, 2, 1],
    [2, 2, 2, 2],
    [1, 2, 1, 1],
    # ...
])
 
alpha = krippendorff.alpha(ratings, level_of_measurement='nominal')
print(f"Krippendorff's Alpha: {alpha:.3f}")
R example
library(irr)
kripp.alpha(data_matrix, method="nominal")

Further Reading

  • Krippendorff, K. (2004). Content Analysis: An Introduction to Its Methodology
  • Hayes & Krippendorff (2007). "Answering the call for a standard reliability measure"
  • krippendorff Python package