Эффективное обнаружение изменений знака в python
Я хочу сделать именно то, что сделал этот парень:
Python - изменения счетных знаков
Однако мне нужно оптимизировать его для быстрого запуска. Короче говоря, я хочу взять временный ряд и рассказывать каждый раз, когда он пересекает ноль (меняет знак). Я хочу записать время между пересечениями нуля. Так как это реальные данные (32-битный float), я сомневаюсь, что у каждого будет число, равное нулю, так что это не важно. В настоящее время у меня есть программа синхронизации, поэтому я приму ваши результаты, чтобы увидеть, кто победит.
Мое решение дает (микросекунды):
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()
Ответы
Ответ 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 будет содержать индексы элементов, перед которыми происходит пересечение нуля. Если вы хотите элементы после, просто добавьте 1 к этому массиву.
Ответ 2
Как заметил Джей Борсет, принятый ответ не обрабатывает массивы, содержащие 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]
Ответ 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))
(тайминги для массива из 10000 образцов синуса, содержащих 20 циклов, всего 40 пересечений):
$ 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
Ответ 4
Джим Бриссом отвечает, если a содержит значение 0:
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, но поскольку знак() возвращает 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
Ответ 5
Вы хотите время? Или вы хотите сделать это как можно быстрее?
Сроки легко. Запустите его на миллион раз, отпустите его и разделите на миллион.
Чтобы сделать это как можно быстрее, вам нужно выяснить, что требуется, и что вы могли бы сделать лучше. Я использую либо 1) метод случайной паузы, либо 2) одношаговый метод.
Ответ 6
Я вижу людей, использующих diff в своих решениях, но xor, кажется, намного быстрее, и результат одинаковый для bools (хорошим указателем на это может быть тот факт, что использование diff дает устаревшее предупреждение....:))
Вот пример:
positive = a2 > 0
np.where(np.bitwise_xor(positive[1:], positive[:-1]))[0]
Время, когда оно измеряет его примерно на полтора раза быстрее для меня:)
Если вам не нужны граничные случаи, лучше использовать
positive = np.signbit(a2)
но положительный = a2 > 0 кажется более быстрым (и более чистым), чем signbit И проверка на 0s (например, положительный = np.bitwise_or (np.signbit(a2), np.logical_not (a2)) медленнее...)
Ответ 7
Другим способом, который может подойти некоторым приложениям, является расширение оценки выражения np.diff(np.sign(a))
.
Если мы сравним, как это выражение реагирует на определенные случаи:
- Восходящее пересечение без нуля:
np.diff(np.sign([-10, 10]))
возвращает array([2])
- Восходящее пересечение с нулем:
np.diff(np.sign([-10, 0, 10]))
возвращает array([1, 1])
- Падающий переход без нуля:
np.diff(np.sign([10, -10]))
возвращает array([-2])
- Нисходящее пересечение с нулем:
np.diff(np.sign([10, 0, -10]))
возвращает array([-1, -1])
Поэтому мы должны оценить np.diff(...)
для возвращенных шаблонов в 1. и 2:
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]
Этот подход должен быть достаточно быстрым, поскольку он может обходиться без использования "медленного" цикла.
Это объединяет подход нескольких других ответов.