Найти индексы строк для нескольких значений в массиве numpy
У меня есть массив X:
X = np.array([[4, 2],
[9, 3],
[8, 5],
[3, 3],
[5, 6]])
И я хочу найти индекс строки из нескольких значений в этом массиве:
searched_values = np.array([[4, 2],
[3, 3],
[5, 6]])
Для этого примера я хотел бы получить результат, подобный:
[0,3,4]
У меня есть код для этого, но я думаю, что он слишком сложный:
X = np.array([[4, 2],
[9, 3],
[8, 5],
[3, 3],
[5, 6]])
searched_values = np.array([[4, 2],
[3, 3],
[5, 6]])
result = []
for s in searched_values:
idx = np.argwhere([np.all((X-s)==0, axis=1)])[0][1]
result.append(idx)
print(result)
Я нашел этот ответ на аналогичный вопрос, но он работает только для одномерных массивов.
Есть ли способ сделать то, что я хочу, более простым способом?
Переведено автоматически
Ответ 1
Подход # 1
Одним из подходов было бы использовать NumPy broadcasting
, вот так -
np.where((X==searched_values[:,None]).all(-1))[1]
Подход # 2
Экономичным подходом к использованию памяти было бы преобразовать каждую строку в эквиваленты линейных индексов, а затем использовать np.in1d
, вот так -
dims = X.max(0)+1
out = np.where(np.in1d(np.ravel_multi_index(X.T,dims),\
np.ravel_multi_index(searched_values.T,dims)))[0]
Подход #3
Другой подход, позволяющий экономить память, использующий np.searchsorted
и с той же философией преобразования в эквиваленты линейных индексов, был бы таким -
dims = X.max(0)+1
X1D = np.ravel_multi_index(X.T,dims)
searched_valuesID = np.ravel_multi_index(searched_values.T,dims)
sidx = X1D.argsort()
out = sidx[np.searchsorted(X1D,searched_valuesID,sorter=sidx)]
Пожалуйста, обратите внимание, что этот np.searchsorted
метод предполагает, что для каждой строки из searched_values
in X
есть совпадение.
Как это np.ravel_multi_index
работает?
Эта функция выдает нам числа, эквивалентные линейному индексу. Он принимает 2D
массив n-dimensional indices
, заданный в виде столбцов, и форму самой этой n-мерной сетки, на которую должны быть отображены эти индексы и вычислены эквивалентные линейные индексы.
Давайте воспользуемся имеющимися у нас входными данными для решения поставленной задачи. Возьмем пример input X
и обратите внимание на его первую строку. Поскольку мы пытаемся преобразовать каждую строку из X
в ее линейный индексный эквивалент и поскольку np.ravel_multi_index
предполагается, что каждый столбец является одним индексирующим кортежем, нам нужно выполнить транспонирование X
перед загрузкой в функцию. Поскольку количество элементов в строке в X
в этом случае равно 2
, n-мерная сетка, на которую будет нанесена карта, будет 2D
. С 3 элементами в строке в X
, это была бы 3D
сетка для отображения и так далее.
Чтобы увидеть, как эта функция будет вычислять линейные индексы, рассмотрим первую строку X
-
In [77]: X
Out[77]:
array([[4, 2],
[9, 3],
[8, 5],
[3, 3],
[5, 6]])
We have the shape of the n-dimensional grid as dims
-
In [78]: dims
Out[78]: array([10, 7])
Let's create the 2-dimensional grid to see how that mapping works and linear indices get computed with np.ravel_multi_index
-
In [79]: out = np.zeros(dims,dtype=int)
In [80]: out
Out[80]:
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]])
Let's set the first indexing tuple from X
, i.e. the first row from X
into the grid -
In [81]: out[4,2] = 1
In [82]: out
Out[82]:
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]])
Now, to see the linear index equivalent of the element just set, let's flatten and use np.where
to detect that 1
.
In [83]: np.where(out.ravel())[0]
Out[83]: array([30])
This could also be computed if row-major ordering is taken into account.
Let's use np.ravel_multi_index
and verify those linear indices -
In [84]: np.ravel_multi_index(X.T,dims)
Out[84]: array([30, 66, 61, 24, 41])
Thus, we would have linear indices corresponding to each indexing tuple from X
, i.e. each row from X
.
Choosing dimensions for np.ravel_multi_index
to form unique linear indices
Итак, идея рассмотрения каждой строки X
как индексирующего кортежа n-мерной сетки и преобразования каждого такого кортежа в скаляр состоит в том, чтобы иметь уникальные скаляры, соответствующие уникальным кортежам, т.Е. Уникальным строкам в X
.
Давайте еще раз взглянем на X
-
In [77]: X
Out[77]:
array([[4, 2],
[9, 3],
[8, 5],
[3, 3],
[5, 6]])
Теперь, как обсуждалось в предыдущем разделе, мы рассматриваем каждую строку как индексирующий кортеж. Внутри каждого такого индексирующего кортежа первый элемент будет представлять первую ось n-dim сетки, второй элемент будет второй осью сетки и так далее до последнего элемента каждой строки в X
. По сути, каждый столбец будет представлять одно измерение или ось сетки. Если мы хотим отобразить все элементы из X
на одну и ту же сетку n-dim, нам нужно учитывать максимальное растяжение каждой оси такой предлагаемой сетки n-dim. Предполагая, что мы имеем дело с положительными числами в X
, такое растяжение будет максимальным для каждого столбца в X
+ 1. Это + 1
потому, что Python следует за 0-based
индексацией. Так, например, X[1,0] == 9
будет отображаться в 10-ю строку предлагаемой сетки. Аналогично, X[4,1] == 6
перейдет к 7th
столбцу этой сетки.
Итак, для нашего примера у нас было -
In [7]: dims = X.max(axis=0) + 1 # Or simply X.max(0) + 1
In [8]: dims
Out[8]: array([10, 7])
Таким образом, нам понадобится сетка, по крайней мере, формы (10,7)
для нашего примера. Увеличение длины по размерам не повредит и также даст нам уникальные линейные индексы.
Заключительные замечания : Здесь следует отметить одну важную вещь: если у нас есть отрицательные числа в X
, нам нужно добавить соответствующие смещения вдоль каждого столбца в X
, чтобы сделать эти индексирующие кортежи положительными числами перед использованием np.ravel_multi_index
.
Ответ 2
Пакет numpy_indexed (отказ от ответственности: я являюсь его автором) содержит функциональность для эффективного выполнения таких операций (также использует searchsorted под капотом). С точки зрения функциональности он действует как векторизованный эквивалент list.index:
import numpy_indexed as npi
result = npi.indices(X, searched_values)
Обратите внимание, что с помощью 'missing' kwarg у вас есть полный контроль над поведением отсутствующих элементов, и это работает также для nd-массивов (fi; стеки изображений).
Обновление: используя те же формы, что и @Rik X=[520000,28,28]
и searched_values=[20000,28,28]
, он выполняется в 0.8064 secs
, используя missing=-1 для обнаружения и обозначения записей, отсутствующих в X.
Ответ 3
Другой альтернативой является использование asvoid
(ниже) для view
каждой строки в качестве единственного значения void
dtype. Это уменьшает 2D-массив до 1D-массива, что позволяет вам использовать np.in1d
как обычно:
import numpy as np
def asvoid(arr):
"""
Based on http://pythonly.ru/a/16973510/190597 (Jaime, 2013-06)
View the array as dtype np.void (bytes). The items along the last axis are
viewed as one value. This allows comparisons to be performed which treat
entire rows as one value.
"""
arr = np.ascontiguousarray(arr)
if np.issubdtype(arr.dtype, np.floating):
""" Care needs to be taken here since
np.array([-0.]).view(np.void) != np.array([0.]).view(np.void)
Adding 0. converts -0. to 0.
"""
arr += 0.
return arr.view(np.dtype((np.void, arr.dtype.itemsize * arr.shape[-1])))
X = np.array([[4, 2],
[9, 3],
[8, 5],
[3, 3],
[5, 6]])
searched_values = np.array([[4, 2],
[3, 3],
[5, 6]])
idx = np.flatnonzero(np.in1d(asvoid(X), asvoid(searched_values)))
print(idx)
# [0 3 4]
Ответ 4
Вот довольно быстрое решение, которое хорошо масштабируется с помощью numpy и hashlib. Оно может обрабатывать матрицы больших размеров или изображения за считанные секунды. Я использовал это для массива 520000 X (28 X 28) и 20000 X (28 X 28) за 2 секунды на моем процессоре
Код:
import numpy as np
import hashlib
X = np.array([[4, 2],
[9, 3],
[8, 5],
[3, 3],
[5, 6]])
searched_values = np.array([[4, 2],
[3, 3],
[5, 6]])
#hash using sha1 appears to be efficient
xhash=[hashlib.sha1(row).digest() for row in X]
yhash=[hashlib.sha1(row).digest() for row in searched_values]
z=np.in1d(xhash,yhash)
##Use unique to get unique indices to ind1 results
_,unique=np.unique(np.array(xhash)[z],return_index=True)
##Compute unique indices by indexing an array of indices
idx=np.array(range(len(xhash)))
unique_idx=idx[z][unique]
print('unique_idx=',unique_idx)
print('X[unique_idx]=',X[unique_idx])
Вывод:
unique_idx= [4 3 0]
X[unique_idx]= [[5 6]
[3 3]
[4 2]]