Skip to content

Commit d16628a

Browse files
Fix array_equal behaviour for masked arrays (#4457)
* 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]>
1 parent 167d149 commit d16628a

File tree

3 files changed

+35
-5
lines changed

3 files changed

+35
-5
lines changed

docs/src/whatsnew/latest.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ This document explains the changes made to Iris for this release
5252
:func:`~iris.fileformats.pp.save_pairs_from_cube` because it had no effect.
5353
(:pull:`5783`)
5454

55+
#. `@stephenworsley`_ made masked arrays on Iris objects now compare as equal
56+
precisely when all unmasked points are equal and when the masks are identical.
57+
This is due to changes in :func:`~iris.util.array_equal` which previously
58+
ignored masks entirely. (:pull:`4457`)
59+
5560

5661
🚀 Performance Enhancements
5762
===========================

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,28 @@ def test_nd(self):
4444
self.assertTrue(array_equal(array_a, array_b))
4545
self.assertFalse(array_equal(array_a, array_c))
4646

47-
def test_masked_is_ignored(self):
47+
def test_masked_is_not_ignored(self):
4848
array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1])
4949
array_b = ma.masked_array([2, 2, 2], mask=[1, 0, 1])
50+
self.assertTrue(array_equal(array_a, array_b))
51+
52+
def test_masked_is_different(self):
53+
array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1])
54+
array_b = ma.masked_array([1, 2, 3], mask=[0, 0, 1])
5055
self.assertFalse(array_equal(array_a, array_b))
5156

57+
def test_masked_isnt_unmasked(self):
58+
array_a = np.array([1, 2, 2])
59+
array_b = ma.masked_array([1, 2, 2], mask=[0, 0, 1])
60+
self.assertFalse(array_equal(array_a, array_b))
61+
62+
def test_masked_unmasked_equivelance(self):
63+
array_a = np.array([1, 2, 2])
64+
array_b = ma.masked_array([1, 2, 2])
65+
array_c = ma.masked_array([1, 2, 2], mask=[0, 0, 0])
66+
self.assertTrue(array_equal(array_a, array_b))
67+
self.assertTrue(array_equal(array_a, array_c))
68+
5269
def test_fully_masked_arrays(self):
5370
array_a = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True)
5471
array_b = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True)

lib/iris/util.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,9 @@ def array_equal(array1, array2, withnans=False):
395395
396396
Notes
397397
-----
398-
This provides much the same functionality as :func:`numpy.array_equal`, but
399-
with additional support for arrays of strings and NaN-tolerant operation.
398+
This provides similar functionality to :func:`numpy.array_equal`, but
399+
will compare arrays as unequal when their masks differ and has
400+
additional support for arrays of strings and NaN-tolerant operation.
400401
401402
This function maintains laziness when called; it does not realise data.
402403
See more at :doc:`/userguide/real_and_lazy_data`.
@@ -406,17 +407,24 @@ def array_equal(array1, array2, withnans=False):
406407

407408
def normalise_array(array):
408409
if not is_lazy_data(array):
409-
array = np.asarray(array)
410+
if not ma.isMaskedArray(array):
411+
array = np.asanyarray(array)
410412
return array
411413

412414
array1, array2 = normalise_array(array1), normalise_array(array2)
413415

414416
eq = array1.shape == array2.shape
417+
if eq:
418+
array1_masked = ma.is_masked(array1)
419+
eq = array1_masked == ma.is_masked(array2)
420+
if eq and array1_masked:
421+
eq = np.array_equal(ma.getmaskarray(array1), ma.getmaskarray(array2))
415422
if eq:
416423
eqs = array1 == array2
417424
if withnans and (array1.dtype.kind == "f" or array2.dtype.kind == "f"):
418425
eqs = np.where(np.isnan(array1) & np.isnan(array2), True, eqs)
419-
eq = bool(np.all(eqs))
426+
eq = np.all(eqs)
427+
eq = bool(eq) or eq is ma.masked
420428

421429
return eq
422430

0 commit comments

Comments
 (0)