summaryrefslogtreecommitdiff
path: root/numpy/lib/function_base.py
diff options
context:
space:
mode:
Diffstat (limited to 'numpy/lib/function_base.py')
-rw-r--r--numpy/lib/function_base.py180
1 files changed, 97 insertions, 83 deletions
diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py
index 7eeed7825..cd3545966 100644
--- a/numpy/lib/function_base.py
+++ b/numpy/lib/function_base.py
@@ -764,6 +764,30 @@ def copy(a, order='K', subok=False):
>>> x[0] == z[0]
False
+ Note that np.copy is a shallow copy and will not copy object
+ elements within arrays. This is mainly important for arrays
+ containing Python objects. The new array will contain the
+ same object which may lead to surprises if that object can
+ be modified (is mutable):
+
+ >>> a = np.array([1, 'm', [2, 3, 4]], dtype=object)
+ >>> b = np.copy(a)
+ >>> b[2][0] = 10
+ >>> a
+ array([1, 'm', list([10, 3, 4])], dtype=object)
+
+ To ensure all elements within an ``object`` array are copied,
+ use `copy.deepcopy`:
+
+ >>> import copy
+ >>> a = np.array([1, 'm', [2, 3, 4]], dtype=object)
+ >>> c = copy.deepcopy(a)
+ >>> c[2][0] = 10
+ >>> c
+ array([1, 'm', list([10, 3, 4])], dtype=object)
+ >>> a
+ array([1, 'm', list([2, 3, 4])], dtype=object)
+
"""
return array(a, order=order, subok=subok, copy=True)
@@ -2026,7 +2050,7 @@ class vectorize:
self.pyfunc = pyfunc
self.cache = cache
self.signature = signature
- self._ufunc = None # Caching to improve default performance
+ self._ufunc = {} # Caching to improve default performance
if doc is None:
self.__doc__ = pyfunc.__doc__
@@ -2091,14 +2115,22 @@ class vectorize:
if self.otypes is not None:
otypes = self.otypes
- nout = len(otypes)
- # Note logic here: We only *use* self._ufunc if func is self.pyfunc
- # even though we set self._ufunc regardless.
- if func is self.pyfunc and self._ufunc is not None:
- ufunc = self._ufunc
+ # self._ufunc is a dictionary whose keys are the number of
+ # arguments (i.e. len(args)) and whose values are ufuncs created
+ # by frompyfunc. len(args) can be different for different calls if
+ # self.pyfunc has parameters with default values. We only use the
+ # cache when func is self.pyfunc, which occurs when the call uses
+ # only positional arguments and no arguments are excluded.
+
+ nin = len(args)
+ nout = len(self.otypes)
+ if func is not self.pyfunc or nin not in self._ufunc:
+ ufunc = frompyfunc(func, nin, nout)
else:
- ufunc = self._ufunc = frompyfunc(func, len(args), nout)
+ ufunc = None # We'll get it from self._ufunc
+ if func is self.pyfunc:
+ ufunc = self._ufunc.setdefault(nin, ufunc)
else:
# Get number of outputs and output types by calling the function on
# the first entries of args. We also cache the result to prevent
@@ -3224,7 +3256,6 @@ def kaiser(M, beta):
>>> plt.show()
"""
- from numpy.dual import i0
if M == 1:
return np.array([1.])
n = arange(0, M)
@@ -3838,15 +3869,20 @@ def _quantile_is_valid(q):
return True
+def _lerp(a, b, t, out=None):
+ """ Linearly interpolate from a to b by a factor of t """
+ return add(a*(1 - t), b*t, out=out)
+
+
def _quantile_ureduce_func(a, q, axis=None, out=None, overwrite_input=False,
interpolation='linear', keepdims=False):
a = asarray(a)
- if q.ndim == 0:
- # Do not allow 0-d arrays because following code fails for scalar
- zerod = True
- q = q[None]
- else:
- zerod = False
+
+ # ufuncs cause 0d array results to decay to scalars (see gh-13105), which
+ # makes them problematic for __setitem__ and attribute access. As a
+ # workaround, we call this on the result of every ufunc on a possibly-0d
+ # array.
+ not_scalar = np.asanyarray
# prepare a for partitioning
if overwrite_input:
@@ -3863,9 +3899,14 @@ def _quantile_ureduce_func(a, q, axis=None, out=None, overwrite_input=False,
if axis is None:
axis = 0
- Nx = ap.shape[axis]
- indices = q * (Nx - 1)
+ if q.ndim > 2:
+ # The code below works fine for nd, but it might not have useful
+ # semantics. For now, keep the supported dimensions the same as it was
+ # before.
+ raise ValueError("q must be a scalar or 1d")
+ Nx = ap.shape[axis]
+ indices = not_scalar(q * (Nx - 1))
# round fractional indices according to interpolation method
if interpolation == 'lower':
indices = floor(indices).astype(intp)
@@ -3882,87 +3923,60 @@ def _quantile_ureduce_func(a, q, axis=None, out=None, overwrite_input=False,
"interpolation can only be 'linear', 'lower' 'higher', "
"'midpoint', or 'nearest'")
- n = np.array(False, dtype=bool) # check for nan's flag
- if np.issubdtype(indices.dtype, np.integer): # take the points along axis
- # Check if the array contains any nan's
- if np.issubdtype(a.dtype, np.inexact):
- indices = concatenate((indices, [-1]))
-
- ap.partition(indices, axis=axis)
- # ensure axis with q-th is first
- ap = np.moveaxis(ap, axis, 0)
- axis = 0
-
- # Check if the array contains any nan's
- if np.issubdtype(a.dtype, np.inexact):
- indices = indices[:-1]
- n = np.isnan(ap[-1:, ...])
-
- if zerod:
- indices = indices[0]
- r = take(ap, indices, axis=axis, out=out)
+ # The dimensions of `q` are prepended to the output shape, so we need the
+ # axis being sampled from `ap` to be first.
+ ap = np.moveaxis(ap, axis, 0)
+ del axis
- else: # weight the points above and below the indices
- indices_below = floor(indices).astype(intp)
- indices_above = indices_below + 1
- indices_above[indices_above > Nx - 1] = Nx - 1
+ if np.issubdtype(indices.dtype, np.integer):
+ # take the points along axis
- # Check if the array contains any nan's
if np.issubdtype(a.dtype, np.inexact):
- indices_above = concatenate((indices_above, [-1]))
-
- weights_above = indices - indices_below
- weights_below = 1 - weights_above
+ # may contain nan, which would sort to the end
+ ap.partition(concatenate((indices.ravel(), [-1])), axis=0)
+ n = np.isnan(ap[-1])
+ else:
+ # cannot contain nan
+ ap.partition(indices.ravel(), axis=0)
+ n = np.array(False, dtype=bool)
- weights_shape = [1, ] * ap.ndim
- weights_shape[axis] = len(indices)
- weights_below.shape = weights_shape
- weights_above.shape = weights_shape
+ r = take(ap, indices, axis=0, out=out)
- ap.partition(concatenate((indices_below, indices_above)), axis=axis)
+ else:
+ # weight the points above and below the indices
- # ensure axis with q-th is first
- ap = np.moveaxis(ap, axis, 0)
- weights_below = np.moveaxis(weights_below, axis, 0)
- weights_above = np.moveaxis(weights_above, axis, 0)
- axis = 0
+ indices_below = not_scalar(floor(indices)).astype(intp)
+ indices_above = not_scalar(indices_below + 1)
+ indices_above[indices_above > Nx - 1] = Nx - 1
- # Check if the array contains any nan's
if np.issubdtype(a.dtype, np.inexact):
- indices_above = indices_above[:-1]
- n = np.isnan(ap[-1:, ...])
+ # may contain nan, which would sort to the end
+ ap.partition(concatenate((
+ indices_below.ravel(), indices_above.ravel(), [-1]
+ )), axis=0)
+ n = np.isnan(ap[-1])
+ else:
+ # cannot contain nan
+ ap.partition(concatenate((
+ indices_below.ravel(), indices_above.ravel()
+ )), axis=0)
+ n = np.array(False, dtype=bool)
- x1 = take(ap, indices_below, axis=axis) * weights_below
- x2 = take(ap, indices_above, axis=axis) * weights_above
+ weights_shape = indices.shape + (1,) * (ap.ndim - 1)
+ weights_above = not_scalar(indices - indices_below).reshape(weights_shape)
- # ensure axis with q-th is first
- x1 = np.moveaxis(x1, axis, 0)
- x2 = np.moveaxis(x2, axis, 0)
+ x_below = take(ap, indices_below, axis=0)
+ x_above = take(ap, indices_above, axis=0)
- if zerod:
- x1 = x1.squeeze(0)
- x2 = x2.squeeze(0)
-
- if out is not None:
- r = add(x1, x2, out=out)
- else:
- r = add(x1, x2)
+ r = _lerp(x_below, x_above, weights_above, out=out)
+ # if any slice contained a nan, then all results on that slice are also nan
if np.any(n):
- if zerod:
- if ap.ndim == 1:
- if out is not None:
- out[...] = a.dtype.type(np.nan)
- r = out
- else:
- r = a.dtype.type(np.nan)
- else:
- r[..., n.squeeze(0)] = a.dtype.type(np.nan)
+ if r.ndim == 0 and out is None:
+ # can't write to a scalar
+ r = a.dtype.type(np.nan)
else:
- if r.ndim == 1:
- r[:] = a.dtype.type(np.nan)
- else:
- r[..., n.repeat(q.size, 0)] = a.dtype.type(np.nan)
+ r[..., n] = a.dtype.type(np.nan)
return r