NumPy выбирает определенный индекс столбца для каждой строки с помощью списка индексов

90

Я изо всех сил пытаюсь выбрать определенные столбцы для каждой строки NumPyматрицы.

Предположим, у меня есть следующая матрица, которую я бы назвал X:

[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

У меня также есть listиндексы столбцов для каждой строки, которую я бы назвал Y:

[1, 0, 2]

Мне нужно получить значения:

[2]
[4]
[9]

Вместо a listс индексами Yя также могу создать матрицу той же формы, Xгде каждый столбец имеет значение bool/ intв диапазоне 0-1, указывая, является ли это обязательным столбцом.

[0, 1, 0]
[1, 0, 0]
[0, 0, 1]

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

Поэтому мне было интересно, есть ли лучшее решение?

Спасибо.

Зи
источник
Подходит ли вам ответ? stackoverflow.com/a/17081678/5046896
GoingMyWay

Ответы:

102

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

>>> a = np.array([True, True, True, False, False])
>>> b = np.array([1,2,3,4,5])
>>> b[a]
array([1, 2, 3])

Чтобы продолжить свой первоначальный пример, вы можете сделать следующее:

>>> a = np.array([[1,2,3], [4,5,6], [7,8,9]])
>>> b = np.array([[False,True,False],[True,False,False],[False,False,True]])
>>> a[b]
array([2, 4, 9])

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

>>> a = np.array([[1,2,3], [4,5,6], [7,8,9]])
>>> a[np.arange(len(a)), [1,0,2]]
array([2, 4, 9])

Надеюсь, это поможет, дайте мне знать, если у вас возникнут еще вопросы.

Слейтер Виктофф
источник
11
+1 для примера с использованием arange. Это было особенно полезно для меня при извлечении разных блоков из нескольких матриц (так что в основном трехмерный случай этого примера)
Griddo
1
Привет, не могли бы вы объяснить, почему мы должны использовать arangeвместо :? Я знаю, что ваш способ работает, а мой нет, но я хотел бы понять, почему.
marcotama
@tamzord, потому что это массив numpy, а не ванильный список python, поэтому :синтаксис работает иначе.
Slater Victoroff
1
@SlaterTyranus, спасибо за ответ. После некоторого чтения я понял, что смешивание :с расширенной индексацией означает: «для каждого подпространства :применять данную расширенную индексацию». Я правильно понимаю?
marcotama
@tamzord, объясни, что ты имеешь в виду под «подпространством»
Slater Victoroff
35

Вы можете сделать что-то вроде этого:

In [7]: a = np.array([[1, 2, 3],
   ...: [4, 5, 6],
   ...: [7, 8, 9]])

In [8]: lst = [1, 0, 2]

In [9]: a[np.arange(len(a)), lst]
Out[9]: array([2, 4, 9])

Подробнее об индексировании многомерных массивов: http://docs.scipy.org/doc/numpy/user/basics.indexing.html#indexing-multi-dimensional-arrays

Ашвини Чаудхари
источник
1
изо всех сил пытается понять, зачем нужен arange, а не просто ':' или range.
MadmanLee
@MadmanLee Привет, использование :будет выводить результаты несколько len(a)раз, вместо этого, указав индекс каждой строки, напечатает ожидаемые результаты.
GoingMyWay
1
Я считаю, что это как раз правильный и элегантный способ решить эту проблему.
GoingMyWay
6

Простой способ может выглядеть так:

In [1]: a = np.array([[1, 2, 3],
   ...: [4, 5, 6],
   ...: [7, 8, 9]])

In [2]: y = [1, 0, 2]  #list of indices we want to select from matrix 'a'

range(a.shape[0]) вернется array([0, 1, 2])

In [3]: a[range(a.shape[0]), y] #we're selecting y indices from every row
Out[3]: array([2, 4, 9])
Дхавал Маятра
источник
1
Пожалуйста, рассмотрите возможность добавления пояснений.
souki
@souki Я добавил объяснение. Спасибо
Дхавал Маятра 01
6

В последних numpyверсиях добавлен take_along_axisput_along_axis), который делает эту индексацию чисто.

In [101]: a = np.arange(1,10).reshape(3,3)                                                             
In [102]: b = np.array([1,0,2])                                                                        
In [103]: np.take_along_axis(a, b[:,None], axis=1)                                                     
Out[103]: 
array([[2],
       [4],
       [9]])

Действует так же, как:

In [104]: a[np.arange(3), b]                                                                           
Out[104]: array([2, 4, 9])

но с другой обработкой оси. Это особенно нацелено на применение результатов argsortи argmax.

hpaulj
источник
3

Вы можете сделать это с помощью итератора. Как это:

np.fromiter((row[index] for row, index in zip(X, Y)), dtype=int)

Время:

N = 1000
X = np.zeros(shape=(N, N))
Y = np.arange(N)

#@Aशwini चhaudhary
%timeit X[np.arange(len(X)), Y]
10000 loops, best of 3: 30.7 us per loop

#mine
%timeit np.fromiter((row[index] for row, index in zip(X, Y)), dtype=int)
1000 loops, best of 3: 1.15 ms per loop

#mine
%timeit np.diag(X.T[Y])
10 loops, best of 3: 20.8 ms per loop
Кей Минагава
источник
1
OP упомянул, что он должен работать быстро на больших массивах, поэтому ваши тесты не очень репрезентативны. Мне любопытно, как ваш последний метод работает для (гораздо) больших массивов!
@moarningsun: Обновлено. np.diag(X.T[Y])так медленно ... Но np.diag(X.T)так быстро (10us). Не знаю почему.
Кей Минагава
0

Другой умный способ - сначала транспонировать массив, а затем проиндексировать его. Наконец, возьмите диагональ, это всегда правильный ответ.

X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
Y = np.array([1, 0, 2, 2])

np.diag(X.T[Y])

Шаг за шагом:

Исходные массивы:

>>> X
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])

>>> Y
array([1, 0, 2, 2])

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

>>> X.T
array([[ 1,  4,  7, 10],
       [ 2,  5,  8, 11],
       [ 3,  6,  9, 12]])

Получите строки в порядке Y.

>>> X.T[Y]
array([[ 2,  5,  8, 11],
       [ 1,  4,  7, 10],
       [ 3,  6,  9, 12],
       [ 3,  6,  9, 12]])

Теперь диагональ должна стать четкой.

>>> np.diag(X.T[Y])
array([ 2,  4,  9, 12]
Томас Девугдт
источник
1
Технически это работает и выглядит очень элегантно. Однако я считаю, что этот подход полностью разрушается, когда вы имеете дело с большими массивами. В моем случае NumPy проглотил 30 ГБ свопа и заполнил мой SSD. Вместо этого я рекомендую использовать расширенный подход к индексации.
5nefarious