Найти уникальные строки в 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, но я не мог понять, как заставить его работать.

+146
источник поделиться
20 ответов

Начиная с NumPy 1.13, можно просто выбрать ось для выбора уникальных значений в любом N-мерном массиве. Чтобы получить уникальные строки, можно сделать:

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

+56
источник

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

np.vstack({tuple(row) for row in a})
+127
источник
другие ответы

Связанные вопросы


Похожие вопросы

Другим вариантом использования структурированных массивов является представление типа 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
+108
источник

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

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

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

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]]
+29
источник

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)

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

+18
источник

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
+16
источник

Вот еще одна вариация для ответа @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.

+6
источник

Я сравнил предложенную альтернативу скорости и обнаружил, что, на удивление, решение 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
    )
+4
источник

Почему бы не использовать 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
+3
источник

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

import numpy_indexed as npi
new_a = npi.unique(a)  # unique elements over axis=0 (rows) by default
+3
источник

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'

+1
источник

На основе ответа на этой странице я написал функцию, которая реплицирует функцию 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
+1
источник

Помимо отличного ответа @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]]
+1
источник

Для общих целей, таких как 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]]])
0
источник

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

Источник: 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]
0
источник

Мы можем на самом деле превратить массив 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]
0
источник

Позволяет получить всю матрицу 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]])
0
источник

Самое простое решение - сделать строки одним элементом, сделав их строками. Каждая строка затем может быть сравнена в целом по своей уникальности с использованием 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]])

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

-2
источник
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])
-3
источник

Посмотрите другие вопросы по меткам или Задайте вопрос