有效地检测python中的符号更改

时间:2010-10-01 20:59:37

标签: python math performance numpy

我想完成这个人的所作所为:

Python - count sign changes

然而,我需要优化它以超快速运行。简而言之,我想要一个时间序列,并告诉每次它越过零(改变符号)。我想记录过零点之间的时间。由于这是真实的数据(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 个答案:

答案 0 :(得分:58)

怎么样:

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添加到该数组。

答案 1 :(得分:25)

正如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)它在输入数组中正确处理零。

然而,有一个缺点,可能是:如果您的输入数组以零开始和停止,它将在开头找到零交叉,但不会在结束时...

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]

答案 2 :(得分:10)

计算零交叉并从代码中挤出几毫秒的另一种方法是使用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

答案 3 :(得分:8)

如果 a 包含值0:

,则Jim Brissom的答案将失败
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

输出:

[ 3  4  6 10 11 12 13 16]  
8  

零交叉的数量应为7,但因为如果传递0,sign()返回0,正数为1,负数值为-1,diff()将计算包含零的过渡两次。

替代方案可能是:

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

给出正确答案:

[ 3  6 10 14 15 18 21]
7

答案 4 :(得分:3)

你想要时间吗?或者你想尽可能快地制作它?

时间很简单。运行它数十万次,秒表它,并划分数十亿。

为了尽可能快地做到这一点,你需要做的是找出花费时间的事情,并且你可以做得更好。我使用1)随机暂停技术,或2)单步技术。

答案 5 :(得分:1)

我看到人们在他们的解决方案中使用了很多差异,但是xor看起来要快得多,并且bool的结果是相同的(一个好的指针也可能是使用diff给出了一个不赞成的警告... 。:)) 这是一个例子:

positive = a2 > 0
np.where(np.bitwise_xor(positive[1:], positive[:-1]))[0]

它测量它的速度大约快一倍半的时间对我来说:)

如果您不关心边缘情况,最好使用

positive = np.signbit(a2)

但是正= a2> 0似乎比signbit更快(更干净)并且检查0(例如,正= np.bitwise_or(np.signbit(a2),np.logical_not(a2))更慢......)

答案 6 :(得分:0)

可能适合某些应用程序的另一种方法是扩展表达式np.diff(np.sign(a))的评估。

如果我们比较这个表达式对某些情况的反应:

  1. 没有零的上升交叉:np.diff(np.sign([-10, 10]))返回array([2])
  2. 上升交叉零:np.diff(np.sign([-10, 0, 10]))返回array([1, 1])
  3. 没有零的下降交叉:np.diff(np.sign([10, -10]))返回array([-2])
  4. 零落交叉:np.diff(np.sign([10, 0, -10]))返回array([-1, -1])
  5. 因此,我们必须在1.和2中评估np.diff(...)返回的模式:

    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
    

    以及案例3和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
    

    在此之后,我们可以使用

    轻松找到索引
    indices_rising = np.where(rising_all)[0]
    indices_falling = np.where(falling_all)[0]
    indices_both = np.where(rising_all | falling_all)[0]
    

    这种方法应该是合理的,因为它可以在不使用“慢”循环的情况下进行管理。

    这结合了其他几个答案的方法。