diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 3b7b0e92a9..d299869f2b 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -52,6 +52,11 @@ This document explains the changes made to Iris for this release :func:`~iris.fileformats.pp.save_pairs_from_cube` because it had no effect. (:pull:`5783`) +#. `@stephenworsley`_ made masked arrays on Iris objects now compare as equal + precisely when all unmasked points are equal and when the masks are identical. + This is due to changes in :func:`~iris.util.array_equal` which previously + ignored masks entirely. (:pull:`4457`) + 🚀 Performance Enhancements =========================== diff --git a/lib/iris/tests/unit/util/test_array_equal.py b/lib/iris/tests/unit/util/test_array_equal.py index f63092587c..d463ca6a4f 100644 --- a/lib/iris/tests/unit/util/test_array_equal.py +++ b/lib/iris/tests/unit/util/test_array_equal.py @@ -44,11 +44,28 @@ def test_nd(self): self.assertTrue(array_equal(array_a, array_b)) self.assertFalse(array_equal(array_a, array_c)) - def test_masked_is_ignored(self): + def test_masked_is_not_ignored(self): array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1]) array_b = ma.masked_array([2, 2, 2], mask=[1, 0, 1]) + self.assertTrue(array_equal(array_a, array_b)) + + def test_masked_is_different(self): + array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1]) + array_b = ma.masked_array([1, 2, 3], mask=[0, 0, 1]) self.assertFalse(array_equal(array_a, array_b)) + def test_masked_isnt_unmasked(self): + array_a = np.array([1, 2, 2]) + array_b = ma.masked_array([1, 2, 2], mask=[0, 0, 1]) + self.assertFalse(array_equal(array_a, array_b)) + + def test_masked_unmasked_equivelance(self): + array_a = np.array([1, 2, 2]) + array_b = ma.masked_array([1, 2, 2]) + array_c = ma.masked_array([1, 2, 2], mask=[0, 0, 0]) + self.assertTrue(array_equal(array_a, array_b)) + self.assertTrue(array_equal(array_a, array_c)) + def test_fully_masked_arrays(self): array_a = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True) array_b = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True) diff --git a/lib/iris/util.py b/lib/iris/util.py index 83fa125864..4924ca68d2 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -395,8 +395,9 @@ def array_equal(array1, array2, withnans=False): Notes ----- - This provides much the same functionality as :func:`numpy.array_equal`, but - with additional support for arrays of strings and NaN-tolerant operation. + This provides similar functionality to :func:`numpy.array_equal`, but + will compare arrays as unequal when their masks differ and has + additional support for arrays of strings and NaN-tolerant operation. This function maintains laziness when called; it does not realise data. See more at :doc:`/userguide/real_and_lazy_data`. @@ -406,17 +407,24 @@ def array_equal(array1, array2, withnans=False): def normalise_array(array): if not is_lazy_data(array): - array = np.asarray(array) + if not ma.isMaskedArray(array): + array = np.asanyarray(array) return array array1, array2 = normalise_array(array1), normalise_array(array2) eq = array1.shape == array2.shape + if eq: + array1_masked = ma.is_masked(array1) + eq = array1_masked == ma.is_masked(array2) + if eq and array1_masked: + eq = np.array_equal(ma.getmaskarray(array1), ma.getmaskarray(array2)) if eq: eqs = array1 == array2 if withnans and (array1.dtype.kind == "f" or array2.dtype.kind == "f"): eqs = np.where(np.isnan(array1) & np.isnan(array2), True, eqs) - eq = bool(np.all(eqs)) + eq = np.all(eqs) + eq = bool(eq) or eq is ma.masked return eq