在numpy.array中查找唯一的行

时间:2013-06-06 19:51:36

标签: python arrays numpy unique

我需要在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,但我无法弄清楚如何使其工作。

20 个答案:

答案 0 :(得分:135)

另一种可能的解决方案

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

答案 1 :(得分:109)

使用结构化数组的另一个选择是使用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]])

修改 根据@ seberg的建议添加了np.ascontiguousarray。如果数组尚未连续,这将减慢方法的速度。

修改 通过这样做可以略微加快上述速度,可能以清晰为代价:

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

答案 2 :(得分:83)

从NumPy 1.13开始,人们可以简单地选择轴来选择任何N-dim阵列中的唯一值。要获得唯一的行,可以这样做:

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

答案 3 :(得分:29)

如果你想避免转换为一系列元组或其他类似数据结构的内存开销,你可以利用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...)的原因:< / p>

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

然后重新形成一个二维数组(-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]]

答案 4 :(得分:19)

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)

这是我看到你改变类型以做你想做的事情的唯一方法,我不确定改变为元组的列表迭代是否可以用你的“不循环”

答案 5 :(得分:16)

np.unique通过对扁平数组进行排序,然后查看每个项是否与前一个相等来工作。这可以手动完成而不会展平:

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

此方法不使用元组,并且应该比此处给出的其他方法更快更简单。

注意:之前的版本在[之后]没有正确的,这意味着使用了错误的索引。此外,Joe Kington提出了一个很好的观点,即 制作各种中间副本。以下方法通过制作排序副本然后使用它的视图来减少:

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

一个有趣的遗留问题是,如果你想沿任意维数组的任意轴排序/唯一,这将更加困难。

编辑:

为了演示速度差异,我在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

然而,如果使用更大的a,这个版本最终会更快,更快:

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

答案 6 :(得分:9)

以下是@Greg pythonic答案的另一个变体

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

答案 7 :(得分:7)

我不喜欢这些答案中的任何一个,因为没有处理线性代数或向量空间意义上的浮点数组,其中两行“相等”意味着“在某些内部”。具有容差阈值https://stackoverflow.com/a/26867764/500207的一个答案将阈值设置为元素和十进制精度,这适用于某些情况但不像数学上那样普遍矢量距离。

这是我的版本:

array(
        1 => array("a", "b"), 
        2 => "c"
);

上面的公共域函数使用scipy.spatial.distance.pdist来查找每对行之间的欧几里德(可自定义)距离。然后,它将每个距离与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) 年比较,以查找彼此thresh范围内的行,并从每个thresh群集返回一行。

如所暗示的,距离thresh不一定是欧几里德 - metric可以计算各种距离,包括pdist(曼哈顿范数)和cityblock(矢量之间的角度) )。

如果cosine(默认值),那么行必须精确到位才能被视为“唯一”。 thresh=0的其他好值使用缩放的机器精度,即thresh

答案 8 :(得分:7)

我已经比较了建议的速度替代方案,并且发现,令人惊讶的是,虚拟视图unique解决方案甚至比numpy的本地unique更快一点axis 1}}参数。如果您正在寻找速度,那么您需要

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

enter image description here

重现情节的代码:

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
    )

答案 9 :(得分:3)

为什么不使用pandas中的drop_duplicates

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

答案 10 :(得分:3)

numpy_indexed包(免责声明:我是它的作者)将Jaime发布的解决方案包含在一个经过测试的界面中,还有更多功能:

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

答案 11 :(得分: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

答案 12 :(得分:1)

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 :(得分: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]]

答案 14 :(得分: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]])

答案 15 :(得分:0)

我们实际上可以将mxn数字numpy数组转换为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]

答案 16 :(得分:0)

这些答案都不适合我。我假设我的唯一行包含字符串而不是数字。然而,另一个线程的答案确实有效:

来源:https://stackoverflow.com/a/38461043/5402386

您可以使用.count()和.index()列表的方法

cross join

答案 17 :(得分:0)

对于像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]]])

答案 18 :(得分:-3)

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

答案 19 :(得分:-3)

最直接的解决方案是通过使行成为字符串来使行成为单个项目。然后可以使用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]])

通过邮件发送我的诺贝尔奖