15

As the title says, suppose I want to write a sign function (let's forget sign(0) for now), obviously we expect sign(2) = 1 and sign(array([-2,-2,2])) = array([-1,-1,1]). The following function won't work however, because it can't handle numpy arrays.

def sign(x):
    if x>0: return 1
    else: return -1

The next function won't work either since x doesn't have a shape member if it's just a single number. Even if some trick like y = x*0 + 1 is used, y won't have a [] method.

def sign(x):
    y = ones(x.shape)
    y[x<0] = -1
    return y

Even with the idea from another question(how can I make a numpy function that accepts a numpy array, an iterable, or a scalar?), the next function won't work when x is a single number because in this case x.shape and y.shape are just () and indexing y is illegal.

def sign(x):
    x = asarray(x)
    y = ones(x.shape)
    y[x<0] = -1
    return y

The only solution seems to be that first decide if x is an array or a number, but I want to know if there is something better. Writing branchy code would be cumbersome if you have lots of small functions like this.

5
  • 1
    Indexing y with a mask is legal: the problem here is that x < 0 is again a scalar rather than a 0-d array. If you try y[asarray(x < 0)] it should work. Commented Oct 24, 2014 at 7:00
  • Have you considered the possibility of using the built in np.sign? Commented Oct 24, 2014 at 8:15
  • @MarkDickinson This is a good one, but it gives errors when x is a single number because now y must be a single number as well -- then y can't be indexed... Commented Oct 24, 2014 at 14:57
  • @Jaime The sign function is just for example, I should have used something like a piecewise function in math. BTW do you know how sign function is implemented in numpy? Commented Oct 24, 2014 at 14:59
  • @Taozi: Right; you'd still need the x = asarray(x) at the beginning if you wanted to take this approach. Commented Oct 24, 2014 at 16:14

8 Answers 8

4

np.vectorize can be used to achieve that, but would be slow because all it does, when your decorated function is called with an array, is looping through the array elements and apply the scalar function to each, i.e. not leveraging numpy's speed.

A method I find useful for vectorizing functions involving if-else is using np.choose:

def sign_non_zero(x):
    return np.choose(
        x > 0,  # bool values, used as indices to the array
        [
            -1, # index=0=False, i.e. x<=0
            1,  # index=1=True, i.e. x>0
        ])

This works when x is either scalar or an array, and is faster than looping in python-space.

The only disadvantage of using np.choose is that it is not intuitive to write if-else logic in that manner, and the code is less readable. Whenver I use it, I include comments like the ones above, to make it easier on the reader to understand what is going on.

Sign up to request clarification or add additional context in comments.

6 Comments

>>> sign_non_zero([1,2,3]) gives 1 # should have been 1,1,1 >>> sign_non_zero([1,2,-3]) gives 1 # should have been 1,1,-1
@BHATIRSHAD, right, as currently stands, sign_non_zero supports scalars and numpy arrays. To also support lists, you can simply replace x with np.asarray(x).
@bhat-irshad This one is perfect for implementing the sign function after x is replaced with np.asarray(x). However using choose seems only convenient when the result is yes-or-no. If you have a threefold decision to make (suppose you consider sign(0)) now, then choose function is useless and we have to face the old question again -- if x can be indexed, aka, if x is a number or an array.
@Taozi I wouldn't say choose is useless if you have a threefold decision to make. Each if or elif statement in your "scalar-version" can translate to a call to choose, so you'd need to use choose twice. The complexity of your scalar-version, in terms of number of if-statemetns, is preserverd in your choose-version, in terms of number of choose-calls. This still has the advatage of being numpy-fast.
nice use of np.choose. You can also take a look at np.select for more complex return types
|
3

i wonder if it's a vectorized function that you want:

>>> import numpy as NP

>>> def fnx(a):
        if a > 0:
            return 1
        else:
            return -1

>>> vfnx = NP.vectorize(fnx)

>>> a = NP.random.randint(1, 10, 5)
array([4, 9, 7, 9, 2])

>>> a0 = 7

>>> vfnx(a)
array([1, 1, 1, 1])

>>> vfnx(a0)
array(1)

2 Comments

This is nice, but is it true as mentioned by shx2 that vectorized function is slow and don't take advantage of numpy's speed? Also if this method used, each function needs to be defined twice - one is the humble version that focus on a single number, one is the vectorized version whose name should be close but different, is this correct?
according to the docs, a vectorized fn is implemented as a python for loop and indeed a major reason for NumPy's performance is array-oriented computation (just a single for loop in the C source) that avoids the second python for loop. But you do not need a second fn; the purpose of a vectorized fn is to handle both NumPy arrays and scalars using the same fn in a single fn call.
3

Here's one solution:

import numpy as np

def sign(x):
    y = np.ones_like(x)
    y[np.asarray(x) < 0] = -1

    if isinstance(x, np.ndarray):
        return y
    else:
        return type(x)(y)

This should return a value of the same type as the input. For example sign(42) gives 1, sign(42.0) gives 1.0. If you give it an ndarray, it will work like np.sign.

In general, you might proceed with the assumption that your input is an ndarray. If you try to access an attribute or method that an ndarray has, but your input does not, then you fall back to operating on a scalar type. Use exceptions to implement this. For example:

def foo_on_scalars(x):
    # do scalar things

def foo(x):
    try:
        # assume x is an ndarray
    except AttributeError:
        foo_on_scalars(x)

Comments

1

The approach I have taken before is alot like your last example, but adding an additional check for scalars at the beginning:

def sign(x):
    if isscalar(x):
        x = (x,)
    x = asarray(x)
    y = ones(x.shape)
    y[x<0] = -1
    return y

Comments

1

The numpy functions naturally handle scalar or array inputs and preserve the shape in the output. So, it's always best to find the numpy functions doing the job. In this case, the function should be np.sign as suggested above. For different logics, you can use np.where(x>0, 1, -1), which works for scalar and array values of x.

Comments

0

you can convert the number to a single-element array first,

and then concentrate on operating on arrays.

you still have to check the type of x though

1 Comment

But then the function is returning a single element array which needs unpacking from the client side.
0

Here is one solution:

>>> def sign(x):
...      if type(x)==int:
...          if x>0: return 1
...          else: return -1 
...      else:
...          x=np.array(x)
...          pos=np.where(x>=0)
...          neg=np.where(x<0)
...          res=np.zeros(x.shape[0])
...          res[pos]=1
...          res[neg]=-1
...          return res.tolist()
... 
>>> sign(56)
1
>>> sign(-556)
-1
>>> sign([23,4,-3,0,45,-3])
[1.0, 1.0, -1.0, 1.0, 1.0, -1.0]
>>> sign(np.array([23,4,-3,0,45,-3]))
[1.0, 1.0, -1.0, 1.0, 1.0, -1.0]

2 Comments

what would be the output of sign(56L)? or sign(np.int32(56))? sign(56.)? Besides, the whole point is to avoid the dupcliation of logic.
@shx2 Exactly what I want to ask, the problem with type judging is that there are so many types. It's possible to use if type(numpyarray) == 'ndarray', but those branches are all that I want to avoid.
0

Simple solution that handles scalars and numpy arrays:

>>> import numpy as np

>>> def sign_non_zero(x):
        return (x > 0) * 1 + (x < 0) * -1

>>> sign_non_zero(2)
1

>>> sign_non_zero(np.array([-2, -2, 2]))
array([-1, -1,  1])

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.