How to detect a sign change for elements in a numpy array
(numpy.diff(numpy.sign(a)) != 0)*1
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.
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.