diff options
author | Travis Oliphant <oliphant@enthought.com> | 2005-09-15 16:24:47 +0000 |
---|---|---|
committer | Travis Oliphant <oliphant@enthought.com> | 2005-09-15 16:24:47 +0000 |
commit | a215f584407a25da6247784fb1f9a232e206a86f (patch) | |
tree | f5d45d1e595426fb1a13a45da4947e4b0156436b /scipy/base/ma.py | |
parent | 3cb3d7798a0050a4c9272cf4f12631a4c338d0a9 (diff) | |
download | numpy-a215f584407a25da6247784fb1f9a232e206a86f.tar.gz |
Added ma hook.
Diffstat (limited to 'scipy/base/ma.py')
-rw-r--r-- | scipy/base/ma.py | 2136 |
1 files changed, 2136 insertions, 0 deletions
diff --git a/scipy/base/ma.py b/scipy/base/ma.py new file mode 100644 index 000000000..20045697b --- /dev/null +++ b/scipy/base/ma.py @@ -0,0 +1,2136 @@ +"""MA: a facility for dealing with missing observations +MA is generally used as a Numeric.array look-alike. +There are some differences in semantics, see manual. +In particular note that slices are copies, not references. +by Paul F. Dubois + L-264 + Lawrence Livermore National Laboratory + dubois@users.sourceforge.net +Copyright 1999, 2000, 2001 Regents of the University of California. +Released for unlimited redistribution; see file Legal.htm +Documentation is in the Numeric manual; see numpy.sourceforge.net +""" +import Numeric +import string, types, sys +from Precision import * +from Numeric import e, pi, NewAxis +MaskType=Int0 +divide_tolerance = 1.e-35 + +class MAError (Exception): + def __init__ (self, args=None): + "Create an exception" + self.args = args + def __str__(self): + "Calculate the string representation" + return str(self.args) + __repr__ = __str__ + +class _MaskedPrintOption: + "One instance of this class, masked_print_option, is created." + def __init__ (self, display): + "Create the masked print option object." + self.set_display(display) + self._enabled = 1 + + def display (self): + "Show what prints for masked values." + return self._display + + def set_display (self, s): + "set_display(s) sets what prints for masked values." + self._display = s + + def enabled (self): + "Is the use of the display value enabled?" + return self._enabled + + def enable(self, flag=1): + "Set the enabling flag to flag." + self._enabled = flag + + def __str__ (self): + return str(self._display) + +#if you single index into a masked location you get this object. +masked_print_option = _MaskedPrintOption('--') + +# Use single element arrays or scalars. +default_real_fill_value = Numeric.array([1.0e20]).astype(Float32) +default_complex_fill_value = Numeric.array([1.0e20 + 0.0j]).astype(Complex32) +default_character_fill_value = '?' +default_integer_fill_value = Numeric.array([0]).astype(UnsignedInt8) +default_object_fill_value = '?' + +def default_fill_value (obj): + "Function to calculate default fill value for an object." + if isinstance(obj, types.FloatType): + return default_real_fill_value + elif isinstance(obj, types.IntType) or isinstance(obj, types.LongType): + return default_integer_fill_value + elif isinstance(obj, types.StringType): + return default_character_fill_value + elif isinstance(obj, types.ComplexType): + return default_complex_fill_value + elif isinstance(obj, MaskedArray) or isinstance(obj, Numeric.arraytype): + x = obj.typecode() + if x in typecodes['Float']: + return default_real_fill_value + if x in typecodes['Integer']: + return default_integer_fill_value + if x in typecodes['Complex']: + return default_complex_fill_value + if x in typecodes['Character']: + return default_character_fill_value + if x in typecodes['UnsignedInteger']: + return Numeric.absolute(default_integer_fill_value) + return default_object_fill_value + else: + return default_object_fill_value + +def minimum_fill_value (obj): + "Function to calculate default fill value suitable for taking minima." + if isinstance(obj, types.FloatType): + return default_real_fill_value + elif isinstance(obj, types.IntType) or isinstance(obj, types.LongType): + return sys.maxint + elif isinstance(obj, MaskedArray) or isinstance(obj, Numeric.arraytype): + x = obj.typecode() + if x in typecodes['Float']: + return default_real_fill_value + if x in typecodes['Integer']: + return sys.maxint + if x in typecodes['UnsignedInteger']: + return sys.maxint + else: + raise TypeError, 'Unsuitable type for calculating minimum.' + +def maximum_fill_value (obj): + "Function to calculate default fill value suitable for taking maxima." + if isinstance(obj, types.FloatType): + return -default_real_fill_value + elif isinstance(obj, types.IntType) or isinstance(obj, types.LongType): + return -default_integer_fill_value + elif isinstance(obj, MaskedArray) or isinstance(obj, Numeric.arraytype): + x = obj.typecode() + if x in typecodes['Float']: + return -default_real_fill_value + if x in typecodes['Integer']: + return -sys.maxint + if x in typecodes['UnsignedInteger']: + return 0 + else: + raise TypeError, 'Unsuitable type for calculating maximum.' + +def set_fill_value (a, fill_value): + "Set fill value of a if it is a masked array." + if isMaskedArray(a): + a.set_fill_value (fill_value) + +def getmask (a): + """Mask of values in a; could be None. + Returns None if a is not a masked array. + To get an array for sure use getmaskarray.""" + if isinstance(a, MaskedArray): + return a.raw_mask() + else: + return None + +def getmaskarray (a): + """Mask of values in a; an array of zeros if mask is None + or not a masked array. Caution: has savespace attribute, + and is a byte-sized integer. + Do not try to add up entries, for example. + """ + m = getmask(a) + if m is None: + return make_mask_none(shape(a)) + else: + return m + +def is_mask (m): + """Is m a legal mask? Does not check contents, only type. + """ + if m is None or (isinstance(m, Numeric.ArrayType) and \ + m.typecode() == MaskType): + return 1 + else: + return 0 + +def make_mask (m, copy=0, flag=0): + """make_mask(m, copy=0, flag=0) + return m as a mask, creating a copy if necessary or requested. + Can accept any sequence of integers or None. Does not check + that contents must be 0s and 1s. + if flag, return None if m contains no true elements. + """ + if m is None: + return None + elif isinstance(m, Numeric.ArrayType): + if m.typecode() == MaskType: + if copy: + result = Numeric.array(m, savespace=1) + else: + result = Numeric.array(m, copy=0, savespace=1) + else: + result = m.astype(MaskType) + result.savespace(1) + else: + result = Numeric.array(filled(m,1), MaskType, savespace=1) + + if flag and not Numeric.sometrue(Numeric.ravel(result)): + return None + else: + return result + +def make_mask_none (s): + "Return a mask of all zeros of shape s." + result = Numeric.zeros(s, MaskType) + result.savespace(1) + result.shape = s + return result + +create_mask = make_mask_none #backwards compatibility + +def mask_or (m1, m2): + """Logical or of the mask candidates m1 and m2, treating None as false. + Result may equal m1 or m2 if the other is None. + """ + if m1 is None: return make_mask(m2) + if m2 is None: return make_mask(m1) + if m1 is m2 and is_mask(m1): return m1 + return make_mask(Numeric.logical_or(m1, m2)) + +def filled (a, value = None): + """a as a contiguous Numeric array with any masked areas replaced by value + if value is None or the special element "masked", fill_value(a) + is used instead. + + If a is already a contiguous Numeric array, a itself is returned. + + filled(a) can be used to be sure that the result is Numeric when + passing an object a to other software ignorant of MA, in particular to + Numeric itself. + """ + if isinstance(a, MaskedArray): + return a.filled(value) + elif isinstance(a, Numeric.ArrayType) and a.iscontiguous(): + return a + elif isinstance(a, types.DictType): + return Numeric.array(a, 'O') + else: + return Numeric.array(a) + +def fill_value (a): + """ + The fill value of a, if it has one; otherwise, the default fill value + for that type. + """ + if isMaskedArray(a): + result = a.fill_value() + else: + result = default_fill_value(a) + return result + +def common_fill_value (a, b): + "The common fill_value of a and b, if there is one, or None" + t1 = fill_value(a) + t2 = fill_value(b) + if t1 == t2: return t1 + return None + +# Domain functions return 1 where the argument(s) are not in the domain. +class domain_check_interval: + "domain_check_interval(a,b)(x) = true where x < a or y > b" + def __init__(self, y1, y2): + "domain_check_interval(a,b)(x) = true where x < a or y > b" + self.y1 = y1 + self.y2 = y2 + + def __call__ (self, x): + "Execute the call behavior." + return Numeric.logical_or(Numeric.greater (x, self.y2), + Numeric.less(x, self.y1) + ) + +class domain_tan: + "domain_tan(eps) = true where abs(cos(x)) < eps)" + def __init__(self, eps): + "domain_tan(eps) = true where abs(cos(x)) < eps)" + self.eps = eps + + def __call__ (self, x): + "Execute the call behavior." + return Numeric.less(Numeric.absolute(Numeric.cos(x)), self.eps) + +class domain_greater: + "domain_greater(v)(x) = true where x <= v" + def __init__(self, critical_value): + "domain_greater(v)(x) = true where x <= v" + self.critical_value = critical_value + + def __call__ (self, x): + "Execute the call behavior." + return Numeric.less_equal (x, self.critical_value) + +class domain_greater_equal: + "domain_greater_equal(v)(x) = true where x < v" + def __init__(self, critical_value): + "domain_greater_equal(v)(x) = true where x < v" + self.critical_value = critical_value + + def __call__ (self, x): + "Execute the call behavior." + return Numeric.less (x, self.critical_value) + +class masked_unary_operation: + def __init__ (self, aufunc, fill=0, domain=None): + """ masked_unary_operation(aufunc, fill=0, domain=None) + aufunc(fill) must be defined + self(x) returns aufunc(x) + with masked values where domain(x) is true or getmask(x) is true. + """ + self.f = aufunc + self.fill = fill + self.domain = domain + self.__doc__ = getattr(aufunc, "__doc__", str(aufunc)) + + def __call__ (self, a, *args, **kwargs): + "Execute the call behavior." +# Numeric tries to return scalars rather than arrays when given scalars. + m = getmask(a) + d1 = filled(a, self.fill) + if self.domain is not None: + m = mask_or(m, self.domain(d1)) + if m is None: + result = self.f(d1, *args, **kwargs) + if type(result) is Numeric.ArrayType: + return masked_array (result) + else: + return result + else: + dx = masked_array(d1, m) + result = self.f(filled(dx, self.fill), *args, **kwargs) + if type(result) is Numeric.ArrayType: + return masked_array(result, m) + elif m[...]: + return masked + else: + return result + + def __str__ (self): + return "Masked version of " + str(self.f) + + +class domain_safe_divide: + def __init__ (self, tolerance=divide_tolerance): + self.tolerance = tolerance + def __call__ (self, a, b): + return Numeric.absolute(a) * self.tolerance >= Numeric.absolute(b) + +class domained_binary_operation: + """Binary operations that have a domain, like divide. These are complicated so they + are a separate class. They have no reduce, outer or accumulate. + """ + def __init__ (self, abfunc, domain, fillx=0, filly=0): + """abfunc(fillx, filly) must be defined. + abfunc(x, filly) = x for all x to enable reduce. + """ + self.f = abfunc + self.domain = domain + self.fillx = fillx + self.filly = filly + self.__doc__ = getattr(abfunc, "__doc__", str(abfunc)) + + def __call__(self, a, b): + "Execute the call behavior." + ma = getmask(a) + mb = getmask(b) + d1 = filled(a, self.fillx) + d2 = filled(b, self.filly) + t = self.domain(d1, d2) + + if Numeric.sometrue(t): + d2 = where(t, self.filly, d2) + mb = mask_or(mb, t) + m = mask_or(ma, mb) + if m is None: + result = self.f(d1, d2) + if type(result) is Numeric.ArrayType: + return masked_array(result) + else: + return result + result = self.f(d1, d2) + if type(result) is Numeric.ArrayType: + if m.shape != result.shape: + m = mask_or(getmaskarray(a), getmaskarray(b)) + return masked_array(result, m) + elif m[...]: + return masked + else: + return result + def __str__ (self): + return "Masked version of " + str(self.f) + +class masked_binary_operation: + def __init__ (self, abfunc, fillx=0, filly=0): + """abfunc(fillx, filly) must be defined. + abfunc(x, filly) = x for all x to enable reduce. + """ + self.f = abfunc + self.fillx = fillx + self.filly = filly + self.__doc__ = getattr(abfunc, "__doc__", str(abfunc)) + + def __call__ (self, a, b, *args, **kwargs): + "Execute the call behavior." + m = mask_or(getmask(a), getmask(b)) + if m is None: + d1 = filled(a, self.fillx) + d2 = filled(b, self.filly) + result = self.f(d1, d2, *args, **kwargs) + if type(result) is Numeric.ArrayType: + return masked_array(result) + else: + return result + d1 = filled(a, self.fillx) + d2 = filled(b, self.filly) + result = self.f(d1, d2, *args, **kwargs) + if type(result) is Numeric.ArrayType: + if m.shape != result.shape: + m = mask_or(getmaskarray(a), getmaskarray(b)) + return masked_array(result, m) + elif m[...]: + return masked + else: + return result + + def reduce (self, target, axis=0): + """Reduce target along the given axis with this function.""" + m = getmask(target) + t = filled(target, self.filly) + if t.shape == (): + t.shape = (1,) + if m is not None: + m = make_mask(m, copy=1) + m.shape = (1,) + if m is None: + return masked_array (self.f.reduce (t, axis)) + else: + t = masked_array (t, m) + t = self.f.reduce(filled(t, self.filly), axis) + m = Numeric.logical_and.reduce(m, axis) + if isinstance(t, Numeric.ArrayType): + return masked_array(t, m, fill_value(target)) + elif m: + return masked + else: + return t + + def outer (self, a, b): + "Return the function applied to the outer product of a and b." + ma = getmask(a) + mb = getmask(b) + if ma is None and mb is None: + m = None + else: + ma = getmaskarray(a) + mb = getmaskarray(b) + m = logical_or.outer(ma, mb) + d = self.f.outer(filled(a, self.fillx), filled(b, self.filly)) + return masked_array(d, m) + + def accumulate (self, target, axis=0): + """Accumulate target along axis after filling with y fill value.""" + t = filled(target, self.filly) + return masked_array (self.f.accumulate (t, axis)) + def __str__ (self): + return "Masked version of " + str(self.f) + +sqrt = masked_unary_operation(Numeric.sqrt, 0.0, domain_greater_equal(0.0)) +log = masked_unary_operation(Numeric.log, 1.0, domain_greater(0.0)) +log10 = masked_unary_operation(Numeric.log10, 1.0, domain_greater(0.0)) +exp = masked_unary_operation(Numeric.exp) +conjugate = masked_unary_operation(Numeric.conjugate) +sin = masked_unary_operation(Numeric.sin) +cos = masked_unary_operation(Numeric.cos) +tan = masked_unary_operation(Numeric.tan, 0.0, domain_tan(1.e-35)) +arcsin = masked_unary_operation(Numeric.arcsin, 0.0, domain_check_interval(-1.0, 1.0)) +arccos = masked_unary_operation(Numeric.arccos, 0.0, domain_check_interval(-1.0, 1.0)) +arctan = masked_unary_operation(Numeric.arctan) +# Missing from Numeric +# arcsinh = masked_unary_operation(Numeric.arcsinh) +# arccosh = masked_unary_operation(Numeric.arccosh) +# arctanh = masked_unary_operation(Numeric.arctanh) +sinh = masked_unary_operation(Numeric.sinh) +cosh = masked_unary_operation(Numeric.cosh) +tanh = masked_unary_operation(Numeric.tanh) +absolute = masked_unary_operation(Numeric.absolute) +fabs = masked_unary_operation(Numeric.fabs) +negative = masked_unary_operation(Numeric.negative) +nonzero = masked_unary_operation(Numeric.nonzero) +around = masked_unary_operation(Numeric.around) +floor = masked_unary_operation(Numeric.floor) +ceil = masked_unary_operation(Numeric.ceil) +sometrue = masked_unary_operation(Numeric.sometrue) +alltrue = masked_unary_operation(Numeric.alltrue, 1) +logical_not = masked_unary_operation(Numeric.logical_not) + +add = masked_binary_operation(Numeric.add) +subtract = masked_binary_operation(Numeric.subtract) +subtract.reduce = None +multiply = masked_binary_operation(Numeric.multiply, 1, 1) +divide = domained_binary_operation(Numeric.divide, domain_safe_divide(), 0, 1) +true_divide = domained_binary_operation(Numeric.true_divide, domain_safe_divide(), 0, 1) +floor_divide = domained_binary_operation(Numeric.floor_divide, domain_safe_divide(), 0, 1) +remainder = domained_binary_operation(Numeric.remainder, domain_safe_divide(), 0, 1) +fmod = domained_binary_operation(Numeric.fmod, domain_safe_divide(), 0, 1) +hypot = masked_binary_operation(Numeric.hypot) +arctan2 = masked_binary_operation(Numeric.arctan2, 0.0, 1.0) +arctan2.reduce = None +equal = masked_binary_operation(Numeric.equal) +equal.reduce = None +not_equal = masked_binary_operation(Numeric.not_equal) +not_equal.reduce = None +less_equal = masked_binary_operation(Numeric.less_equal) +less_equal.reduce = None +greater_equal = masked_binary_operation(Numeric.greater_equal) +greater_equal.reduce = None +less = masked_binary_operation(Numeric.less) +less.reduce = None +greater = masked_binary_operation(Numeric.greater) +greater.reduce = None +logical_and = masked_binary_operation(Numeric.logical_and) +logical_or = masked_binary_operation(Numeric.logical_or) +logical_xor = masked_binary_operation(Numeric.logical_xor) +bitwise_and = masked_binary_operation(Numeric.bitwise_and) +bitwise_or = masked_binary_operation(Numeric.bitwise_or) +bitwise_xor = masked_binary_operation(Numeric.bitwise_xor) +rank = Numeric.rank +shape = Numeric.shape +size = Numeric.size + +class MaskedArray (object): + """Arrays with possibly masked values. + Masked values of 1 exclude element from the computation. + Construction: + x = array(data, typecode=None, copy=1, savespace=0, + mask = None, fill_value=None) + + If copy=0, every effort is made not to copy the data: + If data is a MaskedArray, and argument mask=None, + then the candidate data is data.raw_data() and the + mask used is data.mask(). If data is a Numeric array, + it is used as the candidate raw data. + If savespace != data.spacesaver() or typecode is not None and + is != data.typecode() then a data copy is required. + Otherwise, the candidate is used. + + If a data copy is required, raw data stored is the result of: + Numeric.array(data, typecode=typecode, copy=copy, savespace=savespace) + + If mask is None there are no masked values. Otherwise mask must + be convertible to an array of integers of typecode MaskType, + with values 1 or 0, and of the same shape as x. + + fill_value is used to fill in masked values when necessary, + such as when printing and in method/function filled(). + The fill_value is not used for computation within this module. + + If savespace is 1, the data is given the spacesaver property, and + the mask is replaced by None if all its elements are true. + """ + handler_cache_key = 'MA.MaskedArray' + + def __init__(self, data, typecode=None, + copy=1, savespace=None, mask=None, fill_value=None, + ): + """array(data, typecode=None,copy=1, savespace=None, + mask=None, fill_value=None) + If data already a Numeric array, its typecode and spacesaver() + become the default values for typecode and savespace. + """ + tc = typecode + ss = savespace + need_data_copied = copy + if isinstance(data, MaskedArray): + c = data.raw_data() + ctc = c.typecode() + if tc is None: + tc = ctc + elif tc != ctc: + need_data_copied = 1 + css = c.spacesaver() + if ss is None: + ss = css + elif ss != css: + need_data_copied = 1 + else: + ss = 0 + if mask is None: + mask = data.mask() + elif mask is not None: #attempting to change the mask + need_data_copied = 1 + + elif isinstance(data, Numeric.ArrayType): + c = data + ctc = c.typecode() + if tc is None: + tc = ctc + elif tc != ctc: + need_data_copied = 1 + css = c.spacesaver() + if ss is None: + ss = css + elif ss != css: + need_data_copied = 1 + else: + need_data_copied = 0 #because I'll do it now + if ss is None: + ss = 0 + c = Numeric.array(data, tc, savespace=ss) + + if need_data_copied: + if tc == ctc: + self._data = Numeric.array(c, copy=1, savespace = ss) + else: + self._data = c.astype(tc) + self._data.savespace(ss) + else: + self._data = c + + if mask is None: + self._mask = None + self._shared_mask = 0 + else: + self._mask = make_mask (mask, flag=ss) + if self._mask is None: + self._shared_mask = 0 + else: + self._shared_mask = (self._mask is mask) + nm = size(self._mask) + nd = size(self._data) + if nm != nd: + if nm == 1: + self._mask = Numeric.resize(self._mask, self._data.shape) + self._shared_mask = 0 + elif nd == 1: + self._data = Numeric.resize(self._data, self._mask.shape) + self._data.shape = self._mask.shape + else: + raise MAError, "Mask and data not compatible." + elif nm == 1 and shape(self._mask) != shape(self._data): + self.unshare_mask() + self._mask.shape = self._data.shape + + self.set_fill_value(fill_value) + + def __array__ (self, t = None): + "Special hook for Numeric. Converts to Numeric if possible." + if self._mask is not None: + if Numeric.sometrue(Numeric.ravel(self._mask)): + raise MAError, \ + """Cannot automatically convert masked array to Numeric because data + is masked in one or more locations. + """ + else: # Mask is all false + # Optimize to avoid future invocations of this section. + self._mask = None + self._shared_mask = 0 + if t: + return self._data.astype(t) + else: + return self._data + + def _get_shape(self): + "Return the current shape." + return self._data.shape + + def _set_shape (self, newshape): + "Set the array's shape." + if not self._data.iscontiguous(): + self._data = Numeric.array(self._data, self._data.typecode(), + 1, self._data.spacesaver()) + self._data.shape = newshape + if self._mask is not None: + self.unshare_mask() + if not self._mask.iscontiguous(): + self._mask = Numeric.array(self._mask, MaskType, 1, 1) + self._mask.shape = newshape + + def _get_flat(self): + """Calculate the flat value. + """ + if self._mask is None: + return masked_array(self._data.flat, mask=None, + fill_value = self.fill_value()) + else: + return masked_array(self._data.flat, + mask=self._mask.flat, + fill_value = self.fill_value()) + + def _set_flat (self, value): + "x.flat = value" + y = self.flat + y[:] = value + + def _get_real(self): + "Get the real part of a complex array." + if self._mask is None: + return masked_array(self._data.real, mask=None, + fill_value = self.fill_value()) + else: + return masked_array(self._data.real, mask=self.mask().flat, + fill_value = self.fill_value()) + + def _set_real (self, value): + "x.real = value" + y = self.real + y[...] = value + + def _get_imaginary(self): + "Get the imaginary part of a complex array." + if self._mask is None: + return masked_array(self._data.imaginary, mask=None, + fill_value = self.fill_value()) + else: + return masked_array(self._data.imaginary, mask=self.mask().flat, + fill_value = self.fill_value()) + + def _set_imaginary (self, value): + "x.imaginary = value" + y = self.imaginary + y[...] = value + + def __str__(self): + """Calculate the str representation, using masked for fill if + it is enabled. Otherwise fill with fill value. + """ + if masked_print_option.enabled(): + f = masked_print_option + else: + f = self.fill_value() + return str(filled(self, f)) + + def __repr__(self): + """Calculate the repr representation, using masked for fill if + it is enabled. Otherwise fill with fill value. + """ + with_mask = """\ +array(data = + %(data)s, + mask = + %(mask)s, + fill_value=%(fill)s) +""" + with_mask1 = """\ +array(data = %(data)s, + mask = %(mask)s, + fill_value=%(fill)s) +""" + without_mask = """array( + %(data)s)""" + without_mask1 = """array(%(data)s)""" + + n = len(self.shape) + if self._mask is None: + if n <=1: + return without_mask1 % {'data':str(self.filled())} + return without_mask % {'data':str(self.filled())} + else: + if n <=1: + return with_mask % { + 'data': str(self.filled()), + 'mask': str(self.mask()), + 'fill': str(self.fill_value()) + } + return with_mask % { + 'data': str(self.filled()), + 'mask': str(self.mask()), + 'fill': str(self.fill_value()) + } + without_mask1 = """array(%(data)s)""" + if self._mask is None: + return without_mask % {'data':str(self.filled())} + else: + return with_mask % { + 'data': str(self.filled()), + 'mask': str(self.mask()), + 'fill': str(self.fill_value()) + } + + def __float__(self): + "Convert self to float." + self.unmask() + if self._mask is not None: + raise MAError, 'Cannot convert masked element to a Python float.' + return float(self.raw_data()[...]) + + def __int__(self): + "Convert self to int." + self.unmask() + if self._mask is not None: + raise MAError, 'Cannot convert masked element to a Python int.' + return int(self.raw_data()[...]) + +# Note copy semantics here differ from Numeric + def __getitem__(self, i): + "Get copy of item described by i." + m = self._mask + dout = self._data[i] + ss = self._data.spacesaver() + tc =self._data.typecode() + if type(dout) is Numeric.ArrayType: + if m is None: + result = array(dout, typecode=tc, copy = 1, savespace=ss) + else: + result = array(dout, typecode=tc, copy = 1, savespace=ss, + mask = m[i], fill_value=self.fill_value()) + return result + elif m is None or not m[i]: + return dout #scalar + else: #scalar but masked + return masked + + def __getslice__(self, i, j): + "Get copy of slice described by i, j" + m = self._mask + dout = self._data[i:j] + ss = self._data.spacesaver() + tc =self._data.typecode() + if m is None: + return array(dout, typecode=tc, copy = 1, savespace=ss) + else: + return array(dout, typecode=tc, copy = 1, savespace=ss, + mask = m[i:j], fill_value=self.fill_value()) + +# -------- +# setitem and setslice notes +# note that if value is masked, it means to mask those locations. +# setting a value changes the mask to match the value in those locations. + def __setitem__(self, index, value): + "Set item described by index. If value is masked, mask those locations." + if self is masked: + raise MAError, 'Cannot alter the masked element.' + if value is masked: + if self._mask is None: + self._mask = make_mask_none(self._data.shape) + self._shared_mask = 0 + else: + self.unshare_mask() + self._mask[index] = 1 + return + m = getmask(value) + value = filled(value).astype(self._data.typecode()) + self._data[index] = value + if m is None: + if self._mask is not None: + self.unshare_mask() + self._mask[index] = 0 + else: + if self._mask is None: + self._mask = make_mask_none(self._data.shape) + self._shared_mask = 0 + else: + self.unshare_mask() + self._mask[index] = m + + def __setslice__(self, i, j, value): + "Set slice i:j; if value is masked, mask those locations." + if self is masked: + raise MAError, 'Cannot alter the masked element.' + if value is masked: + if self._mask is None: + self._mask = make_mask_none(self._data.shape) + self._shared_mask = 0 + self._mask[i:j] = 1 + return + m = getmask(value) + value = filled(value).astype(self._data.typecode()) + self._data[i:j] = value + if m is None: + if self._mask is not None: + self.unshare_mask() + self._mask[i:j] = 0 + else: + if self._mask is None: + self._mask = make_mask_none(self._data.shape) + self._shared_mask = 0 + self._mask[i:j] = m + + def __len__ (self): + """Return length of first dimension. This is weird but Python's + slicing behavior depends on it.""" + return len(self._data) + + def __and__(self, other): + "Return bitwise_and" + return bitwise_and(self, other) + + def __or__(self, other): + "Return bitwise_or" + return bitwise_or(self, other) + + def __xor__(self, other): + "Return bitwise_xor" + return bitwise_xor(self, other) + + __rand__ = __and__ + __ror__ = __or__ + __rxor__ = __xor__ + + def __abs__(self): + "Return absolute(self)" + return absolute(self) + + def __neg__(self): + "Return negative(self)" + return negative(self) + + def __pos__(self): + "Return array(self)" + return array(self) + + def __add__(self, other): + "Return add(self, other)" + return add(self, other) + + __radd__ = __add__ + + def __mod__ (self, other): + "Return remainder(self, other)" + return remainder(self, other) + + def __rmod__ (self, other): + "Return remainder(other, self)" + return remainder(other, self) + + def __lshift__ (self, n): + return left_shift(self, n) + + def __rshift__ (self, n): + return right_shift(self, n) + + def __sub__(self, other): + "Return subtract(self, other)" + return subtract(self, other) + + def __rsub__(self, other): + "Return subtract(other, self)" + return subtract(other, self) + + def __mul__(self, other): + "Return multiply(self, other)" + return multiply(self, other) + + __rmul__ = __mul__ + + def __div__(self, other): + "Return divide(self, other)" + return divide(self, other) + + def __rdiv__(self, other): + "Return divide(other, self)" + return divide(other, self) + + def __truediv__(self, other): + "Return divide(self, other)" + return true_divide(self, other) + + def __rtruediv__(self, other): + "Return divide(other, self)" + return true_divide(other, self) + + def __floordiv__(self, other): + "Return divide(self, other)" + return floor_divide(self, other) + + def __rfloordiv__(self, other): + "Return divide(other, self)" + return floor_divide(other, self) + + def __pow__(self,other, third=None): + "Return power(self, other, third)" + return power(self, other, third) + + def __sqrt__(self): + "Return sqrt(self)" + return sqrt(self) + + def __iadd__(self, other): + "Add other to self in place." + t = self._data.typecode() + f = filled(other,0) + t1 = f.typecode() + if t == t1: + pass + elif t in typecodes['Integer']: + if t1 in typecodes['Integer']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + elif t in typecodes['Float']: + if t1 in typecodes['Integer']: + f = f.astype(t) + elif t1 in typecodes['Float']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + elif t in typecodes['Complex']: + if t1 in typecodes['Integer']: + f = f.astype(t) + elif t1 in typecodes['Float']: + f = f.astype(t) + elif t1 in typecodes['Complex']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + else: + raise TypeError, 'Incorrect type for in-place operation.' + + if self._mask is None: + self._data += f + m = getmask(other) + self._mask = m + self._shared_mask = m is not None + else: + result = add(self, masked_array(f, mask=getmask(other))) + self._data = result.raw_data() + self._mask = result.raw_mask() + self._shared_mask = 1 + return self + + def __imul__(self, other): + "Add other to self in place." + t = self._data.typecode() + f = filled(other,0) + t1 = f.typecode() + if t == t1: + pass + elif t in typecodes['Integer']: + if t1 in typecodes['Integer']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + elif t in typecodes['Float']: + if t1 in typecodes['Integer']: + f = f.astype(t) + elif t1 in typecodes['Float']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + elif t in typecodes['Complex']: + if t1 in typecodes['Integer']: + f = f.astype(t) + elif t1 in typecodes['Float']: + f = f.astype(t) + elif t1 in typecodes['Complex']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + else: + raise TypeError, 'Incorrect type for in-place operation.' + + if self._mask is None: + self._data *= f + m = getmask(other) + self._mask = m + self._shared_mask = m is not None + else: + result = multiply(self, masked_array(f, mask=getmask(other))) + self._data = result.raw_data() + self._mask = result.raw_mask() + self._shared_mask = 1 + return self + + def __isub__(self, other): + "Subtract other from self in place." + t = self._data.typecode() + f = filled(other,0) + t1 = f.typecode() + if t == t1: + pass + elif t in typecodes['Integer']: + if t1 in typecodes['Integer']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + elif t in typecodes['Float']: + if t1 in typecodes['Integer']: + f = f.astype(t) + elif t1 in typecodes['Float']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + elif t in typecodes['Complex']: + if t1 in typecodes['Integer']: + f = f.astype(t) + elif t1 in typecodes['Float']: + f = f.astype(t) + elif t1 in typecodes['Complex']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + else: + raise TypeError, 'Incorrect type for in-place operation.' + + if self._mask is None: + self._data -= f + m = getmask(other) + self._mask = m + self._shared_mask = m is not None + else: + result = subtract(self, masked_array(f, mask=getmask(other))) + self._data = result.raw_data() + self._mask = result.raw_mask() + self._shared_mask = 1 + return self + + + + def __idiv__(self, other): + "Divide self by other in place." + t = self._data.typecode() + f = filled(other,0) + t1 = f.typecode() + if t == t1: + pass + elif t in typecodes['Integer']: + if t1 in typecodes['Integer']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + elif t in typecodes['Float']: + if t1 in typecodes['Integer']: + f = f.astype(t) + elif t1 in typecodes['Float']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + elif t in typecodes['Complex']: + if t1 in typecodes['Integer']: + f = f.astype(t) + elif t1 in typecodes['Float']: + f = f.astype(t) + elif t1 in typecodes['Complex']: + f = f.astype(t) + else: + raise TypeError, 'Incorrect type for in-place operation.' + else: + raise TypeError, 'Incorrect type for in-place operation.' + mo = getmask(other) + result = divide(self, masked_array(f, mask=mo)) + self._data = result.raw_data() + dm = result.raw_mask() + if dm is not self._mask: + self._mask = dm + self._shared_mask = 1 + return self + + def __eq__(self,other): + return equal(self,other) + + def __ne__(self,other): + return not_equal(self,other) + + def __lt__(self,other): + return less(self,other) + + def __le__(self,other): + return less_equal(self,other) + + def __gt__(self,other): + return greater(self,other) + + def __ge__(self,other): + return greater_equal(self,other) + + def astype (self, tc): + "return self as array of given type." + d = self._data.astype(tc) + d.savespace(self._data.spacesaver()) + return array(d, mask=self._mask) + + def byte_swapped(self): + """Returns the raw data field, byte_swapped. Included for consistency + with Numeric but doesn't make sense in this context. + """ + return self._data.byte_swapped() + + def compressed (self): + "A 1-D array of all the non-masked data." + d = Numeric.ravel(self._data) + if self._mask is None: + return array(d) + else: + m = 1 - Numeric.ravel(self._mask) + c = Numeric.compress(m, d) + return array(c, copy=0) + + def count (self, axis = None): + "Count of the non-masked elements in a, or along a certain axis." + m = self._mask + s = self._data.shape + ls = len(s) + if m is None: + if ls == 0: + return 1 + if ls == 1: + return s[0] + if axis is None: + return reduce(lambda x,y:x*y, s) + else: + n = s[axis] + t = list(s) + del t[axis] + return ones(t) * n + if axis is None: + w = Numeric.ravel(m).astype(Int) #avoid savespace truncation + n1 = size(w) + if n1 == 1: + n2 = w[0] + else: + n2 = Numeric.add.reduce(w) + return n1 - n2 + else: + n1 = size(m, axis) + n2 = sum(m.astype(Int), axis) + return n1 - n2 + + def dot (self, other): + "s.dot(other) = innerproduct(s, other)" + return innerproduct(self, other) + + def fill_value(self): + "Get the current fill value." + return self._fill_value + + def filled (self, fill_value=None): + """A Numeric array with masked values filled. If fill_value is None, + use self.fill_value(). + + If mask is None, copy data only if not contiguous. + Result is always a contiguous, Numeric array. + """ + d = self._data + m = self._mask + if m is None: + if d.iscontiguous(): + return d + else: + return Numeric.array(d, typecode=d.typecode(), copy=1, + savespace = d.spacesaver()) + value = fill_value + if value is None: + value = self._fill_value + if self is masked: + result = Numeric.array(value) + result.shape = d.shape + else: + try: + result = Numeric.array(d, typecode=d.typecode(), copy=1, + savespace = d.spacesaver()) + Numeric.putmask(result, m, value) + except: + result = Numeric.choose(m, (d, value)) + return result + + def ids (self): + """Return the ids of the data and mask areas""" + return (id(self._data), id(self._mask)) + + def iscontiguous (self): + "Is the data contiguous?" + return self._data.iscontiguous() + + def itemsize(self): + "Item size of each data item." + return self._data.itemsize() + + def mask(self): + "Return the data mask, or None. Result contiguous." + m = self._mask + if m is None: + return m + elif m.iscontiguous(): + return m + else: + return Numeric.array(self._mask) + + def outer(self, other): + "s.outer(other) = outerproduct(s, other)" + return outerproduct(self, other) + + def put (self, values): + """Set the non-masked entries of self to filled(values). + No change to mask + """ + iota = Numeric.arange(self.size()) + if self._mask is None: + ind = iota + else: + ind = Numeric.compress(1 - self._mask, iota) + if len(ind) != size(values): + raise MAError, "x.put(values) incorrect count of values." + Numeric.put (self._data, ind, filled(values)) + + def putmask (self, values): + """Set the masked entries of self to filled(values). + Mask changed to None. + """ + if self._mask is not None: + iota = Numeric.arange(self.size()) + ind = Numeric.compress(self._mask, iota) + if len(ind) != size(values): + raise MAError, "x.put(values) incorrect count of values." + Numeric.put (self._data, ind, filled(values)) + self._mask = None + self._shared_mask = 0 + + def raw_data (self): + """ The raw data; portions may be meaningless. + May be noncontiguous. Expert use only.""" + return self._data + + def raw_mask (self): + """ The raw mask; portions may be meaningless. + May be noncontiguous. Expert use only. + """ + return self._mask + + def spacesaver (self): + "Get the spacesaver attribute." + return self._data.spacesaver() + + def savespace (self, value): + "Set the spacesaver attribute to value" + self._data.savespace(value) + + def set_fill_value (self, v=None): + "Set the fill value to v. Omit v to restore default." + if v is None: + v = default_fill_value (self.raw_data()) + self._fill_value = v + + def size (self, axis = None): + "Number of elements in array, or in a particular axis." + s = self._data.shape + if axis is None: + if len(s) == 0: + return 1 + else: + return reduce(lambda x,y: x*y, s) + else: + return s[axis] + + def spacesaver (self): + "spacesaver() queries the spacesaver attribute." + return self._data.spacesaver() + + def typecode(self): + return self._data.typecode() + + def tolist(self, fill_value=None): + "Convert to list" + return self.filled(fill_value).tolist() + + def tostring(self, fill_value=None): + "Convert to string" + return self.filled(fill_value).tostring() + + def unmask (self): + "Replace the mask by None if possible." + if self._mask is None: return + m = make_mask(self._mask, flag=1) + if m is None: + self._mask = None + self._shared_mask = 0 + + def unshare_mask (self): + "If currently sharing mask, make a copy." + if self._shared_mask: + self._mask = make_mask (self._mask, copy=1, flag=0) + self._shared_mask = 0 + + shape = property(_get_shape, _set_shape, + doc = 'tuple giving the shape of the array') + + flat = property(_get_flat, _set_flat, + doc = 'Access array in flat form.') + + real = property(_get_real, _set_real, + doc = 'Access the real part of the array') + + imaginary = property(_get_imaginary, _set_imaginary, + doc = 'Access the imaginary part of the array') + + imag = imaginary + +array = MaskedArray + +class MaskedScalar (MaskedArray): + def __init__ (self): + MaskedArray.__init__ (self, [0], mask=[1]) + self._data.shape = () + self._mask.shape = () + + shape = property(MaskedArray._get_shape) + +masked = MaskedScalar() + +def isMaskedArray (x): + "Is x a masked array, that is, an instance of MaskedArray?" + return isinstance(x, MaskedArray) + +isarray = isMaskedArray +isMA = isMaskedArray #backward compatibility + +def allclose (a, b, fill_value=1, rtol=1.e-5, atol=1.e-8): + """ Returns true if all components of a and b are equal + subject to given tolerances. + If fill_value is 1, masked values considered equal. + If fill_value is 0, masked values considered unequal. + The relative error rtol should be positive and << 1.0 + The absolute error atol comes into play for those elements + of b that are very small or zero; it says how small a must be also. + """ + m = mask_or(getmask(a), getmask(b)) + d1 = filled(a) + d2 = filled(b) + x = filled(array(d1, copy=0, mask=m), fill_value).astype(Float) + y = filled(array(d2, copy=0, mask=m), 1).astype(Float) + d = Numeric.less_equal(Numeric.absolute(x-y), atol + rtol * Numeric.absolute(y)) + return Numeric.alltrue(Numeric.ravel(d)) + +def allequal (a, b, fill_value=1): + """ + True if all entries of a and b are equal, using + fill_value as a truth value where either or both are masked. + """ + m = mask_or(getmask(a), getmask(b)) + if m is None: + x = filled(a) + y = filled(b) + d = Numeric.equal(x, y) + return Numeric.alltrue(Numeric.ravel(d)) + elif fill_value: + x = filled(a) + y = filled(b) + d = Numeric.equal(x, y) + dm = array(d, mask=m, copy=0) + return Numeric.alltrue(Numeric.ravel(filled(dm, 1))) + else: + return 0 + +def masked_values (data, value, rtol=1.e-5, atol=1.e-8, copy=1, + savespace=0): + """ + masked_values(data, value, rtol=1.e-5, atol=1.e-8) + Create a masked array; mask is None if possible. + If copy==0, and otherwise possible, result + may share data values with original array. + Let d = filled(data, value). Returns d + masked where abs(data-value)<= atol + rtol * abs(value) + if d is of a floating point type. Otherwise returns + masked_object(d, value, copy, savespace) + """ + abs = Numeric.absolute + d = filled(data, value) + if d.typecode() in typecodes['Float']: + m = Numeric.less_equal(abs(d-value), atol+rtol*abs(value)) + m = make_mask(m, flag=1) + return array(d, mask = m, savespace=savespace, copy=copy, + fill_value=value) + else: + return masked_object(d, value, copy, savespace) + +def masked_object (data, value, copy=1, savespace=0): + "Create array masked where exactly data equal to value" + d = filled(data, value) + dm = make_mask(Numeric.equal(d, value), flag=1) + return array(d, mask=dm, copy=copy, savespace=savespace, + fill_value=value) + +def arrayrange(start, stop=None, step=1, typecode=None): + """Just like range() except it returns a array whose type can be specified + by the keyword argument typecode. + """ + return array(Numeric.arrayrange(start, stop, step, typecode)) + +arange = arrayrange + +def fromstring (s, t): + "Construct a masked array from a string. Result will have no mask." + return masked_array(Numeric.fromstring(s, t)) + +def left_shift (a, n): + "Left shift n bits" + m = getmask(a) + if m is None: + d = Numeric.left_shift(filled(a), n) + return masked_array(d) + else: + d = Numeric.left_shift(filled(a,0), n) + return masked_array(d, m) + +def right_shift (a, n): + "Right shift n bits" + m = getmask(a) + if m is None: + d = Numeric.right_shift(filled(a), n) + return masked_array(d) + else: + d = Numeric.right_shift(filled(a,0), n) + return masked_array(d, m) + +def resize (a, new_shape): + """resize(a, new_shape) returns a new array with the specified shape. + The original array's total size can be any size.""" + m = getmask(a) + if m is not None: + m = Numeric.resize(m, new_shape) + result = array(Numeric.resize(filled(a), new_shape), mask=m) + result.set_fill_value(fill_value(a)) + return result + +def repeat(a, repeats, axis=0): + """repeat elements of a repeats times along axis + repeats is a sequence of length a.shape[axis] + telling how many times to repeat each element. + """ + af = filled(a) + if isinstance(repeats, types.IntType): + repeats = tuple([repeats]*(shape(af)[axis])) + + m = getmask(a) + if m is not None: + m = Numeric.repeat(m, repeats, axis) + d = Numeric.repeat(af, repeats, axis) + result = masked_array(d, m) + result.set_fill_value(fill_value(a)) + return result + +def identity(n): + """identity(n) returns the identity matrix of shape n x n. + """ + return array(Numeric.identity(n)) + +def indices (dimensions, typecode=None): + """indices(dimensions,typecode=None) returns an array representing a grid + of indices with row-only, and column-only variation. + """ + return array(Numeric.indices(dimensions, typecode)) + +def zeros (shape, typecode=Int, savespace=0): + """zeros(n, typecode=Int, savespace=0) = + an array of all zeros of the given length or shape.""" + return array(Numeric.zeros(shape, typecode, savespace)) + +def ones (shape, typecode=Int, savespace=0): + """ones(n, typecode=Int, savespace=0) = + an array of all ones of the given length or shape.""" + return array(Numeric.ones(shape, typecode, savespace)) + + +def count (a, axis = None): + "Count of the non-masked elements in a, or along a certain axis." + a = masked_array(a) + return a.count(axis) + +def power (a, b, third=None): + "a**b" + if third is not None: + raise MAError, "3-argument power not supported." + ma = getmask(a) + mb = getmask(b) + m = mask_or(ma, mb) + fa = filled(a, 1) + fb = filled(b, 1) + if fb.typecode() in typecodes["Integer"]: + return masked_array(Numeric.power(fa, fb), m) + md = make_mask(Numeric.less_equal (fa, 0), flag=1) + m = mask_or(m, md) + if m is None: + return masked_array(Numeric.power(fa, fb)) + else: + fa = Numeric.where(m, 1, fa) + return masked_array(Numeric.power(fa, fb), m) + +def masked_array (a, mask=None, fill_value=None): + """masked_array(a, mask=None) = + array(a, mask=mask, copy=0, fill_value=fill_value) + Use fill_value(a) if None. + """ +# +# This is an unfortunate copy of what is in fill_value +# but I want the name fill_value as a parameter. +# + if fill_value is None: + if isMaskedArray(a): + fill_value = a.fill_value() + else: + fill_value = default_fill_value(a) + return array(a, mask=mask, copy=0, fill_value=fill_value) + +sum = add.reduce +product = multiply.reduce + +def average (a, axis=0, weights=None, returned = 0): + """average(a, axis=0, weights=None) + Computes average along indicated axis. + If axis is None, average over the entire array + Inputs can be integer or floating types; result is of type Float. + + If weights are given, result is sum(a*weights)/(sum(weights)*1.0) + weights must have a's shape or be the 1-d with length the size + of a in the given axis. + + If returned, return a tuple: the result and the sum of the weights + or count of values. Results will have the same shape. + + masked values in the weights will be set to 0.0 + """ + a = masked_array(a) + mask = a.mask() + ash = a.shape + if ash == (): + ash = (1,) + if axis is None: + if mask is None: + if weights is None: + n = add.reduce(a.raw_data().flat) + d = reduce(lambda x, y: x * y, ash, 1.0) + else: + w = filled(weights, 0.0).flat + n = Numeric.add.reduce(a.raw_data().flat * w) + d = Numeric.add.reduce(w) + del w + else: + if weights is None: + n = add.reduce(a.flat) + w = Numeric.choose(mask, (1.0,0.0)).flat + d = Numeric.add.reduce(w) + del w + else: + w = array(filled(weights, 0.0), Numeric.Float, mask=mask).flat + n = add.reduce(a.flat * w) + d = add.reduce(w) + del w + else: + if mask is None: + if weights is None: + d = ash[axis] * 1.0 + n = Numeric.add.reduce(a.raw_data(), axis) + else: + w = filled(weights, 0.0) + wsh = w.shape + if wsh == (): + wsh = (1,) + if wsh == ash: + w = Numeric.array(w, Float, copy=0) + n = add.reduce(a*w, axis) + d = add.reduce(w, axis) + del w + elif wsh == (ash[axis],): + ni = ash[axis] + r = [NewAxis]*len(ash) + r[axis] = slice(None,None,1) + w = eval ("w["+ repr(tuple(r)) + "] * ones(ash, Float)") + n = add.reduce(a*w, axis) + d = add.reduce(w, axis) + del w, r + else: + raise ValueError, 'average: weights wrong shape.' + else: + if weights is None: + n = add.reduce(a, axis) + w = Numeric.choose(mask, (1.0, 0.0)) + d = Numeric.add.reduce(w, axis) + del w + else: + w = filled(weights, 0.0) + wsh = w.shape + if wsh == (): + wsh = (1,) + if wsh == ash: + w = array(w, Float, mask=mask, copy=0) + n = add.reduce(a*w, axis) + d = add.reduce(w, axis) + elif wsh == (ash[axis],): + ni = ash[axis] + r = [NewAxis]*len(ash) + r[axis] = slice(None,None,1) + w = eval ("w["+ repr(tuple(r)) + "] * masked_array(ones(ash, Float), mask)") + n = add.reduce(a*w, axis) + d = add.reduce(w, axis) + else: + raise ValueError, 'average: weights wrong shape.' + del w + # print n, d, repr(mask), repr(weights) + result = divide (n, d) + del n + + if isinstance(result, MaskedArray): + result.unmask() + if returned: + if not isinstance(d, MaskedArray): + d = masked_array(d) + if not d.shape == result.shape: + d = ones(result.shape, Float) * d + d.unmask() + if returned: + return result, d + else: + return result + +def where (condition, x, y): + """where(condition, x, y) is x where condition is nonzero, y otherwise. + condition must be convertible to an integer array. + Answer is always the shape of condition. + The type depends on x and y. It is integer if both x and y are + the value masked. + """ + fc = filled(not_equal(condition,0), 0) + if x is masked: + xv = 0 + xm = 1 + else: + xv = filled(x) + xm = getmask(x) + if xm is None: xm = 0 + if y is masked: + yv = 0 + ym = 1 + else: + yv = filled(y) + ym = getmask(y) + if ym is None: ym = 0 + d = Numeric.choose(fc, (yv, xv)) + md = Numeric.choose(fc, (ym, xm)) + m = getmask(condition) + m = make_mask(mask_or(m, md), copy=0, flag=1) + return masked_array(d, m) + +def choose (indices, t): + "Returns array shaped like indices with elements chosen from t" + def fmask (x): + if x is masked: return 1 + return filled(x) + def nmask (x): + if x is masked: return 1 + m = getmask(x) + if m is None: return 0 + return m + c = filled(indices,0) + masks = [nmask(x) for x in t] + a = [fmask(x) for x in t] + d = Numeric.choose(c, a) + m = Numeric.choose(c, masks) + m = make_mask(mask_or(m, getmask(indices)), copy=0, flag=1) + return masked_array(d, m) + +def masked_where(condition, x, copy=1): + """Return x as an array masked where condition is true. + Also masked where x or condition masked. + """ + cm = filled(condition,1) + m = mask_or(getmask(x), cm) + return array(filled(x), copy=copy, mask=m) + +def masked_greater(x, value, copy=1): + "masked_greater(x, value) = x masked where x > value" + return masked_where(greater(x, value), x, copy) + +def masked_greater_equal(x, value, copy=1): + "masked_greater_equal(x, value) = x masked where x >= value" + return masked_where(greater_equal(x, value), x, copy) + +def masked_less(x, value, copy=1): + "masked_less(x, value) = x masked where x < value" + return masked_where(less(x, value), x, copy) + +def masked_less_equal(x, value, copy=1): + "masked_less_equal(x, value) = x masked where x <= value" + return masked_where(less_equal(x, value), x, copy) + +def masked_not_equal(x, value, copy=1): + "masked_not_equal(x, value) = x masked where x != value" + d = filled(x,0) + c = Numeric.not_equal(d, value) + m = mask_or(c, getmask(x)) + return array(d, mask=m, copy=copy) + +def masked_equal(x, value, copy=1): + """masked_equal(x, value) = x masked where x == value + For floating point consider masked_values(x, value) instead. + """ + d = filled(x,0) + c = Numeric.equal(d, value) + m = mask_or(c, getmask(x)) + return array(d, mask=m, copy=copy) + +def masked_inside(x, v1, v2, copy=1): + """x with mask of all values of x that are inside [v1,v2] + v1 and v2 can be given in either order. + """ + if v2 < v1: + t = v2 + v2 = v1 + v1 = t + d=filled(x, 0) + c = Numeric.logical_and(Numeric.less_equal(d, v2), Numeric.greater_equal(d, v1)) + m = mask_or(c, getmask(x)) + return array(d, mask = m, copy=copy) + +def masked_outside(x, v1, v2, copy=1): + """x with mask of all values of x that are outside [v1,v2] + v1 and v2 can be given in either order. + """ + if v2 < v1: + t = v2 + v2 = v1 + v1 = t + d = filled(x,0) + c = Numeric.logical_or(Numeric.less(d, v1), Numeric.greater(d, v2)) + m = mask_or(c, getmask(x)) + return array(d, mask = m, copy=copy) + +def reshape (a, newshape): + "Copy of a with a new shape." + m = getmask(a) + d = Numeric.reshape(filled(a), newshape) + if m is None: + return masked_array(d) + else: + return masked_array(d, mask=Numeric.reshape(m, newshape)) + +def ravel (a): + "a as one-dimensional, may share data and mask" + m = getmask(a) + d = Numeric.ravel(filled(a)) + if m is None: + return masked_array(d) + else: + return masked_array(d, mask=Numeric.ravel(m)) + +def concatenate (arrays, axis=0): + "Concatenate the arrays along the given axis" + d = [] + for x in arrays: + d.append(filled(x)) + d = Numeric.concatenate(d, axis) + for x in arrays: + if getmask(x) is not None: break + else: + return masked_array(d) + dm = [] + for x in arrays: + dm.append(getmaskarray(x)) + dm = Numeric.concatenate(dm, axis) + return masked_array(d, mask=dm) + +def take (a, indices, axis=0): + "take(a, indices, axis=0) returns selection of items from a." + m = getmask(a) + d = masked_array(a).raw_data() + if m is None: + return masked_array(Numeric.take(d, indices, axis)) + else: + return masked_array(Numeric.take(d, indices, axis), + mask = Numeric.take(m, indices, axis)) + +def transpose(a, axes=None): + "transpose(a, axes=None) reorder dimensions per tuple axes" + m = getmask(a) + d = filled(a) + if m is None: + return masked_array(Numeric.transpose(d, axes)) + else: + return masked_array(Numeric.transpose(d, axes), + mask = Numeric.transpose(m, axes)) + +def put (a, indices, values): + "put(a, indices, values) sets storage-indexed locations to corresponding values. values and indices are filled if necessary." + d = a.raw_data() + ind = filled(indices) + v = filled(values) + Numeric.put (d, ind, v) + m = getmask(a) + if m is not None: + a.unshare_mask() + Numeric.put(a.raw_mask(), ind, 0) + +def putmask (a, mask, values): + "put (a, mask, values) sets a where mask is true." + if mask is None: + return + Numeric.putmask(a.raw_data(), mask, values) + m = getmask(a) + if m is None: return + a.unshare_mask() + Numeric.putmask(a.raw_mask(), mask, 0) + +def innerproduct(a,b): + """innerproduct(a,b) returns the dot product of two arrays, which has + shape a.shape[:-1] + b.shape[:-1] with elements computed by summing the + product of the elements from the last dimensions of a and b. + Masked elements are replace by zeros. + """ + fa = filled(a, 0) + fb = filled(b, 0) + if len(fa.shape) == 0: fa.shape = (1,) + if len(fb.shape) == 0: fb.shape = (1,) + return masked_array(Numeric.innerproduct(fa, fb)) + +def outerproduct(a, b): + """outerproduct(a,b) = {a[i]*b[j]}, has shape (len(a),len(b))""" + fa = filled(a,0).flat + fb = filled(b,0).flat + d = Numeric.outerproduct(fa, fb) + ma = getmask(a) + mb = getmask(b) + if ma is None and mb is None: + return masked_array(d) + ma = getmaskarray(a) + mb = getmaskarray(b) + m = make_mask(1-Numeric.outerproduct(1-ma,1-mb), copy=0) + return masked_array(d, m) + +def dot(a, b): + """dot(a,b) returns matrix-multiplication between a and b. The product-sum + is over the last dimension of a and the second-to-last dimension of b. + Masked values are replaced by zeros. See also innerproduct. + """ + return innerproduct(filled(a,0), Numeric.swapaxes(filled(b,0), -1, -2)) + +def compress(condition, x, dimension=-1): + """Select those parts of x for which condition is true. + Masked values in condition are considered false. + """ + c = filled(condition, 0) + m = getmask(x) + if m is not None: + m=Numeric.compress(c, m, dimension) + d = Numeric.compress(c, filled(x), dimension) + return masked_array(d, m) + +class _minimum_operation: + "Object to calculate minima" + def __init__ (self): + """minimum(a, b) or minimum(a) + In one argument case returns the scalar minimum. + """ + pass + + def __call__ (self, a, b=None): + "Execute the call behavior." + if b is None: + m = getmask(a) + if m is None: + d = min(filled(a).flat) + return d + ac = a.compressed() + if len(ac) == 0: + return masked + else: + return min(ac.raw_data()) + else: + return where(less(a, b), a, b)[...] + + def reduce (self, target, axis=0): + """Reduce target along the given axis.""" + m = getmask(target) + if m is None: + t = filled(target) + return masked_array (Numeric.minimum.reduce (t, axis)) + else: + t = Numeric.minimum.reduce(filled(target, minimum_fill_value(target)), axis) + m = Numeric.logical_and.reduce(m, axis) + return masked_array(t, m, fill_value(target)) + + def outer (self, a, b): + "Return the function applied to the outer product of a and b." + ma = getmask(a) + mb = getmask(b) + if ma is None and mb is None: + m = None + else: + ma = getmaskarray(a) + mb = getmaskarray(b) + m = logical_or.outer(ma, mb) + d = Numeric.minimum.outer(filled(a), filled(b)) + return masked_array(d, m) + +minimum = _minimum_operation () + +class _maximum_operation: + "Object to calculate maxima" + def __init__ (self): + """maximum(a, b) or maximum(a) + In one argument case returns the scalar maximum. + """ + pass + + def __call__ (self, a, b=None): + "Execute the call behavior." + if b is None: + m = getmask(a) + if m is None: + d = max(filled(a).flat) + return d + ac = a.compressed() + if len(ac) == 0: + return masked + else: + return max(ac.raw_data()) + else: + return where(greater(a, b), a, b)[...] + + def reduce (self, target, axis=0): + """Reduce target along the given axis.""" + m = getmask(target) + if m is None: + t = filled(target) + return masked_array (Numeric.maximum.reduce (t, axis)) + else: + t = Numeric.maximum.reduce(filled(target, maximum_fill_value(target)), axis) + m = Numeric.logical_and.reduce(m, axis) + return masked_array(t, m, fill_value(target)) + + def outer (self, a, b): + "Return the function applied to the outer product of a and b." + ma = getmask(a) + mb = getmask(b) + if ma is None and mb is None: + m = None + else: + ma = getmaskarray(a) + mb = getmaskarray(b) + m = logical_or.outer(ma, mb) + d = Numeric.maximum.outer(filled(a), filled(b)) + return masked_array(d, m) + +maximum = _maximum_operation () + +def sort (x, axis = -1, fill_value=None): + """If x does not have a mask, return a masked array formed from the + result of Numeric.sort(x, axis). + Otherwise, fill x with fill_value. Sort it. + Set a mask where the result is equal to fill_value. + Note that this may have unintended consequences if the data contains the + fill value at a non-masked site. + + If fill_value is not given the default fill value for x's type will be + used. + """ + if fill_value is None: + fill_value = default_fill_value (x) + d = filled(x, fill_value) + s = Numeric.sort(d, axis) + if getmask(x) is None: + return masked_array(s) + return masked_values(s, fill_value, copy=0) + +def diagonal(a, k = 0, axis1=0, axis2=1): + """diagonal(a,k=0,axis1=0, axis2=1) = the k'th diagonal of a""" + d = Numeric.diagonal(filled(a), k, axis1, axis2) + m = getmask(a) + if m is None: + return masked_array(d, m) + else: + return masked_array(d, Numeric.diagonal(m, k, axis1, axis2)) + +def argsort (x, axis = -1, fill_value=None): + """Treating masked values as if they have the value fill_value, + return sort indices for sorting along given axis. + if fill_value is None, use fill_value(x) + Returns a Numeric array. + """ + d = filled(x, fill_value) + return Numeric.argsort(d, axis) + +def argmin (x, axis = -1, fill_value=None): + """Treating masked values as if they have the value fill_value, + return indices for minimum values along given axis. + if fill_value is None, use fill_value(x). + Returns a Numeric array if x has more than one dimension. + Otherwise, returns a scalar index. + """ + d = filled(x, fill_value) + return Numeric.argmin(d, axis) + +def argmax (x, axis = -1, fill_value=None): + """Treating masked values as if they have the value fill_value, + return sort indices for maximum along given axis. + if fill_value is None, use -fill_value(x) if it exists. + Returns a Numeric array if x has more than one dimension. + Otherwise, returns a scalar index. + """ + if fill_value is None: + fill_value = default_fill_value (x) + try: + fill_value = - fill_value + except: + pass + d = filled(x, fill_value) + return Numeric.argmax(d, axis) + +def fromfunction (f, s): + """apply f to s to create array as in Numeric.""" + return masked_array(Numeric.fromfunction(f,s)) + +def asarray(data, typecode=None): + """asarray(data, typecode=None) = array(data, typecode=None, copy=0) + Returns data if typecode if data is a MaskedArray and typecode None + or the same. + """ + if isinstance(data, MaskedArray) and \ + (typecode is None or typecode == data.typecode()): + return data + return array(data, typecode=typecode, copy=0) + +# This section is stolen from a post about how to limit array printing. +__MaxElements = 300 #Maximum size for printing + +def limitedArrayRepr(a, max_line_width = None, precision = None, suppress_small = None): + "Calculate string representation, limiting size of output." + global __MaxElements + s = a.shape + elems = Numeric.multiply.reduce(s) + if elems > __MaxElements: + if len(s) > 1: + return 'array (%s) , type = %s, has %d elements' % \ + (string.join(map(str, s), ","), a.typecode(), elems) + else: + return Numeric.array2string (a[:__MaxElements], max_line_width, precision, + suppress_small,',',0) + \ + ('\n + %d more elements' % (elems - __MaxElements)) + else: + return Numeric.array2string (a, max_line_width, precision, + suppress_small,',',0) + +__original_str = Numeric.array_str +__original_repr = Numeric.array_repr + +def set_print_limit (m=0): + "Set the maximum # of elements for printing arrays. <=0 = no limit" + import multiarray + global __MaxElements + n = int(m) + __MaxElements = n + if n <= 0: + Numeric.array_str = __original_str + Numeric.array_repr = __original_repr + multiarray.set_string_function(__original_str, 0) + multiarray.set_string_function(__original_repr, 1) + else: + Numeric.array_str = limitedArrayRepr + Numeric.array_repr = limitedArrayRepr + multiarray.set_string_function(limitedArrayRepr, 0) + multiarray.set_string_function(limitedArrayRepr, 1) + +def get_print_limit (): + "Get the maximum # of elements for printing arrays. " + return __MaxElements + +set_print_limit(__MaxElements) |