diff options
Diffstat (limited to 'numpy/lib/function_base.py')
-rw-r--r-- | numpy/lib/function_base.py | 335 |
1 files changed, 245 insertions, 90 deletions
diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index 65f4ecb05..d782f454a 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -1,3 +1,5 @@ +from __future__ import division, absolute_import, print_function + __docformat__ = "restructuredtext en" __all__ = ['select', 'piecewise', 'trim_zeros', 'copy', 'iterable', 'percentile', 'diff', 'gradient', 'angle', 'unwrap', 'sort_complex', @@ -24,13 +26,17 @@ from numpy.core.fromnumeric import ravel, nonzero, choose, sort, mean from numpy.core.numerictypes import typecodes, number from numpy.core import atleast_1d, atleast_2d from numpy.lib.twodim_base import diag -from _compiled_base import _insert, add_docstring -from _compiled_base import digitize, bincount, interp as compiled_interp -from arraysetops import setdiff1d -from utils import deprecate -from _compiled_base import add_newdoc_ufunc +from ._compiled_base import _insert, add_docstring +from ._compiled_base import digitize, bincount, interp as compiled_interp +from .utils import deprecate +from ._compiled_base import add_newdoc_ufunc import numpy as np +import collections +from numpy.compat import long +# Force range to be a generator, for np.delete's usage. +if sys.version_info[0] < 3: + range = xrange def iterable(y): """ @@ -707,7 +713,7 @@ def piecewise(x, condlist, funclist, *args, **kw): y = zeros(x.shape, x.dtype) for k in range(n): item = funclist[k] - if not callable(item): + if not isinstance(item, collections.Callable): y[condlist[k]] = item else: vals = x[condlist[k]] @@ -1267,8 +1273,7 @@ def unique(x): idx = concatenate(([True],tmp[1:]!=tmp[:-1])) return tmp[idx] except AttributeError: - items = list(set(x)) - items.sort() + items = sorted(set(x)) return asarray(items) def extract(condition, arr): @@ -1381,8 +1386,10 @@ def _nanop(op, fill, a, axis=None): y = array(a, subok=True) # We only need to take care of NaN's in floating point arrays - if np.issubdtype(y.dtype, np.integer): + dt = y.dtype + if np.issubdtype(dt, np.integer) or np.issubdtype(dt, np.bool_): return op(y, axis=axis) + mask = isnan(a) # y[mask] = fill # We can't use fancy indexing here as it'll mess w/ MaskedArrays @@ -1471,28 +1478,40 @@ def nansum(a, axis=None): def nanmin(a, axis=None): """ - Return the minimum of an array or minimum along an axis ignoring any NaNs. + Return the minimum of an array or minimum along an axis, ignoring any NaNs. Parameters ---------- a : array_like - Array containing numbers whose minimum is desired. + Array containing numbers whose minimum is desired. If `a` is not + an array, a conversion is attempted. axis : int, optional - Axis along which the minimum is computed.The default is to compute + Axis along which the minimum is computed. The default is to compute the minimum of the flattened array. Returns ------- nanmin : ndarray - A new array or a scalar array with the result. + An array with the same shape as `a`, with the specified axis removed. + If `a` is a 0-d array, or if axis is None, an ndarray scalar is + returned. The same dtype as `a` is returned. See Also -------- - numpy.amin : Minimum across array including any Not a Numbers. - numpy.nanmax : Maximum across array ignoring any Not a Numbers. - isnan : Shows which elements are Not a Number (NaN). - isfinite: Shows which elements are not: Not a Number, positive and - negative infinity + nanmax : + The maximum value of an array along a given axis, ignoring any NaNs. + amin : + The minimum value of an array along a given axis, propagating any NaNs. + fmin : + Element-wise minimum of two arrays, ignoring any NaNs. + minimum : + Element-wise minimum of two arrays, propagating any NaNs. + isnan : + Shows which elements are Not a Number (NaN). + isfinite: + Shows which elements are neither NaN nor infinity. + + amax, fmax, maximum Notes ----- @@ -1503,7 +1522,6 @@ def nanmin(a, axis=None): If the input has a integer type the function is equivalent to np.min. - Examples -------- >>> a = np.array([[1, 2], [3, np.nan]]) @@ -1565,7 +1583,7 @@ def nanargmin(a, axis=None): def nanmax(a, axis=None): """ - Return the maximum of an array or maximum along an axis ignoring any NaNs. + Return the maximum of an array or maximum along an axis, ignoring any NaNs. Parameters ---------- @@ -1580,16 +1598,25 @@ def nanmax(a, axis=None): ------- nanmax : ndarray An array with the same shape as `a`, with the specified axis removed. - If `a` is a 0-d array, or if axis is None, a ndarray scalar is - returned. The the same dtype as `a` is returned. + If `a` is a 0-d array, or if axis is None, an ndarray scalar is + returned. The same dtype as `a` is returned. See Also -------- - numpy.amax : Maximum across array including any Not a Numbers. - numpy.nanmin : Minimum across array ignoring any Not a Numbers. - isnan : Shows which elements are Not a Number (NaN). - isfinite: Shows which elements are not: Not a Number, positive and - negative infinity + nanmin : + The minimum value of an array along a given axis, ignoring any NaNs. + amax : + The maximum value of an array along a given axis, propagating any NaNs. + fmax : + Element-wise maximum of two arrays, ignoring any NaNs. + maximum : + Element-wise maximum of two arrays, propagating any NaNs. + isnan : + Shows which elements are Not a Number (NaN). + isfinite: + Shows which elements are neither NaN nor infinity. + + amin, fmin, minimum Notes ----- @@ -1732,9 +1759,9 @@ class vectorize(object): Set of strings or integers representing the positional or keyword arguments for which the function will not be vectorized. These will be passed directly to `pyfunc` unmodified. - + .. versionadded:: 1.7.0 - + cache : bool, optional If `True`, then cache the first function call that determines the number of outputs if `otypes` is not provided. @@ -1861,7 +1888,7 @@ class vectorize(object): the_args = list(args) def func(*vargs): for _n, _i in enumerate(inds): - the_args[_i] = vargs[_n] + the_args[_i] = vargs[_n] kwargs.update(zip(names, vargs[len(inds):])) return self.pyfunc(*the_args, **kwargs) @@ -1922,7 +1949,7 @@ class vectorize(object): ufunc = frompyfunc(_func, len(args), nout) return ufunc, otypes - + def _vectorize_call(self, func, args): """Vectorized call to `func` over positional `args`.""" if not args: @@ -1931,8 +1958,8 @@ class vectorize(object): ufunc, otypes = self._get_ufunc_and_otypes(func=func, args=args) # Convert args to object arrays first - inputs = [array(_a, copy=False, subok=True, dtype=object) - for _a in args] + inputs = [array(_a, copy=False, subok=True, dtype=object) + for _a in args] outputs = ufunc(*inputs) @@ -2584,7 +2611,7 @@ def _chbevl(x, vals): b0 = vals[0] b1 = 0.0 - for i in xrange(1,len(vals)): + for i in range(1,len(vals)): b2 = b1 b1 = b0 b0 = x*b1 - b2 + vals[i] @@ -2912,7 +2939,7 @@ def median(a, axis=None, out=None, overwrite_input=False): Alternative output array in which to place the result. It must have the same shape and buffer length as the expected output, but the type (of the output) will be cast if necessary. - overwrite_input : bool optional + overwrite_input : bool, optional If True, then allow use of memory of input array (a) for calculations. The input array will be modified by the call to median. This will save memory when you do not need to preserve @@ -3051,7 +3078,7 @@ def percentile(a, q, axis=None, out=None, overwrite_input=False): [ 3, 2, 1]]) >>> np.percentile(a, 50) 3.5 - >>> np.percentile(a, 0.5, axis=0) + >>> np.percentile(a, 50, axis=0) array([ 6.5, 4.5, 2.5]) >>> np.percentile(a, 50, axis=1) array([ 7., 2.]) @@ -3237,7 +3264,7 @@ def add_newdoc(place, obj, doc): """ try: new = {} - exec 'from %s import %s' % (place, obj) in new + exec('from %s import %s' % (place, obj), new) if isinstance(doc, str): add_docstring(new[obj], doc.strip()) elif isinstance(doc, tuple): @@ -3381,7 +3408,8 @@ def meshgrid(*xi, **kwargs): def delete(arr, obj, axis=None): """ - Return a new array with sub-arrays along an axis deleted. + Return a new array with sub-arrays along an axis deleted. For a one + dimensional array, this returns those entries not returned by `arr[obj]`. Parameters ---------- @@ -3405,6 +3433,15 @@ def delete(arr, obj, axis=None): insert : Insert elements into an array. append : Append elements at the end of an array. + Notes + ----- + Often it is preferable to use a boolean mask. For example: + >>> mask = np.ones(len(arr), dtype=bool) + >>> mask[[0,2,4]] = False + >>> result = arr[mask,...] + Is equivalent to `np.delete(arr, [0,2,4], axis=0)`, but allows further + use of `mask`. + Examples -------- >>> arr = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]]) @@ -3431,7 +3468,6 @@ def delete(arr, obj, axis=None): except AttributeError: pass - arr = asarray(arr) ndim = arr.ndim if axis is None: @@ -3440,34 +3476,35 @@ def delete(arr, obj, axis=None): ndim = arr.ndim; axis = ndim-1; if ndim == 0: + warnings.warn("in the future the special handling of scalars " + "will be removed from delete and raise an error", + DeprecationWarning) if wrap: return wrap(arr) else: return arr.copy() + slobj = [slice(None)]*ndim N = arr.shape[axis] newshape = list(arr.shape) - if isinstance(obj, (int, long, integer)): - if (obj < 0): obj += N - if (obj < 0 or obj >=N): - raise ValueError( - "invalid entry") - newshape[axis]-=1; - new = empty(newshape, arr.dtype, arr.flags.fnc) - slobj[axis] = slice(None, obj) - new[slobj] = arr[slobj] - slobj[axis] = slice(obj,None) - slobj2 = [slice(None)]*ndim - slobj2[axis] = slice(obj+1,None) - new[slobj] = arr[slobj2] - elif isinstance(obj, slice): + + if isinstance(obj, slice): start, stop, step = obj.indices(N) - numtodel = len(xrange(start, stop, step)) + xr = range(start, stop, step) + numtodel = len(xr) + if numtodel <= 0: if wrap: - return wrap(new) + return wrap(arr.copy()) else: return arr.copy() + + # Invert if step is negative: + if step < 0: + step = -step + start = xr[-1] + stop = xr[0] + 1 + newshape[axis] -= numtodel new = empty(newshape, arr.dtype, arr.flags.fnc) # copy initial chunk @@ -3488,24 +3525,77 @@ def delete(arr, obj, axis=None): if step == 1: pass else: # use array indexing. - obj = arange(start, stop, step, dtype=intp) - all = arange(start, stop, dtype=intp) - obj = setdiff1d(all, obj) + keep = ones(stop-start, dtype=bool) + keep[:stop-start:step] = False slobj[axis] = slice(start, stop-numtodel) slobj2 = [slice(None)]*ndim - slobj2[axis] = obj + slobj2[axis] = slice(start, stop) + arr = arr[slobj2] + slobj2[axis] = keep new[slobj] = arr[slobj2] - else: # default behavior - obj = array(obj, dtype=intp, copy=0, ndmin=1) - all = arange(N, dtype=intp) - obj = setdiff1d(all, obj) - slobj[axis] = obj + if wrap: + return wrap(new) + else: + return new + + _obj = obj + obj = np.asarray(obj) + # After removing the special handling of booleans and out of + # bounds values, the conversion to the array can be removed. + if obj.dtype == bool: + warnings.warn("in the future insert will treat boolean arrays " + "and array-likes as boolean index instead " + "of casting it to integer", FutureWarning) + obj = obj.astype(intp) + if isinstance(_obj, (int, long, integer)): + # optimization for a single value + obj = obj.item() + if (obj < -N or obj >=N): + raise IndexError("index %i is out of bounds for axis " + "%i with size %i" % (obj, axis, N)) + if (obj < 0): obj += N + newshape[axis]-=1; + new = empty(newshape, arr.dtype, arr.flags.fnc) + slobj[axis] = slice(None, obj) + new[slobj] = arr[slobj] + slobj[axis] = slice(obj,None) + slobj2 = [slice(None)]*ndim + slobj2[axis] = slice(obj+1,None) + new[slobj] = arr[slobj2] + else: + if obj.size == 0 and not isinstance(_obj, np.ndarray): + obj = obj.astype(intp) + if not np.can_cast(obj, intp, 'same_kind'): + # obj.size = 1 special case always failed and would just + # give superfluous warnings. + warnings.warn("using a non-integer array as obj in delete " + "will result in an error in the future", DeprecationWarning) + obj = obj.astype(intp) + keep = ones(N, dtype=bool) + + # Test if there are out of bound indices, this is deprecated + inside_bounds = (obj < N) & (obj >= -N) + if not inside_bounds.all(): + warnings.warn("in the future out of bounds indices will raise an " + "error instead of being ignored by `numpy.delete`.", + DeprecationWarning) + obj = obj[inside_bounds] + positive_indices = obj >= 0 + if not positive_indices.all(): + warnings.warn("in the future negative indices will not be ignored " + "by `numpy.delete`.", FutureWarning) + obj = obj[positive_indices] + + keep[obj,] = False + slobj[axis] = keep new = arr[slobj] + if wrap: return wrap(new) else: return new + def insert(arr, obj, values, axis=None): """ Insert values along the given axis before the given indices. @@ -3517,9 +3607,16 @@ def insert(arr, obj, values, axis=None): obj : int, slice or sequence of ints Object that defines the index or indices before which `values` is inserted. + + .. versionadded:: 1.8.0 + + Support for multiple insertions when `obj` is a single scalar or a + sequence with one element (similar to calling insert multiple times). values : array_like Values to insert into `arr`. If the type of `values` is different from that of `arr`, `values` is converted to the type of `arr`. + `values` should be shaped so that ``arr[...,obj,...] = values`` + is legal. axis : int, optional Axis along which to insert `values`. If `axis` is None then `arr` is flattened first. @@ -3534,8 +3631,15 @@ def insert(arr, obj, values, axis=None): See Also -------- append : Append elements at the end of an array. + concatenate : Join a sequence of arrays together. delete : Delete elements from an array. + Notes + ----- + Note that for higher dimensional inserts `obj=0` behaves very different + from `obj=[0]` just like `arr[:,0,:] = values` is different from + `arr[:,[0],:] = values`. + Examples -------- >>> a = np.array([[1, 1], [2, 2], [3, 3]]) @@ -3550,6 +3654,15 @@ def insert(arr, obj, values, axis=None): [2, 5, 2], [3, 5, 3]]) + Difference between sequence and scalars: + >>> np.insert(a, [1], [[1],[2],[3]], axis=1) + array([[1, 1, 1], + [2, 2, 2], + [3, 3, 3]]) + >>> np.array_equal(np.insert(a, 1, [1, 2, 3], axis=1), + ... np.insert(a, [1], [[1],[2],[3]], axis=1)) + True + >>> b = a.flatten() >>> b array([1, 1, 2, 2, 3, 3]) @@ -3584,6 +3697,9 @@ def insert(arr, obj, values, axis=None): ndim = arr.ndim axis = ndim-1 if (ndim == 0): + warnings.warn("in the future the special handling of scalars " + "will be removed from insert and raise an error", + DeprecationWarning) arr = arr.copy() arr[...] = values if wrap: @@ -3594,38 +3710,77 @@ def insert(arr, obj, values, axis=None): N = arr.shape[axis] newshape = list(arr.shape) - if isinstance(obj, (int, long, integer)): - if (obj < 0): obj += N - if obj < 0 or obj > N: - raise ValueError( - "index (%d) out of range (0<=index<=%d) "\ - "in dimension %d" % (obj, N, axis)) - if isscalar(values): - obj = [obj] - else: - values = asarray(values) - if ndim > values.ndim: - obj = [obj] - else: - obj = [obj] * len(values) - - elif isinstance(obj, slice): + if isinstance(obj, slice): # turn it into a range object - obj = arange(*obj.indices(N),**{'dtype':intp}) + indices = arange(*obj.indices(N),**{'dtype':intp}) + else: + # need to copy obj, because indices will be changed in-place + indices = np.array(obj) + if indices.dtype == bool: + # See also delete + warnings.warn("in the future insert will treat boolean arrays " + "and array-likes as a boolean index instead " + "of casting it to integer", FutureWarning) + indices = indices.astype(intp) + # Code after warning period: + #if obj.ndim != 1: + # raise ValueError('boolean array argument obj to insert ' + # 'must be one dimensional') + #indices = np.flatnonzero(obj) + elif indices.ndim > 1: + raise ValueError("index array argument obj to insert must " + "be one dimensional or scalar") + if indices.size == 1: + index = indices.item() + if index < -N or index > N: + raise IndexError("index %i is out of bounds for axis " + "%i with size %i" % (obj, axis, N)) + if (index < 0): index += N + + values = array(values, copy=False, ndmin=arr.ndim) + if indices.ndim == 0: + # broadcasting is very different here, since a[:,0,:] = ... behaves + # very different from a[:,[0],:] = ...! This changes values so that + # it works likes the second case. (here a[:,0:1,:]) + values = np.rollaxis(values, 0, axis+1) + numnew = values.shape[axis] + newshape[axis] += numnew + new = empty(newshape, arr.dtype, arr.flags.fnc) + slobj[axis] = slice(None, index) + new[slobj] = arr[slobj] + slobj[axis] = slice(index, index+numnew) + new[slobj] = values + slobj[axis] = slice(index+numnew, None) + slobj2 = [slice(None)] * ndim + slobj2[axis] = slice(index, None) + new[slobj] = arr[slobj2] + if wrap: + return wrap(new) + return new + elif indices.size == 0 and not isinstance(obj, np.ndarray): + # Can safely cast the empty list to intp + indices = indices.astype(intp) + + if not np.can_cast(indices, intp, 'same_kind'): + warnings.warn("using a non-integer array as obj in insert " + "will result in an error in the future", + DeprecationWarning) + indices = indices.astype(intp) - # get two sets of indices - # one is the indices which will hold the new stuff - # two is the indices where arr will be copied over + indices[indices < 0] += N + + numnew = len(indices) + order = indices.argsort(kind='mergesort') # stable sort + indices[order] += np.arange(numnew) - obj = asarray(obj, dtype=intp) - numnew = len(obj) - index1 = obj + arange(numnew) - index2 = setdiff1d(arange(numnew+N),index1) newshape[axis] += numnew + old_mask = ones(newshape[axis], dtype=bool) + old_mask[indices] = False + new = empty(newshape, arr.dtype, arr.flags.fnc) slobj2 = [slice(None)]*ndim - slobj[axis] = index1 - slobj2[axis] = index2 + slobj[axis] = indices + slobj2[axis] = old_mask new[slobj] = values new[slobj2] = arr |