1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
|
|
# Copyright 2014-2018 by Christopher C. Little. |
4
|
|
|
# This file is part of Abydos. |
5
|
|
|
# |
6
|
|
|
# Abydos is free software: you can redistribute it and/or modify |
7
|
|
|
# it under the terms of the GNU General Public License as published by |
8
|
|
|
# the Free Software Foundation, either version 3 of the License, or |
9
|
|
|
# (at your option) any later version. |
10
|
|
|
# |
11
|
|
|
# Abydos is distributed in the hope that it will be useful, |
12
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
13
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14
|
|
|
# GNU General Public License for more details. |
15
|
|
|
# |
16
|
|
|
# You should have received a copy of the GNU General Public License |
17
|
|
|
# along with Abydos. If not, see <http://www.gnu.org/licenses/>. |
18
|
|
|
|
19
|
|
|
"""abydos.tests.test_stats_confusion_table. |
20
|
|
|
|
21
|
|
|
This module contains unit tests for abydos.stats.confusion_table |
22
|
|
|
""" |
23
|
|
|
|
24
|
|
|
from __future__ import division, unicode_literals |
25
|
|
|
|
26
|
|
|
import unittest |
27
|
|
|
from math import isnan, sqrt |
28
|
|
|
|
29
|
|
|
from abydos.stats.confusion_table import ConfusionTable |
30
|
|
|
|
31
|
|
|
|
32
|
|
|
UNIT_TABLE = ConfusionTable(1, 1, 1, 1) |
33
|
|
|
NULL_TABLE = ConfusionTable(0, 0, 0, 0) |
34
|
|
|
SCALE_TABLE = ConfusionTable(1, 2, 3, 4) |
35
|
|
|
# https://en.wikipedia.org/wiki/Confusion_matrix#Table_of_confusion |
36
|
|
|
CATSNDOGS_TABLE = ConfusionTable(5, 17, 2, 3) |
37
|
|
|
# https://en.wikipedia.org/wiki/Sensitivity_and_specificity#Worked_example |
38
|
|
|
WORKED_EG_TABLE = ConfusionTable(20, 1820, 180, 10) |
39
|
|
|
VERY_POOR_TABLE = ConfusionTable(0, 0, 200, 200) |
40
|
|
|
|
41
|
|
|
ALL_TABLES = (UNIT_TABLE, NULL_TABLE, SCALE_TABLE, CATSNDOGS_TABLE, |
42
|
|
|
WORKED_EG_TABLE, VERY_POOR_TABLE) |
43
|
|
|
|
44
|
|
|
# def ct2arrays(ct): |
45
|
|
|
# y_pred = [] |
46
|
|
|
# y_true = [] |
47
|
|
|
# y_pred += [1]*ct.tpos |
48
|
|
|
# y_true += [1]*ct.tpos |
49
|
|
|
# y_pred += [0]*ct.tneg |
50
|
|
|
# y_true += [0]*ct.tneg |
51
|
|
|
# y_pred += [1]*ct.fpos |
52
|
|
|
# y_true += [0]*ct.fpos |
53
|
|
|
# y_pred += [0]*ct.fneg |
54
|
|
|
# y_true += [1]*ct.fneg |
55
|
|
|
# return y_pred, y_true |
56
|
|
|
|
57
|
|
|
|
58
|
|
|
class ConstructorTestCases(unittest.TestCase): |
59
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable constructors.""" |
60
|
|
|
|
61
|
|
|
def test_constructors(self): |
62
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable constructors.""" |
63
|
|
|
self.assertEqual(ConfusionTable(), ConfusionTable()) |
64
|
|
|
self.assertEqual(ConfusionTable(), ConfusionTable(0)) |
65
|
|
|
self.assertEqual(ConfusionTable(), ConfusionTable(0, 0)) |
66
|
|
|
self.assertEqual(ConfusionTable(), ConfusionTable(0, 0, 0)) |
67
|
|
|
self.assertEqual(ConfusionTable(), ConfusionTable(0, 0, 0, 0)) |
68
|
|
|
self.assertNotEquals(ConfusionTable(), ConfusionTable(1)) |
69
|
|
|
self.assertNotEquals(ConfusionTable(), ConfusionTable(0, 1)) |
70
|
|
|
self.assertNotEquals(ConfusionTable(), ConfusionTable(0, 0, 1)) |
71
|
|
|
self.assertNotEquals(ConfusionTable(), ConfusionTable(0, 0, 0, 1)) |
72
|
|
|
|
73
|
|
|
# test int constructor & __eq__ by value |
74
|
|
|
self.assertEqual(SCALE_TABLE, ConfusionTable(1, 2, 3, 4)) |
75
|
|
|
# test tuple constructor |
76
|
|
|
self.assertEqual(SCALE_TABLE, ConfusionTable((1, 2, 3, 4))) |
77
|
|
|
self.assertEqual(SCALE_TABLE, ConfusionTable((1, 2, 3, 4), 5, 6, 7)) |
78
|
|
|
# test list constructor |
79
|
|
|
self.assertEqual(SCALE_TABLE, ConfusionTable([1, 2, 3, 4])) |
80
|
|
|
self.assertEqual(SCALE_TABLE, ConfusionTable([1, 2, 3, 4], 5, 6, 7)) |
81
|
|
|
# test dict constructor |
82
|
|
|
self.assertEqual(SCALE_TABLE, ConfusionTable({'tp': 1, 'tn': 2, |
83
|
|
|
'fp': 3, 'fn': 4})) |
84
|
|
|
self.assertEqual(SCALE_TABLE, ConfusionTable({'tp': 1, 'tn': 2, |
85
|
|
|
'fp': 3, 'fn': 4}, |
86
|
|
|
5, 6, 7)) |
87
|
|
|
self.assertEqual(NULL_TABLE, ConfusionTable({})) |
88
|
|
|
self.assertEqual(NULL_TABLE, ConfusionTable({'pt': 1, 'nt': 2, |
89
|
|
|
'pf': 3, 'nf': 4})) |
90
|
|
|
|
91
|
|
|
# test __eq__ by id() |
92
|
|
|
self.assertTrue(SCALE_TABLE == SCALE_TABLE) |
93
|
|
|
self.assertFalse(CATSNDOGS_TABLE == SCALE_TABLE) |
94
|
|
|
# test __eq__ by tuple |
95
|
|
|
self.assertTrue(SCALE_TABLE == (1, 2, 3, 4)) |
96
|
|
|
self.assertFalse(CATSNDOGS_TABLE == (1, 2, 3, 4)) |
97
|
|
|
# test __eq__ by list |
98
|
|
|
self.assertTrue(SCALE_TABLE == [1, 2, 3, 4]) |
99
|
|
|
self.assertFalse(CATSNDOGS_TABLE == [1, 2, 3, 4]) |
100
|
|
|
# test __eq__ by dict |
101
|
|
|
self.assertTrue(SCALE_TABLE == {'tp': 1, 'tn': 2, |
102
|
|
|
'fp': 3, 'fn': 4}) |
103
|
|
|
self.assertFalse(CATSNDOGS_TABLE == {'tp': 1, 'tn': 2, |
104
|
|
|
'fp': 3, 'fn': 4}) |
105
|
|
|
# test __eq__ with non-ConfusionTable/tuple/list/dict |
106
|
|
|
self.assertFalse(SCALE_TABLE == 5) |
107
|
|
|
|
108
|
|
|
# test invalid tuple constructor |
109
|
|
|
self.assertRaises(AttributeError, ConfusionTable, (1, 2,)) |
110
|
|
|
|
111
|
|
|
|
112
|
|
|
class CastTestCases(unittest.TestCase): |
113
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable cast methods.""" |
114
|
|
|
|
115
|
|
|
def test_to_tuple(self): |
116
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.to_tuple.""" |
117
|
|
|
self.assertIsInstance(SCALE_TABLE.to_tuple(), tuple) |
118
|
|
|
self.assertEqual(SCALE_TABLE.to_tuple(), (1, 2, 3, 4)) |
119
|
|
|
self.assertEqual(list(SCALE_TABLE.to_tuple()), [1, 2, 3, 4]) |
120
|
|
|
|
121
|
|
|
def test_to_dict(self): |
122
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.to_dict.""" |
123
|
|
|
self.assertIsInstance(SCALE_TABLE.to_dict(), dict) |
124
|
|
|
self.assertEqual(SCALE_TABLE.to_dict(), {'tp': 1, 'tn': 2, |
125
|
|
|
'fp': 3, 'fn': 4}) |
126
|
|
|
|
127
|
|
|
def test_str(self): |
128
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable._str_.""" |
129
|
|
|
self.assertIsInstance(str(SCALE_TABLE), str) |
130
|
|
|
self.assertEqual(str(SCALE_TABLE), 'tp:1, tn:2, fp:3, fn:4') |
131
|
|
|
|
132
|
|
|
|
133
|
|
|
class PopulationTestCases(unittest.TestCase): |
134
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable population methods.""" |
135
|
|
|
|
136
|
|
|
def test_correct_pop(self): |
137
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.correct_pop.""" |
138
|
|
|
self.assertEqual(UNIT_TABLE.correct_pop(), 2) |
139
|
|
|
self.assertEqual(NULL_TABLE.correct_pop(), 0) |
140
|
|
|
self.assertEqual(SCALE_TABLE.correct_pop(), 3) |
141
|
|
|
self.assertEqual(CATSNDOGS_TABLE.correct_pop(), 22) |
142
|
|
|
self.assertEqual(WORKED_EG_TABLE.correct_pop(), 1840) |
143
|
|
|
|
144
|
|
|
def test_error_pop(self): |
145
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.error_pop.""" |
146
|
|
|
self.assertEqual(UNIT_TABLE.error_pop(), 2) |
147
|
|
|
self.assertEqual(NULL_TABLE.error_pop(), 0) |
148
|
|
|
self.assertEqual(SCALE_TABLE.error_pop(), 7) |
149
|
|
|
self.assertEqual(CATSNDOGS_TABLE.error_pop(), 5) |
150
|
|
|
self.assertEqual(WORKED_EG_TABLE.error_pop(), 190) |
151
|
|
|
|
152
|
|
|
def test_test_pos_pop(self): |
153
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.test_pos_pop.""" |
154
|
|
|
self.assertEqual(UNIT_TABLE.test_pos_pop(), 2) |
155
|
|
|
self.assertEqual(NULL_TABLE.test_pos_pop(), 0) |
156
|
|
|
self.assertEqual(SCALE_TABLE.test_pos_pop(), 4) |
157
|
|
|
self.assertEqual(CATSNDOGS_TABLE.test_pos_pop(), 7) |
158
|
|
|
self.assertEqual(WORKED_EG_TABLE.test_pos_pop(), 200) |
159
|
|
|
|
160
|
|
|
def test_test_neg_pop(self): |
161
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.test_neg_pop.""" |
162
|
|
|
self.assertEqual(UNIT_TABLE.test_neg_pop(), 2) |
163
|
|
|
self.assertEqual(NULL_TABLE.test_neg_pop(), 0) |
164
|
|
|
self.assertEqual(SCALE_TABLE.test_neg_pop(), 6) |
165
|
|
|
self.assertEqual(CATSNDOGS_TABLE.test_neg_pop(), 20) |
166
|
|
|
self.assertEqual(WORKED_EG_TABLE.test_neg_pop(), 1830) |
167
|
|
|
|
168
|
|
|
def test_cond_pos_pop(self): |
169
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.cond_pos_pop.""" |
170
|
|
|
self.assertEqual(UNIT_TABLE.cond_pos_pop(), 2) |
171
|
|
|
self.assertEqual(NULL_TABLE.cond_pos_pop(), 0) |
172
|
|
|
self.assertEqual(SCALE_TABLE.cond_pos_pop(), 5) |
173
|
|
|
self.assertEqual(CATSNDOGS_TABLE.cond_pos_pop(), 8) |
174
|
|
|
self.assertEqual(WORKED_EG_TABLE.cond_pos_pop(), 30) |
175
|
|
|
|
176
|
|
|
def test_cond_neg_pop(self): |
177
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.cond_neg_pop.""" |
178
|
|
|
self.assertEqual(UNIT_TABLE.cond_neg_pop(), 2) |
179
|
|
|
self.assertEqual(NULL_TABLE.cond_neg_pop(), 0) |
180
|
|
|
self.assertEqual(SCALE_TABLE.cond_neg_pop(), 5) |
181
|
|
|
self.assertEqual(CATSNDOGS_TABLE.cond_neg_pop(), 19) |
182
|
|
|
self.assertEqual(WORKED_EG_TABLE.cond_neg_pop(), 2000) |
183
|
|
|
|
184
|
|
|
def test_population(self): |
185
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.population.""" |
186
|
|
|
self.assertEqual(UNIT_TABLE.population(), 4) |
187
|
|
|
self.assertEqual(NULL_TABLE.population(), 0) |
188
|
|
|
self.assertEqual(SCALE_TABLE.population(), 10) |
189
|
|
|
self.assertEqual(CATSNDOGS_TABLE.population(), 27) |
190
|
|
|
self.assertEqual(WORKED_EG_TABLE.population(), 2030) |
191
|
|
|
|
192
|
|
|
|
193
|
|
|
class StatisticalRatioTestCases(unittest.TestCase): |
194
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable ratio methods.""" |
195
|
|
|
|
196
|
|
|
def test_precision(self): |
197
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.precision.""" |
198
|
|
|
self.assertEqual(UNIT_TABLE.precision(), 0.5) |
199
|
|
|
self.assertTrue(isnan(NULL_TABLE.precision())) |
200
|
|
|
self.assertAlmostEqual(SCALE_TABLE.precision(), 0.25) |
201
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.precision(), 5/7) |
202
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.precision(), 0.1) |
203
|
|
|
|
204
|
|
|
def test_precision_gain(self): |
205
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.precision_gain.""" |
206
|
|
|
self.assertEqual(UNIT_TABLE.precision_gain(), 1) |
207
|
|
|
self.assertTrue(isnan(NULL_TABLE.precision_gain())) |
208
|
|
|
self.assertAlmostEqual(SCALE_TABLE.precision_gain(), 0.25/0.5) |
209
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.precision_gain(), (5/7)/(8/27)) |
210
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.precision_gain(), 0.1/(30/2030)) |
211
|
|
|
|
212
|
|
|
def test_recall(self): |
213
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.recall.""" |
214
|
|
|
self.assertEqual(UNIT_TABLE.recall(), 0.5) |
215
|
|
|
self.assertTrue(isnan(NULL_TABLE.recall())) |
216
|
|
|
self.assertAlmostEqual(SCALE_TABLE.recall(), 0.2) |
217
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.recall(), 5/8) |
218
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.recall(), 2/3) |
219
|
|
|
|
220
|
|
|
def test_specificity(self): |
221
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.specificity.""" |
222
|
|
|
self.assertEqual(UNIT_TABLE.specificity(), 0.5) |
223
|
|
|
self.assertTrue(isnan(NULL_TABLE.specificity())) |
224
|
|
|
self.assertAlmostEqual(SCALE_TABLE.specificity(), 0.4) |
225
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.specificity(), 17/19) |
226
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.specificity(), 0.91) |
227
|
|
|
|
228
|
|
|
def test_npv(self): |
229
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.npv.""" |
230
|
|
|
self.assertEqual(UNIT_TABLE.npv(), 0.5) |
231
|
|
|
self.assertTrue(isnan(NULL_TABLE.npv())) |
232
|
|
|
self.assertAlmostEqual(SCALE_TABLE.npv(), 1/3) |
233
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.npv(), 17/20) |
234
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.npv(), 182/183) |
235
|
|
|
|
236
|
|
|
def test_fallout(self): |
237
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.fallout.""" |
238
|
|
|
self.assertEqual(UNIT_TABLE.fallout(), 0.5) |
239
|
|
|
self.assertTrue(isnan(NULL_TABLE.fallout())) |
240
|
|
|
self.assertAlmostEqual(SCALE_TABLE.fallout(), 0.6) |
241
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.fallout(), 2/19) |
242
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.fallout(), 0.09) |
243
|
|
|
|
244
|
|
|
def test_fdr(self): |
245
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.fdr.""" |
246
|
|
|
self.assertEqual(UNIT_TABLE.fdr(), 0.5) |
247
|
|
|
self.assertTrue(isnan(NULL_TABLE.fdr())) |
248
|
|
|
self.assertAlmostEqual(SCALE_TABLE.fdr(), 0.75) |
249
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.fdr(), 2/7) |
250
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.fdr(), 0.9) |
251
|
|
|
|
252
|
|
|
def test_accuracy(self): |
253
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.accuracy.""" |
254
|
|
|
self.assertEqual(UNIT_TABLE.accuracy(), 0.5) |
255
|
|
|
self.assertTrue(isnan(NULL_TABLE.accuracy())) |
256
|
|
|
self.assertAlmostEqual(SCALE_TABLE.accuracy(), 3/10) |
257
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.accuracy(), 22/27) |
258
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.accuracy(), 184/203) |
259
|
|
|
|
260
|
|
|
def test_accuracy_gain(self): |
261
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.accuracy_gain.""" |
262
|
|
|
self.assertEqual(UNIT_TABLE.accuracy_gain(), 1) |
263
|
|
|
self.assertTrue(isnan(NULL_TABLE.accuracy_gain())) |
264
|
|
|
self.assertAlmostEqual(SCALE_TABLE.accuracy_gain(), |
265
|
|
|
(3/10)/((5/10)**2+(5/10)**2)) |
266
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.accuracy_gain(), |
267
|
|
|
(22/27)/((8/27)**2+(19/27)**2)) |
268
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.accuracy_gain(), |
269
|
|
|
(184/203)/((30/2030)**2+(2000/2030)**2)) |
270
|
|
|
|
271
|
|
|
def test_balanced_accuracy(self): |
272
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.balanced_accuracy.""" # noqa: E501 |
273
|
|
|
self.assertEqual(UNIT_TABLE.balanced_accuracy(), 0.5) |
274
|
|
|
self.assertTrue(isnan(NULL_TABLE.balanced_accuracy())) |
275
|
|
|
self.assertAlmostEqual(SCALE_TABLE.balanced_accuracy(), 0.3) |
276
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.balanced_accuracy(), 231/304) |
277
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.balanced_accuracy(), 473/600) |
278
|
|
|
|
279
|
|
|
def test_informedness(self): |
280
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.informedness.""" |
281
|
|
|
self.assertEqual(UNIT_TABLE.informedness(), 0) |
282
|
|
|
self.assertTrue(isnan(NULL_TABLE.informedness())) |
283
|
|
|
self.assertAlmostEqual(SCALE_TABLE.informedness(), -0.4) |
284
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.informedness(), 79/152) |
285
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.informedness(), 2/3-0.09) |
286
|
|
|
|
287
|
|
|
def test_markedness(self): |
288
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.markedness.""" |
289
|
|
|
self.assertEqual(UNIT_TABLE.markedness(), 0) |
290
|
|
|
self.assertTrue(isnan(NULL_TABLE.markedness())) |
291
|
|
|
self.assertAlmostEqual(SCALE_TABLE.markedness(), -5/12) |
292
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.markedness(), 79/140) |
293
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.markedness(), 173/1830) |
294
|
|
|
|
295
|
|
|
|
296
|
|
|
class PrMeansTestCases(unittest.TestCase): |
297
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable PR methods.""" |
298
|
|
|
|
299
|
|
|
prre = tuple(((i.precision(), i.recall()) for i in ALL_TABLES)) |
300
|
|
|
|
301
|
|
|
def test_pr_amean(self): |
302
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_amean.""" |
303
|
|
|
self.assertEqual(UNIT_TABLE.pr_amean(), 0.5) |
304
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_amean())) |
305
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_amean(), 0.225) |
306
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_amean(), 0.6696428571428572) |
307
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_amean(), 0.3833333333333333) |
308
|
|
|
self.assertAlmostEqual(VERY_POOR_TABLE.pr_amean(), 0.0) |
309
|
|
|
|
310
|
|
|
def test_pr_gmean(self): |
311
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_gmean.""" |
312
|
|
|
self.assertEqual(UNIT_TABLE.pr_gmean(), 0.5) |
313
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_gmean())) |
314
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_gmean(), 0.22360679774997899) |
315
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_gmean(), 0.66815310478106094) |
316
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_gmean(), 0.25819888974716115) |
317
|
|
|
self.assertAlmostEqual(VERY_POOR_TABLE.pr_gmean(), 0.0) |
318
|
|
|
|
319
|
|
|
def test_pr_hmean(self): |
320
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_hmean.""" |
321
|
|
|
self.assertEqual(UNIT_TABLE.pr_hmean(), 0.5) |
322
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_hmean())) |
323
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_hmean(), 0.22222222222222221) |
324
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_hmean(), 0.66666666666666663) |
325
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hmean(), 0.17391304347826086) |
326
|
|
|
self.assertAlmostEqual(VERY_POOR_TABLE.pr_hmean(), 0.0) |
327
|
|
|
|
328
|
|
|
def test_pr_qmean(self): |
329
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_qmean.""" |
330
|
|
|
self.assertEqual(UNIT_TABLE.pr_qmean(), |
331
|
|
|
sqrt(sum(i**2 for i in self.prre[0])/2)) |
332
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_qmean())) |
333
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_qmean(), |
334
|
|
|
sqrt(sum(i**2 for i in self.prre[2])/2)) |
335
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_qmean(), |
336
|
|
|
sqrt(sum(i**2 for i in self.prre[3])/2)) |
337
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_qmean(), |
338
|
|
|
sqrt(sum(i**2 for i in self.prre[4])/2)) |
339
|
|
|
self.assertAlmostEqual(VERY_POOR_TABLE.pr_qmean(), 0.0) |
340
|
|
|
|
341
|
|
|
def test_pr_cmean(self): |
342
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_cmean.""" |
343
|
|
|
self.assertEqual(UNIT_TABLE.pr_cmean(), 0.5) |
344
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_cmean())) |
345
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_cmean(), 41/180) |
346
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_cmean(), 113/168) |
347
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_cmean(), 409/690) |
348
|
|
|
|
349
|
|
|
def test_pr_lmean(self): |
350
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_lmean.""" |
351
|
|
|
self.assertEqual(UNIT_TABLE.pr_lmean(), 0.5) |
352
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_lmean())) |
353
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_lmean(), 0.2240710058862275) |
354
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_lmean(), 0.6686496151266621) |
355
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_lmean(), 0.2986983802717959) |
356
|
|
|
self.assertAlmostEqual(VERY_POOR_TABLE.pr_lmean(), 0.0) |
357
|
|
|
|
358
|
|
|
def test_pr_imean(self): |
359
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_imean.""" |
360
|
|
|
self.assertEqual(UNIT_TABLE.pr_imean(), 0.5) |
361
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_imean())) |
362
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_imean(), 0.224535791730617) |
363
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_imean(), 0.6691463467789889) |
364
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_imean(), 0.34277561539033635) |
365
|
|
|
self.assertTrue(isnan(VERY_POOR_TABLE.pr_imean())) |
366
|
|
|
|
367
|
|
|
def test_pr_seiffert_mean(self): |
368
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_seiffert_mean.""" # noqa: E501 |
369
|
|
|
self.assertTrue(isnan(UNIT_TABLE.pr_seiffert_mean())) |
370
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_seiffert_mean())) |
371
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_seiffert_mean(), 0.2245354073) |
372
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_seiffert_mean(), |
373
|
|
|
0.6691461993) |
374
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_seiffert_mean(), |
375
|
|
|
0.3406355792) |
376
|
|
|
self.assertTrue(isnan(VERY_POOR_TABLE.pr_seiffert_mean())) |
377
|
|
|
|
378
|
|
|
def test_pr_lehmer_mean(self): |
379
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_lehmer_mean.""" |
380
|
|
|
self.assertEqual(UNIT_TABLE.pr_lehmer_mean(3), 0.5) |
381
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_lehmer_mean(3))) |
382
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_lehmer_mean(3), 189/820) |
383
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_lehmer_mean(3), 4275/6328) |
384
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_lehmer_mean(3), 8027/12270) |
385
|
|
|
|
386
|
|
|
self.assertEqual(UNIT_TABLE.pr_lehmer_mean(), 0.5) |
387
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_lehmer_mean())) |
388
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_lehmer_mean(), 41/180) |
389
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_lehmer_mean(), 113/168) |
390
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_lehmer_mean(), 409/690) |
391
|
|
|
|
392
|
|
|
self.assertEqual(UNIT_TABLE.pr_lehmer_mean(2), 0.5) |
393
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_lehmer_mean(2))) |
394
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_lehmer_mean(2), 41/180) |
395
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_lehmer_mean(2), 113/168) |
396
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_lehmer_mean(2), 409/690) |
397
|
|
|
|
398
|
|
|
# check equivalences to other specific means |
399
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_lehmer_mean(0), |
400
|
|
|
WORKED_EG_TABLE.pr_hmean()) |
401
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_lehmer_mean(0.5), |
402
|
|
|
WORKED_EG_TABLE.pr_gmean()) |
403
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_lehmer_mean(1), |
404
|
|
|
WORKED_EG_TABLE.pr_amean()) |
405
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_lehmer_mean(2), |
406
|
|
|
WORKED_EG_TABLE.pr_cmean()) |
407
|
|
|
|
408
|
|
|
def test_pr_heronian_mean(self): |
409
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_heronian_mean.""" # noqa: E501 |
410
|
|
|
self.assertEqual(UNIT_TABLE.pr_heronian_mean(), 0.5) |
411
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_heronian_mean())) |
412
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_heronian_mean(), 0.2245355992) |
413
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_heronian_mean(), |
414
|
|
|
0.6691462730) |
415
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_heronian_mean(), |
416
|
|
|
0.3416218521) |
417
|
|
|
self.assertEqual(VERY_POOR_TABLE.pr_heronian_mean(), 0) |
418
|
|
|
|
419
|
|
|
def test_pr_hoelder_mean(self): |
420
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_hoelder_mean.""" |
421
|
|
|
self.assertEqual(UNIT_TABLE.pr_hoelder_mean(), 0.5) |
422
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_hoelder_mean())) |
423
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_hoelder_mean(), |
424
|
|
|
0.22638462845343543) |
425
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_hoelder_mean(), |
426
|
|
|
0.6711293026059334) |
427
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(), |
428
|
|
|
0.4766783215358364) |
429
|
|
|
|
430
|
|
|
self.assertEqual(UNIT_TABLE.pr_hoelder_mean(0), 0.5) |
431
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_hoelder_mean(0))) |
432
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_hoelder_mean(0), |
433
|
|
|
0.22360679774997899) |
434
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_hoelder_mean(0), |
435
|
|
|
0.66815310478106094) |
436
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(0), |
437
|
|
|
0.25819888974716115) |
438
|
|
|
|
439
|
|
|
self.assertEqual(UNIT_TABLE.pr_hoelder_mean(1), 0.5) |
440
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_hoelder_mean(1))) |
441
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_hoelder_mean(1), 9/40) |
442
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_hoelder_mean(1), 75/112) |
443
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(1), 23/60) |
444
|
|
|
|
445
|
|
|
self.assertEqual(UNIT_TABLE.pr_hoelder_mean(2), 0.5) |
446
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_hoelder_mean(2))) |
447
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_hoelder_mean(2), |
448
|
|
|
0.22638462845343543) |
449
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_hoelder_mean(2), |
450
|
|
|
0.6711293026059334) |
451
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(2), |
452
|
|
|
0.4766783215358364) |
453
|
|
|
|
454
|
|
|
self.assertEqual(UNIT_TABLE.pr_hoelder_mean(3), 0.5) |
455
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_hoelder_mean(3))) |
456
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_hoelder_mean(3), |
457
|
|
|
0.2277441728906747) |
458
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_hoelder_mean(3), |
459
|
|
|
0.6726059172248808) |
460
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(3), |
461
|
|
|
0.5297282909519099) |
462
|
|
|
|
463
|
|
|
# check equivalences to other specific means |
464
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(-1), |
465
|
|
|
WORKED_EG_TABLE.pr_hmean()) |
466
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(0), |
467
|
|
|
WORKED_EG_TABLE.pr_gmean()) |
468
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(1), |
469
|
|
|
WORKED_EG_TABLE.pr_amean()) |
470
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_hoelder_mean(2), |
471
|
|
|
WORKED_EG_TABLE.pr_qmean()) |
472
|
|
|
|
473
|
|
|
def test_pr_agmean(self): |
474
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_agmean. |
475
|
|
|
|
476
|
|
|
Test values computed via http://arithmeticgeometricmean.blogspot.de/ |
477
|
|
|
""" |
478
|
|
|
self.assertEqual(UNIT_TABLE.pr_agmean(), 0.5) |
479
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_agmean())) |
480
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_agmean(), 0.2243028580287603) |
481
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_agmean(), 0.6688977735879823) |
482
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_agmean(), 0.3176780357448827) |
483
|
|
|
self.assertAlmostEqual(VERY_POOR_TABLE.pr_agmean(), 0.0) |
484
|
|
|
|
485
|
|
|
def test_pr_ghmean(self): |
486
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_ghmean.""" |
487
|
|
|
self.assertEqual(UNIT_TABLE.pr_ghmean(), 0.5) |
488
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_ghmean())) |
489
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_ghmean(), 0.2229128974) |
490
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_ghmean(), 0.6674092650) |
491
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_ghmean(), 0.2098560781) |
492
|
|
|
self.assertAlmostEqual(VERY_POOR_TABLE.pr_ghmean(), 0.0) |
493
|
|
|
|
494
|
|
|
def test_pr_aghmean(self): |
495
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.pr_aghmean.""" |
496
|
|
|
self.assertEqual(UNIT_TABLE.pr_aghmean(), 0.5) |
497
|
|
|
self.assertTrue(isnan(NULL_TABLE.pr_aghmean())) |
498
|
|
|
self.assertAlmostEqual(SCALE_TABLE.pr_aghmean(), 0.2236067977) |
499
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.pr_aghmean(), 0.6681531047) |
500
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.pr_aghmean(), 0.2581988897) |
501
|
|
|
self.assertAlmostEqual(VERY_POOR_TABLE.pr_aghmean(), 0.0) |
502
|
|
|
|
503
|
|
|
|
504
|
|
|
class StatisticalMeasureTestCases(unittest.TestCase): |
505
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable stats functions.""" |
506
|
|
|
|
507
|
|
|
prre = tuple(((i.precision(), i.recall()) for i in ALL_TABLES)) |
508
|
|
|
|
509
|
|
|
def test_fbeta_score(self): |
510
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.fbeta_score.""" |
511
|
|
|
self.assertEqual(UNIT_TABLE.fbeta_score(1), 0.5) |
512
|
|
|
self.assertTrue(isnan(NULL_TABLE.fbeta_score(1))) |
513
|
|
|
self.assertAlmostEqual(SCALE_TABLE.fbeta_score(1), 2/9) |
514
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.fbeta_score(1), 2/3) |
515
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.fbeta_score(1), 4/23) |
516
|
|
|
self.assertRaises(AttributeError, UNIT_TABLE.fbeta_score, -1) |
517
|
|
|
|
518
|
|
|
def test_f2_score(self): |
519
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.f2_score.""" |
520
|
|
|
self.assertEqual(UNIT_TABLE.f2_score(), 0.5) |
521
|
|
|
self.assertTrue(isnan(NULL_TABLE.f2_score())) |
522
|
|
|
self.assertAlmostEqual(SCALE_TABLE.f2_score(), 5/24) |
523
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.f2_score(), 25/39) |
524
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.f2_score(), 5/16) |
525
|
|
|
|
526
|
|
|
def test_fhalf_score(self): |
527
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.fhalf_score.""" |
528
|
|
|
self.assertEqual(UNIT_TABLE.fhalf_score(), 0.5) |
529
|
|
|
self.assertTrue(isnan(NULL_TABLE.fhalf_score())) |
530
|
|
|
self.assertAlmostEqual(SCALE_TABLE.fhalf_score(), 5/21) |
531
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.fhalf_score(), 25/36) |
532
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.fhalf_score(), 10/83) |
533
|
|
|
|
534
|
|
|
def test_e_score(self): |
535
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.e_score.""" |
536
|
|
|
self.assertEqual(UNIT_TABLE.e_score(), 0.5) |
537
|
|
|
self.assertTrue(isnan(NULL_TABLE.e_score())) |
538
|
|
|
self.assertAlmostEqual(SCALE_TABLE.e_score(), 7/9) |
539
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.e_score(), 1/3) |
540
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.e_score(), 19/23) |
541
|
|
|
|
542
|
|
|
def test_f1_score(self): |
543
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.f1_score.""" |
544
|
|
|
self.assertEqual(UNIT_TABLE.f1_score(), 0.5) |
545
|
|
|
self.assertTrue(isnan(NULL_TABLE.f1_score())) |
546
|
|
|
self.assertAlmostEqual(SCALE_TABLE.f1_score(), 2/9) |
547
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.f1_score(), 2/3) |
548
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.f1_score(), 4/23) |
549
|
|
|
|
550
|
|
|
def test_f_measure(self): |
551
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.f_measure.""" |
552
|
|
|
self.assertEqual(UNIT_TABLE.f_measure(), 0.5) |
553
|
|
|
self.assertTrue(isnan(NULL_TABLE.f_measure())) |
554
|
|
|
self.assertAlmostEqual(SCALE_TABLE.f_measure(), 2/9) |
555
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.f_measure(), 2/3) |
556
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.f_measure(), 4/23) |
557
|
|
|
|
558
|
|
|
def test_g_measure(self): |
559
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.g_measure.""" |
560
|
|
|
self.assertEqual(UNIT_TABLE.g_measure(), 0.5) |
561
|
|
|
self.assertTrue(isnan(NULL_TABLE.g_measure())) |
562
|
|
|
self.assertAlmostEqual(SCALE_TABLE.g_measure(), 0.22360679774997899) |
563
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.g_measure(), |
564
|
|
|
0.66815310478106094) |
565
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.g_measure(), |
566
|
|
|
0.25819888974716115) |
567
|
|
|
|
568
|
|
|
def test_mcc(self): |
569
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.mcc.""" |
570
|
|
|
self.assertEqual(UNIT_TABLE.mcc(), 0) |
571
|
|
|
self.assertTrue(isnan(NULL_TABLE.mcc())) |
572
|
|
|
self.assertAlmostEqual(SCALE_TABLE.mcc(), -10/sqrt(600)) |
573
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.mcc(), 79/sqrt(21280)) |
574
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.mcc(), 34600/sqrt(21960000000)) |
575
|
|
|
|
576
|
|
|
def test_significance(self): |
577
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.significance.""" |
578
|
|
|
self.assertEqual(UNIT_TABLE.significance(), 0) |
579
|
|
|
self.assertTrue(isnan(NULL_TABLE.significance())) |
580
|
|
|
self.assertAlmostEqual(SCALE_TABLE.significance(), 5/3) |
581
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.significance(), 79**2/21280*27) |
582
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.significance(), |
583
|
|
|
34600**2/21960000000*2030) |
584
|
|
|
|
585
|
|
|
def test_kappa_statistic(self): |
586
|
|
|
"""Test abydos.stats.confusion_table.ConfusionTable.kappa_statistic.""" |
587
|
|
|
def _quick_kappa(acc, racc): |
588
|
|
|
return (acc-racc)/(1-racc) |
589
|
|
|
|
590
|
|
|
self.assertEqual(UNIT_TABLE.kappa_statistic(), 0) |
591
|
|
|
self.assertTrue(isnan(NULL_TABLE.kappa_statistic())) |
592
|
|
|
self.assertAlmostEqual(SCALE_TABLE.kappa_statistic(), |
593
|
|
|
_quick_kappa((3/10), (1/2))) |
594
|
|
|
self.assertAlmostEqual(CATSNDOGS_TABLE.kappa_statistic(), |
595
|
|
|
_quick_kappa((22/27), (436/27**2))) |
596
|
|
|
self.assertAlmostEqual(WORKED_EG_TABLE.kappa_statistic(), |
597
|
|
|
_quick_kappa((184/203), |
598
|
|
|
(((2000*1830)+6000)/2030**2))) |
599
|
|
|
|
600
|
|
|
|
601
|
|
|
if __name__ == '__main__': |
602
|
|
|
unittest.main() |
603
|
|
|
|