Понимание странного логического поведения индексации 2d-массива в numpy
Почему это работает:
a=np.random.rand(10,20)
x_range=np.arange(10)
y_range=np.arange(20)
a_tmp=a[x_range<5,:]
b=a_tmp[:,np.in1d(y_range,[3,4,8])]
и это не так:
a=np.random.rand(10,20)
x_range=np.arange(10)
y_range=np.arange(20)
b=a[x_range<5,np.in1d(y_range,[3,4,8])]
Ответы
Ответ 1
Справочная документация Numpy по индексированию содержит ответы, но требует немного тщательного чтения.
Ответ здесь заключается в том, что индексирование с булевыми значениями эквивалентно индексированию с целыми массивами, полученными сначала преобразованием булевых массивов с помощью np.nonzero
. Следовательно, с булевыми массивами m1
, m2
a[m1, m2] == a[m1.nonzero(), m2.nonzero()]
который (когда он преуспеет, т.е. m1.nonzero().shape == m2.nonzero().shape
), эквивалентен:
[a[i, i] for i in range(a.shape[0]) if m1[i] and m2[i]]
Я не уверен, почему он был разработан так, чтобы работать так: обычно это не то, что вам нужно.
Чтобы получить более интуитивный результат, вы можете вместо этого сделать
a[np.ix_(m1, m2)]
который дает результат, эквивалентный
[[a[i,j] for j in range(a.shape[1]) if m2[j]] for i in range(a.shape[0]) if m1[i]]
Ответ 2
Альтернативой np.ix_
является преобразование булевых массивов в целые массивы (используя np.nonzero()
), а затем используйте np.newaxis
для создания массивов правильной формы, чтобы использовать преимущества трансляции.
import numpy as np
a=np.random.rand(10,20)
x_range=np.arange(10)
y_range=np.arange(20)
a_tmp=a[x_range<5,:]
b_correct=a_tmp[:,np.in1d(y_range,[3,4,8])]
m1=(x_range<5).nonzero()[0]
m2=np.in1d(y_range,[3,4,8]).nonzero()
b=a[m1[:,np.newaxis], m2]
assert np.allclose(b,b_correct)
b2=a[np.ix_(x_range<5,np.in1d(y_range,[3,4,8]))]
assert np.allclose(b2,b_correct)
np.ix_
имеет тенденцию быть медленнее, чем двойное индексирование.
Решение с длинными формами выглядит немного быстрее:
длинная-форма:
In [83]: %timeit a[(x_range<5).nonzero()[0][:,np.newaxis], (np.in1d(y_range,[3,4,8])).nonzero()[0]]
10000 loops, best of 3: 131 us per loop
двойное индексирование:
In [85]: %timeit a[x_range<5,:][:,np.in1d(y_range,[3,4,8])]
10000 loops, best of 3: 144 us per loop
, используя np.ix _:
In [84]: %timeit a[np.ix_(x_range<5,np.in1d(y_range,[3,4,8]))]
10000 loops, best of 3: 160 us per loop
Примечание. Было бы неплохо проверить эти тайминги на вашем компьютере, поскольку рейтинги могут измениться в зависимости от вашей версии Python, numpy или hardware.