Подсчет вхождений определенного истинного / ложного порядка в массиве Numpy - PullRequest
0 голосов
/ 09 ноября 2018

У меня есть Numpy Array со значениями True и False, такими как:

test = np.array([False, False, False, True, False, True, False, True, False,False, False, False, True, True, False, True])

Я хотел бы знать, сколько раз в массиве встречается следующий шаблон (False, True, False). В приведенном выше тесте это будет 4. Это не единственный шаблон, но я предполагаю, что, когда я пойму этот код, я, вероятно, смогу создать и другие.

Конечно, я могу перебрать массив. Если первое значение равно, сравните следующее и в противном случае перейдите к следующему значению в цикле. Как это:

totalTimes=0
def swapToBegin(x):
    if(x>=len(test)):
        x-=len(test)
    return(x)
for i in range(len(test)):
    if(test[i]==False):
        if(test[swapToBegin(i+1)]==True):
            if test[swapToBegin(i+2)]==False:
                totalTimes += 1

Однако, поскольку мне нужно делать это много раз, этот код будет очень медленным. Небольшие улучшения могут быть сделаны, так как это было сделано очень быстро, чтобы показать, что мне нужно. Но должно быть лучшее решение.

Есть ли лучший способ поиска шаблона в массиве? Не нужно объединять конец и начало массива, так как я смогу сделать это позже. Но если это можно включить, было бы неплохо.

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

Вы не предоставили никаких подробностей о том, насколько велика test, поэтому для оценки методов, которые я использовал, в ней есть 1000 элементов. Следующая важная часть состоит в том, чтобы фактически профилировать код. Вы не можете сказать, что это медленно (или быстро), пока не найдутся убедительные цифры, подтверждающие это. Ваш код работает на моем компьютере около 1,49 мс.

Вы можете часто получать улучшения с помощью numpy, удаляя циклы python и заменяя их на numpy функции. Таким образом, вместо того, чтобы тестировать каждый элемент по отдельности (множество условий if может замедлить процесс), я поместил все это в сравнение с одним массивом, а затем использовал all, чтобы проверить, что каждый элемент соответствует.

check = array([False, True, False])
sum([(test[i:i+3]==check).all() for i in range(len(test) - 2)])

Профилирование показывает, что оно работает за 1,91 мс.

Это на самом деле шаг назад. Итак, что может быть причиной замедления? Что ж, доступ к массиву с использованием [] создает новый объект массива, который может быть его частью. Лучшим подходом может быть создание одного большого массива со смещениями, а затем использование трансляции для сравнения.

sum((c_[test[:-2], test[1:-1], test[2:]] == check).all(1))

На этот раз check сравнивается с каждой строкой массива c_[test[:-2], test[1:-1], test[2:]]. Аргумент оси (1) all используется только для подсчета строк, которые соответствуют каждому элементу. Это работает в 40.1us. Это огромное улучшение.

Конечно, создание массива для широковещательной передачи будет сопряжено с большими затратами с точки зрения копирования элементов. Почему бы не сделать сравнения напрямую?

sum(all([test[i:len(test)-2+i]==v for i, v in enumerate(check)], 0))

Это работает в 18.7us.

Последняя идея ускорить процесс - это as_strided. Это продвинутый трюк для изменения шагов массива, чтобы получить массив смещений без копирования каких-либо данных. Обычно это не стоит усилий, но я привожу их сюда просто для удовольствия.

sum((np.lib.index_tricks.as_strided(test, (len(test) - len(check) + 1, len(check)), test.strides + (1, )) == check).all(1))

Это также работает около 40us. Таким образом, дополнительные усилия ничего не добавляют в этом случае.

0 голосов
/ 09 ноября 2018

Вы можете использовать массив, содержащий [False, True, False] и искать его вместо этого.

searchfor = np.array([False, True, False])
...