高效检测Python中的符号变化

47 投票
7 回答
93027 浏览
提问于 2025-04-16 04:52

我想做的事情和这个人一样:

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就可以了。

撰写回答