diff options
author | Charles Harris <charlesr.harris@gmail.com> | 2015-06-17 15:44:39 -0400 |
---|---|---|
committer | Charles Harris <charlesr.harris@gmail.com> | 2015-06-17 15:44:39 -0400 |
commit | 6c1e1de675c3a774bef31be789c43600da60addc (patch) | |
tree | 4e070ab64428b7263549bba306ea8860b095ae01 /numpy/ma/core.py | |
parent | f4e0bdd0386f89bc2c40c5329663710293140ae6 (diff) | |
parent | 3c6b6baba587297633b8744a3b2ade8644ad8a5e (diff) | |
download | numpy-6c1e1de675c3a774bef31be789c43600da60addc.tar.gz |
Merge pull request #3907 from mhvk/ma/allow-subclass-in-ufunc
BUG allow subclasses in MaskedArray ufuncs -- for non-ndarray _data
Diffstat (limited to 'numpy/ma/core.py')
-rw-r--r-- | numpy/ma/core.py | 188 |
1 files changed, 94 insertions, 94 deletions
diff --git a/numpy/ma/core.py b/numpy/ma/core.py index 8e14d7c1a..24e18a9d6 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -880,14 +880,10 @@ class _MaskedUnaryOperation: except TypeError: pass # Transform to - if isinstance(a, MaskedArray): - subtype = type(a) - else: - subtype = MaskedArray - result = result.view(subtype) - result._mask = m - result._update_from(a) - return result + masked_result = result.view(get_masked_subclass(a)) + masked_result._mask = m + masked_result._update_from(result) + return masked_result # def __str__ (self): return "Masked version of %s. [Invalid values are masked]" % str(self.f) @@ -928,8 +924,12 @@ class _MaskedBinaryOperation: def __call__ (self, a, b, *args, **kwargs): "Execute the call behavior." # Get the data, as ndarray - (da, db) = (getdata(a, subok=False), getdata(b, subok=False)) - # Get the mask + (da, db) = (getdata(a), getdata(b)) + # Get the result + with np.errstate(): + np.seterr(divide='ignore', invalid='ignore') + result = self.f(da, db, *args, **kwargs) + # Get the mask for the result (ma, mb) = (getmask(a), getmask(b)) if ma is nomask: if mb is nomask: @@ -940,12 +940,6 @@ class _MaskedBinaryOperation: m = umath.logical_or(ma, getmaskarray(b)) else: m = umath.logical_or(ma, mb) - # Get the result - with np.errstate(divide='ignore', invalid='ignore'): - result = self.f(da, db, *args, **kwargs) - # check it worked - if result is NotImplemented: - return NotImplemented # Case 1. : scalar if not result.ndim: if m: @@ -953,28 +947,26 @@ class _MaskedBinaryOperation: return result # Case 2. : array # Revert result to da where masked - if m is not nomask: - np.copyto(result, da, casting='unsafe', where=m) + if m is not nomask and m.any(): + # any errors, just abort; impossible to guarantee masked values + try: + np.copyto(result, 0, casting='unsafe', where=m) + # avoid using "*" since this may be overlaid + masked_da = umath.multiply(m, da) + # only add back if it can be cast safely + if np.can_cast(masked_da.dtype, result.dtype, casting='safe'): + result += masked_da + except: + pass # Transforms to a (subclass of) MaskedArray - result = result.view(get_masked_subclass(a, b)) - result._mask = m - # Update the optional info from the inputs - if isinstance(b, MaskedArray): - if isinstance(a, MaskedArray): - result._update_from(a) - else: - result._update_from(b) - elif isinstance(a, MaskedArray): - result._update_from(a) - return result - + masked_result = result.view(get_masked_subclass(a, b)) + masked_result._mask = m + masked_result._update_from(result) + return masked_result def reduce(self, target, axis=0, dtype=None): """Reduce `target` along the given `axis`.""" - if isinstance(target, MaskedArray): - tclass = type(target) - else: - tclass = MaskedArray + tclass = get_masked_subclass(target) m = getmask(target) t = filled(target, self.filly) if t.shape == (): @@ -982,24 +974,30 @@ class _MaskedBinaryOperation: if m is not nomask: m = make_mask(m, copy=1) m.shape = (1,) + if m is nomask: - return self.f.reduce(t, axis).view(tclass) - t = t.view(tclass) - t._mask = m - tr = self.f.reduce(getdata(t), axis, dtype=dtype or t.dtype) - mr = umath.logical_and.reduce(m, axis) - tr = tr.view(tclass) - if mr.ndim > 0: - tr._mask = mr - return tr - elif mr: - return masked - return tr + tr = self.f.reduce(t, axis) + mr = nomask + else: + tr = self.f.reduce(t, axis, dtype=dtype or t.dtype) + mr = umath.logical_and.reduce(m, axis) - def outer (self, a, b): + if not tr.shape: + if mr: + return masked + else: + return tr + masked_tr = tr.view(tclass) + masked_tr._mask = mr + masked_tr._update_from(tr) + return masked_tr + + def outer(self, a, b): """Return the function applied to the outer product of a and b. """ + (da, db) = (getdata(a), getdata(b)) + d = self.f.outer(da, db) ma = getmask(a) mb = getmask(b) if ma is nomask and mb is nomask: @@ -1010,31 +1008,28 @@ class _MaskedBinaryOperation: m = umath.logical_or.outer(ma, mb) if (not m.ndim) and m: return masked - (da, db) = (getdata(a), getdata(b)) - d = self.f.outer(da, db) - # check it worked - if d is NotImplemented: - return NotImplemented if m is not nomask: np.copyto(d, da, where=m) - if d.shape: - d = d.view(get_masked_subclass(a, b)) - d._mask = m - return d + if not d.shape: + return d + masked_d = d.view(get_masked_subclass(a, b)) + masked_d._mask = m + masked_d._update_from(d) + return masked_d - def accumulate (self, target, axis=0): + def accumulate(self, target, axis=0): """Accumulate `target` along `axis` after filling with y fill value. """ - if isinstance(target, MaskedArray): - tclass = type(target) - else: - tclass = MaskedArray + tclass = get_masked_subclass(target) t = filled(target, self.filly) - return self.f.accumulate(t, axis).view(tclass) + result = self.f.accumulate(t, axis) + masked_result = result.view(tclass) + masked_result._update_from(result) + return masked_result - def __str__ (self): + def __str__(self): return "Masked version of " + str(self.f) @@ -1074,19 +1069,15 @@ class _DomainedBinaryOperation: def __call__(self, a, b, *args, **kwargs): "Execute the call behavior." - # Get the data and the mask - (da, db) = (getdata(a, subok=False), getdata(b, subok=False)) - (ma, mb) = (getmask(a), getmask(b)) + # Get the data + (da, db) = (getdata(a), getdata(b)) # Get the result with np.errstate(divide='ignore', invalid='ignore'): result = self.f(da, db, *args, **kwargs) - # check it worked - if result is NotImplemented: - return NotImplemented - # Get the mask as a combination of ma, mb and invalid + # Get the mask as a combination of the source masks and invalid m = ~umath.isfinite(result) - m |= ma - m |= mb + m |= getmask(a) + m |= getmask(b) # Apply the domain domain = ufunc_domain.get(self.f, None) if domain is not None: @@ -1097,18 +1088,23 @@ class _DomainedBinaryOperation: return masked else: return result - # When the mask is True, put back da - np.copyto(result, da, casting='unsafe', where=m) - result = result.view(get_masked_subclass(a, b)) - result._mask = m - if isinstance(b, MaskedArray): - if isinstance(a, MaskedArray): - result._update_from(a) - else: - result._update_from(b) - elif isinstance(a, MaskedArray): - result._update_from(a) - return result + # When the mask is True, put back da if possible + # any errors, just abort; impossible to guarantee masked values + try: + np.copyto(result, 0, casting='unsafe', where=m) + # avoid using "*" since this may be overlaid + masked_da = umath.multiply(m, da) + # only add back if it can be cast safely + if np.can_cast(masked_da.dtype, result.dtype, casting='safe'): + result += masked_da + except: + pass + + # Transforms to a (subclass of) MaskedArray + masked_result = result.view(get_masked_subclass(a, b)) + masked_result._mask = m + masked_result._update_from(result) + return masked_result def __str__ (self): return "Masked version of " + str(self.f) @@ -1361,7 +1357,7 @@ def getmaskarray(arr): """ mask = getmask(arr) if mask is nomask: - mask = make_mask_none(np.shape(arr), getdata(arr).dtype) + mask = make_mask_none(np.shape(arr), getattr(arr, 'dtype', None)) return mask def is_mask(m): @@ -3756,34 +3752,38 @@ class MaskedArray(ndarray): return check # def __add__(self, other): - "Add other to self, and return a new masked array." + "Add self to other, and return a new masked array." if self._delegate_binop(other): return NotImplemented return add(self, other) # def __radd__(self, other): "Add other to self, and return a new masked array." - return add(self, other) + # In analogy with __rsub__ and __rdiv__, use original order: + # we get here from `other + self`. + return add(other, self) # def __sub__(self, other): - "Subtract other to self, and return a new masked array." + "Subtract other from self, and return a new masked array." if self._delegate_binop(other): return NotImplemented return subtract(self, other) # def __rsub__(self, other): - "Subtract other to self, and return a new masked array." + "Subtract self from other, and return a new masked array." return subtract(other, self) # def __mul__(self, other): - "Multiply other by self, and return a new masked array." + "Multiply self by other, and return a new masked array." if self._delegate_binop(other): return NotImplemented return multiply(self, other) # def __rmul__(self, other): "Multiply other by self, and return a new masked array." - return multiply(self, other) + # In analogy with __rsub__ and __rdiv__, use original order: + # we get here from `other * self`. + return multiply(other, self) # def __div__(self, other): "Divide other into self, and return a new masked array." @@ -3798,7 +3798,7 @@ class MaskedArray(ndarray): return true_divide(self, other) # def __rtruediv__(self, other): - "Divide other into self, and return a new masked array." + "Divide self into other, and return a new masked array." return true_divide(other, self) # def __floordiv__(self, other): @@ -3808,7 +3808,7 @@ class MaskedArray(ndarray): return floor_divide(self, other) # def __rfloordiv__(self, other): - "Divide other into self, and return a new masked array." + "Divide self into other, and return a new masked array." return floor_divide(other, self) # def __pow__(self, other): @@ -3818,7 +3818,7 @@ class MaskedArray(ndarray): return power(self, other) # def __rpow__(self, other): - "Raise self to the power other, masking the potential NaNs/Infs" + "Raise other to the power self, masking the potential NaNs/Infs" return power(other, self) #............................................ def __iadd__(self, other): |