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