blob: 3d30d88f697ecd5149498a45c995c79231ecbfb4 [file] [log] [blame]
Larry Hastingsf5e987b2013-10-19 11:50:09 -07001"""Test suite for statistics module, including helper NumericTestCase and
2approx_equal function.
3
4"""
5
6import collections
7import decimal
8import doctest
9import math
10import random
Serhiy Storchakab12cb6a2013-12-08 18:16:18 +020011import sys
Larry Hastingsf5e987b2013-10-19 11:50:09 -070012import types
13import unittest
14
15from decimal import Decimal
16from fractions import Fraction
17
18
19# Module to be tested.
20import statistics
21
22
23# === Helper functions and class ===
24
25def _calc_errors(actual, expected):
26 """Return the absolute and relative errors between two numbers.
27
28 >>> _calc_errors(100, 75)
29 (25, 0.25)
30 >>> _calc_errors(100, 100)
31 (0, 0.0)
32
33 Returns the (absolute error, relative error) between the two arguments.
34 """
35 base = max(abs(actual), abs(expected))
36 abs_err = abs(actual - expected)
37 rel_err = abs_err/base if base else float('inf')
38 return (abs_err, rel_err)
39
40
41def approx_equal(x, y, tol=1e-12, rel=1e-7):
42 """approx_equal(x, y [, tol [, rel]]) => True|False
43
44 Return True if numbers x and y are approximately equal, to within some
45 margin of error, otherwise return False. Numbers which compare equal
46 will also compare approximately equal.
47
48 x is approximately equal to y if the difference between them is less than
49 an absolute error tol or a relative error rel, whichever is bigger.
50
51 If given, both tol and rel must be finite, non-negative numbers. If not
52 given, default values are tol=1e-12 and rel=1e-7.
53
54 >>> approx_equal(1.2589, 1.2587, tol=0.0003, rel=0)
55 True
56 >>> approx_equal(1.2589, 1.2587, tol=0.0001, rel=0)
57 False
58
59 Absolute error is defined as abs(x-y); if that is less than or equal to
60 tol, x and y are considered approximately equal.
61
62 Relative error is defined as abs((x-y)/x) or abs((x-y)/y), whichever is
63 smaller, provided x or y are not zero. If that figure is less than or
64 equal to rel, x and y are considered approximately equal.
65
66 Complex numbers are not directly supported. If you wish to compare to
67 complex numbers, extract their real and imaginary parts and compare them
68 individually.
69
70 NANs always compare unequal, even with themselves. Infinities compare
71 approximately equal if they have the same sign (both positive or both
72 negative). Infinities with different signs compare unequal; so do
73 comparisons of infinities with finite numbers.
74 """
75 if tol < 0 or rel < 0:
76 raise ValueError('error tolerances must be non-negative')
77 # NANs are never equal to anything, approximately or otherwise.
78 if math.isnan(x) or math.isnan(y):
79 return False
80 # Numbers which compare equal also compare approximately equal.
81 if x == y:
82 # This includes the case of two infinities with the same sign.
83 return True
84 if math.isinf(x) or math.isinf(y):
85 # This includes the case of two infinities of opposite sign, or
86 # one infinity and one finite number.
87 return False
88 # Two finite numbers.
89 actual_error = abs(x - y)
90 allowed_error = max(tol, rel*max(abs(x), abs(y)))
91 return actual_error <= allowed_error
92
93
94# This class exists only as somewhere to stick a docstring containing
95# doctests. The following docstring and tests were originally in a separate
96# module. Now that it has been merged in here, I need somewhere to hang the.
97# docstring. Ultimately, this class will die, and the information below will
98# either become redundant, or be moved into more appropriate places.
99class _DoNothing:
100 """
101 When doing numeric work, especially with floats, exact equality is often
102 not what you want. Due to round-off error, it is often a bad idea to try
103 to compare floats with equality. Instead the usual procedure is to test
104 them with some (hopefully small!) allowance for error.
105
106 The ``approx_equal`` function allows you to specify either an absolute
107 error tolerance, or a relative error, or both.
108
109 Absolute error tolerances are simple, but you need to know the magnitude
110 of the quantities being compared:
111
112 >>> approx_equal(12.345, 12.346, tol=1e-3)
113 True
114 >>> approx_equal(12.345e6, 12.346e6, tol=1e-3) # tol is too small.
115 False
116
117 Relative errors are more suitable when the values you are comparing can
118 vary in magnitude:
119
120 >>> approx_equal(12.345, 12.346, rel=1e-4)
121 True
122 >>> approx_equal(12.345e6, 12.346e6, rel=1e-4)
123 True
124
125 but a naive implementation of relative error testing can run into trouble
126 around zero.
127
128 If you supply both an absolute tolerance and a relative error, the
129 comparison succeeds if either individual test succeeds:
130
131 >>> approx_equal(12.345e6, 12.346e6, tol=1e-3, rel=1e-4)
132 True
133
134 """
135 pass
136
137
138
139# We prefer this for testing numeric values that may not be exactly equal,
140# and avoid using TestCase.assertAlmostEqual, because it sucks :-)
141
142class NumericTestCase(unittest.TestCase):
143 """Unit test class for numeric work.
144
145 This subclasses TestCase. In addition to the standard method
146 ``TestCase.assertAlmostEqual``, ``assertApproxEqual`` is provided.
147 """
148 # By default, we expect exact equality, unless overridden.
149 tol = rel = 0
150
151 def assertApproxEqual(
152 self, first, second, tol=None, rel=None, msg=None
153 ):
154 """Test passes if ``first`` and ``second`` are approximately equal.
155
156 This test passes if ``first`` and ``second`` are equal to
157 within ``tol``, an absolute error, or ``rel``, a relative error.
158
159 If either ``tol`` or ``rel`` are None or not given, they default to
160 test attributes of the same name (by default, 0).
161
162 The objects may be either numbers, or sequences of numbers. Sequences
163 are tested element-by-element.
164
165 >>> class MyTest(NumericTestCase):
166 ... def test_number(self):
167 ... x = 1.0/6
168 ... y = sum([x]*6)
169 ... self.assertApproxEqual(y, 1.0, tol=1e-15)
170 ... def test_sequence(self):
171 ... a = [1.001, 1.001e-10, 1.001e10]
172 ... b = [1.0, 1e-10, 1e10]
173 ... self.assertApproxEqual(a, b, rel=1e-3)
174 ...
175 >>> import unittest
176 >>> from io import StringIO # Suppress test runner output.
177 >>> suite = unittest.TestLoader().loadTestsFromTestCase(MyTest)
178 >>> unittest.TextTestRunner(stream=StringIO()).run(suite)
179 <unittest.runner.TextTestResult run=2 errors=0 failures=0>
180
181 """
182 if tol is None:
183 tol = self.tol
184 if rel is None:
185 rel = self.rel
186 if (
187 isinstance(first, collections.Sequence) and
188 isinstance(second, collections.Sequence)
189 ):
190 check = self._check_approx_seq
191 else:
192 check = self._check_approx_num
193 check(first, second, tol, rel, msg)
194
195 def _check_approx_seq(self, first, second, tol, rel, msg):
196 if len(first) != len(second):
197 standardMsg = (
198 "sequences differ in length: %d items != %d items"
199 % (len(first), len(second))
200 )
201 msg = self._formatMessage(msg, standardMsg)
202 raise self.failureException(msg)
203 for i, (a,e) in enumerate(zip(first, second)):
204 self._check_approx_num(a, e, tol, rel, msg, i)
205
206 def _check_approx_num(self, first, second, tol, rel, msg, idx=None):
207 if approx_equal(first, second, tol, rel):
208 # Test passes. Return early, we are done.
209 return None
210 # Otherwise we failed.
211 standardMsg = self._make_std_err_msg(first, second, tol, rel, idx)
212 msg = self._formatMessage(msg, standardMsg)
213 raise self.failureException(msg)
214
215 @staticmethod
216 def _make_std_err_msg(first, second, tol, rel, idx):
217 # Create the standard error message for approx_equal failures.
218 assert first != second
219 template = (
220 ' %r != %r\n'
221 ' values differ by more than tol=%r and rel=%r\n'
222 ' -> absolute error = %r\n'
223 ' -> relative error = %r'
224 )
225 if idx is not None:
226 header = 'numeric sequences first differ at index %d.\n' % idx
227 template = header + template
228 # Calculate actual errors:
229 abs_err, rel_err = _calc_errors(first, second)
230 return template % (first, second, tol, rel, abs_err, rel_err)
231
232
233# ========================
234# === Test the helpers ===
235# ========================
236
237
238# --- Tests for approx_equal ---
239
240class ApproxEqualSymmetryTest(unittest.TestCase):
241 # Test symmetry of approx_equal.
242
243 def test_relative_symmetry(self):
244 # Check that approx_equal treats relative error symmetrically.
245 # (a-b)/a is usually not equal to (a-b)/b. Ensure that this
246 # doesn't matter.
247 #
248 # Note: the reason for this test is that an early version
249 # of approx_equal was not symmetric. A relative error test
250 # would pass, or fail, depending on which value was passed
251 # as the first argument.
252 #
253 args1 = [2456, 37.8, -12.45, Decimal('2.54'), Fraction(17, 54)]
254 args2 = [2459, 37.2, -12.41, Decimal('2.59'), Fraction(15, 54)]
255 assert len(args1) == len(args2)
256 for a, b in zip(args1, args2):
257 self.do_relative_symmetry(a, b)
258
259 def do_relative_symmetry(self, a, b):
260 a, b = min(a, b), max(a, b)
261 assert a < b
262 delta = b - a # The absolute difference between the values.
263 rel_err1, rel_err2 = abs(delta/a), abs(delta/b)
264 # Choose an error margin halfway between the two.
265 rel = (rel_err1 + rel_err2)/2
266 # Now see that values a and b compare approx equal regardless of
267 # which is given first.
268 self.assertTrue(approx_equal(a, b, tol=0, rel=rel))
269 self.assertTrue(approx_equal(b, a, tol=0, rel=rel))
270
271 def test_symmetry(self):
272 # Test that approx_equal(a, b) == approx_equal(b, a)
273 args = [-23, -2, 5, 107, 93568]
274 delta = 2
Christian Heimesad393602013-11-26 01:32:15 +0100275 for a in args:
Larry Hastingsf5e987b2013-10-19 11:50:09 -0700276 for type_ in (int, float, Decimal, Fraction):
Christian Heimesad393602013-11-26 01:32:15 +0100277 x = type_(a)*100
Larry Hastingsf5e987b2013-10-19 11:50:09 -0700278 y = x + delta
279 r = abs(delta/max(x, y))
280 # There are five cases to check:
281 # 1) actual error <= tol, <= rel
282 self.do_symmetry_test(x, y, tol=delta, rel=r)
283 self.do_symmetry_test(x, y, tol=delta+1, rel=2*r)
284 # 2) actual error > tol, > rel
285 self.do_symmetry_test(x, y, tol=delta-1, rel=r/2)
286 # 3) actual error <= tol, > rel
287 self.do_symmetry_test(x, y, tol=delta, rel=r/2)
288 # 4) actual error > tol, <= rel
289 self.do_symmetry_test(x, y, tol=delta-1, rel=r)
290 self.do_symmetry_test(x, y, tol=delta-1, rel=2*r)
291 # 5) exact equality test
292 self.do_symmetry_test(x, x, tol=0, rel=0)
293 self.do_symmetry_test(x, y, tol=0, rel=0)
294
295 def do_symmetry_test(self, a, b, tol, rel):
296 template = "approx_equal comparisons don't match for %r"
297 flag1 = approx_equal(a, b, tol, rel)
298 flag2 = approx_equal(b, a, tol, rel)
299 self.assertEqual(flag1, flag2, template.format((a, b, tol, rel)))
300
301
302class ApproxEqualExactTest(unittest.TestCase):
303 # Test the approx_equal function with exactly equal values.
304 # Equal values should compare as approximately equal.
305 # Test cases for exactly equal values, which should compare approx
306 # equal regardless of the error tolerances given.
307
308 def do_exactly_equal_test(self, x, tol, rel):
309 result = approx_equal(x, x, tol=tol, rel=rel)
310 self.assertTrue(result, 'equality failure for x=%r' % x)
311 result = approx_equal(-x, -x, tol=tol, rel=rel)
312 self.assertTrue(result, 'equality failure for x=%r' % -x)
313
314 def test_exactly_equal_ints(self):
315 # Test that equal int values are exactly equal.
316 for n in [42, 19740, 14974, 230, 1795, 700245, 36587]:
317 self.do_exactly_equal_test(n, 0, 0)
318
319 def test_exactly_equal_floats(self):
320 # Test that equal float values are exactly equal.
321 for x in [0.42, 1.9740, 1497.4, 23.0, 179.5, 70.0245, 36.587]:
322 self.do_exactly_equal_test(x, 0, 0)
323
324 def test_exactly_equal_fractions(self):
325 # Test that equal Fraction values are exactly equal.
326 F = Fraction
327 for f in [F(1, 2), F(0), F(5, 3), F(9, 7), F(35, 36), F(3, 7)]:
328 self.do_exactly_equal_test(f, 0, 0)
329
330 def test_exactly_equal_decimals(self):
331 # Test that equal Decimal values are exactly equal.
332 D = Decimal
333 for d in map(D, "8.2 31.274 912.04 16.745 1.2047".split()):
334 self.do_exactly_equal_test(d, 0, 0)
335
336 def test_exactly_equal_absolute(self):
337 # Test that equal values are exactly equal with an absolute error.
338 for n in [16, 1013, 1372, 1198, 971, 4]:
339 # Test as ints.
340 self.do_exactly_equal_test(n, 0.01, 0)
341 # Test as floats.
342 self.do_exactly_equal_test(n/10, 0.01, 0)
343 # Test as Fractions.
344 f = Fraction(n, 1234)
345 self.do_exactly_equal_test(f, 0.01, 0)
346
347 def test_exactly_equal_absolute_decimals(self):
348 # Test equal Decimal values are exactly equal with an absolute error.
349 self.do_exactly_equal_test(Decimal("3.571"), Decimal("0.01"), 0)
350 self.do_exactly_equal_test(-Decimal("81.3971"), Decimal("0.01"), 0)
351
352 def test_exactly_equal_relative(self):
353 # Test that equal values are exactly equal with a relative error.
354 for x in [8347, 101.3, -7910.28, Fraction(5, 21)]:
355 self.do_exactly_equal_test(x, 0, 0.01)
356 self.do_exactly_equal_test(Decimal("11.68"), 0, Decimal("0.01"))
357
358 def test_exactly_equal_both(self):
359 # Test that equal values are equal when both tol and rel are given.
360 for x in [41017, 16.742, -813.02, Fraction(3, 8)]:
361 self.do_exactly_equal_test(x, 0.1, 0.01)
362 D = Decimal
363 self.do_exactly_equal_test(D("7.2"), D("0.1"), D("0.01"))
364
365
366class ApproxEqualUnequalTest(unittest.TestCase):
367 # Unequal values should compare unequal with zero error tolerances.
368 # Test cases for unequal values, with exact equality test.
369
370 def do_exactly_unequal_test(self, x):
371 for a in (x, -x):
372 result = approx_equal(a, a+1, tol=0, rel=0)
373 self.assertFalse(result, 'inequality failure for x=%r' % a)
374
375 def test_exactly_unequal_ints(self):
376 # Test unequal int values are unequal with zero error tolerance.
377 for n in [951, 572305, 478, 917, 17240]:
378 self.do_exactly_unequal_test(n)
379
380 def test_exactly_unequal_floats(self):
381 # Test unequal float values are unequal with zero error tolerance.
382 for x in [9.51, 5723.05, 47.8, 9.17, 17.24]:
383 self.do_exactly_unequal_test(x)
384
385 def test_exactly_unequal_fractions(self):
386 # Test that unequal Fractions are unequal with zero error tolerance.
387 F = Fraction
388 for f in [F(1, 5), F(7, 9), F(12, 11), F(101, 99023)]:
389 self.do_exactly_unequal_test(f)
390
391 def test_exactly_unequal_decimals(self):
392 # Test that unequal Decimals are unequal with zero error tolerance.
393 for d in map(Decimal, "3.1415 298.12 3.47 18.996 0.00245".split()):
394 self.do_exactly_unequal_test(d)
395
396
397class ApproxEqualInexactTest(unittest.TestCase):
398 # Inexact test cases for approx_error.
399 # Test cases when comparing two values that are not exactly equal.
400
401 # === Absolute error tests ===
402
403 def do_approx_equal_abs_test(self, x, delta):
404 template = "Test failure for x={!r}, y={!r}"
405 for y in (x + delta, x - delta):
406 msg = template.format(x, y)
407 self.assertTrue(approx_equal(x, y, tol=2*delta, rel=0), msg)
408 self.assertFalse(approx_equal(x, y, tol=delta/2, rel=0), msg)
409
410 def test_approx_equal_absolute_ints(self):
411 # Test approximate equality of ints with an absolute error.
412 for n in [-10737, -1975, -7, -2, 0, 1, 9, 37, 423, 9874, 23789110]:
413 self.do_approx_equal_abs_test(n, 10)
414 self.do_approx_equal_abs_test(n, 2)
415
416 def test_approx_equal_absolute_floats(self):
417 # Test approximate equality of floats with an absolute error.
418 for x in [-284.126, -97.1, -3.4, -2.15, 0.5, 1.0, 7.8, 4.23, 3817.4]:
419 self.do_approx_equal_abs_test(x, 1.5)
420 self.do_approx_equal_abs_test(x, 0.01)
421 self.do_approx_equal_abs_test(x, 0.0001)
422
423 def test_approx_equal_absolute_fractions(self):
424 # Test approximate equality of Fractions with an absolute error.
425 delta = Fraction(1, 29)
426 numerators = [-84, -15, -2, -1, 0, 1, 5, 17, 23, 34, 71]
427 for f in (Fraction(n, 29) for n in numerators):
428 self.do_approx_equal_abs_test(f, delta)
429 self.do_approx_equal_abs_test(f, float(delta))
430
431 def test_approx_equal_absolute_decimals(self):
432 # Test approximate equality of Decimals with an absolute error.
433 delta = Decimal("0.01")
434 for d in map(Decimal, "1.0 3.5 36.08 61.79 7912.3648".split()):
435 self.do_approx_equal_abs_test(d, delta)
436 self.do_approx_equal_abs_test(-d, delta)
437
438 def test_cross_zero(self):
439 # Test for the case of the two values having opposite signs.
440 self.assertTrue(approx_equal(1e-5, -1e-5, tol=1e-4, rel=0))
441
442 # === Relative error tests ===
443
444 def do_approx_equal_rel_test(self, x, delta):
445 template = "Test failure for x={!r}, y={!r}"
446 for y in (x*(1+delta), x*(1-delta)):
447 msg = template.format(x, y)
448 self.assertTrue(approx_equal(x, y, tol=0, rel=2*delta), msg)
449 self.assertFalse(approx_equal(x, y, tol=0, rel=delta/2), msg)
450
451 def test_approx_equal_relative_ints(self):
452 # Test approximate equality of ints with a relative error.
453 self.assertTrue(approx_equal(64, 47, tol=0, rel=0.36))
454 self.assertTrue(approx_equal(64, 47, tol=0, rel=0.37))
455 # ---
456 self.assertTrue(approx_equal(449, 512, tol=0, rel=0.125))
457 self.assertTrue(approx_equal(448, 512, tol=0, rel=0.125))
458 self.assertFalse(approx_equal(447, 512, tol=0, rel=0.125))
459
460 def test_approx_equal_relative_floats(self):
461 # Test approximate equality of floats with a relative error.
462 for x in [-178.34, -0.1, 0.1, 1.0, 36.97, 2847.136, 9145.074]:
463 self.do_approx_equal_rel_test(x, 0.02)
464 self.do_approx_equal_rel_test(x, 0.0001)
465
466 def test_approx_equal_relative_fractions(self):
467 # Test approximate equality of Fractions with a relative error.
468 F = Fraction
469 delta = Fraction(3, 8)
470 for f in [F(3, 84), F(17, 30), F(49, 50), F(92, 85)]:
471 for d in (delta, float(delta)):
472 self.do_approx_equal_rel_test(f, d)
473 self.do_approx_equal_rel_test(-f, d)
474
475 def test_approx_equal_relative_decimals(self):
476 # Test approximate equality of Decimals with a relative error.
477 for d in map(Decimal, "0.02 1.0 5.7 13.67 94.138 91027.9321".split()):
478 self.do_approx_equal_rel_test(d, Decimal("0.001"))
479 self.do_approx_equal_rel_test(-d, Decimal("0.05"))
480
481 # === Both absolute and relative error tests ===
482
483 # There are four cases to consider:
484 # 1) actual error <= both absolute and relative error
485 # 2) actual error <= absolute error but > relative error
486 # 3) actual error <= relative error but > absolute error
487 # 4) actual error > both absolute and relative error
488
489 def do_check_both(self, a, b, tol, rel, tol_flag, rel_flag):
490 check = self.assertTrue if tol_flag else self.assertFalse
491 check(approx_equal(a, b, tol=tol, rel=0))
492 check = self.assertTrue if rel_flag else self.assertFalse
493 check(approx_equal(a, b, tol=0, rel=rel))
494 check = self.assertTrue if (tol_flag or rel_flag) else self.assertFalse
495 check(approx_equal(a, b, tol=tol, rel=rel))
496
497 def test_approx_equal_both1(self):
498 # Test actual error <= both absolute and relative error.
499 self.do_check_both(7.955, 7.952, 0.004, 3.8e-4, True, True)
500 self.do_check_both(-7.387, -7.386, 0.002, 0.0002, True, True)
501
502 def test_approx_equal_both2(self):
503 # Test actual error <= absolute error but > relative error.
504 self.do_check_both(7.955, 7.952, 0.004, 3.7e-4, True, False)
505
506 def test_approx_equal_both3(self):
507 # Test actual error <= relative error but > absolute error.
508 self.do_check_both(7.955, 7.952, 0.001, 3.8e-4, False, True)
509
510 def test_approx_equal_both4(self):
511 # Test actual error > both absolute and relative error.
512 self.do_check_both(2.78, 2.75, 0.01, 0.001, False, False)
513 self.do_check_both(971.44, 971.47, 0.02, 3e-5, False, False)
514
515
516class ApproxEqualSpecialsTest(unittest.TestCase):
517 # Test approx_equal with NANs and INFs and zeroes.
518
519 def test_inf(self):
520 for type_ in (float, Decimal):
521 inf = type_('inf')
522 self.assertTrue(approx_equal(inf, inf))
523 self.assertTrue(approx_equal(inf, inf, 0, 0))
524 self.assertTrue(approx_equal(inf, inf, 1, 0.01))
525 self.assertTrue(approx_equal(-inf, -inf))
526 self.assertFalse(approx_equal(inf, -inf))
527 self.assertFalse(approx_equal(inf, 1000))
528
529 def test_nan(self):
530 for type_ in (float, Decimal):
531 nan = type_('nan')
532 for other in (nan, type_('inf'), 1000):
533 self.assertFalse(approx_equal(nan, other))
534
535 def test_float_zeroes(self):
536 nzero = math.copysign(0.0, -1)
537 self.assertTrue(approx_equal(nzero, 0.0, tol=0.1, rel=0.1))
538
539 def test_decimal_zeroes(self):
540 nzero = Decimal("-0.0")
541 self.assertTrue(approx_equal(nzero, Decimal(0), tol=0.1, rel=0.1))
542
543
544class TestApproxEqualErrors(unittest.TestCase):
545 # Test error conditions of approx_equal.
546
547 def test_bad_tol(self):
548 # Test negative tol raises.
549 self.assertRaises(ValueError, approx_equal, 100, 100, -1, 0.1)
550
551 def test_bad_rel(self):
552 # Test negative rel raises.
553 self.assertRaises(ValueError, approx_equal, 100, 100, 1, -0.1)
554
555
556# --- Tests for NumericTestCase ---
557
558# The formatting routine that generates the error messages is complex enough
559# that it too needs testing.
560
561class TestNumericTestCase(unittest.TestCase):
562 # The exact wording of NumericTestCase error messages is *not* guaranteed,
563 # but we need to give them some sort of test to ensure that they are
564 # generated correctly. As a compromise, we look for specific substrings
565 # that are expected to be found even if the overall error message changes.
566
567 def do_test(self, args):
568 actual_msg = NumericTestCase._make_std_err_msg(*args)
569 expected = self.generate_substrings(*args)
570 for substring in expected:
571 self.assertIn(substring, actual_msg)
572
573 def test_numerictestcase_is_testcase(self):
574 # Ensure that NumericTestCase actually is a TestCase.
575 self.assertTrue(issubclass(NumericTestCase, unittest.TestCase))
576
577 def test_error_msg_numeric(self):
578 # Test the error message generated for numeric comparisons.
579 args = (2.5, 4.0, 0.5, 0.25, None)
580 self.do_test(args)
581
582 def test_error_msg_sequence(self):
583 # Test the error message generated for sequence comparisons.
584 args = (3.75, 8.25, 1.25, 0.5, 7)
585 self.do_test(args)
586
587 def generate_substrings(self, first, second, tol, rel, idx):
588 """Return substrings we expect to see in error messages."""
589 abs_err, rel_err = _calc_errors(first, second)
590 substrings = [
591 'tol=%r' % tol,
592 'rel=%r' % rel,
593 'absolute error = %r' % abs_err,
594 'relative error = %r' % rel_err,
595 ]
596 if idx is not None:
597 substrings.append('differ at index %d' % idx)
598 return substrings
599
600
601# =======================================
602# === Tests for the statistics module ===
603# =======================================
604
605
606class GlobalsTest(unittest.TestCase):
607 module = statistics
608 expected_metadata = ["__doc__", "__all__"]
609
610 def test_meta(self):
611 # Test for the existence of metadata.
612 for meta in self.expected_metadata:
613 self.assertTrue(hasattr(self.module, meta),
614 "%s not present" % meta)
615
616 def test_check_all(self):
617 # Check everything in __all__ exists and is public.
618 module = self.module
619 for name in module.__all__:
620 # No private names in __all__:
621 self.assertFalse(name.startswith("_"),
622 'private name "%s" in __all__' % name)
623 # And anything in __all__ must exist:
624 self.assertTrue(hasattr(module, name),
625 'missing name "%s" in __all__' % name)
626
627
628class DocTests(unittest.TestCase):
Serhiy Storchakab12cb6a2013-12-08 18:16:18 +0200629 @unittest.skipIf(sys.flags.optimize >= 2,
630 "Docstrings are omitted with -OO and above")
Larry Hastingsf5e987b2013-10-19 11:50:09 -0700631 def test_doc_tests(self):
632 failed, tried = doctest.testmod(statistics)
633 self.assertGreater(tried, 0)
634 self.assertEqual(failed, 0)
635
636class StatisticsErrorTest(unittest.TestCase):
637 def test_has_exception(self):
638 errmsg = (
639 "Expected StatisticsError to be a ValueError, but got a"
640 " subclass of %r instead."
641 )
642 self.assertTrue(hasattr(statistics, 'StatisticsError'))
643 self.assertTrue(
644 issubclass(statistics.StatisticsError, ValueError),
645 errmsg % statistics.StatisticsError.__base__
646 )
647
648
649# === Tests for private utility functions ===
650
651class ExactRatioTest(unittest.TestCase):
652 # Test _exact_ratio utility.
653
654 def test_int(self):
655 for i in (-20, -3, 0, 5, 99, 10**20):
656 self.assertEqual(statistics._exact_ratio(i), (i, 1))
657
658 def test_fraction(self):
659 numerators = (-5, 1, 12, 38)
660 for n in numerators:
661 f = Fraction(n, 37)
662 self.assertEqual(statistics._exact_ratio(f), (n, 37))
663
664 def test_float(self):
665 self.assertEqual(statistics._exact_ratio(0.125), (1, 8))
666 self.assertEqual(statistics._exact_ratio(1.125), (9, 8))
667 data = [random.uniform(-100, 100) for _ in range(100)]
668 for x in data:
669 num, den = statistics._exact_ratio(x)
670 self.assertEqual(x, num/den)
671
672 def test_decimal(self):
673 D = Decimal
674 _exact_ratio = statistics._exact_ratio
675 self.assertEqual(_exact_ratio(D("0.125")), (125, 1000))
676 self.assertEqual(_exact_ratio(D("12.345")), (12345, 1000))
677 self.assertEqual(_exact_ratio(D("-1.98")), (-198, 100))
678
679
680class DecimalToRatioTest(unittest.TestCase):
681 # Test _decimal_to_ratio private function.
682
683 def testSpecialsRaise(self):
684 # Test that NANs and INFs raise ValueError.
685 # Non-special values are covered by _exact_ratio above.
686 for d in (Decimal('NAN'), Decimal('sNAN'), Decimal('INF')):
687 self.assertRaises(ValueError, statistics._decimal_to_ratio, d)
688
689
690
691# === Tests for public functions ===
692
693class UnivariateCommonMixin:
694 # Common tests for most univariate functions that take a data argument.
695
696 def test_no_args(self):
697 # Fail if given no arguments.
698 self.assertRaises(TypeError, self.func)
699
700 def test_empty_data(self):
701 # Fail when the data argument (first argument) is empty.
702 for empty in ([], (), iter([])):
703 self.assertRaises(statistics.StatisticsError, self.func, empty)
704
705 def prepare_data(self):
706 """Return int data for various tests."""
707 data = list(range(10))
708 while data == sorted(data):
709 random.shuffle(data)
710 return data
711
712 def test_no_inplace_modifications(self):
713 # Test that the function does not modify its input data.
714 data = self.prepare_data()
715 assert len(data) != 1 # Necessary to avoid infinite loop.
716 assert data != sorted(data)
717 saved = data[:]
718 assert data is not saved
719 _ = self.func(data)
720 self.assertListEqual(data, saved, "data has been modified")
721
722 def test_order_doesnt_matter(self):
723 # Test that the order of data points doesn't change the result.
724
725 # CAUTION: due to floating point rounding errors, the result actually
726 # may depend on the order. Consider this test representing an ideal.
727 # To avoid this test failing, only test with exact values such as ints
728 # or Fractions.
729 data = [1, 2, 3, 3, 3, 4, 5, 6]*100
730 expected = self.func(data)
731 random.shuffle(data)
732 actual = self.func(data)
733 self.assertEqual(expected, actual)
734
735 def test_type_of_data_collection(self):
736 # Test that the type of iterable data doesn't effect the result.
737 class MyList(list):
738 pass
739 class MyTuple(tuple):
740 pass
741 def generator(data):
742 return (obj for obj in data)
743 data = self.prepare_data()
744 expected = self.func(data)
745 for kind in (list, tuple, iter, MyList, MyTuple, generator):
746 result = self.func(kind(data))
747 self.assertEqual(result, expected)
748
749 def test_range_data(self):
750 # Test that functions work with range objects.
751 data = range(20, 50, 3)
752 expected = self.func(list(data))
753 self.assertEqual(self.func(data), expected)
754
755 def test_bad_arg_types(self):
756 # Test that function raises when given data of the wrong type.
757
758 # Don't roll the following into a loop like this:
759 # for bad in list_of_bad:
760 # self.check_for_type_error(bad)
761 #
762 # Since assertRaises doesn't show the arguments that caused the test
763 # failure, it is very difficult to debug these test failures when the
764 # following are in a loop.
765 self.check_for_type_error(None)
766 self.check_for_type_error(23)
767 self.check_for_type_error(42.0)
768 self.check_for_type_error(object())
769
770 def check_for_type_error(self, *args):
771 self.assertRaises(TypeError, self.func, *args)
772
773 def test_type_of_data_element(self):
774 # Check the type of data elements doesn't affect the numeric result.
775 # This is a weaker test than UnivariateTypeMixin.testTypesConserved,
776 # because it checks the numeric result by equality, but not by type.
777 class MyFloat(float):
778 def __truediv__(self, other):
779 return type(self)(super().__truediv__(other))
780 def __add__(self, other):
781 return type(self)(super().__add__(other))
782 __radd__ = __add__
783
784 raw = self.prepare_data()
785 expected = self.func(raw)
786 for kind in (float, MyFloat, Decimal, Fraction):
787 data = [kind(x) for x in raw]
788 result = type(expected)(self.func(data))
789 self.assertEqual(result, expected)
790
791
792class UnivariateTypeMixin:
793 """Mixin class for type-conserving functions.
794
795 This mixin class holds test(s) for functions which conserve the type of
796 individual data points. E.g. the mean of a list of Fractions should itself
797 be a Fraction.
798
799 Not all tests to do with types need go in this class. Only those that
800 rely on the function returning the same type as its input data.
801 """
802 def test_types_conserved(self):
803 # Test that functions keeps the same type as their data points.
804 # (Excludes mixed data types.) This only tests the type of the return
805 # result, not the value.
806 class MyFloat(float):
807 def __truediv__(self, other):
808 return type(self)(super().__truediv__(other))
809 def __sub__(self, other):
810 return type(self)(super().__sub__(other))
811 def __rsub__(self, other):
812 return type(self)(super().__rsub__(other))
813 def __pow__(self, other):
814 return type(self)(super().__pow__(other))
815 def __add__(self, other):
816 return type(self)(super().__add__(other))
817 __radd__ = __add__
818
819 data = self.prepare_data()
820 for kind in (float, Decimal, Fraction, MyFloat):
821 d = [kind(x) for x in data]
822 result = self.func(d)
823 self.assertIs(type(result), kind)
824
825
826class TestSum(NumericTestCase, UnivariateCommonMixin, UnivariateTypeMixin):
827 # Test cases for statistics._sum() function.
828
829 def setUp(self):
830 self.func = statistics._sum
831
832 def test_empty_data(self):
833 # Override test for empty data.
834 for data in ([], (), iter([])):
835 self.assertEqual(self.func(data), 0)
836 self.assertEqual(self.func(data, 23), 23)
837 self.assertEqual(self.func(data, 2.3), 2.3)
838
839 def test_ints(self):
840 self.assertEqual(self.func([1, 5, 3, -4, -8, 20, 42, 1]), 60)
841 self.assertEqual(self.func([4, 2, 3, -8, 7], 1000), 1008)
842
843 def test_floats(self):
844 self.assertEqual(self.func([0.25]*20), 5.0)
845 self.assertEqual(self.func([0.125, 0.25, 0.5, 0.75], 1.5), 3.125)
846
847 def test_fractions(self):
848 F = Fraction
849 self.assertEqual(self.func([Fraction(1, 1000)]*500), Fraction(1, 2))
850
851 def test_decimals(self):
852 D = Decimal
853 data = [D("0.001"), D("5.246"), D("1.702"), D("-0.025"),
854 D("3.974"), D("2.328"), D("4.617"), D("2.843"),
855 ]
856 self.assertEqual(self.func(data), Decimal("20.686"))
857
858 def test_compare_with_math_fsum(self):
859 # Compare with the math.fsum function.
860 # Ideally we ought to get the exact same result, but sometimes
861 # we differ by a very slight amount :-(
862 data = [random.uniform(-100, 1000) for _ in range(1000)]
863 self.assertApproxEqual(self.func(data), math.fsum(data), rel=2e-16)
864
865 def test_start_argument(self):
866 # Test that the optional start argument works correctly.
867 data = [random.uniform(1, 1000) for _ in range(100)]
868 t = self.func(data)
869 self.assertEqual(t+42, self.func(data, 42))
870 self.assertEqual(t-23, self.func(data, -23))
871 self.assertEqual(t+1e20, self.func(data, 1e20))
872
873 def test_strings_fail(self):
874 # Sum of strings should fail.
875 self.assertRaises(TypeError, self.func, [1, 2, 3], '999')
876 self.assertRaises(TypeError, self.func, [1, 2, 3, '999'])
877
878 def test_bytes_fail(self):
879 # Sum of bytes should fail.
880 self.assertRaises(TypeError, self.func, [1, 2, 3], b'999')
881 self.assertRaises(TypeError, self.func, [1, 2, 3, b'999'])
882
883 def test_mixed_sum(self):
884 # Mixed sums are allowed.
885
886 # Careful here: order matters. Can't mix Fraction and Decimal directly,
887 # only after they're converted to float.
888 data = [1, 2, Fraction(1, 2), 3.0, Decimal("0.25")]
889 self.assertEqual(self.func(data), 6.75)
890
891
892class SumInternalsTest(NumericTestCase):
893 # Test internals of the sum function.
894
895 def test_ignore_instance_float_method(self):
896 # Test that __float__ methods on data instances are ignored.
897
898 # Python typically calls __dunder__ methods on the class, not the
899 # instance. The ``sum`` implementation calls __float__ directly. To
900 # better match the behaviour of Python, we call it only on the class,
901 # not the instance. This test will fail if somebody "fixes" that code.
902
903 # Create a fake __float__ method.
904 def __float__(self):
905 raise AssertionError('test fails')
906
907 # Inject it into an instance.
908 class MyNumber(Fraction):
909 pass
910 x = MyNumber(3)
911 x.__float__ = types.MethodType(__float__, x)
912
913 # Check it works as expected.
914 self.assertRaises(AssertionError, x.__float__)
915 self.assertEqual(float(x), 3.0)
916 # And now test the function.
917 self.assertEqual(statistics._sum([1.0, 2.0, x, 4.0]), 10.0)
918
919
920class SumTortureTest(NumericTestCase):
921 def test_torture(self):
922 # Tim Peters' torture test for sum, and variants of same.
923 self.assertEqual(statistics._sum([1, 1e100, 1, -1e100]*10000), 20000.0)
924 self.assertEqual(statistics._sum([1e100, 1, 1, -1e100]*10000), 20000.0)
925 self.assertApproxEqual(
926 statistics._sum([1e-100, 1, 1e-100, -1]*10000), 2.0e-96, rel=5e-16
927 )
928
929
930class SumSpecialValues(NumericTestCase):
931 # Test that sum works correctly with IEEE-754 special values.
932
933 def test_nan(self):
934 for type_ in (float, Decimal):
935 nan = type_('nan')
936 result = statistics._sum([1, nan, 2])
937 self.assertIs(type(result), type_)
938 self.assertTrue(math.isnan(result))
939
940 def check_infinity(self, x, inf):
941 """Check x is an infinity of the same type and sign as inf."""
942 self.assertTrue(math.isinf(x))
943 self.assertIs(type(x), type(inf))
944 self.assertEqual(x > 0, inf > 0)
945 assert x == inf
946
947 def do_test_inf(self, inf):
948 # Adding a single infinity gives infinity.
949 result = statistics._sum([1, 2, inf, 3])
950 self.check_infinity(result, inf)
951 # Adding two infinities of the same sign also gives infinity.
952 result = statistics._sum([1, 2, inf, 3, inf, 4])
953 self.check_infinity(result, inf)
954
955 def test_float_inf(self):
956 inf = float('inf')
957 for sign in (+1, -1):
958 self.do_test_inf(sign*inf)
959
960 def test_decimal_inf(self):
961 inf = Decimal('inf')
962 for sign in (+1, -1):
963 self.do_test_inf(sign*inf)
964
965 def test_float_mismatched_infs(self):
966 # Test that adding two infinities of opposite sign gives a NAN.
967 inf = float('inf')
968 result = statistics._sum([1, 2, inf, 3, -inf, 4])
969 self.assertTrue(math.isnan(result))
970
971 def test_decimal_mismatched_infs_to_nan(self):
972 # Test adding Decimal INFs with opposite sign returns NAN.
973 inf = Decimal('inf')
974 data = [1, 2, inf, 3, -inf, 4]
975 with decimal.localcontext(decimal.ExtendedContext):
976 self.assertTrue(math.isnan(statistics._sum(data)))
977
978 def test_decimal_mismatched_infs_to_nan(self):
979 # Test adding Decimal INFs with opposite sign raises InvalidOperation.
980 inf = Decimal('inf')
981 data = [1, 2, inf, 3, -inf, 4]
982 with decimal.localcontext(decimal.BasicContext):
983 self.assertRaises(decimal.InvalidOperation, statistics._sum, data)
984
985 def test_decimal_snan_raises(self):
986 # Adding sNAN should raise InvalidOperation.
987 sNAN = Decimal('sNAN')
988 data = [1, sNAN, 2]
989 self.assertRaises(decimal.InvalidOperation, statistics._sum, data)
990
991
992# === Tests for averages ===
993
994class AverageMixin(UnivariateCommonMixin):
995 # Mixin class holding common tests for averages.
996
997 def test_single_value(self):
998 # Average of a single value is the value itself.
999 for x in (23, 42.5, 1.3e15, Fraction(15, 19), Decimal('0.28')):
1000 self.assertEqual(self.func([x]), x)
1001
1002 def test_repeated_single_value(self):
1003 # The average of a single repeated value is the value itself.
1004 for x in (3.5, 17, 2.5e15, Fraction(61, 67), Decimal('4.9712')):
1005 for count in (2, 5, 10, 20):
1006 data = [x]*count
1007 self.assertEqual(self.func(data), x)
1008
1009
1010class TestMean(NumericTestCase, AverageMixin, UnivariateTypeMixin):
1011 def setUp(self):
1012 self.func = statistics.mean
1013
1014 def test_torture_pep(self):
1015 # "Torture Test" from PEP-450.
1016 self.assertEqual(self.func([1e100, 1, 3, -1e100]), 1)
1017
1018 def test_ints(self):
1019 # Test mean with ints.
1020 data = [0, 1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 7, 7, 7, 8, 9]
1021 random.shuffle(data)
1022 self.assertEqual(self.func(data), 4.8125)
1023
1024 def test_floats(self):
1025 # Test mean with floats.
1026 data = [17.25, 19.75, 20.0, 21.5, 21.75, 23.25, 25.125, 27.5]
1027 random.shuffle(data)
1028 self.assertEqual(self.func(data), 22.015625)
1029
1030 def test_decimals(self):
1031 # Test mean with ints.
1032 D = Decimal
1033 data = [D("1.634"), D("2.517"), D("3.912"), D("4.072"), D("5.813")]
1034 random.shuffle(data)
1035 self.assertEqual(self.func(data), D("3.5896"))
1036
1037 def test_fractions(self):
1038 # Test mean with Fractions.
1039 F = Fraction
1040 data = [F(1, 2), F(2, 3), F(3, 4), F(4, 5), F(5, 6), F(6, 7), F(7, 8)]
1041 random.shuffle(data)
1042 self.assertEqual(self.func(data), F(1479, 1960))
1043
1044 def test_inf(self):
1045 # Test mean with infinities.
1046 raw = [1, 3, 5, 7, 9] # Use only ints, to avoid TypeError later.
1047 for kind in (float, Decimal):
1048 for sign in (1, -1):
1049 inf = kind("inf")*sign
1050 data = raw + [inf]
1051 result = self.func(data)
1052 self.assertTrue(math.isinf(result))
1053 self.assertEqual(result, inf)
1054
1055 def test_mismatched_infs(self):
1056 # Test mean with infinities of opposite sign.
1057 data = [2, 4, 6, float('inf'), 1, 3, 5, float('-inf')]
1058 result = self.func(data)
1059 self.assertTrue(math.isnan(result))
1060
1061 def test_nan(self):
1062 # Test mean with NANs.
1063 raw = [1, 3, 5, 7, 9] # Use only ints, to avoid TypeError later.
1064 for kind in (float, Decimal):
1065 inf = kind("nan")
1066 data = raw + [inf]
1067 result = self.func(data)
1068 self.assertTrue(math.isnan(result))
1069
1070 def test_big_data(self):
1071 # Test adding a large constant to every data point.
1072 c = 1e9
1073 data = [3.4, 4.5, 4.9, 6.7, 6.8, 7.2, 8.0, 8.1, 9.4]
1074 expected = self.func(data) + c
1075 assert expected != c
1076 result = self.func([x+c for x in data])
1077 self.assertEqual(result, expected)
1078
1079 def test_doubled_data(self):
1080 # Mean of [a,b,c...z] should be same as for [a,a,b,b,c,c...z,z].
1081 data = [random.uniform(-3, 5) for _ in range(1000)]
1082 expected = self.func(data)
1083 actual = self.func(data*2)
1084 self.assertApproxEqual(actual, expected)
1085
1086
1087class TestMedian(NumericTestCase, AverageMixin):
1088 # Common tests for median and all median.* functions.
1089 def setUp(self):
1090 self.func = statistics.median
1091
1092 def prepare_data(self):
1093 """Overload method from UnivariateCommonMixin."""
1094 data = super().prepare_data()
1095 if len(data)%2 != 1:
1096 data.append(2)
1097 return data
1098
1099 def test_even_ints(self):
1100 # Test median with an even number of int data points.
1101 data = [1, 2, 3, 4, 5, 6]
1102 assert len(data)%2 == 0
1103 self.assertEqual(self.func(data), 3.5)
1104
1105 def test_odd_ints(self):
1106 # Test median with an odd number of int data points.
1107 data = [1, 2, 3, 4, 5, 6, 9]
1108 assert len(data)%2 == 1
1109 self.assertEqual(self.func(data), 4)
1110
1111 def test_odd_fractions(self):
1112 # Test median works with an odd number of Fractions.
1113 F = Fraction
1114 data = [F(1, 7), F(2, 7), F(3, 7), F(4, 7), F(5, 7)]
1115 assert len(data)%2 == 1
1116 random.shuffle(data)
1117 self.assertEqual(self.func(data), F(3, 7))
1118
1119 def test_even_fractions(self):
1120 # Test median works with an even number of Fractions.
1121 F = Fraction
1122 data = [F(1, 7), F(2, 7), F(3, 7), F(4, 7), F(5, 7), F(6, 7)]
1123 assert len(data)%2 == 0
1124 random.shuffle(data)
1125 self.assertEqual(self.func(data), F(1, 2))
1126
1127 def test_odd_decimals(self):
1128 # Test median works with an odd number of Decimals.
1129 D = Decimal
1130 data = [D('2.5'), D('3.1'), D('4.2'), D('5.7'), D('5.8')]
1131 assert len(data)%2 == 1
1132 random.shuffle(data)
1133 self.assertEqual(self.func(data), D('4.2'))
1134
1135 def test_even_decimals(self):
1136 # Test median works with an even number of Decimals.
1137 D = Decimal
1138 data = [D('1.2'), D('2.5'), D('3.1'), D('4.2'), D('5.7'), D('5.8')]
1139 assert len(data)%2 == 0
1140 random.shuffle(data)
1141 self.assertEqual(self.func(data), D('3.65'))
1142
1143
1144class TestMedianDataType(NumericTestCase, UnivariateTypeMixin):
1145 # Test conservation of data element type for median.
1146 def setUp(self):
1147 self.func = statistics.median
1148
1149 def prepare_data(self):
1150 data = list(range(15))
1151 assert len(data)%2 == 1
1152 while data == sorted(data):
1153 random.shuffle(data)
1154 return data
1155
1156
1157class TestMedianLow(TestMedian, UnivariateTypeMixin):
1158 def setUp(self):
1159 self.func = statistics.median_low
1160
1161 def test_even_ints(self):
1162 # Test median_low with an even number of ints.
1163 data = [1, 2, 3, 4, 5, 6]
1164 assert len(data)%2 == 0
1165 self.assertEqual(self.func(data), 3)
1166
1167 def test_even_fractions(self):
1168 # Test median_low works with an even number of Fractions.
1169 F = Fraction
1170 data = [F(1, 7), F(2, 7), F(3, 7), F(4, 7), F(5, 7), F(6, 7)]
1171 assert len(data)%2 == 0
1172 random.shuffle(data)
1173 self.assertEqual(self.func(data), F(3, 7))
1174
1175 def test_even_decimals(self):
1176 # Test median_low works with an even number of Decimals.
1177 D = Decimal
1178 data = [D('1.1'), D('2.2'), D('3.3'), D('4.4'), D('5.5'), D('6.6')]
1179 assert len(data)%2 == 0
1180 random.shuffle(data)
1181 self.assertEqual(self.func(data), D('3.3'))
1182
1183
1184class TestMedianHigh(TestMedian, UnivariateTypeMixin):
1185 def setUp(self):
1186 self.func = statistics.median_high
1187
1188 def test_even_ints(self):
1189 # Test median_high with an even number of ints.
1190 data = [1, 2, 3, 4, 5, 6]
1191 assert len(data)%2 == 0
1192 self.assertEqual(self.func(data), 4)
1193
1194 def test_even_fractions(self):
1195 # Test median_high works with an even number of Fractions.
1196 F = Fraction
1197 data = [F(1, 7), F(2, 7), F(3, 7), F(4, 7), F(5, 7), F(6, 7)]
1198 assert len(data)%2 == 0
1199 random.shuffle(data)
1200 self.assertEqual(self.func(data), F(4, 7))
1201
1202 def test_even_decimals(self):
1203 # Test median_high works with an even number of Decimals.
1204 D = Decimal
1205 data = [D('1.1'), D('2.2'), D('3.3'), D('4.4'), D('5.5'), D('6.6')]
1206 assert len(data)%2 == 0
1207 random.shuffle(data)
1208 self.assertEqual(self.func(data), D('4.4'))
1209
1210
1211class TestMedianGrouped(TestMedian):
1212 # Test median_grouped.
1213 # Doesn't conserve data element types, so don't use TestMedianType.
1214 def setUp(self):
1215 self.func = statistics.median_grouped
1216
1217 def test_odd_number_repeated(self):
1218 # Test median.grouped with repeated median values.
1219 data = [12, 13, 14, 14, 14, 15, 15]
1220 assert len(data)%2 == 1
1221 self.assertEqual(self.func(data), 14)
1222 #---
1223 data = [12, 13, 14, 14, 14, 14, 15]
1224 assert len(data)%2 == 1
1225 self.assertEqual(self.func(data), 13.875)
1226 #---
1227 data = [5, 10, 10, 15, 20, 20, 20, 20, 25, 25, 30]
1228 assert len(data)%2 == 1
1229 self.assertEqual(self.func(data, 5), 19.375)
1230 #---
1231 data = [16, 18, 18, 18, 18, 20, 20, 20, 22, 22, 22, 24, 24, 26, 28]
1232 assert len(data)%2 == 1
1233 self.assertApproxEqual(self.func(data, 2), 20.66666667, tol=1e-8)
1234
1235 def test_even_number_repeated(self):
1236 # Test median.grouped with repeated median values.
1237 data = [5, 10, 10, 15, 20, 20, 20, 25, 25, 30]
1238 assert len(data)%2 == 0
1239 self.assertApproxEqual(self.func(data, 5), 19.16666667, tol=1e-8)
1240 #---
1241 data = [2, 3, 4, 4, 4, 5]
1242 assert len(data)%2 == 0
1243 self.assertApproxEqual(self.func(data), 3.83333333, tol=1e-8)
1244 #---
1245 data = [2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6]
1246 assert len(data)%2 == 0
1247 self.assertEqual(self.func(data), 4.5)
1248 #---
1249 data = [3, 4, 4, 4, 5, 5, 5, 5, 6, 6]
1250 assert len(data)%2 == 0
1251 self.assertEqual(self.func(data), 4.75)
1252
1253 def test_repeated_single_value(self):
1254 # Override method from AverageMixin.
1255 # Yet again, failure of median_grouped to conserve the data type
1256 # causes me headaches :-(
1257 for x in (5.3, 68, 4.3e17, Fraction(29, 101), Decimal('32.9714')):
1258 for count in (2, 5, 10, 20):
1259 data = [x]*count
1260 self.assertEqual(self.func(data), float(x))
1261
1262 def test_odd_fractions(self):
1263 # Test median_grouped works with an odd number of Fractions.
1264 F = Fraction
1265 data = [F(5, 4), F(9, 4), F(13, 4), F(13, 4), F(17, 4)]
1266 assert len(data)%2 == 1
1267 random.shuffle(data)
1268 self.assertEqual(self.func(data), 3.0)
1269
1270 def test_even_fractions(self):
1271 # Test median_grouped works with an even number of Fractions.
1272 F = Fraction
1273 data = [F(5, 4), F(9, 4), F(13, 4), F(13, 4), F(17, 4), F(17, 4)]
1274 assert len(data)%2 == 0
1275 random.shuffle(data)
1276 self.assertEqual(self.func(data), 3.25)
1277
1278 def test_odd_decimals(self):
1279 # Test median_grouped works with an odd number of Decimals.
1280 D = Decimal
1281 data = [D('5.5'), D('6.5'), D('6.5'), D('7.5'), D('8.5')]
1282 assert len(data)%2 == 1
1283 random.shuffle(data)
1284 self.assertEqual(self.func(data), 6.75)
1285
1286 def test_even_decimals(self):
1287 # Test median_grouped works with an even number of Decimals.
1288 D = Decimal
1289 data = [D('5.5'), D('5.5'), D('6.5'), D('6.5'), D('7.5'), D('8.5')]
1290 assert len(data)%2 == 0
1291 random.shuffle(data)
1292 self.assertEqual(self.func(data), 6.5)
1293 #---
1294 data = [D('5.5'), D('5.5'), D('6.5'), D('7.5'), D('7.5'), D('8.5')]
1295 assert len(data)%2 == 0
1296 random.shuffle(data)
1297 self.assertEqual(self.func(data), 7.0)
1298
1299 def test_interval(self):
1300 # Test median_grouped with interval argument.
1301 data = [2.25, 2.5, 2.5, 2.75, 2.75, 3.0, 3.0, 3.25, 3.5, 3.75]
1302 self.assertEqual(self.func(data, 0.25), 2.875)
1303 data = [2.25, 2.5, 2.5, 2.75, 2.75, 2.75, 3.0, 3.0, 3.25, 3.5, 3.75]
1304 self.assertApproxEqual(self.func(data, 0.25), 2.83333333, tol=1e-8)
1305 data = [220, 220, 240, 260, 260, 260, 260, 280, 280, 300, 320, 340]
1306 self.assertEqual(self.func(data, 20), 265.0)
1307
1308
1309class TestMode(NumericTestCase, AverageMixin, UnivariateTypeMixin):
1310 # Test cases for the discrete version of mode.
1311 def setUp(self):
1312 self.func = statistics.mode
1313
1314 def prepare_data(self):
1315 """Overload method from UnivariateCommonMixin."""
1316 # Make sure test data has exactly one mode.
1317 return [1, 1, 1, 1, 3, 4, 7, 9, 0, 8, 2]
1318
1319 def test_range_data(self):
1320 # Override test from UnivariateCommonMixin.
1321 data = range(20, 50, 3)
1322 self.assertRaises(statistics.StatisticsError, self.func, data)
1323
1324 def test_nominal_data(self):
1325 # Test mode with nominal data.
1326 data = 'abcbdb'
1327 self.assertEqual(self.func(data), 'b')
1328 data = 'fe fi fo fum fi fi'.split()
1329 self.assertEqual(self.func(data), 'fi')
1330
1331 def test_discrete_data(self):
1332 # Test mode with discrete numeric data.
1333 data = list(range(10))
1334 for i in range(10):
1335 d = data + [i]
1336 random.shuffle(d)
1337 self.assertEqual(self.func(d), i)
1338
1339 def test_bimodal_data(self):
1340 # Test mode with bimodal data.
1341 data = [1, 1, 2, 2, 2, 2, 3, 4, 5, 6, 6, 6, 6, 7, 8, 9, 9]
1342 assert data.count(2) == data.count(6) == 4
1343 # Check for an exception.
1344 self.assertRaises(statistics.StatisticsError, self.func, data)
1345
1346 def test_unique_data_failure(self):
1347 # Test mode exception when data points are all unique.
1348 data = list(range(10))
1349 self.assertRaises(statistics.StatisticsError, self.func, data)
1350
1351 def test_none_data(self):
1352 # Test that mode raises TypeError if given None as data.
1353
1354 # This test is necessary because the implementation of mode uses
1355 # collections.Counter, which accepts None and returns an empty dict.
1356 self.assertRaises(TypeError, self.func, None)
1357
1358
1359# === Tests for variances and standard deviations ===
1360
1361class VarianceStdevMixin(UnivariateCommonMixin):
1362 # Mixin class holding common tests for variance and std dev.
1363
1364 # Subclasses should inherit from this before NumericTestClass, in order
1365 # to see the rel attribute below. See testShiftData for an explanation.
1366
1367 rel = 1e-12
1368
1369 def test_single_value(self):
1370 # Deviation of a single value is zero.
1371 for x in (11, 19.8, 4.6e14, Fraction(21, 34), Decimal('8.392')):
1372 self.assertEqual(self.func([x]), 0)
1373
1374 def test_repeated_single_value(self):
1375 # The deviation of a single repeated value is zero.
1376 for x in (7.2, 49, 8.1e15, Fraction(3, 7), Decimal('62.4802')):
1377 for count in (2, 3, 5, 15):
1378 data = [x]*count
1379 self.assertEqual(self.func(data), 0)
1380
1381 def test_domain_error_regression(self):
1382 # Regression test for a domain error exception.
1383 # (Thanks to Geremy Condra.)
1384 data = [0.123456789012345]*10000
1385 # All the items are identical, so variance should be exactly zero.
1386 # We allow some small round-off error, but not much.
1387 result = self.func(data)
1388 self.assertApproxEqual(result, 0.0, tol=5e-17)
1389 self.assertGreaterEqual(result, 0) # A negative result must fail.
1390
1391 def test_shift_data(self):
1392 # Test that shifting the data by a constant amount does not affect
1393 # the variance or stdev. Or at least not much.
1394
1395 # Due to rounding, this test should be considered an ideal. We allow
1396 # some tolerance away from "no change at all" by setting tol and/or rel
1397 # attributes. Subclasses may set tighter or looser error tolerances.
1398 raw = [1.03, 1.27, 1.94, 2.04, 2.58, 3.14, 4.75, 4.98, 5.42, 6.78]
1399 expected = self.func(raw)
1400 # Don't set shift too high, the bigger it is, the more rounding error.
1401 shift = 1e5
1402 data = [x + shift for x in raw]
1403 self.assertApproxEqual(self.func(data), expected)
1404
1405 def test_shift_data_exact(self):
1406 # Like test_shift_data, but result is always exact.
1407 raw = [1, 3, 3, 4, 5, 7, 9, 10, 11, 16]
1408 assert all(x==int(x) for x in raw)
1409 expected = self.func(raw)
1410 shift = 10**9
1411 data = [x + shift for x in raw]
1412 self.assertEqual(self.func(data), expected)
1413
1414 def test_iter_list_same(self):
1415 # Test that iter data and list data give the same result.
1416
1417 # This is an explicit test that iterators and lists are treated the
1418 # same; justification for this test over and above the similar test
1419 # in UnivariateCommonMixin is that an earlier design had variance and
1420 # friends swap between one- and two-pass algorithms, which would
1421 # sometimes give different results.
1422 data = [random.uniform(-3, 8) for _ in range(1000)]
1423 expected = self.func(data)
1424 self.assertEqual(self.func(iter(data)), expected)
1425
1426
1427class TestPVariance(VarianceStdevMixin, NumericTestCase, UnivariateTypeMixin):
1428 # Tests for population variance.
1429 def setUp(self):
1430 self.func = statistics.pvariance
1431
1432 def test_exact_uniform(self):
1433 # Test the variance against an exact result for uniform data.
1434 data = list(range(10000))
1435 random.shuffle(data)
1436 expected = (10000**2 - 1)/12 # Exact value.
1437 self.assertEqual(self.func(data), expected)
1438
1439 def test_ints(self):
1440 # Test population variance with int data.
1441 data = [4, 7, 13, 16]
1442 exact = 22.5
1443 self.assertEqual(self.func(data), exact)
1444
1445 def test_fractions(self):
1446 # Test population variance with Fraction data.
1447 F = Fraction
1448 data = [F(1, 4), F(1, 4), F(3, 4), F(7, 4)]
1449 exact = F(3, 8)
1450 result = self.func(data)
1451 self.assertEqual(result, exact)
1452 self.assertIsInstance(result, Fraction)
1453
1454 def test_decimals(self):
1455 # Test population variance with Decimal data.
1456 D = Decimal
1457 data = [D("12.1"), D("12.2"), D("12.5"), D("12.9")]
1458 exact = D('0.096875')
1459 result = self.func(data)
1460 self.assertEqual(result, exact)
1461 self.assertIsInstance(result, Decimal)
1462
1463
1464class TestVariance(VarianceStdevMixin, NumericTestCase, UnivariateTypeMixin):
1465 # Tests for sample variance.
1466 def setUp(self):
1467 self.func = statistics.variance
1468
1469 def test_single_value(self):
1470 # Override method from VarianceStdevMixin.
1471 for x in (35, 24.7, 8.2e15, Fraction(19, 30), Decimal('4.2084')):
1472 self.assertRaises(statistics.StatisticsError, self.func, [x])
1473
1474 def test_ints(self):
1475 # Test sample variance with int data.
1476 data = [4, 7, 13, 16]
1477 exact = 30
1478 self.assertEqual(self.func(data), exact)
1479
1480 def test_fractions(self):
1481 # Test sample variance with Fraction data.
1482 F = Fraction
1483 data = [F(1, 4), F(1, 4), F(3, 4), F(7, 4)]
1484 exact = F(1, 2)
1485 result = self.func(data)
1486 self.assertEqual(result, exact)
1487 self.assertIsInstance(result, Fraction)
1488
1489 def test_decimals(self):
1490 # Test sample variance with Decimal data.
1491 D = Decimal
1492 data = [D(2), D(2), D(7), D(9)]
1493 exact = 4*D('9.5')/D(3)
1494 result = self.func(data)
1495 self.assertEqual(result, exact)
1496 self.assertIsInstance(result, Decimal)
1497
1498
1499class TestPStdev(VarianceStdevMixin, NumericTestCase):
1500 # Tests for population standard deviation.
1501 def setUp(self):
1502 self.func = statistics.pstdev
1503
1504 def test_compare_to_variance(self):
1505 # Test that stdev is, in fact, the square root of variance.
1506 data = [random.uniform(-17, 24) for _ in range(1000)]
1507 expected = math.sqrt(statistics.pvariance(data))
1508 self.assertEqual(self.func(data), expected)
1509
1510
1511class TestStdev(VarianceStdevMixin, NumericTestCase):
1512 # Tests for sample standard deviation.
1513 def setUp(self):
1514 self.func = statistics.stdev
1515
1516 def test_single_value(self):
1517 # Override method from VarianceStdevMixin.
1518 for x in (81, 203.74, 3.9e14, Fraction(5, 21), Decimal('35.719')):
1519 self.assertRaises(statistics.StatisticsError, self.func, [x])
1520
1521 def test_compare_to_variance(self):
1522 # Test that stdev is, in fact, the square root of variance.
1523 data = [random.uniform(-2, 9) for _ in range(1000)]
1524 expected = math.sqrt(statistics.variance(data))
1525 self.assertEqual(self.func(data), expected)
1526
1527
1528# === Run tests ===
1529
1530def load_tests(loader, tests, ignore):
1531 """Used for doctest/unittest integration."""
1532 tests.addTests(doctest.DocTestSuite())
1533 return tests
1534
1535
1536if __name__ == "__main__":
1537 unittest.main()