Код Python для подсчета числа пересечений нуля в массиве
Я рассчитываю подсчитать количество раз, когда значения в массиве изменяются в полярности (EDIT: количество раз, когда значения в массиве пересекают ноль).
Предположим, что у меня есть массив:
[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, и сохранить историю предыдущей полярности.
Можем ли мы сделать это быстрее?
EDIT: Моя цель - найти что-то быстрее, потому что у меня есть эти массивы длиной около 68554308, и я должен делать эти вычисления на более чем 100 таких массивах.
Ответы
Ответ 1
Это дает тот же результат:
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
Ответ 2
Здесь a 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
Ответ 3
На основе ответа Скотта
В выражении генератора, предложенном Скоттом, используется 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
больше не доступен.
Ответ 4
Я думаю, что цикл - это прямой способ:
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
EDIT:
@Alik отметил, что для огромных списков лучший вариант в пространстве и времени (по крайней мере, из рассмотренных нами решений) заключается не в вызове change_sign
в выражении генератора, а в простом выполнении:
z_cross = sum(1 for i, _ in enumerate(a) if (i+1 < len(a)) if a[i]*a[i+1]<0)
Ответ 5
Похоже, вы хотите группировать числа по их знаку. Это можно сделать с помощью встроенного метода 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
(лямбда-функцию) функции groupby
.
Ответ 6
Вы можете достичь этого, используя понимание списка:
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))])