Найти уникальные строки в 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-мерном массиве. Чтобы получить уникальные строки, можно сделать:

unique_rows = np.unique(original_array, axis=0)

Ответ 2

Еще одно возможное решение

np.vstack({tuple(row) for row in a})

Ответ 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

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

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

В качестве быстрого примера:

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]]

Ответ 5

np.unique, когда я запускаю его на np.random.random(100).reshape(10,10), возвращает все уникальные отдельные элементы, но вам нужны уникальные строки, поэтому сначала вам нужно поместить их в кортежи:

array = #your numpy array of lists
new_array = [tuple(row) for row in array]
uniques = np.unique(new_array)

Это единственный способ увидеть, как вы меняете типы, чтобы делать то, что вы хотите, и я не уверен, что итерация списка, чтобы изменить на кортежи, в порядке с вашей "непересекающейся"

Ответ 6

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

ind = np.lexsort(a.T)
a[ind[np.concatenate(([True],np.any(a[ind[1:]]!=a[ind[:-1]],axis=1)))]]

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

ПРИМЕЧАНИЕ. В предыдущей версии этого параметра не было ind right после символа [, что означает, что использовались неправильные индексы. Кроме того, Джо Кингтон прекрасно понимает, что это делает множество промежуточных копий. Следующий метод делает меньше, создавая отсортированную копию и затем используя ее виды:

b = a[np.lexsort(a.T)]
b[np.concatenate(([True], np.any(b[1:] != b[:-1],axis=1)))]

Это быстрее и использует меньше памяти.

Кроме того, если вы хотите найти уникальные строки в ndarray независимо от того, сколько измерений в массиве, будет работать следующее:

b = a[lexsort(a.reshape((a.shape[0],-1)).T)];
b[np.concatenate(([True], np.any(b[1:]!=b[:-1],axis=tuple(range(1,a.ndim)))))]

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

Edit:

Чтобы продемонстрировать разницу в скорости, я провел несколько тестов в ipython из трех различных методов, описанных в ответах. С вашим точным a, не так уж и много, хотя эта версия немного быстрее:

In [87]: %timeit unique(a.view(dtype)).view('<i8')
10000 loops, best of 3: 48.4 us per loop

In [88]: %timeit ind = np.lexsort(a.T); a[np.concatenate(([True], np.any(a[ind[1:]]!= a[ind[:-1]], axis=1)))]
10000 loops, best of 3: 37.6 us per loop

In [89]: %timeit b = [tuple(row) for row in a]; np.unique(b)
10000 loops, best of 3: 41.6 us per loop

При использовании большего размера, эта версия заканчивается намного быстрее:

In [96]: a = np.random.randint(0,2,size=(10000,6))

In [97]: %timeit unique(a.view(dtype)).view('<i8')
10 loops, best of 3: 24.4 ms per loop

In [98]: %timeit b = [tuple(row) for row in a]; np.unique(b)
10 loops, best of 3: 28.2 ms per loop

In [99]: %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: 3.25 ms per loop

Ответ 7

Вот еще одна вариация для ответа @Greg pythonic

np.vstack(set(map(tuple, a)))

Ответ 8

Мне не понравился ни один из этих ответов, потому что ни один из них не обрабатывает массивы с плавающей запятой в линейной алгебре или смысле векторного пространства, где две строки "равны" означают "в пределах некоторого ε". Один ответ, который имеет порог допуска, fooobar.com/questions/46697/..., принял пороговое значение как для элементарной, так и десятичной точности, которое работает в некоторых случаях, но не является математически общим как истинное векторное расстояние.

Вот моя версия:

from scipy.spatial.distance import squareform, pdist

def uniqueRows(arr, thresh=0.0, metric='euclidean'):
    "Returns subset of rows that are unique, in terms of Euclidean distance"
    distances = squareform(pdist(arr, metric=metric))
    idxset = {tuple(np.nonzero(v)[0]) for v in distances <= thresh}
    return arr[[x[0] for x in idxset]]

# With this, unique columns are super-easy:
def uniqueColumns(arr, *args, **kwargs):
    return uniqueRows(arr.T, *args, **kwargs)

Функция public-domain выше использует scipy.spatial.distance.pdist, чтобы найти эвклидовое (настраиваемое) расстояние между каждой парой строк. Затем он сравнивает каждое расстояние до thresh old, чтобы найти строки, которые находятся внутри thresh друг от друга, и возвращает только одну строку из каждого thresh -кластера.

Как намечено, расстояние metric не обязательно должно быть Euclidean- pdist может вычислять разные расстояния, включая cityblock (Манхэттен-норма) и cosine (угол между векторами).

Если thresh=0 (по умолчанию), то строки должны быть битовыми, чтобы считаться "уникальными". Другие хорошие значения для thresh используют масштабированную машинную точность, т.е. thresh=np.spacing(1)*1e3.

Ответ 9

Я сравнил предложенную альтернативу скорости и обнаружил, что, на удивление, решение void view unique даже немного быстрее, чем numpy native unique с аргументом axis. Если вы ищете скорость, вам нужно

numpy.unique(
    a.view(numpy.dtype((numpy.void, a.dtype.itemsize*a.shape[1])))
    ).view(a.dtype).reshape(-1, a.shape[1])

введите описание изображения здесь


Код для воспроизведения сюжета:

import numpy
import perfplot


def unique_void_view(a):
    return numpy.unique(
        a.view(numpy.dtype((numpy.void, a.dtype.itemsize*a.shape[1])))
        ).view(a.dtype).reshape(-1, a.shape[1])


def lexsort(a):
    ind = numpy.lexsort(a.T)
    return a[ind[
        numpy.concatenate((
            [True], numpy.any(a[ind[1:]] != a[ind[:-1]], axis=1)
            ))
        ]]


def vstack(a):
    return numpy.vstack({tuple(row) for row in a})


def unique_axis(a):
    return numpy.unique(a, axis=0)


perfplot.show(
    setup=lambda n: numpy.random.randint(2, size=(n, 20)),
    kernels=[unique_void_view, lexsort, vstack, unique_axis],
    n_range=[2**k for k in range(15)],
    logx=True,
    logy=True,
    xlabel='len(a)',
    equality_check=None
    )

Ответ 10

Почему бы не использовать drop_duplicates из pandas:

>>> timeit pd.DataFrame(image.reshape(-1,3)).drop_duplicates().values
1 loops, best of 3: 3.08 s per loop

>>> timeit np.vstack({tuple(r) for r in image.reshape(-1,3)})
1 loops, best of 3: 51 s per loop

Ответ 11

Пакет numpy_indexed (отказ от ответственности: я являюсь его автором) завершает решение, опубликованное Jaime в приятном и проверенном интерфейсе, а также множество других функций

import numpy_indexed as npi
new_a = npi.unique(a)  # unique elements over axis=0 (rows) by default

Ответ 12

np.unique дает список кортежей:

>>> np.unique([(1, 1), (2, 2), (3, 3), (4, 4), (2, 2)])
Out[9]: 
array([[1, 1],
       [2, 2],
       [3, 3],
       [4, 4]])

Со списком списков он вызывает TypeError: unhashable type: 'list'

Ответ 13

На основе ответа на этой странице я написал функцию, которая реплицирует функцию MATLAB unique(input,'rows'), а дополнительная функция принимает допуски для проверки уникальности. Он также возвращает индексы такие, что c = data[ia,:] и data = c[ic,:]. Сообщите, если вы видите какие-либо расхождения или ошибки.

def unique_rows(data, prec=5):
    import numpy as np
    d_r = np.fix(data * 10 ** prec) / 10 ** prec + 0.0
    b = np.ascontiguousarray(d_r).view(np.dtype((np.void, d_r.dtype.itemsize * d_r.shape[1])))
    _, ia = np.unique(b, return_index=True)
    _, ic = np.unique(b, return_inverse=True)
    return np.unique(b).view(d_r.dtype).reshape(-1, d_r.shape[1]), ia, ic

Ответ 14

Помимо отличного ответа @Jaime, другим способом свернуть строку является использование a.strides[0] (предполагая, что a является C-смежным), который равен a.dtype.itemsize*a.shape[0]. Кроме того, void(n) является ярлыком для dtype((void,n)). мы приходим наконец к этой кратчайшей версии:

a[unique(a.view(void(a.strides[0])),1)[1]]

Для

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

Ответ 15

Для общих целей, таких как 3D или более высокие многомерные вложенные массивы, попробуйте следующее:

import numpy as np

def unique_nested_arrays(ar):
    origin_shape = ar.shape
    origin_dtype = ar.dtype
    ar = ar.reshape(origin_shape[0], np.prod(origin_shape[1:]))
    ar = np.ascontiguousarray(ar)
    unique_ar = np.unique(ar.view([('', origin_dtype)]*np.prod(origin_shape[1:])))
    return unique_ar.view(origin_dtype).reshape((unique_ar.shape[0], ) + origin_shape[1:])

который удовлетворяет вашему 2D-набору данных:

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]])
unique_nested_arrays(a)

