I want to do exactly what this guy did:
However I need to optimize it to run super fast. In brief I want to take a time series and tell every time it crosses crosses zero (changes sign). I want to record the time in between zero crossings. Since this is real data (32 bit float) I doubt I’ll every have a number which is exactly zero, so that is not important. I currently have a timing program in place so I’ll time your results to see who wins.
My solution gives (micro seconds):
open data 8384 sign data 8123 zcd data 415466
As you can see the zero-crossing detector is the slow part. Here’s my code.
import numpy, datetime
class timer():
def __init__(self):
self.t0 = datetime.datetime.now()
self.t = datetime.datetime.now()
def __call__(self,text='unknown'):
print text,'t',(datetime.datetime.now()-self.t).microseconds
self.t=datetime.datetime.now()
def zcd(data,t):
sign_array=numpy.sign(data)
t('sign data')
out=[]
current = sign_array[0]
count=0
for i in sign_array[1:]:
if i!=current:
out.append(count)
current=i
count=0
else: count+=1
t('zcd data')
return out
def main():
t = timer()
data = numpy.fromfile('deci.dat',dtype=numpy.float32)
t('open data')
zcd(data,t)
if __name__=='__main__':
main()
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
What about:
import numpy a = [1, 2, 1, 1, -3, -4, 7, 8, 9, 10, -2, 1, -3, 5, 6, 7, -10] zero_crossings = numpy.where(numpy.diff(numpy.sign(a)))[0]
Output:
> zero_crossings array([ 3, 5, 9, 10, 11, 12, 15])
I.e., zero_crossings will contain the indices of elements before which a zero crossing occurs. If you want the elements after, just add 1 to that array.
Method 2
As remarked by Jay Borseth the accepted answer does not handle arrays containing 0 correctly.
I propose using:
import numpy as np a = np.array([-2, -1, 0, 1, 2]) zero_crossings = np.where(np.diff(np.signbit(a)))[0] print(zero_crossings) # output: [1]
Since a) using numpy.signbit() is a little bit quicker than numpy.sign(), since it’s implementation is simpler, I guess and b) it deals correctly with zeros in the input array.
However there is one drawback, maybe: If your input array starts and stops with zeros, it will find a zero crossing at the beginning, but not at the end…
import numpy as np a = np.array([0, -2, -1, 0, 1, 2, 0]) zero_crossings = np.where(np.diff(np.signbit(a)))[0] print(zero_crossings) # output: [0 2]
Method 3
Another way to count zero crossings and squeeze just a few more milliseconds out of the code is to use nonzero and compute the signs directly. Assuming you have a one-dimensional array of data:
def crossings_nonzero_all(data):
pos = data > 0
npos = ~pos
return ((pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:])).nonzero()[0]
Alternatively, if you just want to count the zero crossings for a particular direction of crossing zero (e.g., from positive to negative), this is even faster:
def crossings_nonzero_pos2neg(data):
pos = data > 0
return (pos[:-1] & ~pos[1:]).nonzero()[0]
On my machine these are a bit faster than the where(diff(sign)) method (timings for an array of 10000 sine samples containing 20 cycles, 40 crossings in all):
$ python -mtimeit 'crossings_where(data)' 10000 loops, best of 3: 119 usec per loop $ python -mtimeit 'crossings_nonzero_all(data)' 10000 loops, best of 3: 61.7 usec per loop $ python -mtimeit 'crossings_nonzero_pos2neg(data)' 10000 loops, best of 3: 55.5 usec per loop
Method 4
Jim Brissom’s answer fails if a contains the value 0:
import numpy a2 = [1, 2, 1, 1, 0, -3, -4, 7, 8, 9, 10, -2, 1, -3, 5, 6, 7, -10] zero_crossings2 = numpy.where(numpy.diff(numpy.sign(a2)))[0] print zero_crossings2 print len(zero_crossings2) # should be 7
Output:
[ 3 4 6 10 11 12 13 16] 8
The number of zero crossing should be 7, but because sign() returns 0 if 0 is passed, 1 for positive, and -1 for negative values, diff() will count the transition containing zero twice.
An alternative might be:
a3 = [1, 2, 1, 1, 0, -3, -4, 7, 8, 9, 10, 0, -2, 0, 0, 1, 0, -3, 0, 5, 6, 7, -10] s3= numpy.sign(a3) s3[s3==0] = -1 # replace zeros with -1 zero_crossings3 = numpy.where(numpy.diff(s3))[0] print s3 print zero_crossings3 print len(zero_crossings3) # should be 7
which give the correct answer of:
[ 3 6 10 14 15 18 21] 7
Method 5
Another way that might suit certain applications is to extend the evaluation of the expression np.diff(np.sign(a)).
If we compare how this expression reacts to certain cases:
- Rising crossing without zero:
np.diff(np.sign([-10, 10]))returnsarray([2]) - Rising crossing with zero:
np.diff(np.sign([-10, 0, 10]))returnsarray([1, 1]) - Falling crossing without zero:
np.diff(np.sign([10, -10]))returnsarray([-2]) - Falling crossing with zero:
np.diff(np.sign([10, 0, -10]))returnsarray([-1, -1])
So we have to evaluate np.diff(...) for the returned patterns in 1. and 2:
sdiff = np.diff(np.sign(a)) rising_1 = (sdiff == 2) rising_2 = (sdiff[:-1] == 1) & (sdiff[1:] == 1) rising_all = rising_1 rising_all[1:] = rising_all[1:] | rising_2
and for the cases 3. and 4.:
falling_1 = (sdiff == -2) #the signs need to be the opposite falling_2 = (sdiff[:-1] == -1) & (sdiff[1:] == -1) falling_all = falling_1 falling_all[1:] = falling_all[1:] | falling_2
After this we can easily find the indices with
indices_rising = np.where(rising_all)[0] indices_falling = np.where(falling_all)[0] indices_both = np.where(rising_all | falling_all)[0]
This approach should be reasonable fast because it can manage without using a “slow” loop.
This combines the approach of several other answers.
Method 6
I see people using diff a lot in their solutions, but xor seems to be much faster and the result is the same for bools (a good pointer to that might also be the fact that using diff gives a deprecated warning…. 🙂 )
Here is an example:
positive = a2 > 0 np.where(np.bitwise_xor(positive[1:], positive[:-1]))[0]
timeit measures it to be around one and a half faster to diff for me:)
If you do not care about edge cases it might be better to use
positive = np.signbit(a2)
but positive = a2 >0 seems faster (and cleaner) than signbit AND checking for 0s (e.g. positive = np.bitwise_or(np.signbit(a2),np.logical_not(a2)) is slower…)
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0