【问题标题】:Efficiently detect sign-changes in python有效检测python中的符号变化
【发布时间】:2011-04-20 01:43:15
【问题描述】:

我想做这家伙所做的事情:

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()

【问题讨论】:

  • 有一个'timeit'模块,你知道吗? :)
  • 有趣...我更喜欢我的,因为它可以放在整个函数中。您可以每隔几行删除一个 t() 并快速找到瓶颈。如果我只是想计时我的功能,我会使用 linux $ time python zcd.py
  • 我猜time('sign data') 应该是t('sign data')。是吗?
  • @Muhammad Alkarouri - 是的,谢谢。我会解决的。

标签: python math performance numpy


【解决方案1】:

怎么样:

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 将包含元素的索引 before 发生过零。如果你想要元素之后,只需在那个数组中加1。

【讨论】:

  • 我认为你倒退了; zero_crossings 包含元素的 lidices before 发生零交叉,如果您想要元素 after 则将 1 添加到数组中。否则,优秀,简洁的答案!
  • 当数组中有零时这不起作用。它将检测到它们两次!示例:a = [2,1,0,-1,2] 将给出array([1, 2, 3])
  • 如果您只对计数感兴趣(而不对索引感兴趣),则删除所有 0 即可。 np.where(np.diff(np.sign( [i for i in a if i] )))[0].shape[0]
  • 这几乎可以用作 numpy.sign(0) = 0numpy.sign(2) = 1numpy.sign(-2) = -1。所以你可能想要numpy.where(numpy.diff(numpy.sign(a) >= 0))[0]
  • 如何应用这个逻辑来只发现负号到正号的变化?例如从系列[-2, -4, -2, 1, 2, 8, -1, -1, 0] 我需要输出[0, 0, 0, 1, 0 ,0 ,0 ,0, 1]
【解决方案2】:

正如 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,-1,0,-1,-2,0]$....我猜,将零计为正数也不是最终的解决方案。
  • @mikuszefski 你是对的! [ 1, 2, 0, -1, 0, 0, -1, 2] 应该产生 2 零交叉,但它不会。
【解决方案3】:

另一种计算零交叉并从代码中挤出几毫秒的方法是使用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)) 方法快一点(包含 20 个周期、总共 40 个交叉点的 10000 个正弦样本数组的时序):

$ 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

【讨论】:

  • 您可以将(pos[:-1] & npos[1:]) | (npos[:-1] & pos[1:]) 缩短为pos[:-1] ^ npos[1:],其中^ 是XOR 运算符。
  • crossings_nonzero_pos2neg([1,2,-1,1,2]) Traceback(最近一次调用最后):文件“”,第 1 行,在 crosss_nonzero_pos2neg([1,2,-1,1 ,2]) 文件“”,第 2 行,在crossings_nonzero_pos2neg pos = data > 0 TypeError: '>' not supported between 'list' and 'int'
  • @Mainland 使用“numpy.asarray()”在传入列表之前进行转换。
【解决方案4】:

如果 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,但是因为sign()如果通过0则返回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

【讨论】:

  • 谢谢——我刚刚遇到了这个答案。我想知道是否有一种简单的方法可以知道过零的 “符号”(高于 0 或低于 0)?坡度应该会有所帮助。
  • 这不会处理 0 前后的元素符号相同的情况。
  • 而不是使用numpy.sign,它返回-1、0 或1 表示负数、零或正数,您应该只使用numpy.where(numpy.diff(a2 > 0))[0]。或者使用 Dominik Neise 的回答,np.signbit
  • 不幸的是,此解决方案不适用于其他 Python 容器类型,例如 dique。但是,other solution 确实如此。
【解决方案5】:

你想计时吗?或者你想让它尽可能快吗?

时间很容易。运行无数次,秒表,然后除以无数次。

为了尽可能快地完成,您需要做的是找出需要时间的事情以及您可以以更好的方式完成。我使用 1) 随机暂停技术,或 2) 单步技术。

【讨论】:

  • 计时很简单,而且运行速度足够快,运行一次就可以得到准确的时间。我希望脚本快速运行,因为它是半实时数据处理器的一部分。
  • @dustynachos:FWIW,这是一个使用随机暂停来获得超过 40 倍的加速的详细说明。 stackoverflow.com/questions/926266/…
【解决方案6】:

另一种可能适合某些应用程序的方法是扩展表达式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])

所以我们必须为 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]

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

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

【讨论】:

    【解决方案7】:

    我看到人们在他们的解决方案中经常使用 diff,但是 xor 似乎要快得多,并且结果对于 bool 是相同的(一个很好的指针也可能是使用 diff 给出了一个不推荐使用的警告的事实...... . :) ) 这是一个例子:

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

    timeit 测量它比我快一分半:)

    如果你不关心边缘情况,使用它可能会更好

    positive = np.signbit(a2)
    

    但是 positive = a2 >0 似乎比符号位和检查 0 更快(更干净)(例如 positive = np.bitwise_or(np.signbit(a2),np.logical_not(a2)) 更慢...)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-09
      • 1970-01-01
      • 1970-01-01
      • 2011-02-08
      • 2011-11-24
      • 1970-01-01
      • 2012-03-16
      • 2016-01-01
      相关资源
      最近更新 更多