дает:

array([[0, 1, 1, 1, 0, 0],
   [1, 1, 1, 0, 0, 0],
   [1, 1, 1, 1, 1, 0]])

Но и 3D-массивы вроде:

b = np.array([[[1, 1, 1], [0, 1, 1]],
              [[0, 1, 1], [1, 1, 1]],
              [[1, 1, 1], [0, 1, 1]],
              [[1, 1, 1], [1, 1, 1]]])
unique_nested_arrays(b)

дает:

array([[[0, 1, 1], [1, 1, 1]],
   [[1, 1, 1], [0, 1, 1]],
   [[1, 1, 1], [1, 1, 1]]])

Ответ 16

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

Источник: fooobar.com/questions/46702/...

Вы можете использовать методы списка .count() и .index()

coor = np.array([[10, 10], [12, 9], [10, 5], [12, 9]])
coor_tuple = [tuple(x) for x in coor]
unique_coor = sorted(set(coor_tuple), key=lambda x: coor_tuple.index(x))
unique_count = [coor_tuple.count(x) for x in unique_coor]
unique_index = [coor_tuple.index(x) for x in unique_coor]

Ответ 17

Мы можем на самом деле превратить массив numpy numx mxn в массив строк mx 1 numpy, попробуйте использовать следующую функцию, он обеспечивает count, inverse_idx и т.д., так же, как numpy.unique:

