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

Gotchas, где Numpy отличается от прямого питона?

Люди,

есть коллекция gotchas, где Numpy отличается от python, моменты, которые были озадачены и стоили времени?

"Ужас в тот момент я буду никогда не забуду!" "Вы будете, однако, - сказала королева, - если вы не сделайте меморандум об этом".

Например, NaN всегда проблемы и где угодно. Если вы можете объяснить это, не запуская его, дайте себе точку -

from numpy import array, NaN, isnan

pynan = float("nan")
print pynan is pynan, pynan is NaN, NaN is NaN
a = (0, pynan)
print a, a[1] is pynan, any([aa is pynan for aa in a])

a = array(( 0, NaN ))
print a, a[1] is NaN, isnan( a[1] )

(Я не стучаю в ноль, много хорошей работы там, просто подумайте, что FAQ или Wiki из gotchas будут полезны.)

Изменить: я надеялся собрать полдюжины gotchas (сюрпризы для людей, изучающих Numpy).
Тогда, если есть общие gotchas или, лучше, общие объяснения, мы могли бы поговорить о добавлении их в сообщество Wiki (где?) Похоже, что пока у нас недостаточно.

4b9b3361

Ответ 1

Самое большое для меня было то, что почти каждый стандартный оператор перегружен для распространения по массиву.

Определите список и массив

>>> l = range(10)
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> import numpy
>>> a = numpy.array(l)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Умножение дублирует список python, но распространяется по массиву numpy

>>> l * 2
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a * 2
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

Дополнение и деление не определены в списках python

