Подтвердить что ты не робот

Проверка того, содержит ли массив Numpy заданную строку

Существует ли Pythonic и эффективный способ проверить, содержит ли массив Numpy хотя бы один экземпляр данной строки? "Эффективным" я подразумеваю, что он заканчивает поиск первой совпадающей строки, а не итерации по всему массиву, даже если результат уже найден.

С массивами Python это можно выполнить очень чисто с помощью if row in array:, но это не работает так, как я ожидал бы для массивов Numpy, как показано ниже.

С массивами Python:

>>> a = [[1,2],[10,20],[100,200]]
>>> [1,2] in a
True
>>> [1,20] in a
False

но массивы Numpy дают разные и довольно странные результаты. (Метод __contains__ ndarray кажется недокументированным.)

>>> a = np.array([[1,2],[10,20],[100,200]])
>>> np.array([1,2]) in a
True
>>> np.array([1,20]) in a
True
>>> np.array([1,42]) in a
True
>>> np.array([42,1]) in a
False
4b9b3361

Ответ 1

Numpys __contains__ есть на момент написания этого (a == b).any(), который, возможно, справедлив только в том случае, если b является скалярный (немного волосатый, но я считаю, что это работает только в 1.7 или более позднем - это будет правильный общий метод (a == b).all(np.arange(a.ndim - b.ndim, a.ndim)).any(), что имеет смысл для всех комбинаций размерности a и b)...

EDIT: просто чтобы быть ясным, это не обязательно ожидаемый результат при трансляции. Также кто-то может утверждать, что он должен обрабатывать элементы в a отдельно, как это делает np.in1d. Я не уверен, что есть один четкий способ, которым он должен работать.

Теперь вы хотите, чтобы numpy остановился, когда обнаружил первое вхождение. В настоящее время этого AFAIK не существует. Это сложно, потому что numpy основан в основном на ufuncs, которые делают то же самое по всему массиву. Numpy оптимизирует эти сокращения, но эффективно работает только тогда, когда уменьшенный массив уже является булевым массивом (т.е. np.ones(10, dtype=bool).any()).

В противном случае для __contains__, которая не существует, потребуется специальная функция. Это может показаться странным, но вы должны помнить, что numpy поддерживает многие типы данных и имеет более крупный механизм для выбора правильных и выбора правильной функции для работы над ним. Другими словами, механизм ufunc не может этого сделать, и реализация __contains__ или такая особенность на самом деле не является тривиальной из-за типов данных.

Вы можете, конечно, написать его в python, или, поскольку вы, вероятно, знаете свой тип данных, писать его сами в Cython/C очень просто.


Это сказало. Часто гораздо лучше использовать подход, основанный на сортировке для этих вещей. Это немного утомительно, так как нет searchsorted для lexsort, но он работает (вы также можете злоупотреблять scipy.spatial.cKDTree, если хотите). Предполагается, что вы хотите сравнить только по последней оси:

# Unfortunatly you need to use structured arrays:
sorted = np.ascontiguousarray(a).view([('', a.dtype)] * a.shape[-1]).ravel()

# Actually at this point, you can also use np.in1d, if you already have many b
# then that is even better.

sorted.sort()

b_comp = np.ascontiguousarray(b).view(sorted.dtype)
ind = sorted.searchsorted(b_comp)

result = sorted[ind] == b_comp

Это также работает для массива b, и если вы сохраняете отсортированный массив вокруг, также намного лучше, если вы делаете это для одного значения (строки) в b за раз, когда a остается то же самое (иначе я бы просто np.in1d после просмотра его как повторения). Важно: вы должны сделать np.ascontiguousarray для обеспечения безопасности. Обычно он ничего не делает, но если это произойдет, это будет большой потенциальной ошибкой в ​​противном случае.

Ответ 2

Вы можете использовать .tolist()

>>> a = np.array([[1,2],[10,20],[100,200]])
>>> [1,2] in a.tolist()
True
>>> [1,20] in a.tolist()
False
>>> [1,20] in a.tolist()
False
>>> [1,42] in a.tolist()
False
>>> [42,1] in a.tolist()
False

Или используйте представление:

>>> any((a[:]==[1,2]).all(1))
True
>>> any((a[:]==[1,20]).all(1))
False

Или сгенерируйте список numpy (возможно, ОЧЕНЬ МЕДЛЕННО):

any(([1,2] == x).all() for x in a)     # stops on first occurrence 

Или используйте логические функции numpy:

