-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathkrippendorff_alpha.py
128 lines (98 loc) · 3.83 KB
/
krippendorff_alpha.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#! /usr/bin/env python
# -*- coding: utf-8
'''
Python implementation of Krippendorff's alpha -- inter-rater reliability
(c)2011-17 Thomas Grill (http://grrrr.org)
Python version >= 2.4 required
'''
from __future__ import print_function
try:
import numpy as np
except ImportError:
np = None
def nominal_metric(a, b):
return a != b
def interval_metric(a, b):
return (a-b)**2
def ratio_metric(a, b):
return ((a-b)/(a+b))**2
def krippendorff_alpha(data, metric=interval_metric, force_vecmath=False, convert_items=float, missing_items=None):
'''
Calculate Krippendorff's alpha (inter-rater reliability):
data is in the format
[
{unit1:value, unit2:value, ...}, # coder 1
{unit1:value, unit3:value, ...}, # coder 2
... # more coders
]
or
it is a sequence of (masked) sequences (list, numpy.array, numpy.ma.array, e.g.) with rows corresponding to coders and columns to items
metric: function calculating the pairwise distance
force_vecmath: force vector math for custom metrics (numpy required)
convert_items: function for the type conversion of items (default: float)
missing_items: indicator for missing items (default: None)
'''
# number of coders
m = len(data)
# set of constants identifying missing values
if missing_items is None:
maskitems = []
else:
maskitems = list(missing_items)
if np is not None:
maskitems.append(np.ma.masked_singleton)
# convert input data to a dict of items
units = {}
for d in data:
try:
# try if d behaves as a dict
diter = d.items()
except AttributeError:
# sequence assumed for d
diter = enumerate(d)
for it, g in diter:
if g not in maskitems:
try:
its = units[it]
except KeyError:
its = []
units[it] = its
its.append(convert_items(g))
units = dict((it, d) for it, d in units.items() if len(d) > 1) # units with pairable values
n = sum(len(pv) for pv in units.values()) # number of pairable values
if n == 0:
raise ValueError("No items to compare.")
np_metric = (np is not None) and ((metric in (interval_metric, nominal_metric, ratio_metric)) or force_vecmath)
Do = 0.
for grades in units.values():
if np_metric:
gr = np.asarray(grades)
Du = sum(np.sum(metric(gr, gri)) for gri in gr)
else:
Du = sum(metric(gi, gj) for gi in grades for gj in grades)
Do += Du/float(len(grades)-1)
Do /= float(n)
if Do == 0:
return 1.
De = 0.
for g1 in units.values():
if np_metric:
d1 = np.asarray(g1)
for g2 in units.values():
De += sum(np.sum(metric(d1, gj)) for gj in g2)
else:
for g2 in units.values():
De += sum(metric(gi, gj) for gi in g1 for gj in g2)
De /= float(n*(n-1))
return 1.-Do/De if (Do and De) else 1.
if __name__ == '__main__':
print("Example from http://en.wikipedia.org/wiki/Krippendorff's_Alpha")
data = (
"* * * * * 3 4 1 2 1 1 3 3 * 3", # coder A
"1 * 2 1 3 3 4 3 * * * * * * *", # coder B
"* * 2 1 3 4 4 * 2 1 1 3 3 * 4", # coder C
)
missing = '*' # indicator for missing values
array = [d.split() for d in data] # convert to 2D list of string items
print("nominal metric: %.3f" % krippendorff_alpha(array, nominal_metric, missing_items=missing))
print("interval metric: %.3f" % krippendorff_alpha(array, interval_metric, missing_items=missing))