>>> l + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "int") to list
>>> a + 2
array([ 2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
>>> l / 2.0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'list' and 'float'
>>> a / 2.0
array([ 0. ,  0.5,  1. ,  1.5,  2. ,  2.5,  3. ,  3.5,  4. ,  4.5])

Перегрузка нескольких элементов для обработки списков, например массивов

>>> a + a
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
>>> a + l
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

Ответ 2

Так как __eq__ не возвращает bool, использование массивов numpy в любых контейнерах предотвращает тестирование равенства без конкретной работы с контейнером.

Пример:

>>> import numpy
>>> a = numpy.array(range(3))
>>> b = numpy.array(range(3))
>>> a == b
array([ True,  True,  True], dtype=bool)
>>> x = (a, 'banana')
>>> y = (b, 'banana')
>>> x == y
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()

Это ужасная проблема. Например, вы не можете писать unittests для контейнеров, которые используют TestCase.assertEqual(), и вместо этого должны писать пользовательские функции сравнения. Предположим, что мы пишем функцию обхода special_eq_for_numpy_and_tuples. Теперь мы можем сделать это в unittest:

x = (array1, 'deserialized')
y = (array2, 'deserialized')
self.failUnless( special_eq_for_numpy_and_tuples(x, y) )

Теперь мы должны сделать это для каждого типа контейнера, который мы можем использовать для хранения массивов numpy. Кроме того, __eq__ может возвращать bool, а не массив bools:

>>> a = numpy.array(range(3))
>>> b = numpy.array(range(5))
>>> a == b
False

Теперь каждая из наших специфичных для контейнера функций сравнения равенства также должна обрабатывать этот частный случай.

Может быть, мы можем исправить эту бородавку с помощью подкласса?

>>> class SaneEqualityArray (numpy.ndarray):
...   def __eq__(self, other):
...     return isinstance(other, SaneEqualityArray) and self.shape == other.shape and (numpy.ndarray.__eq__(self, other)).all()
... 
>>> a = SaneEqualityArray( (2, 3) )
>>> a.fill(7)
>>> b = SaneEqualityArray( (2, 3) )
>>> b.fill(7)
>>> a == b
True
>>> x = (a, 'banana')
>>> y = (b, 'banana')
>>> x == y
True
>>> c = SaneEqualityArray( (7, 7) )
>>> c.fill(7)
>>> a == c
False

Это похоже на правильную вещь. Класс также должен явно экспортировать элементное сравнение, поскольку это часто полезно.

Ответ 3

Я думаю, что это смешно:

>>> import numpy as n
>>> a = n.array([[1,2],[3,4]])
>>> a[1], a[0] = a[0], a[1]
>>> a
array([[1, 2],
       [1, 2]])

Для списков Python, с другой стороны, это работает по назначению:

>>> b = [[1,2],[3,4]]
>>> b[1], b[0] = b[0], b[1]
>>> b
[[3, 4], [1, 2]]

Смешная заметка: у самой numpy была ошибка в функции shuffle, потому что она использовала эту нотацию:-) (см. здесь).

Причина в том, что в первом случае мы имеем дело с представлениями массива, поэтому значения перезаписываются на месте.

Ответ 4

NaN не является ничем не похожим на None, поэтому вы не можете использовать его. Что делает его немного сложнее, так это то, что NaN == NaN есть False, как требует IEEE-754. Для этого вам нужно использовать функцию numpy.isnan(), чтобы проверить, не является ли float числом. Или стандартная библиотека math.isnan(), если вы используете Python 2.6 +.

Ответ 5

Нарезка создает представления, а не копии.

>>> l = [1, 2, 3, 4]
>>> s = l[2:3]
>>> s[0] = 5
>>> l
[1, 2, 3, 4]

>>> a = array([1, 2, 3, 4])
>>> s = a[2:3]
>>> s[0] = 5
>>> a
array([1, 2, 5, 4])

Ответ 6

Значение истинности массива Numpy отличается от значения типа последовательности python, где истинна любая непустая последовательность.

>>> import numpy as np
>>> l = [0,1,2,3]
>>> a = np.arange(4)
>>> if l: print "Im true"
... 
Im true
>>> if a: print "Im true"
... 
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()
>>>

Числовые типы являются истинными, когда они отличны от нуля и как набор чисел, массив Nupy наследует это определение. Но с набором чисел истина могла бы разумно означать, что "все элементы отличны от нуля" или "по крайней мере один элемент отличен от нуля". Numpy отказывается догадываться о том, какое определение подразумевается, и повышает указанное выше исключение. Использование методов .any() и .all() позволяет указать, что означает значение true.

>>> if a.any(): print "Im true"
... 
Im true
>>> if a.all(): print "Im true"
... 
>>>

Ответ 7

In [1]: bool([])
Out[1]: False

In [2]: bool(array([]))
Out[2]: False

In [3]: bool([0])
Out[3]: True

In [4]: bool(array([0]))
Out[4]: False

Поэтому не проверяйте пустоту массива, проверяя его значение истины. Используйте size(array()).

И не используйте len(array()), либо:

In [1]: size(array([]))
Out[1]: 0

In [2]: len(array([]))
Out[2]: 0

In [3]: size(array([0]))
Out[3]: 1

In [4]: len(array([0]))
Out[4]: 1

In [5]: size(array(0))
Out[5]: 1

In [6]: len(array(0))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-5b2872696128> in <module>()
----> 1 len(array(0))

TypeError: len() of unsized object

Ответ 8

(Связанный, но NumPy vs. SciPy, а не NumPy vs Python)


Нарезка за пределами массива реального размера работает по-другому:

>>> import numpy, scipy.sparse

>>> m = numpy.random.rand(2, 5) # create a 2x5 dense matrix
>>> print m[:3, :] # works like list slicing in Python: clips to real size
[[ 0.12245393  0.20642799  0.98128601  0.06102106  0.74091038]
[ 0.0527411   0.9131837   0.6475907   0.27900378  0.22396443]]

>>> s = scipy.sparse.lil_matrix(m) # same for csr_matrix and other sparse formats
>>> print s[:3, :] # doesn't clip!
IndexError: row index out of bounds

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

Ответ 9

print pynan is pynan, pynan is NaN, NaN is NaN

Это проверяет идентичность, то есть, если это тот же объект. Следовательно, результат должен, очевидно, быть True, False, True, потому что когда вы делаете float (независимо), вы создаете новый объект float.

a = (0, pynan)
print a, a[1] is pynan, any([aa is pynan for aa in a])

Я не знаю, что это такое, что вы удивитесь с этим.

a = array(( 0, NaN ))
print a, a[1] is NaN, isnan( a[1] )

Это я должен был бежать.:-) Когда вы вставляете NaN в массив, он преобразуется в объект numpy.float64, поэтому "[1] сбой NaN не работает.