any(np.equal(a,[1,2]).all(1))

Если у вас есть время:

import numpy as np
import time

n=300000
a=np.arange(n*3).reshape(n,3)
b=a.tolist()

t1,t2,t3=a[n//100][0],a[n//2][0],a[-10][0]

tests=[ ('early hit',[t1, t1+1, t1+2]),
        ('middle hit',[t2,t2+1,t2+2]),
        ('late hit', [t3,t3+1,t3+2]),
        ('miss',[0,2,0])]

fmt='\t{:20}{:.5f} seconds and is {}'     

for test, tgt in tests:
    print('\n{}: {} in {:,} elements:'.format(test,tgt,n))

    name='view'
    t1=time.time()
    result=(a[...]==tgt).all(1).any()
    t2=time.time()
    print(fmt.format(name,t2-t1,result))

    name='python list'
    t1=time.time()
    result = True if tgt in b else False
    t2=time.time()
    print(fmt.format(name,t2-t1,result))

    name='gen over numpy'
    t1=time.time()
    result=any((tgt == x).all() for x in a)
    t2=time.time()
    print(fmt.format(name,t2-t1,result))

    name='logic equal'
    t1=time.time()
    np.equal(a,tgt).all(1).any()
    t2=time.time()
    print(fmt.format(name,t2-t1,result))

Вы можете видеть, что ударить или пропустить, процедуры numpy имеют одинаковую скорость поиска массива. Оператор Python in потенциально намного быстрее для раннего попадания, а генератор - просто плохая новость, если вам нужно пройти весь путь через массив.

Ниже приведены результаты для массива элементов размером 300 000 x 3:

early hit: [9000, 9001, 9002] in 300,000 elements:
    view                0.01002 seconds and is True
    python list         0.00305 seconds and is True
    gen over numpy      0.06470 seconds and is True
    logic equal         0.00909 seconds and is True

middle hit: [450000, 450001, 450002] in 300,000 elements:
    view                0.00915 seconds and is True
    python list         0.15458 seconds and is True
    gen over numpy      3.24386 seconds and is True
    logic equal         0.00937 seconds and is True

late hit: [899970, 899971, 899972] in 300,000 elements:
    view                0.00936 seconds and is True
    python list         0.30604 seconds and is True
    gen over numpy      6.47660 seconds and is True
    logic equal         0.00965 seconds and is True

miss: [0, 2, 0] in 300,000 elements:
    view                0.00936 seconds and is False
    python list         0.01287 seconds and is False
    gen over numpy      6.49190 seconds and is False
    logic equal         0.00965 seconds and is False

И для массива 3 000 000 x 3:

early hit: [90000, 90001, 90002] in 3,000,000 elements:
    view                0.10128 seconds and is True
    python list         0.02982 seconds and is True
    gen over numpy      0.66057 seconds and is True
    logic equal         0.09128 seconds and is True

middle hit: [4500000, 4500001, 4500002] in 3,000,000 elements:
    view                0.09331 seconds and is True
    python list         1.48180 seconds and is True
    gen over numpy      32.69874 seconds and is True
    logic equal         0.09438 seconds and is True

late hit: [8999970, 8999971, 8999972] in 3,000,000 elements:
    view                0.09868 seconds and is True
    python list         3.01236 seconds and is True
    gen over numpy      65.15087 seconds and is True
    logic equal         0.09591 seconds and is True

miss: [0, 2, 0] in 3,000,000 elements:
    view                0.09588 seconds and is False
    python list         0.12904 seconds and is False
    gen over numpy      64.46789 seconds and is False
    logic equal         0.09671 seconds and is False

Что, по-видимому, указывает, что np.equal является самым быстрым способом с чистым numpy для этого...

Ответ 3

Я думаю,

equal([1,2], a).all(axis=1)   # also,  ([1,2]==a).all(axis=1)
# array([ True, False, False], dtype=bool)

отобразит строки, которые соответствуют. Как указывает Джейми, чтобы узнать, существует ли хотя бы одна такая строка, используйте any:

equal([1,2], a).all(axis=1).any()
# True

Помимо этого:
Я подозреваю, что in__contains__) как указано выше, но использует any вместо all.

Ответ 4

Если вы действительно хотите остановиться в первом вхождении, вы можете написать цикл, например:

import numpy as np

needle = np.array([10, 20])
haystack = np.array([[1,2],[10,20],[100,200]])
found = False
for row in haystack:
    if np.all(row == needle):
        found = True
        break
print("Found: ", found)

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