Repository: numpy/numpy

Issue:
masked array with named components does not behave as expected (Trac #1810)
_Original ticket http://projects.scipy.org/numpy/ticket/1810 on 2011-04-27 by trac user risa2000, assigned to @pierregm._

In the included file is the log from IPython.
According to doc, assigning valid value should unmask the item
-> this is not happening (see In&Out 4,5 and 6,7 in the file)

The mask is intact (In&Out 8)

Manually changing mask (In&Out 9+10) seems to change the mask, but the unmasked value is arbitrary and not possible to change (In&Out 11+12 and 13+14).



Discussion:
Attachment added by trac user risa2000 on 2011-04-27: [masked_all.txt](http://projects.scipy.org/numpy/attachment/ticket/1810/masked_all.txt)

As a workaround, note that if you use `a['a'][0] = 1` instead of `a[0]['a'] = 1`, the assignment works correctly.


Resolving patch:
diff --git a/numpy/ma/core.py b/numpy/ma/core.py
--- a/numpy/ma/core.py
+++ b/numpy/ma/core.py
@@ -2951,7 +2951,7 @@ def __getitem__(self, indx):
 # If we can make mvoid a subclass of np.void, that'd be what we'd need
 #                return mvoid(dout, mask=mask)
                 if flatten_mask(mask).any():
-                    dout = mvoid(dout, mask=mask)
+                    dout = mvoid(dout, mask=mask, hardmask=self._hardmask)
                 else:
                     return dout
             # Just a scalar............
@@ -5509,11 +5509,13 @@ class mvoid(MaskedArray):
     Fake a 'void' object to use for masked array with structured dtypes.
     """
     #
-    def __new__(self, data, mask=nomask, dtype=None, fill_value=None):
+    def __new__(self, data, mask=nomask, dtype=None, fill_value=None,
+                hardmask=False):
         dtype = dtype or data.dtype
         _data = ndarray((), dtype=dtype)
         _data[()] = data
         _data = _data.view(self)
+        _data._hardmask = hardmask
         if mask is not nomask:
             if isinstance(mask, np.void):
                 _data._mask = mask
@@ -5543,7 +5545,10 @@ def __getitem__(self, indx):
 
     def __setitem__(self, indx, value):
         self._data[indx] = value
-        self._mask[indx] |= getattr(value, "_mask", False)
+        if self._hardmask:
+            self._mask[indx] |= getattr(value, "_mask", False)
+        else:
+            self._mask[indx] = getattr(value, "_mask", False)
 
     def __str__(self):
         m = self._mask


Test patch:
diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py
--- a/numpy/ma/tests/test_core.py
+++ b/numpy/ma/tests/test_core.py
@@ -3453,6 +3453,30 @@ def test_getitem(self):
         assert_equal_records(a[-2]._data, a._data[-2])
         assert_equal_records(a[-2]._mask, a._mask[-2])
 
+    def test_setitem(self):
+        # Issue 2403
+        ndtype = np.dtype([('a', float), ('b', int)])
+        mdtype = np.dtype([('a', bool), ('b', bool)])
+        # soft mask
+        control = np.array([(False, True), (True, True)], dtype=mdtype)
+        a = np.ma.masked_all((2,), dtype=ndtype)
+        a['a'][0] = 2
+        assert_equal(a.mask, control)
+        a = np.ma.masked_all((2,), dtype=ndtype)
+        a[0]['a'] = 2
+        assert_equal(a.mask, control)
+        # hard mask
+        control = np.array([(True, True), (True, True)], dtype=mdtype)
+        a = np.ma.masked_all((2,), dtype=ndtype)
+        a.harden_mask()
+        a['a'][0] = 2
+        assert_equal(a.mask, control)
+        a = np.ma.masked_all((2,), dtype=ndtype)
+        a.harden_mask()
+        a[0]['a'] = 2
+        assert_equal(a.mask, control)
+
+
 #------------------------------------------------------------------------------
 
 class TestMaskedView(TestCase):