Raw agreement is the diagonal divided by the total. In the example, (168 + 76) / 300 = 0.813, or 81.3 percent. That number looks strong, and on its own it is misleading. Seventeen points of disagreement is 56 items out of 300, and the balance of those disagreements leans toward false sends.
The deeper problem with raw agreement is that it ignores the label mix. Suppose your corpus is 95 percent "send" because most drafts are fine. A scorer that always says "send" would score 95 percent agreement while doing nothing. Jacob Cohen introduced kappa in 1960 specifically because percent agreement cannot account for the agreement two raters would reach by guessing given their own label frequencies (McHugh on the kappa statistic). The formula is:
kappa = (Po - Pe) / (1 - Pe)
where Po is observed agreement and Pe is expected agreement by chance. Pe is computed from the margins: for each label, multiply the human's row total by the model's column total, sum those products, and divide by the square of the total count. This is the standard calculation, and a worked version of it on a 100-item table appears in Galileo's write-up of the metric.
Running the numbers on the subject line table:
- Po = (168 + 76) / 300 = 0.8133
- Pe = ((192 x 200) + (108 x 100)) / 300² = (38,400 + 10,800) / 90,000 = 0.5467
- kappa = (0.8133 - 0.5467) / (1 - 0.5467) = 0.2666 / 0.4533 = 0.588
That is a big drop, from 81 percent agreement to a chance-corrected 0.59. The scorer is clearly better than chance, but it is not yet in the range where most teams would let it replace a human read.
A code block you can run
import numpy as np
from sklearn.metrics import cohen_kappa_score
# rows: human label (1 = send, 0 = hold); cols: model label
human = np.array([1]*192 + [0]*108)
model = np.array([1]*168 + [0]*24 + [1]*32 + [0]*76)
po = (human == model).mean()
pe = sum((human == k).mean() * (model == k).mean() for k in (0, 1))
kappa = (po - pe) / (1 - pe)
print(f"raw agreement: {po:.4f}")
print(f"expected: {pe:.4f}")
print(f"kappa: {kappa:.4f}")
print(f"sklearn check: {cohen_kappa_score(human, model):.4f}")
The manual calculation and the library call should agree to four decimals. If they do not, you have a label encoding problem, not a scorer problem.
Reading the kappa value
Kappa runs from -1 to +1. Negative values mean the two raters disagree more than chance would predict, which usually means the rubric is inverted or misread. The conventional bands are 0.81 to 1.00 almost perfect, 0.61 to 0.80 substantial, 0.41 to 0.60 moderate, 0.21 to 0.40 fair and below 0.20 slight to poor (Galileo). McHugh argues Cohen's original bands are too lenient for consequential settings, where a value as low as 0.41 should not be treated as acceptable (McHugh). For a decision that changes what a customer sees, treat 0.6 as a floor and 0.7 as comfortable.
One caution before you fall in love with the number. Kappa is sensitive to the prevalence of each label. When one class dominates, kappa can look low even for a genuinely useful scorer, and the paradox has been documented well enough that some researchers argue for other coefficients altogether (the ResearchGate discussion of percent agreement versus kappa). Always report the confusion table next to the kappa. The table tells you what kind of error you have; the kappa tells you how much of the agreement is real.