38

I have a numpy array with positive and negative values in.

a = array([1,1,-1,-2,-3,4,5])

I want to create another array which contains a value at each index where a sign change occurs (For example, if the current element is positive and the previous element is negative and vice versa).

For the array above, I would expect to get the following result

array([0,0,1,0,0,1,0])

Alternatively, a list of the positions in the array where the sign changes occur or list of booleans instead of 0's and 1's is fine.

9 Answers 9

39

Something like

a = array([1,1,-1,-2,-3,4,5])
asign = np.sign(a)
signchange = ((np.roll(asign, 1) - asign) != 0).astype(int)
print signchange
array([0, 0, 1, 0, 0, 1, 0])

Now, numpy.roll does a circular shift, so if the last element has different sign than the first, the first element in the signchange array will be 1. If this is not desired, one can of course do a simple

signchange[0] = 0

Also, np.sign considers 0 to have it's own sign, different from either positive or negative values. E.g. the "signchange" array for [-1,0,1] would be [0,1,1] even though the zero line was "crossed" only once. If this is undesired, one could insert the lines

sz = asign == 0
while sz.any():
    asign[sz] = np.roll(asign, 1)[sz]
    sz = asign == 0

between lines 2 and 3 in the first example.

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

2 Comments

Note that this will indicate a sign change in the first position if the last element is a different sign than the first. It also considers 0 to be a different sign than positive or negative. So [-1, 0, 1] will give signchange = [1, 1, 1]. This may be desired behavior, but I thought I'd point it out.
@tgray Yeah, I amended my answer to point out how to fix those issues, if desired.
29
(numpy.diff(numpy.sign(a)) != 0)*1

4 Comments

Great solution... now, how to sum for total sign changes elegantly per row?
Specify the axis for diff and sum
What does the *1 do?
Converts True to 1 and False to 0
16

Three methods to determine the location of sign change occurrences

import numpy as np
a = np.array([1,1,-1,-2,-3,4,5])

Method 1: Multiply adjacent items in array and find negative

idx1 = np.where(a[:-1] * a[1:] < 0 )[0] +1
idx1
Out[2]: array([2, 5], dtype=int64)

%timeit np.where(a[:-1] * a[1:] < 0 )[0] + 1
4.31 µs ± 15.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Method 2 (fastest): Where adjacent signs are not equal

idx2 = np.where(np.sign(a[:-1]) != np.sign(a[1:]))[0] + 1
idx2
Out[4]: array([2, 5], dtype=int64)

%timeit np.where(np.sign(a[:-1]) != np.sign(a[1:]))[0] + 1
3.94 µs ± 20.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Method 3: As proposed by ianalis. Most IMO elegant but a little slower

idx3 = np.where(np.diff(np.sign(a)) != 0)[0] + 1
idx3
Out[6]: array([2, 5], dtype=int64)

%timeit np.where(np.diff(np.sign(a)) != 0)[0] + 1
9.7 µs ± 36.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Edit:

For large arrays method 1 is the best.

enter image description here

Comments

1

The answers above use list comprehensions and some numpy magic to get the result you want. Here is a very straight forward, if a little convoluted, way of doing the same:

import numpy as np

arr = np.array([1,1,-1,-2,-3,4,5])

result = []
for i, v in enumerate(arr):
    if i == 0:
        change = False
    elif v < 0 and arr[i-1] > 0:
        change = True
    elif v > 0 and arr[i-1] < 0:
        change = True
    else:
        change = False

    result.append(change)

print result

1 Comment

it won't register a change in np.array([1,0,-1])
1

How about

[0 if x == 0 else 1 if numpy.sign(a[x-1]) != numpy.sign(y) else 0 for x, y in enumerate(a)]

numpy.sign assigns 0 its own sign, so 0s will be sign changes from anything except other 0s, which is probably what you want.

Comments

1

Another idea on getting the 'strict' sign changes from positive to negative and negative to positive (excluding zeros):

a = np.array([0.4, 0.5, -0.2, -0.6, 5, 0, 0, 5, 0,-2])

# Get associated index
ind =np.arange(len(a))

# remove zero from array but keep original index
a2 =a[a!=0.]
ind2  =ind[a!=0.]

# Detect sign changes in reduced array
idx=np.where(np.diff(np.sign(a2)) != 0)[0] + 1

# Get sign changes index for original array
ind2[idx]
array([2, 4, 9])

Comments

0

For the direct interpretation of this question, where 0's aren't their own case, it's probably easier to use greater than sign. Here's an example:

a = array([1, 1, -1, -2, -3, 0, 4, 0, 5, 6])

x = greater_equal(a, 0)
sign_change = x[:-1]-x[1:]

Which gives, when printed with T or F to indicate the sign change between different numbers:

 1 F 1 T -1 F -2 F -3 T 0 F 4 F 0 F 5 F 6

when printed using:

print `a[0]`+"".join([(" T" if sign_change[i] else " F")+" "+`a[i+1]` for i in range(len(sign_change))])

Also note that this is one element shorter than the original array, which makes sense since you're asking for the change of sign. If you want to include the change between the last and first element, you can use roll, as others have suggested.

1 Comment

Now this code doesn't work. It gives an error :::: TypeError: numpy boolean subtract, the - operator, is deprecated, use the bitwise_xor, the ^ operator, or the logical_xor function instead. So, please use other answers
0

If you're only interested in change in one direction (below from negative to postive values (positive including zero)):

arr = np.array([1,1,-1,-2,-3,4,5])

np.where(np.diff((arr>=0)*1)==1)

returns the index of every last negative value before change of sign

Comments

0

If direction of sign change is of interest

This method returns indices at which a sign change occurs. It can also serve to filter for only some changes e.g. positive to negative, vice-versa or both.

If the index of the entry after sign change is desired, add 1 to the elements of the array (as in @DougR and @lhoupert's answers)

import numpy as np
a = np.array([1,1,-1,-2,-3,4,5])

# For positive to negative only
np.where((a[:-1] < 0) & (a[1:] > 0))[0]
# >> array([4])

# For positive to negative only
np.where(a[:-1] > 0) & (a[1:] < 0))[0]
# >> array([1])

# For any sign change combine with bitwise OR
np.where(((a[:-1] < 0) & (a[1:] > 0) | (a[:-1] > 0) & (a[1:] < 0)))[0]
# >> array([1, 4])

It is the fastest method especially for larger arrays, here compared to tests by @DougR:

array instantiation and methods

Additionally, it can be very useful when determining an estimates of minima of a function that is not callable / is only accessible through a finite scalar array - that is if you do not expect inflection points:

# Example discrete data
x = np.linspace(-0.3, 10.3, 20)
y = -np.cos(x) - 0.5* x
y_prime = np.sin(x) - 0.5 

# Get x[i] where sgn( f'(x[i]) ) < sgn( f'(x[i+1]) ) aka last x before local minimum
idx = np.where((y_prime[:-1] < 0) & (y_prime[1:] > 0))[0]
x[idx]
# >> array([0.25789474, 6.39473684])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.