Python 计算数组中过零次数的代码

Python code for counting number of zero crossings in an array

我想计算数组中的值改变极性的次数(编辑:数组中的值越过零的次数)。

假设我有一个数组:

[80.6  120.8  -115.6  -76.1  131.3  105.1  138.4  -81.3
 -95.3  89.2  -154.1  121.4  -85.1  96.8  68.2]`

我希望计数为 8。

一个解决方案是 运行 循环并检查是否大于或小于 0,并保留先前极性的历史记录。

我们可以做得更快吗?

编辑:我的目的真的是找到更快的东西,因为我有这些长度约为 68554308 的数组,我必须对 100 多个这样的数组进行这些计算。

我认为循环是一种直接的方式:

a = [80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3, 89.2, -154.1, 121.4, -85.1, 96.8, 68.2]

def change_sign(v1, v2):
    return v1 * v2 < 0

s = 0
for ind, _ in enumerate(a):
    if ind+1 < len(a):
        if change_sign(a[ind], a[ind+1]):
            s += 1
print s  # prints 8

您可以使用生成器表达式,但它变得很丑陋:

z_cross = sum(1 for ind, val in enumerate(a) if (ind+1 < len(a)) 
              if change_sign(a[ind], a[ind+1]))
print z_cross  # prints 8

编辑:

@Alik 指出,对于巨大的列表,space 和时间(至少在我们考虑过的解决方案之外)的最佳选择不是在生成器表达式中调用 change_sign,而是简单地做:

z_cross = sum(1 for i, _ in enumerate(a) if (i+1 < len(a)) if a[i]*a[i+1]<0)

您似乎想按符号对数字进行分组。这可以使用内置方法 groupby:

来完成
In [2]: l = [80.6,  120.8,  -115.6,  -76.1,  131.3,  105.1,  138.4,  -81.3, -95.3,  89.2,  -154.1,  121.4,  -85.1,  96.8,  68.2]

In [3]: from itertools import groupby

In [5]: list(groupby(l, lambda x: x < 0))
Out[5]: 
[(False, <itertools._grouper at 0x7fc9022095f8>),
 (True, <itertools._grouper at 0x7fc902209828>),
 (False, <itertools._grouper at 0x7fc902209550>),
 (True, <itertools._grouper at 0x7fc902209e80>),
 (False, <itertools._grouper at 0x7fc902209198>),
 (True, <itertools._grouper at 0x7fc9022092e8>),
 (False, <itertools._grouper at 0x7fc902209240>),
 (True, <itertools._grouper at 0x7fc902209908>),
 (False, <itertools._grouper at 0x7fc9019a64e0>)]

那么你应该使用函数 len 其中 returns 组数:

In [7]: len(list(groupby(l, lambda x: x < 0)))
Out[7]: 9

显然,至少会有一组(对于非空列表),但是如果你想计算序列改变其极性的点数,你可以只减去一组。不要忘记空列表的情况。

你还应该注意零元素:它们不应该被提取到另一个组中吗?如果是这样,您可以只更改 groupby 函数的 key 参数(lambda 函数)。

您可以使用列表理解来实现它:

myList = [80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3,  89.2, -154.1, 121.4, -85.1, 96.8, 68.2]
len([x for i, x in enumerate(myList) if i > 0 and ((myList[i-1] > 0 and myList[i] < 0) or (myList[i-1] < 0 and myList[i] > 0))])

这是一个 numpy 解决方案。 Numpy 的方法通常非常快速且经过优化,但如果您还没有使用 numpy,那么将列表转换为 numpy 数组可能会产生一些开销:

import numpy as np
my_list = [80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3,  89.2, -154.1, 121.4, -85.1, 96.8, 68.2]
(np.diff(np.sign(my_list)) != 0).sum()
Out[8]: 8

基于

Scott 提出的生成器表达式使用 enumerate 其中 returns 个包含索引和列表项的元组。列表项根本不在表达式中使用,稍后将被丢弃。所以就时间而言更好的解决方案是

sum(1 for i in range(1, len(a)) if a[i-1]*a[i]<0)

如果您的列表 a 确实很大,range 可能会抛出异常。您可以将其替换为 itertools.isliceitertools.count.

在 Python 版本 2.x 中,使用 xrange 代替 Python 3 的 range。 在 Python 3 中,xrange 不再可用。

这会产生相同的结果:

import numpy as np
my_array = np.array([80.6, 120.8, -115.6, -76.1, 131.3, 105.1, 138.4, -81.3, -95.3,  
                     89.2, -154.1, 121.4, -85.1, 96.8, 68.2])
((my_array[:-1] * my_array[1:]) < 0).sum()

给出:

8

似乎是最快的解决方案:

%timeit ((my_array[:-1] * my_array[1:]) < 0).sum()
100000 loops, best of 3: 11.6 µs per loop

与迄今为止最快的相比:

%timeit (np.diff(np.sign(my_array)) != 0).sum()
10000 loops, best of 3: 22.2 µs per loop

也适用于更大的数组:

big = np.random.randint(-10, 10, size=10000000)

这个:

%timeit ((big[:-1] * big[1:]) < 0).sum()
10 loops, best of 3: 62.1 ms per loop

对比:

%timeit (np.diff(np.sign(big)) != 0).sum()
1 loops, best of 3: 97.6 ms per loop