Skip to content

Commit

Permalink
ENH: Allow relative and/or absolute precision in assert_almost_equal
Browse files Browse the repository at this point in the history
This commit makes `assert_almost_equal` accept both relative and
absolute precision when comparing numbers, through two new keyword
arguments: `rtol`, and `atol`, respectively.

Under the hood, `_libs.testing.assert_almost_equal` is now calling
`math.isclose`, instead of an adaptaion of
[numpy.testing.assert_almost_equal](https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.testing.assert_almost_equal.html).
  • Loading branch information
Joao Veiga committed Mar 25, 2020
1 parent 28e0f18 commit 6333db4
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 134 deletions.
4 changes: 4 additions & 0 deletions doc/source/whatsnew/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ Deprecations
- Setting values with ``.loc`` using a positional slice is deprecated and will raise in a future version. Use ``.loc`` with labels or ``.iloc`` with positions instead (:issue:`31840`)
- :meth:`DataFrame.to_dict` has deprecated accepting short names for ``orient`` in future versions (:issue:`32515`)
- :meth:`Categorical.to_dense` is deprecated and will be removed in a future version, use ``np.asarray(cat)`` instead (:issue:`32639`)
- :meth:`util.testing.assert_almost_equal` now accepts both relative and absolute
precision through the ``rtol``, and ``atol`` parameters, thus deprecating the
``check_less_precise`` parameter. (:issue:`13357`).
-

.. ---------------------------------------------------------------------------
Expand Down
59 changes: 20 additions & 39 deletions pandas/_libs/testing.pyx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import math

import numpy as np

from pandas.core.dtypes.missing import isna, array_equivalent
Expand Down Expand Up @@ -38,13 +40,6 @@ cdef bint is_dictlike(obj):
return hasattr(obj, 'keys') and hasattr(obj, '__getitem__')


cdef bint decimal_almost_equal(double desired, double actual, int decimal):
# Code from
# http://docs.scipy.org/doc/numpy/reference/generated
# /numpy.testing.assert_almost_equal.html
return abs(desired - actual) < (0.5 * 10.0 ** -decimal)


cpdef assert_dict_equal(a, b, bint compare_keys=True):
assert is_dictlike(a) and is_dictlike(b), (
"Cannot compare dict objects, one or both is not dict-like"
Expand All @@ -63,7 +58,7 @@ cpdef assert_dict_equal(a, b, bint compare_keys=True):


cpdef assert_almost_equal(a, b,
check_less_precise=False,
rtol=1.e-5, atol=1.e-8,
bint check_dtype=True,
obj=None, lobj=None, robj=None, index_values=None):
"""
Expand All @@ -73,31 +68,33 @@ cpdef assert_almost_equal(a, b,
----------
a : object
b : object
check_less_precise : bool or int, default False
Specify comparison precision.
5 digits (False) or 3 digits (True) after decimal points are
compared. If an integer, then this will be the number of decimal
points to compare
rtol : float, default 1e-5
Relative tolerance.
.. versionadded:: 1.1.0
atol : float, default 1e-8
Absolute tolerance.
.. versionadded:: 1.1.0
check_dtype: bool, default True
check dtype if both a and b are np.ndarray
check dtype if both a and b are np.ndarray.
obj : str, default None
Specify object name being compared, internally used to show
appropriate assertion message
appropriate assertion message.
lobj : str, default None
Specify left object name being compared, internally used to show
appropriate assertion message
appropriate assertion message.
robj : str, default None
Specify right object name being compared, internally used to show
appropriate assertion message
appropriate assertion message.
index_values : ndarray, default None
Specify shared index values of objects being compared, internally used
to show appropriate assertion message
to show appropriate assertion message.
.. versionadded:: 1.1.0
"""
cdef:
int decimal
double diff = 0.0
Py_ssize_t i, na, nb
double fa, fb
Expand All @@ -108,8 +105,6 @@ cpdef assert_almost_equal(a, b,
if robj is None:
robj = b

assert isinstance(check_less_precise, (int, bool))

if isinstance(a, dict) or isinstance(b, dict):
return assert_dict_equal(a, b)

Expand Down Expand Up @@ -167,8 +162,7 @@ cpdef assert_almost_equal(a, b,

for i in range(len(a)):
try:
assert_almost_equal(a[i], b[i],
check_less_precise=check_less_precise)
assert_almost_equal(a[i], b[i], rtol=rtol, atol=atol)
except AssertionError:
is_unequal = True
diff += 1
Expand Down Expand Up @@ -200,24 +194,11 @@ cpdef assert_almost_equal(a, b,
# inf comparison
return True

if check_less_precise is True:
decimal = 3
elif check_less_precise is False:
decimal = 5
else:
decimal = check_less_precise

fa, fb = a, b

# case for zero
if abs(fa) < 1e-5:
if not decimal_almost_equal(fa, fb, decimal):
assert False, (f'(very low values) expected {fb:.5f} '
f'but got {fa:.5f}, with decimal {decimal}')
else:
if not decimal_almost_equal(1, fb / fa, decimal):
assert False, (f'expected {fb:.5f} but got {fa:.5f}, '
f'with decimal {decimal}')
if not math.isclose(fa, fb, rel_tol=rtol, abs_tol=atol):
assert False, (f"expected {fb:.5f} but got {fa:.5f}, "
f"with rtol={rtol}, atol={atol}")
return True

raise AssertionError(f"{a} != {b}")

0 comments on commit 6333db4

Please sign in to comment.