diff options
-rw-r--r-- | doc/release/2.0.0-notes.rst | 7 | ||||
-rw-r--r-- | numpy/core/numeric.py | 90 | ||||
-rw-r--r-- | numpy/core/tests/test_numeric.py | 124 |
3 files changed, 220 insertions, 1 deletions
diff --git a/doc/release/2.0.0-notes.rst b/doc/release/2.0.0-notes.rst index defdc9c00..7c2bfa8a5 100644 --- a/doc/release/2.0.0-notes.rst +++ b/doc/release/2.0.0-notes.rst @@ -94,6 +94,13 @@ A generic sampling function has been added which will generate samples from a given array-like. The samples can be with or without replacement, and with uniform or given non-uniform probabilities. +New function isclose +-------------------- + +Returns a boolean array where two arrays are element-wise equal within a +tolerance. Both relative and absolute tolerance can be specified. The +function is NA aware. + Preliminary multi-dimensional support in the polynomial package --------------------------------------------------------------- diff --git a/numpy/core/numeric.py b/numpy/core/numeric.py index bb13d573e..846429016 100644 --- a/numpy/core/numeric.py +++ b/numpy/core/numeric.py @@ -12,7 +12,7 @@ __all__ = ['newaxis', 'ndarray', 'flatiter', 'nditer', 'nested_iters', 'ufunc', 'array_repr', 'array_str', 'set_string_function', 'little_endian', 'require', 'fromiter', 'array_equal', 'array_equiv', - 'indices', 'fromfunction', + 'indices', 'fromfunction', 'isclose', 'load', 'loads', 'isscalar', 'binary_repr', 'base_repr', 'ones', 'identity', 'allclose', 'compare_chararrays', 'putmask', 'seterr', 'geterr', 'setbufsize', 'getbufsize', @@ -2024,6 +2024,94 @@ def allclose(a, b, rtol=1.e-5, atol=1.e-8): y = y[~xinf] return all(less_equal(absolute(x-y), atol + rtol * absolute(y))) +def isclose(a, b, rtol=1.e-5, atol=1.e-8, equal_nan=False): + """ + Returns a boolean array where two arrays are element-wise equal within a + tolerance. + + The tolerance values are positive, typically very small numbers. The + relative difference (`rtol` * abs(`b`)) and the absolute difference + `atol` are added together to compare against the absolute difference + between `a` and `b`. + + Parameters + ---------- + a, b : array_like + Input arrays to compare. + rtol : float + The relative tolerance parameter (see Notes). + atol : float + The absolute tolerance parameter (see Notes). + equal_nan : bool + Whether to compare NaN's as equal. If True, NaN's in `a` will be + considered equal to NaN's in `b` in the output array. + + Returns + ------- + y : array_like + Returns a boolean array of where `a` and `b` are equal within the + given tolerance. If both `a` and `b` are scalars, returns a single + boolean value. + + See Also + -------- + allclose + + Notes + ----- + .. versionadded:: 1.7.0 + + For finite values, isclose uses the following equation to test whether + two floating point values are equivalent. + + absolute(`a` - `b`) <= (`atol` + `rtol` * absolute(`b`)) + + The above equation is not symmetric in `a` and `b`, so that + `isclose(a, b)` might be different from `isclose(b, a)` in + some rare cases. + + Examples + -------- + >>> np.isclose([1e10,1e-7], [1.00001e10,1e-8]) + array([True, False]) + >>> np.isclose([1e10,1e-8], [1.00001e10,1e-9]) + array([True, True]) + >>> np.isclose([1e10,1e-8], [1.0001e10,1e-9]) + array([False, True]) + >>> np.isclose([1.0, np.nan], [1.0, np.nan]) + array([True, False]) + >>> np.isclose([1.0, np.nan], [1.0, np.nan], equal_nan=True) + array([True, True]) + """ + def within_tol(x, y, atol, rtol): + result = less_equal(abs(x-y), atol + rtol * abs(y)) + if isscalar(a) and isscalar(b): + result = bool(result) + return result + x = array(a, copy=False, subok=True, ndmin=1) + y = array(b, copy=False, subok=True, ndmin=1) + xfin = isfinite(x) + yfin = isfinite(y) + if all(xfin, skipna=True) and all(yfin, skipna=True): + return within_tol(x, y, atol, rtol) + else: + finite = xfin & yfin + cond = zeros_like(finite, subok=True, maskna=finite.flags.maskna) + # Because we're using boolean indexing, x & y must be the same shape. + # Ideally, we'd just do x, y = broadcast_arrays(x, y). It's in + # lib.stride_tricks, though, so we can't import it here. + x = x * ones_like(cond) + y = y * ones_like(cond) + # Avoid subtraction with infinite/nan values and indexing with na... + finite[isna(finite)] = False + cond[finite] = within_tol(x[finite], y[finite], atol, rtol) + # Check for equality of infinite values... + cond[~finite] = (x[~finite] == y[~finite]) + if equal_nan: + # Make NaN == NaN + cond[isnan(x) & isnan(y)] = True + return cond + def array_equal(a1, a2): """ True if two arrays have the same shape and elements, False otherwise. diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index de41f0c1f..5233d0f88 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -1220,6 +1220,130 @@ class TestAllclose(object): assert_array_equal(y,array([0,inf])) +class TestIsclose(object): + rtol = 1e-5 + atol = 1e-8 + + def setup(self): + atol = self.atol + rtol = self.rtol + arr = array([100,1000]) + aran = arange(125).reshape((5,5,5)) + + self.all_close_tests = [ + ([1, 0], [1, 0]), + ([atol], [0]), + ([1], [1 + rtol + atol]), + (arr, arr + arr*rtol), + (arr, arr + arr*rtol + atol), + (aran, aran + aran*rtol), + (inf, inf), + (inf, [inf]), + ([inf, -inf], [inf, -inf]), + ] + self.none_close_tests = [ + ([inf, 0], [1, inf]), + ([inf, -inf], [1, 0]), + ([inf, inf], [1, -inf]), + ([inf, inf], [1, 0]), + ([nan, 0], [nan, -inf]), + ([atol*2], [0]), + ([1], [1 + rtol + atol*2]), + (aran, aran + rtol*1.1*aran + atol*1.1), + (array([inf, 1]), array([0, inf])), + ] + self.some_close_tests = [ + ([inf, 0], [inf, atol*2]), + ([atol, 1, 1e6*(1 + 2*rtol) + atol], [0, nan, 1e6]), + (arange(3), [0, 1, 2.1]), + (nan, [nan, nan, nan]), + ([0], [atol, inf, -inf, nan]), + (0, [atol, inf, -inf, nan]), + ] + self.some_close_results = [ + [True, False], + [True, False, False], + [True, True, False], + [False, False, False], + [True, False, False, False], + [True, False, False, False], + ] + + def test_ip_isclose(self): + self.setup() + tests = self.some_close_tests + results = self.some_close_results + for (x, y), result in zip(tests, results): + yield (assert_array_equal, isclose(x, y), result) + + def tst_all_isclose(self, x, y): + assert_(all(isclose(x, y)), "%s and %s not close" % (x, y)) + + def tst_none_isclose(self, x, y): + msg = "%s and %s shouldn't be close" + assert_(not any(isclose(x, y)), msg % (x, y)) + + def tst_isclose_allclose(self, x, y): + msg = "isclose.all() and allclose aren't same for %s and %s" + assert_array_equal(isclose(x, y).all(), allclose(x, y), msg % (x, y)) + + def test_ip_all_isclose(self): + self.setup() + for (x,y) in self.all_close_tests: + yield (self.tst_all_isclose, x, y) + + def test_ip_none_isclose(self): + self.setup() + for (x,y) in self.none_close_tests: + yield (self.tst_none_isclose, x, y) + + def test_ip_isclose_allclose(self): + self.setup() + tests = (self.all_close_tests + self.none_close_tests + + self.some_close_tests) + for (x, y) in tests: + yield (self.tst_isclose_allclose, x, y) + + def test_equal_nan(self): + assert_array_equal(isclose(nan, nan, equal_nan=True), [True]) + arr = array([1.0, nan]) + assert_array_equal(isclose(arr, arr, equal_nan=True), [True, True]) + + def test_masked_arrays(self): + x = np.ma.masked_where([True, True, False], np.arange(3)) + assert_(type(x) == type(isclose(2, x))) + + x = np.ma.masked_where([True, True, False], [nan, inf, nan]) + assert_(type(x) == type(isclose(inf, x))) + + x = np.ma.masked_where([True, True, False], [nan, nan, nan]) + y = isclose(nan, x, equal_nan=True) + assert_(type(x) == type(y)) + # Ensure that the mask isn't modified... + assert_array_equal([True, True, False], y.mask) + + def test_maskna_arrays(self): + x = array([NA, 1, 2, 3]) + y = array([0, 1, 2, NA]) + assert_array_equal(isclose(x, y), array([NA, True, True, NA])) + + assert_array_equal(isclose(NA, arange(3)), array([NA, NA, NA])) + + x = array([NA, nan, 2, 3]) + y = array([nan, 1, 2, NA]) + assert_array_equal(isclose(x, y), array([NA, False, True, NA])) + + def test_scalar_return(self): + assert_(isscalar(isclose(1, 1))) + + def test_no_parameter_modification(self): + x = array([inf, 1]) + y = array([0, inf]) + isclose(x, y) + assert_array_equal(x, array([inf, 1])) + assert_array_equal(y, array([0, inf])) + + class TestStdVar(TestCase): def setUp(self): self.A = array([1,-1,1,-1]) |