Skip to content

Commit

Permalink
Fix array_equal behaviour for masked arrays (#4457)
Browse files Browse the repository at this point in the history
* fix array_equal behaviour for masked arrays

* address review comment

* fix tests

* fix bug, add test and whatsnew

* address review comments

* Update lib/iris/tests/unit/util/test_array_equal.py

Co-authored-by: Patrick Peglar <[email protected]>

---------

Co-authored-by: Patrick Peglar <[email protected]>
  • Loading branch information
stephenworsley and pp-mo committed Jun 19, 2024
1 parent 167d149 commit d16628a
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 5 deletions.
5 changes: 5 additions & 0 deletions docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
===========================
Expand Down
19 changes: 18 additions & 1 deletion lib/iris/tests/unit/util/test_array_equal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 12 additions & 4 deletions lib/iris/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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

Expand Down

0 comments on commit d16628a

Please sign in to comment.