在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,但我不知道如何使它工作。

158513 次浏览

np。给出一个元组列表的唯一作品:

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

当我在np.random.random(100).reshape(10,10)上运行它时,np.unique返回所有唯一的单独元素,但你想要唯一的行,所以首先你需要将它们放入元组:

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

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

如果希望避免转换为一系列元组或其他类似数据结构的内存开销,可以利用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]]

np。Unique的工作原理是对一个扁平数组排序,然后查看每一项是否等于前一项。这可以手动完成,无需压平:

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

这个方法不使用元组,应该比这里给出的其他方法更快更简单。

注意:以前的版本在A[后面没有ind,这意味着使用了错误的索引。此外,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中对答案中描述的三种不同方法进行了一些测试。对于你的 exact 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

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

还有另一个可能的解决方案

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

根据本页的答案,我写了一个函数,它复制了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

这里是@Greg pythonic answer的另一种变体

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

为什么不使用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

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

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

对于一般用途,如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]]])

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

以下是我的看法:

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)

上面的公共域函数使用scipy.spatial.distance.pdist来查找行每一对之间的欧几里得(可定制)距离。然后,它将每个距离与threshold进行比较,以找到彼此在thresh内的行,并从每个thresh-cluster中只返回一行。

如前所述,metric的距离不必是欧几里得的——__abc1可以计算各种各样的距离,包括cityblock(曼哈顿范数)和cosine(向量之间的夹角)。

如果thresh=0(默认值),则行必须精确到“唯一”。thresh的其他好的值使用缩放的机器精度,即thresh=np.spacing(1)*1e3

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

把我的诺贝尔奖寄出去

这些答案对我都没用。我假设我的唯一行包含字符串而不是数字。然而,来自另一个帖子的答案确实起作用了:

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

你可以使用.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]

除了@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]]
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])

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

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

我们实际上可以将m x n数值numpy数组转换为m x 1 numpy字符串数组,请尝试使用以下函数,它提供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]

我比较了建议的速度替代方案,惊讶地发现,void视图unique解决方案甚至比numpy的原生uniqueaxis参数还要快一点。如果你想要速度,你会想要

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

我已经在npx.unique_rows中实现了最快的变体。

这个也有GitHub上的bug报告

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)],
xlabel="len(a)",
equality_check=None,
)

让我们以列表的形式获取整个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]])