Skip to content

Commit dd638d8

Browse files
committed
FIX: Krippendorff alpha returns 1.0 instead of NaN for unpairable identical ratings
The num_categories == 1 early return reported perfect agreement before the total_n == 0 degenerate check, so data where every item is rated by a single rater (no pairable coincidences) produced alpha = 1.0 whenever all ratings happened to be identical. The statistic is undefined in that case; the docstring already promises NaN for undefined input. Move the single-category early return after the coincidence construction so unpairable data reaches the existing total_n == 0 NaN path while pairable identical ratings still return 1.0.
1 parent 5503ecb commit dd638d8

2 files changed

Lines changed: 40 additions & 10 deletions

File tree

‎pyrit/score/scorer_evaluation/krippendorff.py‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,11 @@ def krippendorff_alpha(
238238
239239
Returns:
240240
float: Krippendorff's alpha, where 1.0 indicates perfect agreement. The
241-
value can be below 0 when agreement is worse than chance. ``np.nan`` is
242-
returned when the statistic is undefined (e.g., fewer than two usable
243-
ratings or zero expected disagreement with non-zero observed
244-
disagreement).
241+
value can be below 0 when agreement is worse than chance. ``np.nan`` is
242+
returned when the statistic is undefined (e.g., fewer than two usable
243+
ratings, no item with two or more ratings to form a pairable
244+
coincidence, or zero expected disagreement with non-zero observed
245+
disagreement).
245246
246247
Raises:
247248
ValueError: If ``level_of_measurement`` is not ``"ordinal"``.
@@ -254,11 +255,6 @@ def krippendorff_alpha(
254255
if len(valid_ratings) < 2:
255256
return np.nan
256257

257-
# All ratings identical - perfect agreement
258-
num_categories = len(categories)
259-
if num_categories == 1:
260-
return 1.0
261-
262258
# Build value counts matrix
263259
value_counts = _build_value_counts_matrix(data, valid_mask, categories)
264260

@@ -268,10 +264,19 @@ def krippendorff_alpha(
268264
# Build expected coincidence matrix
269265
expected_matrix, n_v, total_n = _build_expected_matrix(coincidence_matrix)
270266

271-
# Check for degenerate case
267+
# Check for degenerate case. This also covers the single-category case where
268+
# no item has two or more ratings: with nothing pairable, alpha is undefined
269+
# even when every rating happens to be identical, so it must not report 1.0.
272270
if total_n == 0:
273271
return np.nan
274272

273+
# All ratings identical and pairable - perfect agreement. The general path
274+
# below would also return 1.0 (every ordinal distance is zero); the early
275+
# return just skips building the all-zero distance matrix.
276+
num_categories = len(categories)
277+
if num_categories == 1:
278+
return 1.0
279+
275280
# Build ordinal distance matrix
276281
distance_matrix = _build_ordinal_distance_matrix(num_categories, n_v)
277282

‎tests/unit/score/test_krippendorff.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,31 @@ def test_insufficient_data_returns_nan(self):
6969
result = krippendorff_alpha(data, level_of_measurement="ordinal")
7070
assert np.isnan(result)
7171

72+
def test_unpairable_identical_ratings_returns_nan(self):
73+
"""Test that identical ratings with no pairable item return NaN, not 1.0.
74+
75+
Every item is rated by exactly one rater, so no coincidence can be
76+
formed and alpha is undefined, even though all ratings happen to be
77+
identical. Reporting 1.0 would claim perfect reliability from data that
78+
cannot measure reliability at all.
79+
"""
80+
data = np.array([[1, np.nan], [np.nan, 1]])
81+
result = krippendorff_alpha(data, level_of_measurement="ordinal")
82+
assert np.isnan(result)
83+
84+
def test_unpairable_identical_ratings_mixed_with_pairable_returns_value(self):
85+
"""Test that one pairable item is enough to define alpha."""
86+
# Item 0 is rated by two raters (pairable); item 1 by a single rater.
87+
data = np.array([[1, 2], [1, np.nan]])
88+
result = krippendorff_alpha(data, level_of_measurement="ordinal")
89+
assert not np.isnan(result)
90+
91+
def test_pairable_identical_ratings_still_return_one(self):
92+
"""Test that identical ratings with a pairable item still return 1.0."""
93+
data = np.array([[2, 2], [2, np.nan]])
94+
result = krippendorff_alpha(data, level_of_measurement="ordinal")
95+
assert result == 1.0
96+
7297
def test_all_missing_returns_nan(self):
7398
"""Test that all missing values returns NaN."""
7499
data = np.array([[np.nan, np.nan], [np.nan, np.nan]])

0 commit comments

Comments
 (0)