Поиск уникальных строк в numpy.array
Мне нужно найти уникальные строки в numpy.array
.
Например:
>>> a # I have
array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
>>> new_a # I want to get to
array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 0]])
Я знаю, что могу создать набор и выполнять цикл над массивом, но я ищу эффективное чистое numpy
решение. Я считаю, что есть способ установить тип данных равным void, и тогда я мог бы просто использовать numpy.unique
, но я не мог понять, как заставить это работать.
Переведено автоматически
Ответ 1
Начиная с NumPy 1.13, можно просто выбрать ось для выбора уникальных значений в любом N-dim массиве. Чтобы получить уникальные строки, используйте np.unique
следующим образом:
unique_rows = np.unique(original_array, axis=0)
Ответ 2
Еще одно возможное решение
np.vstack({tuple(row) for row in a})
Редактировать: Как упоминали другие, этот подход устарел с версии NumPy 1.16. В современных версиях вы можете сделать
np.vstack(tuple(set(map(tuple,a))))
Где map(tuple,a)
делает каждую строку матрицы a
хэшируемой, превращая ее в кортежи. set(map(tuple,a))
создает набор из всех этих уникальных строк. Наборы являются непоследовательными итерациями и как таковые больше не могут использоваться напрямую для построения массивов NumPy. Внешний вызов tuple
устраняет эту проблему путем преобразования набора в кортеж, что делает его приемлемым для создания массива.
Ответ 3
Другим вариантом использования структурированных массивов является использование представления типа void
, которое объединяет всю строку в один элемент:
a = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
b = np.ascontiguousarray(a).view(np.dtype((np.void, a.dtype.itemsize * a.shape[1])))
_, idx = np.unique(b, return_index=True)
unique_a = a[idx]
>>> unique_a
array([[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
Редактировать
Добавлено np.ascontiguousarray
по рекомендации @seberg . Это замедлит работу метода, если массив еще не является непрерывным.
РЕДАКТИРОВАТЬ Вышесказанное можно немного ускорить, возможно, за счет ясности, выполнив:
unique_a = np.unique(b).view(a.dtype).reshape(-1, a.shape[1])
Кроме того, по крайней мере, в моей системе, с точки зрения производительности он на одном уровне или даже лучше, чем метод lexsort:
a = np.random.randint(2, size=(10000, 6))
%timeit np.unique(a.view(np.dtype((np.void, a.dtype.itemsize*a.shape[1])))).view(a.dtype).reshape(-1, a.shape[1])
100 loops, best of 3: 3.17 ms per loop
%timeit ind = np.lexsort(a.T); a[np.concatenate(([True],np.any(a[ind[1:]]!=a[ind[:-1]],axis=1)))]
100 loops, best of 3: 5.93 ms per loop
a = np.random.randint(2, size=(10000, 100))
%timeit np.unique(a.view(np.dtype((np.void, a.dtype.itemsize*a.shape[1])))).view(a.dtype).reshape(-1, a.shape[1])
10 loops, best of 3: 29.9 ms per loop
%timeit ind = np.lexsort(a.T); a[np.concatenate(([True],np.any(a[ind[1:]]!=a[ind[:-1]],axis=1)))]
10 loops, best of 3: 116 ms per loop
Ответ 4
Если вы хотите избежать затрат памяти на преобразование в серию кортежей или другую подобную структуру данных, вы можете использовать структурированные массивы numpy.
Хитрость заключается в просмотре вашего исходного массива как структурированного массива, где каждый элемент соответствует строке исходного массива. Это не создает копию и довольно эффективно.
В качестве краткого примера:
import numpy as np
data = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
ncols = data.shape[1]
dtype = data.dtype.descr * ncols
struct = data.view(dtype)
uniq = np.unique(struct)
uniq = uniq.view(data.dtype).reshape(-1, ncols)
print uniq
Чтобы понять, что происходит, взгляните на промежуточные результаты.
Когда мы рассматриваем объекты как структурированный массив, каждый элемент массива является строкой в вашем исходном массиве. (По сути, это структура данных, аналогичная списку кортежей.)
In [71]: struct
Out[71]:
array([[(1, 1, 1, 0, 0, 0)],
[(0, 1, 1, 1, 0, 0)],
[(0, 1, 1, 1, 0, 0)],
[(1, 1, 1, 0, 0, 0)],
[(1, 1, 1, 1, 1, 0)]],
dtype=[('f0', '<i8'), ('f1', '<i8'), ('f2', '<i8'), ('f3', '<i8'), ('f4', '<i8'), ('f5', '<i8')])
In [72]: struct[0]
Out[72]:
array([(1, 1, 1, 0, 0, 0)],
dtype=[('f0', '<i8'), ('f1', '<i8'), ('f2', '<i8'), ('f3', '<i8'), ('f4', '<i8'), ('f5', '<i8')])
Как только мы запустим numpy.unique
, мы получим обратно структурированный массив:
In [73]: np.unique(struct)
Out[73]:
array([(0, 1, 1, 1, 0, 0), (1, 1, 1, 0, 0, 0), (1, 1, 1, 1, 1, 0)],
dtype=[('f0', '<i8'), ('f1', '<i8'), ('f2', '<i8'), ('f3', '<i8'), ('f4', '<i8'), ('f5', '<i8')])
Которые затем нам нужно просмотреть как "обычный" массив (_
хранит результат последнего вычисления в ipython
, вот почему вы видите _.view...
):
In [74]: _.view(data.dtype)
Out[74]: array([0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0])
А затем преобразуйте обратно в 2D массив (-1
это заполнитель, который сообщает numpy вычислить правильное количество строк, указать количество столбцов):
In [75]: _.reshape(-1, ncols)
Out[75]:
array([[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
Очевидно, что если вы хотите быть более кратким, вы могли бы написать это как:
import numpy as np
def unique_rows(data):
uniq = np.unique(data.view(data.dtype.descr * data.shape[1]))
return uniq.view(data.dtype).reshape(-1, data.shape[1])
data = np.array([[1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0],
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 0]])
print unique_rows(data)
Что приводит к:
[[0 1 1 1 0 0]
[1 1 1 0 0 0]
[1 1 1 1 1 0]]