高效检测Python中的符号变化
我想做的事情和这个人一样:
不过我需要优化一下,让它运行得超级快。简单来说,我想处理一个时间序列,记录每次它穿过零点(也就是符号变化)的时刻。我想记录在零点穿越之间的时间。因为这是实际数据(32位浮点数),我怀疑我不会遇到完全等于零的数字,所以这点不太重要。我现在有一个计时程序,可以用来测试你的结果,看看谁的更快。
我的解决方案的运行时间(微秒):
open data 8384
sign data 8123
zcd data 415466
如你所见,零交叉检测的部分比较慢。下面是我的代码。
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()
7 个回答
14
另一种计算零交叉点的方法,并且能让代码运行得更快几毫秒,就是使用 nonzero
直接计算符号。假设你有一个一维的 data
数组:
def crossings_nonzero_all(data):
pos = data > 0
npos = ~pos
return ((pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:])).nonzero()[0]
另外,如果你只想计算某个特定方向的零交叉(比如,从正数变成负数),这样做会更快:
def crossings_nonzero_pos2neg(data):
pos = data > 0
return (pos[:-1] & ~pos[1:]).nonzero()[0]
在我的机器上,这种方法比 where(diff(sign))
更快(这是针对一个包含10000个正弦样本的数组,里面有20个周期,总共40个交叉点的计时结果):
$ 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
47
正如Jay Borseth所提到的,之前的答案没有正确处理包含0的数组。
我建议使用:
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]
因为a) 使用numpy.signbit()比numpy.sign()稍微快一点,因为它的实现更简单,我猜是这样,b) 它能正确处理输入数组中的0。
不过可能有一个缺点:如果你的输入数组的开头和结尾都是0,它会在开头找到一个零交叉,但在结尾却找不到……
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]
102
那这个呢:
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]
输出结果:
> zero_crossings
array([ 3, 5, 9, 10, 11, 12, 15])
也就是说,zero_crossings 这个数组里会包含那些元素的索引,在它们之前发生了零交叉。如果你想要的是在它们之后的元素,只需要在这个数组的每个值上加1就可以了。