I have worked on a generalized piecewise function (genpiecewise) that are simpler and more general than the current numpy.piecewise implementation. The new generalized piecewise function allows functions of the type f(x0, x1,.. , xn) i.e. to have arbitrary number of input arguments that are evaluated conditionally. The generalized piecewise function passes all the tests for numpy.piecewise function except the undocumented features of numpy.piecewise which allows condlist to be a single bool list/array or a single int array.
A new keyword "fillvalue" opens up the possibility to specify "out of bounds values" to other values than 0 eg. Np.nan. Examples: Example 1) >>> x = numpy.linspace(-2,2,5) >>> numpy.piecewise(x, x<1, [1]) # = numpy.where(x<0, 1, 0) array([ 1., 1., 1., 0., 0.]) # can be written as >>> genpiecewise([x<1],[1]) array([1, 1, 1, 0, 0]) # or >>> genpiecewise([x<1],[1], x) array([ 1., 1., 1., 0., 0.]) # or >>> genpiecewise([x<1],[1.0]) array([ 1., 1., 1., 0., 0.]) Example 2) >>> numpy.piecewise(x, [x < 0, x >= 0], [lambda x: -x, lambda x: x]) array([ 2., 1., 0., 1., 2.]) # can be written as >>> genpiecewise([x < 0, x >= 0], [lambda x: -x, lambda x: x], (x,)) array([ 2., 1., 0., 1., 2.]) # or genpiecewise([x < 0,], [lambda x: -x, lambda x: x], x) array([ 2., 1., 0., 1., 2.]) Example 3) # New functionality >>> X,Y = numpy.meshgrid(x,x) >>> genpiecewise([X*Y<0,], [lambda x,y: -x*y, lambda x,y: x*y], xi=(X,Y)) array([[ 4., 2., -0., 2., 4.], [ 2., 1., -0., 1., 2.], [-0., -0., 0., 0., 0.], [ 2., 1., 0., 1., 2.], [ 4., 2., 0., 2., 4.]]) >>> genpiecewise([X*Y<-0.5, X*Y>0.5], [lambda x,y: -x*y, lambda x,y: x*y], >>> xi=(X,Y), fillvalue=numpy.nan) array([[ 4., 2., nan, 2., 4.], [ 2., 1., nan, 1., 2.], [ nan, nan, nan, nan, nan], [ 2., 1., nan, 1., 2.], [ 4., 2., nan, 2., 4.]]) >>> genpiecewise([X*Y<-0.5, X*Y>0.5], [lambda x,y: -x*y, lambda x,y: x*y, >>> numpy.nan], (X,Y)) array([[ 4., 2., nan, 2., 4.], [ 2., 1., nan, 1., 2.], [ nan, nan, nan, nan, nan], [ 2., 1., nan, 1., 2.], [ 4., 2., nan, 2., 4.]]) My question is: are there any interest in the community for such a function? Could some or all of this functionality replace the current numpy.piecewise? (This function could replace the function _lazywhere (in scipy.stats._distn_infrastructure) which is heavily used in scipy.stats) Per A. Brodtkorb The code looks like this: def valarray(shape, value=np.nan, typecode=None): """Return an array of all value. """ out = ones(shape, dtype=bool) * value if typecode is not None: out = out.astype(typecode) if not isinstance(out, np.ndarray): out = asarray(out) return out def genpiecewise(condlist, funclist, xi=None, fillvalue=0, args=(), **kw): """ Evaluate a piecewise-defined function. Given a set of conditions and corresponding functions, evaluate each function on the input data wherever its condition is true. Parameters ---------- condlist : list of bool arrays Each boolean array corresponds to a function in `funclist`. Wherever `condlist[i]` is True, `funclist[i](x0,x1,...,xn)` is used as the output value. Each boolean array in `condlist` selects a piece of `xi`, and should therefore be of the same shape as `xi`. The length of `condlist` must correspond to that of `funclist`. If one extra function is given, i.e. if ``len(funclist) - len(condlist) == 1``, then that extra function is the default value, used wherever all conditions are false. funclist : list of callables, f(*(xi + args), **kw), or scalars Each function is evaluated over `x` wherever its corresponding condition is True. It should take an array as input and give an array or a scalar value as output. If, instead of a callable, a scalar is provided then a constant function (``lambda x: scalar``) is assumed. xi : tuple input arguments to the functions in funclist, i.e., (x0, x1,...., xn) fillvalue : scalar fillvalue for out of range values. Default 0. args : tuple, optional Any further arguments given here passed to the functions upon execution, i.e., if called ``piecewise(..., ..., args=(1, 'a'))``, then each function is called as ``f(x0, x1,..., xn, 1, 'a')``. kw : dict, optional Keyword arguments used in calling `piecewise` are passed to the functions upon execution, i.e., if called ``piecewise(..., ..., lambda=1)``, then each function is called as ``f(x0, x1,..., xn, lambda=1)``. Returns ------- out : ndarray The output is the same shape and type as x and is found by calling the functions in `funclist` on the appropriate portions of `x`, as defined by the boolean arrays in `condlist`. Portions not covered by any condition have undefined values. See Also -------- choose, select, where Notes ----- This is similar to choose or select, except that functions are evaluated on elements of `xi` that satisfy the corresponding condition from `condlist`. The result is:: |-- |funclist[0](x0[condlist[0]],x1[condlist[0]],...,xn[condlist[0]]) out = |funclist[1](x0[condlist[1]],x1[condlist[1]],...,xn[condlist[1]]) |... |funclist[n2](x0[condlist[n2]],x1[condlist[n2]],...,xn[condlist[n2]]) |-- Examples -------- Define the sigma function, which is -1 for ``x < 0`` and +1 for ``x >= 0``. >>> x = np.linspace(-2.5, 2.5, 6) >>> genpiecewise([x < 0, x >= 0], [-1, 1]) array([-1., -1., -1., 1., 1., 1.]) Define the absolute value, which is ``-x`` for ``x <0`` and ``x`` for ``x >= 0``. >>> genpiecewise([x < 0, x >= 0], [lambda x: -x, lambda x: x], (x,)) array([ 2.5, 1.5, 0.5, 0.5, 1.5, 2.5]) """ def otherwise_condition(condlist): return ~np.logical_or.reduce(condlist, axis=0) def check_shapes(condlist, funclist): nc, nf = len(condlist), len(funclist) if nc not in [nf-1, nf]: raise ValueError("function list and condition list" + " must be the same length") check_shapes(condlist, funclist) if xi is not None and not isinstance(xi, tuple): xi = (xi,) condlist = np.broadcast_arrays(*condlist) if len(condlist) == len(funclist)-1: condlist.append(otherwise_condition(condlist)) arrays = dtype = None if xi is not None: arrays = np.broadcast_arrays(*xi) dtype = np.result_type(*arrays) else: # funclist is a list of scalars only dtype = np.result_type(*funclist) out = valarray(condlist[0].shape, fillvalue, dtype) for cond, func in zip(condlist, funclist): if isinstance(func, collections.Callable): temp = tuple(np.extract(cond, arr) for arr in arrays) + args np.place(out, cond, func(*temp, **kw)) else: # func is a scalar value np.place(out, cond, func) return out
_______________________________________________ NumPy-Discussion mailing list NumPy-Discussion@scipy.org http://mail.scipy.org/mailman/listinfo/numpy-discussion