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.

enter image description here


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.

Tags:

Python

Numpy