Проверка наличия массива NumPy в списке Python - PullRequest
3 голосов
/ 28 мая 2020

У меня есть список из numpy массивов, и я хотел бы проверить, есть ли данный массив в списке. У этого есть очень странное поведение, и мне интересно, как это обойти. Вот простая версия проблемы:

import numpy as np
x = np.array([1,1])
a = [x,1]

x in a        # Returns True
(x+1) in a    # Throws ValueError
1 in a        # Throws ValueError

Я не понимаю, что здесь происходит. Есть ли хороший способ решения этой проблемы?

Я работаю с Python 3.7.

Изменить: точная ошибка:

ValueError: The truth value of an array with more than one element is ambiguous.  Use a.any() or a.all()

My numpy версия 1.18.1.

Ответы [ 4 ]

1 голос
/ 28 мая 2020

Причина в том, что in более или менее интерпретируется как

def in_sequence(elt, seq):
    for i in seq:
        if elt == i:
            return True
    return False

А 1 == x не дает False, но вызывает исключение, потому что внутренне numpy преобразует его в массив логические. В большинстве случаев это имеет смысл, но здесь это приводит к глупому поведению.

Звучит как ошибка, но исправить ее нелегко. Обработка 1 == np.array(1, 1) так же, как np.array(1, 1) == np.array(1, 1), является основной особенностью numpy. А делегирование сравнения на равенство классам - основная особенность Python. Поэтому я даже представить себе не могу, что должно быть правильным. *

1 голос
/ 28 мая 2020

( EDIT : для включения более общего и, возможно, более чистого подхода)

Один из способов обойти это - реализовать NumPy безопасную версию in:

import numpy as np


def in_np(x, items):
    for item in items:
        if isinstance(x, np.ndarray) and isinstance(item, np.ndarray) \
                and x.shape == item.shape and np.all(x == item):
            return True
        elif isinstance(x, np.ndarray) or isinstance(item, np.ndarray):
            pass
        elif x == item:
            return True
    return False
x = np.array([1, 1])
a = [x, 1]

for y in (x, 0, 1, x + 1, np.array([1, 1, 1])):
    print(in_np(y, a))
# True
# False
# True
# False
# False

Или, что еще лучше, написать версию in с произвольным сравнением (возможно, с поведением по умолчанию in), а затем использовать np.array_equal(), который имеет semanti c, который соответствует ожидаемому поведению ==. В коде:

import operator


def in_(x, items, eq=operator.eq):
    for item in items:
        if eq(x, item):
            return True
    return False
x = np.array([1, 1])
a = [x, 1]

for y in (x, 0, 1, x + 1, np.array([1, 1, 1])):
    print(in_(y, a, np.array_equal))
# True
# False
# True
# False
# False

Наконец, обратите внимание, что items может быть любым итеративным, но сложность операции не будет O(1) для контейнеров хеширования, таких как set(), хотя он все равно будет давать правильные результаты:

print(in_(1, {1, 2, 3}))
# True
print(in_(0, {1, 2, 3}))
# False

in_(1, {1: 2, 3: 4})
# True
in_(0, {1: 2, 3: 4})
# False
0 голосов
/ 28 мая 2020

При использовании x in [1,x], python будет сравнивать x с каждым из элементов в списке, а во время сравнения x == 1 результатом будет numpy массив:

>>> x == 1
array([ True,  True])

и интерпретация этого массива как значения bool вызовет ошибку из-за внутренней неоднозначности:

>>> bool(x == 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
0 голосов
/ 28 мая 2020

Сделать это можно так:

import numpy as np
x = np.array([1,1])
a = np.array([x.tolist(), 1])

x in a # True
(x+1) in a # False
1 in a # True
...