This repository was archived by the owner on Jun 1, 2022. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathhelpers.py
More file actions
234 lines (191 loc) · 8.4 KB
/
Copy pathhelpers.py
File metadata and controls
234 lines (191 loc) · 8.4 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import os
import math
import segeval as se
import numpy as np
import itertools as it
import functools
import collections
import random
from decimal import Decimal
# Directory containing our segmentation files
DATA_DIR = '/Users/ryanshaw/Code/u-series-segmentations'
# Boundary similarity parameter: Maximum distance (in potential boundary positions) that a transposition may span
MAX_TRANSPOSE_DISTANCE = 9
def datapath(path):
return os.path.join(DATA_DIR, path)
def remove_coder(dataset, coder):
for item, coder_segmentations in dataset.items():
dataset[item].pop(coder)
dataset.coders.discard(coder)
return dataset
def load_dataset(name):
dataset = se.input_linear_mass_json(datapath(name))
dataset.coders = tuple(sorted(set.intersection(
*[ set(data.keys()) for data in dataset.values() ])))
return dataset
def rename_coders(dataset, old_names, suffix):
if len(set.intersection(set(old_names), set(dataset.coders))) == 0:
return dataset
rename = lambda x: x+suffix if x in old_names else x
d = se.data.Dataset(
{ item: { rename(c):s for c,s in segmentations.items() }
for item, segmentations in dataset.items() })
d.coders = tuple(map(rename, dataset.coders))
return d
def merge_datasets(*datasets):
repeated_coders = set.intersection(*[ set(d.coders)
for d in datasets ])
ds = [datasets[0]] + [ rename_coders(d, repeated_coders, '-%s' % i)
for i, d in enumerate(datasets[1:], start=1) ]
items = set.intersection(*[ set(d.keys()) for d in ds ])
merged = se.data.Dataset(
{ item: { c:s for c,s in it.chain.from_iterable(
[ d[item].items() for d in ds ]) }
for item in items })
merged.coders = tuple(it.chain(*[ d.coders for d in ds ]))
return merged
def mean_std_n(values):
floats = np.array(list(values), dtype=float)
# ddof=1 means calculate sample std dev
return (np.mean(floats), np.std(floats, ddof=1), len(floats))
def mean_std_n_str(values):
return '{0:.4f} ±{1:.4f}, n={2}'.format(*mean_std_n(values))
def mean_ci_n(values):
mean, sd, n = mean_std_n(values)
se = sd / np.sqrt(n)
ci = 1.96 * se
return mean, ci, n
def mean_ci_n_str(values):
return '{0:.4f} ±{1:.4f} (95% CI), n={2}'.format(*mean_ci_n(values))
def b_confusion(hypothesis, reference):
def sum_matrices(a,b):
m = se.ConfusionMatrix()
classes = a.classes() | b.classes()
for c1,c2 in it.product(classes,classes):
m[c1][c2] = a[c1][c2] + b[c1][c2]
return m
cms = se.boundary_confusion_matrix(hypothesis, reference, n_t=MAX_TRANSPOSE_DISTANCE)
return functools.reduce(sum_matrices, cms.values())
def b_statistics(*datasets):
return se.boundary_statistics(*datasets, n_t=MAX_TRANSPOSE_DISTANCE)
def b(*datasets, return_parts=False):
return se.boundary_similarity(*datasets, n_t=MAX_TRANSPOSE_DISTANCE, return_parts=return_parts)
def boundary_pair_scores(total_score, n_pairs, additions, _, transpositions):
n_matches = n_pairs - len(additions) - len(transpositions)
scores = (((Decimal(1),) * n_matches) +
((Decimal(0),) * len(additions)) +
tuple([ (1 - (abs(t[0]-t[1]) / Decimal(MAX_TRANSPOSE_DISTANCE))) for t in transpositions ]))
return scores
def b_pairwise(dataset):
return { label: boundary_pair_scores(*parts)
for label, parts in b(dataset, return_parts=True).items() }
def wd(dataset):
return se.window_diff(dataset)
def actual_agreement(dataset, return_parts=False):
return se.actual_agreement_linear(dataset, n_t=MAX_TRANSPOSE_DISTANCE, return_parts=return_parts)
def pi(dataset, return_parts=False):
return se.fleiss_pi_linear(dataset, n_t=MAX_TRANSPOSE_DISTANCE, return_parts=return_parts)
def pi_variance(dataset):
# This uses formula 13 in Fleiss et al. 1979 (single-category π* variance)
n_coders = len(dataset.coders)
n_possible_boundaries = potential_boundaries(dataset)
return Decimal(2) / (n_possible_boundaries * n_coders * (n_coders-1))
def pi_z_test(dataset):
# This uses formula 16 in Fleiss et al. 1979 (single-category π* z score)
n_coders = len(dataset.coders)
n_possible_boundaries = potential_boundaries(dataset)
n_actual_boundaries = boundaries_placed(dataset)
p_boundary = Decimal(n_actual_boundaries) / (n_possible_boundaries * n_coders)
a = pi(dataset) + (Decimal(1) / (n_possible_boundaries*(n_coders-1)))
b = ( (n_possible_boundaries*n_coders*(n_coders-1)) / Decimal(2) )**Decimal(0.5)
return a * b
def kappa(dataset):
return se.fleiss_kappa_linear(dataset, n_t=MAX_TRANSPOSE_DISTANCE)
def bias(dataset):
return se.artstein_poesio_bias_linear(dataset, n_t=MAX_TRANSPOSE_DISTANCE)
def pairwise_similarity(dataset, coder, item):
scores = tuple(it.chain(*[ v for k,v in b_pairwise(dataset).items()
if (coder in k) and (item in k) ]))
return mean_std_n(scores)[0]
def strip_prefixes(strings):
return [ s.split(':')[-1] for s in strings ]
def total_mass(dataset, item):
sums = [ sum(s) for s in dataset[item].values() ]
assert(all(s == sums[0] for s in sums))
return sums[0]
def total_masses_for(dataset):
return { item: total_mass(dataset, item) for item in dataset.keys() }
def total_boundaries_laid(dataset, item):
return sum([ (len(s) - 1) for s in dataset[item].values() ])
def total_segments_created(dataset, item):
return sum([ len(s) for s in dataset[item].values() ])
def boundaries_placed(dataset, coder=None):
if coder is not None:
return sum([ (len(v[coder]) - 1) for v in dataset.values() ])
else:
return sum([ boundaries_placed(dataset, c) for c in dataset.coders ])
def potential_boundaries(dataset):
return sum([ (total_mass(dataset, item) - 1) for item in dataset.keys() ])
def boundary_placement_rate(dataset, coder=None):
if coder is not None:
return (boundaries_placed(dataset, coder)
/ potential_boundaries(dataset))
else:
return (boundaries_placed(dataset)
/ (potential_boundaries(dataset) * len(dataset.coders)))
def boundary_ratios_for(dataset):
d = collections.defaultdict(dict)
for item, segmentations in dataset.items():
for coder, masses in segmentations.items():
actual_boundaries = len(masses) - 1
possible_boundaries = sum(masses) - 1
d[item][coder] = (actual_boundaries, possible_boundaries)
return d
def all_segments_of(dataset, item=None):
if item is not None:
return tuple(it.chain.from_iterable(
dataset[item].values()))
else:
return tuple(it.chain.from_iterable(
[ all_segments_of(dataset, i) for i in dataset.keys() ]))
def null_segmentations_for(dataset):
return se.data.Dataset({ item: { 'NULL': (total_mass(dataset, item),) }
for item in dataset.keys() })
def random_segmentation_of(dataset, item, ratio):
mass = 0
masses = []
for x in range(total_mass(dataset, item)):
mass += 1
if (random.random() < ratio):
masses.append(mass)
mass = 0
masses.append(mass)
return masses
def random_segmentations_for(dataset):
d = collections.defaultdict(dict)
p_b = boundary_placement_rate(dataset)
for item in dataset.keys():
d[item]['RANDOM'] = random_segmentation_of(dataset, item, p_b)
return se.data.Dataset(d)
def uniform_segmentation_of(dataset, item, target_mass):
total_m = total_mass(dataset, item)
n_segments = round(total_m / target_mass)
segment_m = round(total_m / n_segments)
masses = [segment_m] * n_segments
remainder = math.floor((total_m - (n_segments * segment_m)) / n_segments)
for i in range(len(masses)):
masses[i] += remainder
masses[-1] += (total_m - (n_segments * segment_m)) % n_segments
assert sum(masses) == total_m
return masses
def uniform_segmentations_for(dataset):
uniform_segment_mass = math.ceil(np.median(all_segments_of(dataset)))
d = collections.defaultdict(dict)
for item in dataset.keys():
d[item]['UNIFORM'] = uniform_segmentation_of(
dataset, item, uniform_segment_mass)
return se.data.Dataset(d)
def keep(dataset, items):
return se.data.Dataset({ item: segmentations
for item, segmentations in dataset.items()
if item in items })