问题 用于计算数组中零交叉数的Python代码


我期待计算数组中的值改变极性的次数(编辑:数组中的值交叉为零的次数)。

假设我有一个数组:

[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多个这样的数组上进行这些计算。


9728
2018-05-16 06:27


起源

什么是极性? - Alik
你确定你的预期数量是8,而不是6? - Scott
好的,那你在寻找过零的数量? - Scott
您应该考虑并行化代码:看看 multiprocessing.Pool.map_async, github.com/pydata/numexpr ,PyCUDA或MapReduce。 - Kolmar
确实!我一直在试验这个。看起来如此简单,我从来没有尝试过这种乘法,认为它太昂贵了。绝对是最好的答案。 - Rahul Murmuria


答案:


这会产生相同的结果:

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

VS:

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

8
2018-05-16 21:44



很好的答案。谢谢你在这里发帖。 - Scott
这不会占用像这样的列表 [ 1, 2, 1, 0, -1, -2, -1, 0, 1, 0]。我们不想添加: ... + (x == 0)? - user2437378


这是一个 numpy 解。 Numpy的方法通常非常快速且经过优化,但如果您还没有使用过 numpy 将列表转换为a可能会有一些开销 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

5
2018-05-16 08:22



这很疯狂。我针对我和@Alik解决方案运行此操作,并使用numpy获得不同的结果。知道为什么吗?看到 stackoverflow.com/questions/30279315/...。 - Scott
这计算输入中每​​个0的额外交叉。我认为通过以下方式解决了这个问题: (np.diff(np.sign(my_list)) != 0).sum() - (my_list == 0).sum() - user2437378


基于 斯科特的回答

Scott使用的生成器表达式 enumerate 返回包含索引和列表项的元组。列表项根本不在表达式中使用,稍后会丢弃。因此,在时间方面更好的解决方案

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

如果你的清单 a 真是太大了 range 可能会抛出异常。你可以用它替换它 itertools.islice 和 itertools.count

在Python 2.x版中,使用 xrange 而不是Python 3的 range。 在Python 3中, xrange 不再被提供。


2
2018-05-16 08:26



这个 当零时不起作用 在阵列中! [ 1, 2, 0, -1, 0, 0, -1, 2] 应该屈服 2 零过境,但事实并非如此。这是 一个正确处理零的解决方案。 - Serge Stroobandt


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

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指出,对于大型列表而言,空间和时间的最佳选择(至少在我们考虑过的解决方案中)不是 change_sign 在生成器表达式,但只是做:

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

1
2018-05-16 06:32



请注意: v1 和 v2 如果有不同的迹象 v1*v2 < 0,所以你可以稍微简化你的代码 - Alik
我不确定,我习惯用乘法测试符号,所以对我来说它使代码更具可读性。无论如何,你总是可以发表评论 change_sign 功能 - Alik
它可能在您的生成器表达式中很有用,但它肯定需要注释。 z_cross = sum(1 for i, _ in enumerate(a) if (i+1 < len(a)) if a[i]*a[i+1]<0)。还要注意,这不是列表理解,而是一个 发电机表达。 - Alik
@Scott,我希望scipy / numpy中的东西更快!感谢您积极的头脑风暴。在选择答案之前,我会花几分钟时间。 - Rahul Murmuria
@Scott,做了一些测试。平原循环似乎是最慢的解决方案,然后是你的发电机表达和 修改了生成器表达 领先25% - Alik


好像,你想按照他们的标志对数字进行分组。这可以使用内置方法完成 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 返回组的数量:

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

显然,将至少有一个组(对于非空列表),但如果要计算点数,序列改变其极性,则可以减去一个组。不要忘记清单案例。

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


0
2018-05-16 06:47



非常有前景的解决这可能比循环更快吗?我正在进行一些测试。 - Rahul Murmuria
这绝对是一种不同的方法,但使用groupby和lambda会比使用简单的for循环更昂贵。 - Shan Valleru
@RahulMurmuria,不,这比循环更快,但它肯定比常规的基于循环的解决方案更具可读性。如果你在谈论速度,你还应该提供最坏的情况和预期的时间。 - soon


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

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

0
2018-05-16 07:01



在第一次迭代中,你不是要将mylist中的最后一个值与第一个值进行比较吗?即何时 i=0 你有 myList[-1]>0 and myList[0]<0... - Scott
如果第一个值是-80.6,这可能是坏事,在我们的例子中我们预计会有9个过零点,但是你的解决方案会给出10个。 - Scott
谢谢斯科特,这是事实,我添加了解决这个问题的条件。 - fmatheis