Все это кажется мне совершенно неудивительным. Но тогда я ничего не знаю о NumPy.: -)

Ответ 10

Никто, кажется, не упомянул об этом до сих пор:

>>> all(False for i in range(3))
False
>>> from numpy import all
>>> all(False for i in range(3))
True
>>> any(False for i in range(3))
False
>>> from numpy import any
>>> any(False for i in range(3))
True

numpy any и all не играют хорошо с генераторами и не предупреждают вас о том, что они этого не делают.

Ответ 11

от Нила Мартинсена-Баррелла в numpy-discussion 7 сентября -

Тип ndarray, доступный в Numpy, концептуально не расширение Итераторы Python. Если вы хотите помогите другим пользователям Numpy с этим вы можете редактировать документацию в редакторе онлайн-документации по адресу numpy-docs

Ответ 12

Я обнаружил тот факт, что умножение списков элементов просто создает представление об элементах, которые поймали меня.

>>> a=[0]*5
>>>a
[0,0,0,0,0]
>>>a[2] = 1
>>>a
[0,0,1,0,0]
>>>b = [np.ones(3)]*5
>>>b
[array([ 1.,  1.,  1.]), array([ 1.,  1.,  1.]), array([ 1.,  1.,  1.]), array([ 1.,  1.,  1.]), array([ 1.,  1.,  1.])]
>>>b[2][1] = 2
>>>b
[array([ 1.,  2.,  1.]), array([ 1.,  2.,  1.]), array([ 1.,  2.,  1.]), array([ 1.,  2.,  1.]), array([ 1.,  2.,  1.])]

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

Простое решение состоит в том, чтобы итеративно создавать каждый из массивов (используя "для цикла" или понимания списка) или использовать более высокий размерный массив (где, например, каждый из этих 1D-массивов представляет собой строку в вашей 2D-массиве, которая обычно быстрее).

Ответ 13

Не такая большая проблема: с булевым нарезкой я иногда хотел, чтобы я мог

  x[ 3 <= y < 7 ]

как двойное сравнение python. Вместо этого я должен написать

  x[ np.logical_and(3<=y, y<7) ]

(Если вы не знаете что-то лучше?)

Кроме того, np.logical_and и np.logical_or принимают только по два аргумента, я бы хотел, чтобы они взяли переменное число или список, чтобы я мог использовать не более двух логических предложений.

(numpy 1.3, возможно, все это изменилось в более поздних версиях.)

Ответ 14

Удивление с присваиванием *= в сочетании с numpy.array:

>>> from numpy import array

>>> a = array([1, 2, 3])
>>> a *= 1.1  
>>> print(a) 
[1 2 3]  # not quite what we expect or would like to see

>>> print(a.dtype)
int64  # and this is why

>>> a = 1.1 * a  # here, a new array is created
>>> print(a, a.dtype)
[ 1.1  2.2  3.3] float64  # with the expected outcome

Удивительно, раздражает, но понятно. Оператор *= не будет изменять тип данных array, поэтому умножение int array на float приведет к отказу от обычного значения этого умножения. Версия Python a = 1; a *= 1.1 с другой стороны работает так, как ожидалось.

Ответ 15

A 0-d массив None выглядит как None, но это не одно и то же:

In [1]: print None
None

In [2]: import numpy

In [3]: print numpy.array(None)
None

In [4]: numpy.array(None) is None
Out[4]: False

In [5]: numpy.array(None) == None
Out[5]: False

In [6]: print repr(numpy.array(None))
array(None, dtype=object)