diff options
Diffstat (limited to 'numpy/ma')
-rw-r--r-- | numpy/ma/core.py | 92 | ||||
-rw-r--r-- | numpy/ma/extras.py | 45 | ||||
-rw-r--r-- | numpy/ma/tests/test_core.py | 21 | ||||
-rw-r--r-- | numpy/ma/tests/test_extras.py | 3 | ||||
-rw-r--r-- | numpy/ma/tests/test_old_ma.py | 3 |
5 files changed, 113 insertions, 51 deletions
diff --git a/numpy/ma/core.py b/numpy/ma/core.py index 7dadb9d98..9ca9136dd 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -845,8 +845,7 @@ class _MaskedUnaryOperation: d = getdata(a) # Case 1.1. : Domained function if self.domain is not None: - with np.errstate(): - np.seterr(divide='ignore', invalid='ignore') + with np.errstate(divide='ignore', invalid='ignore'): result = self.f(d, *args, **kwargs) # Make a mask m = ~umath.isfinite(result) @@ -934,8 +933,7 @@ class _MaskedBinaryOperation: else: m = umath.logical_or(ma, mb) # Get the result - with np.errstate(): - np.seterr(divide='ignore', invalid='ignore') + with np.errstate(divide='ignore', invalid='ignore'): result = self.f(da, db, *args, **kwargs) # check it worked if result is NotImplemented: @@ -947,11 +945,8 @@ class _MaskedBinaryOperation: return result # Case 2. : array # Revert result to da where masked - if m.any(): - np.copyto(result, 0, casting='unsafe', where=m) - # This only makes sense if the operation preserved the dtype - if result.dtype == da.dtype: - result += m * da + if m is not nomask: + np.copyto(result, da, casting='unsafe', where=m) # Transforms to a (subclass of) MaskedArray result = result.view(get_masked_subclass(a, b)) result._mask = m @@ -1075,8 +1070,7 @@ class _DomainedBinaryOperation: (da, db) = (getdata(a, subok=False), getdata(b, subok=False)) (ma, mb) = (getmask(a), getmask(b)) # Get the result - with np.errstate(): - np.seterr(divide='ignore', invalid='ignore') + with np.errstate(divide='ignore', invalid='ignore'): result = self.f(da, db, *args, **kwargs) # check it worked if result is NotImplemented: @@ -1096,8 +1090,7 @@ class _DomainedBinaryOperation: else: return result # When the mask is True, put back da - np.copyto(result, 0, casting='unsafe', where=m) - result += m * da + np.copyto(result, da, casting='unsafe', where=m) result = result.view(get_masked_subclass(a, b)) result._mask = m if isinstance(b, MaskedArray): @@ -2792,12 +2785,50 @@ class MaskedArray(ndarray): """ # Get main attributes ......... self._update_from(obj) + # We have to decide how to initialize self.mask, based on + # obj.mask. This is very difficult. There might be some + # correspondence between the elements in the array we are being + # created from (= obj) and us. Or... there might not. This method can + # be called in all kinds of places for all kinds of reasons -- could + # be empty_like, could be slicing, could be a ufunc, could be a view, + # ... The numpy subclassing interface simply doesn't give us any way + # to know, which means that at best this method will be based on + # guesswork and heuristics. To make things worse, there isn't even any + # clear consensus about what the desired behavior is. For instance, + # most users think that np.empty_like(marr) -- which goes via this + # method -- should return a masked array with an empty mask (see + # gh-3404 and linked discussions), but others disagree, and they have + # existing code which depends on empty_like returning an array that + # matches the input mask. + # + # Historically our algorithm was: if the template object mask had the + # same *number of elements* as us, then we used *it's mask object + # itself* as our mask, so that writes to us would also write to the + # original array. This is horribly broken in multiple ways. + # + # Now what we do instead is, if the template object mask has the same + # number of elements as us, and we do not have the same base pointer + # as the template object (b/c views like arr[...] should keep the same + # mask), then we make a copy of the template object mask and use + # that. This is also horribly broken but somewhat less so. Maybe. if isinstance(obj, ndarray): - odtype = obj.dtype - if odtype.names: - _mask = getattr(obj, '_mask', make_mask_none(obj.shape, odtype)) + # XX: This looks like a bug -- shouldn't it check self.dtype + # instead? + if obj.dtype.names: + _mask = getattr(obj, '_mask', + make_mask_none(obj.shape, obj.dtype)) else: _mask = getattr(obj, '_mask', nomask) + # If self and obj point to exactly the same data, then probably + # self is a simple view of obj (e.g., self = obj[...]), so they + # should share the same mask. (This isn't 100% reliable, e.g. self + # could be the first row of obj, or have strange strides, but as a + # heuristic it's not bad.) In all other cases, we make a copy of + # the mask, so that future modifications to 'self' do not end up + # side-effecting 'obj' as well. + if (obj.__array_interface__["data"][0] + != self.__array_interface__["data"][0]): + _mask = _mask.copy() else: _mask = nomask self._mask = _mask @@ -3842,8 +3873,7 @@ class MaskedArray(ndarray): "Raise self to the power other, in place." other_data = getdata(other) other_mask = getmask(other) - with np.errstate(): - np.seterr(divide='ignore', invalid='ignore') + with np.errstate(divide='ignore', invalid='ignore'): ndarray.__ipow__(self._data, np.where(self._mask, 1, other_data)) invalid = np.logical_not(np.isfinite(self._data)) if invalid.any(): @@ -4948,7 +4978,7 @@ class MaskedArray(ndarray): Returns ------- - {ndarray, scalar} + ndarray or scalar If multi-dimension input, returns a new ndarray of indices to the minimum values along the given axis. Otherwise, returns a scalar of index to the minimum values along the given axis. @@ -5031,6 +5061,10 @@ class MaskedArray(ndarray): endwith : {True, False}, optional Whether missing values (if any) should be forced in the upper indices (at the end of the array) (True) or lower indices (at the beginning). + When the array contains unmasked values of the largest (or smallest if + False) representable value of the datatype the ordering of these values + and the masked values is undefined. To enforce the masked values are + at the end (beginning) in this case one must sort the mask. fill_value : {var}, optional Value used internally for the masked values. If ``fill_value`` is not None, it supersedes ``endwith``. @@ -5079,7 +5113,11 @@ class MaskedArray(ndarray): return self if fill_value is None: if endwith: - filler = minimum_fill_value(self) + # nan > inf + if np.issubdtype(self.dtype, np.floating): + filler = np.nan + else: + filler = minimum_fill_value(self) else: filler = maximum_fill_value(self) else: @@ -5596,9 +5634,8 @@ class mvoid(MaskedArray): """ # def __new__(self, data, mask=nomask, dtype=None, fill_value=None, - hardmask=False): - dtype = dtype or data.dtype - _data = np.array(data, dtype=dtype) + hardmask=False, copy=False, subok=True): + _data = np.array(data, copy=copy, subok=subok, dtype=dtype) _data = _data.view(self) _data._hardmask = hardmask if mask is not nomask: @@ -6118,8 +6155,7 @@ def power(a, b, third=None): else: basetype = MaskedArray # Get the result and view it as a (subclass of) MaskedArray - with np.errstate(): - np.seterr(divide='ignore', invalid='ignore') + with np.errstate(divide='ignore', invalid='ignore'): result = np.where(m, fa, umath.power(fa, fb)).view(basetype) result._update_from(a) # Find where we're in trouble w/ NaNs and Infs @@ -6190,7 +6226,11 @@ def sort(a, axis= -1, kind='quicksort', order=None, endwith=True, fill_value=Non axis = 0 if fill_value is None: if endwith: - filler = minimum_fill_value(a) + # nan > inf + if np.issubdtype(a.dtype, np.floating): + filler = np.nan + else: + filler = minimum_fill_value(a) else: filler = maximum_fill_value(a) else: diff --git a/numpy/ma/extras.py b/numpy/ma/extras.py index a993fd05d..726be6cb6 100644 --- a/numpy/ma/extras.py +++ b/numpy/ma/extras.py @@ -658,21 +658,21 @@ def median(a, axis=None, out=None, overwrite_input=False): Examples -------- >>> x = np.ma.array(np.arange(8), mask=[0]*4 + [1]*4) - >>> np.ma.extras.median(x) + >>> np.ma.median(x) 1.5 >>> x = np.ma.array(np.arange(10).reshape(2, 5), mask=[0]*6 + [1]*4) - >>> np.ma.extras.median(x) + >>> np.ma.median(x) 2.5 - >>> np.ma.extras.median(x, axis=-1, overwrite_input=True) + >>> np.ma.median(x, axis=-1, overwrite_input=True) masked_array(data = [ 2. 5.], mask = False, fill_value = 1e+20) """ if not hasattr(a, 'mask') or np.count_nonzero(a.mask) == 0: - return masked_array(np.median(a, axis=axis, out=out, - overwrite_input=overwrite_input), copy=False) + return masked_array(np.median(getattr(a, 'data', a), axis=axis, + out=out, overwrite_input=overwrite_input), copy=False) if overwrite_input: if axis is None: asorted = a.ravel() @@ -705,7 +705,14 @@ def median(a, axis=None, out=None, overwrite_input=False): low = high else: low[odd] = high[odd] - return np.ma.mean([low, high], axis=0, out=out) + + if np.issubdtype(asorted.dtype, np.inexact): + # avoid inf / x = masked + s = np.ma.sum([low, high], axis=0, out=out) + np.true_divide(s.data, 2., casting='unsafe', out=s.data) + else: + s = np.ma.mean([low, high], axis=0, out=out) + return s #.............................................................................. @@ -746,11 +753,11 @@ def compress_rowcols(x, axis=None): [False False False]], fill_value = 999999) - >>> np.ma.extras.compress_rowcols(x) + >>> np.ma.compress_rowcols(x) array([[7, 8]]) - >>> np.ma.extras.compress_rowcols(x, 0) + >>> np.ma.compress_rowcols(x, 0) array([[6, 7, 8]]) - >>> np.ma.extras.compress_rowcols(x, 1) + >>> np.ma.compress_rowcols(x, 1) array([[1, 2], [4, 5], [7, 8]]) @@ -781,7 +788,7 @@ def compress_rows(a): """ Suppress whole rows of a 2-D array that contain masked values. - This is equivalent to ``np.ma.extras.compress_rowcols(a, 0)``, see + This is equivalent to ``np.ma.compress_rowcols(a, 0)``, see `extras.compress_rowcols` for details. See Also @@ -795,7 +802,7 @@ def compress_cols(a): """ Suppress whole columns of a 2-D array that contain masked values. - This is equivalent to ``np.ma.extras.compress_rowcols(a, 1)``, see + This is equivalent to ``np.ma.compress_rowcols(a, 1)``, see `extras.compress_rowcols` for details. See Also @@ -1226,7 +1233,7 @@ def setdiff1d(ar1, ar2, assume_unique=False): Examples -------- >>> x = np.ma.array([1, 2, 3, 4], mask=[0, 1, 0, 1]) - >>> np.ma.extras.setdiff1d(x, [1, 2]) + >>> np.ma.setdiff1d(x, [1, 2]) masked_array(data = [3 --], mask = [False True], fill_value = 999999) @@ -1638,7 +1645,7 @@ def notmasked_edges(a, axis=None): >>> np.array(am[~am.mask]) array([0, 1, 2, 3, 6]) - >>> np.ma.extras.notmasked_edges(ma) + >>> np.ma.notmasked_edges(ma) array([0, 6]) """ @@ -1677,7 +1684,7 @@ def flatnotmasked_contiguous(a): Examples -------- >>> a = np.ma.arange(10) - >>> np.ma.extras.flatnotmasked_contiguous(a) + >>> np.ma.flatnotmasked_contiguous(a) slice(0, 10, None) >>> mask = (a < 3) | (a > 8) | (a == 5) @@ -1685,10 +1692,10 @@ def flatnotmasked_contiguous(a): >>> np.array(a[~a.mask]) array([3, 4, 6, 7, 8]) - >>> np.ma.extras.flatnotmasked_contiguous(a) + >>> np.ma.flatnotmasked_contiguous(a) [slice(3, 5, None), slice(6, 9, None)] >>> a[:] = np.ma.masked - >>> print np.ma.extras.flatnotmasked_edges(a) + >>> print np.ma.flatnotmasked_edges(a) None """ @@ -1741,7 +1748,7 @@ def notmasked_contiguous(a, axis=None): >>> np.array(ma[~ma.mask]) array([0, 1, 2, 3, 6]) - >>> np.ma.extras.notmasked_contiguous(ma) + >>> np.ma.notmasked_contiguous(ma) [slice(0, 4, None), slice(6, 7, None)] """ @@ -1810,7 +1817,7 @@ def clump_unmasked(a): -------- >>> a = np.ma.masked_array(np.arange(10)) >>> a[[0, 1, 2, 6, 8, 9]] = np.ma.masked - >>> np.ma.extras.clump_unmasked(a) + >>> np.ma.clump_unmasked(a) [slice(3, 6, None), slice(7, 8, None)] """ @@ -1854,7 +1861,7 @@ def clump_masked(a): -------- >>> a = np.ma.masked_array(np.arange(10)) >>> a[[0, 1, 2, 6, 8, 9]] = np.ma.masked - >>> np.ma.extras.clump_masked(a) + >>> np.ma.clump_masked(a) [slice(0, 3, None), slice(6, 7, None), slice(8, 10, None)] """ diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index e6f659041..4ac3465aa 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -194,8 +194,7 @@ class TestMaskedArray(TestCase): def test_fix_invalid(self): # Checks fix_invalid. - with np.errstate(): - np.seterr(invalid='ignore') + with np.errstate(invalid='ignore'): data = masked_array([np.nan, 0., 1.], mask=[0, 0, 1]) data_fixed = fix_invalid(data) assert_equal(data_fixed._data, [data.fill_value, 0., 1.]) @@ -815,7 +814,7 @@ class TestMaskedArrayArithmetic(TestCase): res = count(ott) self.assertTrue(res.dtype.type is np.intp) assert_equal(3, res) - + ott = ott.reshape((2, 2)) res = count(ott) assert_(res.dtype.type is np.intp) @@ -2227,6 +2226,13 @@ class TestMaskedArrayMethods(TestCase): assert_equal(b.shape, a.shape) assert_equal(b.fill_value, a.fill_value) + # check empty_like mask handling + a = masked_array([1, 2, 3], mask=[False, True, False]) + b = empty_like(a) + assert_(not np.may_share_memory(a.mask, b.mask)) + b = a.view(masked_array) + assert_(np.may_share_memory(a.mask, b.mask)) + def test_put(self): # Tests put. d = arange(5) @@ -3523,8 +3529,15 @@ class TestMaskedFields(TestCase): assert_equal_records(a[-2]._mask, a._mask[-2]) def test_setitem(self): - # Issue 2403 + # Issue 4866: check that one can set individual items in [record][col] + # and [col][record] order ndtype = np.dtype([('a', float), ('b', int)]) + ma = np.ma.MaskedArray([(1.0, 1), (2.0, 2)], dtype=ndtype) + ma['a'][1] = 3.0 + assert_equal(ma['a'], np.array([1.0, 3.0])) + ma[1]['a'] = 4.0 + assert_equal(ma['a'], np.array([1.0, 4.0])) + # Issue 2403 mdtype = np.dtype([('a', bool), ('b', bool)]) # soft mask control = np.array([(False, True), (True, True)], dtype=mdtype) diff --git a/numpy/ma/tests/test_extras.py b/numpy/ma/tests/test_extras.py index 6ce1dc346..d7bc765a9 100644 --- a/numpy/ma/tests/test_extras.py +++ b/numpy/ma/tests/test_extras.py @@ -504,6 +504,9 @@ class TestApplyOverAxes(TestCase): class TestMedian(TestCase): + def test_pytype(self): + r = np.ma.median([[np.inf, np.inf], [np.inf, np.inf]], axis=-1) + assert_equal(r, np.inf) def test_2d(self): # Tests median w/ 2D diff --git a/numpy/ma/tests/test_old_ma.py b/numpy/ma/tests/test_old_ma.py index 87c2133d7..047f91c77 100644 --- a/numpy/ma/tests/test_old_ma.py +++ b/numpy/ma/tests/test_old_ma.py @@ -607,8 +607,7 @@ class TestMa(TestCase): def test_testScalarArithmetic(self): xm = array(0, mask=1) #TODO FIXME: Find out what the following raises a warning in r8247 - with np.errstate(): - np.seterr(divide='ignore') + with np.errstate(divide='ignore'): self.assertTrue((1 / array(0)).mask) self.assertTrue((1 + xm).mask) self.assertTrue((-xm).mask) |