问题 用于计算数组中零交叉数的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
起源
答案:
这会产生相同的结果:
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
这是一个 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
基于 斯科特的回答
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
我认为循环是一种直接的方式:
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
好像,你想按照他们的标志对数字进行分组。这可以使用内置方法完成 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
您可以使用列表理解来实现它:
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