Download this notebook (.ipynb)

Analysis: Measure Inter-Rater Reliability#

Researchers often code the same design protocol into categories such as problem analysis, solution generation, and evaluation. This notebook compares the three nominal inter-rater reliability estimators in design-research-analysis, including their treatment of one missing rating.

Setup#

python -m pip install design-research-analysis==0.3.1

Step 1: Represent items by rows and raters by columns#

[1]:
import design_research_analysis as analysis


def format_interval(
    result: analysis.InterraterReliabilityResult,
) -> tuple[float, float] | str:
    """Format an optional bootstrap interval for display."""
    interval = result.confidence_interval
    if interval is None:
        return "not estimated"
    low, high = interval
    return round(low, 3), round(high, 3)


codings: list[list[str | None]] = [
    ["problem", "problem", "problem"],
    ["solution", "problem", "problem"],
    ["evaluation", "evaluation", "evaluation"],
    ["solution", "solution", "solution"],
    ["problem", "solution", "solution"],
    ["evaluation", "evaluation", None],
]
print("Items:", len(codings))
print("Raters:", len(codings[0]))
for index, row in enumerate(codings, start=1):
    print(f"{index}: {row}")
Items: 6
Raters: 3
1: ['problem', 'problem', 'problem']
2: ['solution', 'problem', 'problem']
3: ['evaluation', 'evaluation', 'evaluation']
4: ['solution', 'solution', 'solution']
5: ['problem', 'solution', 'solution']
6: ['evaluation', 'evaluation', None]

Step 2: Use Cohen kappa for two raters#

[2]:
cohen = analysis.compute_interrater_reliability(
    [row[:2] for row in codings],
    method="cohen_kappa",
    n_bootstrap=100,
    seed=17,
)
print("Coefficient:", f"{cohen.coefficient:.3f}")
print("95% interval:", format_interval(cohen))
print("Items used:", f"{cohen.n_items_used}/{cohen.n_items}")
Coefficient: 0.500
95% interval: (-0.048, 1.0)
Items used: 6/6

Step 3: Use Fleiss kappa for complete multi-rater rows#

[3]:
fleiss = analysis.compute_interrater_reliability(
    codings,
    method="fleiss_kappa",
    n_bootstrap=100,
    seed=17,
)
print("Coefficient:", f"{fleiss.coefficient:.3f}")
print("95% interval:", format_interval(fleiss))
print("Items used:", f"{fleiss.n_items_used}/{fleiss.n_items}")
print("Missing ratings:", fleiss.missing_ratings)
Coefficient: 0.583
95% interval: (-0.211, 1.0)
Items used: 5/6
Missing ratings: 1

Step 4: Retain partial rows with Krippendorff alpha#

[4]:
alpha = analysis.compute_interrater_reliability(
    codings,
    method="krippendorff_alpha",
    n_bootstrap=100,
    seed=17,
)
print("Coefficient:", f"{alpha.coefficient:.3f}")
print("95% interval:", format_interval(alpha))
print("Items used:", f"{alpha.n_items_used}/{alpha.n_items}")
print("Missing ratings:", alpha.missing_ratings)
Coefficient: 0.667
95% interval: (0.255, 1.0)
Items used: 6/6
Missing ratings: 1

Step 5: Compare the estimators before choosing one#

[5]:
for result in (cohen, fleiss, alpha):
    print(
        f"{result.method}: coefficient={result.coefficient:.3f}, "
        f"items={result.n_items_used}/{result.n_items}, "
        f"missing={result.missing_ratings}"
    )
cohen_kappa: coefficient=0.500, items=6/6, missing=0
fleiss_kappa: coefficient=0.583, items=5/6, missing=1
krippendorff_alpha: coefficient=0.667, items=6/6, missing=1

Interpretation#

These APIs treat labels as nominal. Choose the estimator from the coding design and missing-data policy, not from whichever coefficient is largest. Audit reliability before treating coded events as outcomes or process states.