import numpy as np

def uniqueRow(a):
    #This function turn m x n numpy array into m x 1 numpy array storing 
    #string, and so the np.unique can be used

    #Input: an m x n numpy array (a)
    #Output unique m' x n numpy array (unique), inverse_indx, and counts 

    s = np.chararray((a.shape[0],1))
    s[:] = '-'

    b = (a).astype(np.str)

    s2 = np.expand_dims(b[:,0],axis=1) + s + np.expand_dims(b[:,1],axis=1)

    n = a.shape[1] - 2    

    for i in range(0,n):
         s2 = s2 + s + np.expand_dims(b[:,i+2],axis=1)

    s3, idx, inv_, c = np.unique(s2,return_index = True,  return_inverse = True, return_counts = True)

    return a[idx], inv_, c

Пример:

A = np.array([[ 3.17   9.502  3.291],
  [ 9.984  2.773  6.852],
  [ 1.172  8.885  4.258],
  [ 9.73   7.518  3.227],
  [ 8.113  9.563  9.117],
  [ 9.984  2.773  6.852],
  [ 9.73   7.518  3.227]])

B, inv_, c = uniqueRow(A)

Results:

B:
[[ 1.172  8.885  4.258]
[ 3.17   9.502  3.291]
[ 8.113  9.563  9.117]
[ 9.73   7.518  3.227]
[ 9.984  2.773  6.852]]

inv_:
[3 4 1 0 2 4 0]

c:
[2 1 1 1 2]

Ответ 18

Позволяет получить всю матрицу numpy в виде списка, затем удалить дубликаты из этого списка и, наконец, вернуть наш уникальный список обратно в матрицу numpy:

matrix_as_list=data.tolist() 
matrix_as_list:
[[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]]

uniq_list=list()
uniq_list.append(matrix_as_list[0])

[uniq_list.append(item) for item in matrix_as_list if item not in uniq_list]

unique_matrix=np.array(uniq_list)
unique_matrix:
array([[1, 1, 1, 0, 0, 0],
       [0, 1, 1, 1, 0, 0],
       [1, 1, 1, 1, 1, 0]])

Ответ 19

Самое простое решение - сделать строки одним элементом, сделав их строками. Каждая строка затем может быть сравнена в целом по своей уникальности с использованием numpy. Это решение является обобщающим, вам просто нужно изменить форму и перенести свой массив для других комбинаций. Вот решение проблемы.

import numpy as np

original = 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]])

uniques, index = np.unique([str(i) for i in original], return_index=True)
cleaned = original[index]
print(cleaned)    

Дает:

 array([[0, 1, 1, 1, 0, 0],
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 1, 1, 0]])

Отправьте мою благородную премию по почте

Ответ 20

import numpy as np
original = 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]])
# create a view that the subarray as tuple and return unique indeies.
_, unique_index = np.unique(original.view(original.dtype.descr * original.shape[1]),
                            return_index=True)
# get unique set
print(original[unique_index])