在numpy.array中查找唯一的行

Aka*_*all 185 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]])
Run Code Online (Sandbox Code Playgroud)

我知道我可以在阵列上创建一个集合并循环,但我正在寻找一个有效的纯numpy解决方案.我相信有一种方法可以将数据类型设置为void然后我可以使用numpy.unique,但我无法弄清楚如何使其工作.

Gre*_*kel 138

又一种可能的解决方案

np.vstack({tuple(row) for row in a})
Run Code Online (Sandbox Code Playgroud)

  • +1这是明确的,短的和pythonic.除非速度是一个真正的问题,否则这些类型的解决方案应优先考虑IMO这个问题的复杂,更高的投票答案. (20认同)
  • 优秀!大括号或set()函数可以解决这个问题. (3认同)
  • @Greg von Winckel你能否提出一些不会改变顺序的东西. (2认同)
  • 为了避免 FutureWarning,请将集合转换为列表,例如: `np.vstack(list({tuple(row) for row in AIPbiased[i, :, :]}))` FutureWarning:要堆栈的数组必须作为“序列”类型,例如列表或元组。从 NumPy 1.16 开始,对非序列迭代(例如生成器)的支持已被弃用,并且将来会引发错误。 (2认同)

Jai*_*ime 110

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

编辑 添加np.ascontiguousarray以下@ seberg的推荐.如果数组尚未连续,这将减慢方法的速度.

编辑 上面的内容可能会略微加快,可能是以清晰为代价,通过以下方式:

unique_a = np.unique(b).view(a.dtype).reshape(-1, a.shape[1])
Run Code Online (Sandbox Code Playgroud)

此外,至少在我的系统上,性能方面与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
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,如果将此方法用于浮点数,那么"-0."将不会比较等于"+ 0",而逐个元素的比较将具有"-0.== + 0.`(由ieee浮动标准指定).请参见http://stackoverflow.com/questions/26782038/how-to-eliminate-the-extra-minus-sign-when-rounding-negative-numbers-towards-zer (9认同)
  • 非常感谢.这是我正在寻找的答案,你能解释一下这一步中发生了什么:`b = a.view(np.dtype((np.void,a.dtype.itemsize*a.shape [1]) ))`? (3认同)
  • @Akavall它使用`np.void`数据类型创建数据视图,其大小为整行中的字节数.如果您有一个`np.uint8`s数组并将其视为`np.uint16`s,它将每两列组合成一个列,但更灵活,它与您获得的类似. (3认同)
  • @Jaime,你可以添加一个`np.ascontiguousarray`或类似的通常是安全的(我知道它有点限制,然后必要,但......).行*必须*是连续的,以便视图按预期工作. (3认同)
  • @ConstantineEvans这是最近的补充:在numpy 1.6中,试图在`np.void`数组上运行`np.unique`会返回与mergesort相关的错误,该错误没有针对该类型实现.它在1.7中工作正常. (2认同)

aiw*_*bdn 93

从NumPy 1.13开始,人们可以简单地选择轴来选择任何N-dim阵列中的唯一值.要获得唯一的行,可以执行以下操作:

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

  • 小心这个功能.`np.unique(list_cor,axis = 0)`获取_array,删除重复的行_; 它不会将数组过滤为原始array_中唯一的_元素.例如,参见[here](/sf/ask/3329354101/). (11认同)

Joe*_*ton 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
Run Code Online (Sandbox Code Playgroud)

要了解发生了什么,请查看中间结果.

一旦我们将事物视为结构化数组,数组中的每个元素都是原始数组中的一行.(基本上,它是与元组列表类似的数据结构.)

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')])
Run Code Online (Sandbox Code Playgroud)

一旦我们运行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')])
Run Code Online (Sandbox Code Playgroud)

然后我们需要将其视为"普通"数组(_存储最后一次计算的结果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])
Run Code Online (Sandbox Code Playgroud)

然后重新形成一个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]])
Run Code Online (Sandbox Code Playgroud)

显然,如果你想更简洁,你可以把它写成:

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)
Run Code Online (Sandbox Code Playgroud)

结果如下:

[[0 1 1 1 0 0]
 [1 1 1 0 0 0]
 [1 1 1 1 1 0]]
Run Code Online (Sandbox Code Playgroud)

  • @cge - 尝试使用更大的数组.是的,排序numpy数组比排序列表要慢.但是,在大多数使用ndarray的情况下,速度并不是主要的考虑因素.这是内存使用情况.元组列表将使用比此解决方案多_vastly_的内存.即使你有足够的内存,有一个相当大的数组,将它转换为元组列表比速度优势有更大的开销. (3认同)

Rya*_*axe 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)
Run Code Online (Sandbox Code Playgroud)

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

  • +1这是明确的,短的和pythonic.除非速度是一个真正的问题,否则这些类型的解决方案应优先考虑IMO这个问题的复杂,更高的投票答案. (5认同)
  • 这实际上不适用于我的数据,`uniques`包含唯一的元素.可能我误解了阵列的预期形状 - 你能在这里更精确吗? (4认同)

cge*_*cge 16

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

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

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

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

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

这更快,使用更少的内存.

此外,如果要在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)))))]
Run Code Online (Sandbox Code Playgroud)

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

编辑:

为了演示速度差异,我在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
Run Code Online (Sandbox Code Playgroud)

然而,如果使用更大的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
Run Code Online (Sandbox Code Playgroud)


div*_*nex 9

这是@Greg pythonic答案的另一个变种

np.vstack(set(map(tuple, a)))
Run Code Online (Sandbox Code Playgroud)


Nic*_*mer 8

我比对速度的建议的替代和令人惊讶地发现,虚空视图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])
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述


重现情节的代码:

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
    )
Run Code Online (Sandbox Code Playgroud)


Ahm*_*sih 7

我不喜欢这些答案中的任何一个因为没有处理线性代数或向量空间意义上的浮点数组,其中两行"相等"意味着"在某些内部".具有容差阈值的一个答案,/sf/answers/1880743511/,将阈值设置为元素和小数精度,这适用于某些情况,但不像数学上那样通用真矢量距离.

这是我的版本:

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)
Run Code Online (Sandbox Code Playgroud)

上面的公共域函数scipy.spatial.distance.pdist用于查找每对行之间的欧几里德(可自定义)距离.然后,它将每个距离与thresh旧的比较,以找到彼此之间的行thresh,并从每个thresh集群返回一行.

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

如果thresh=0(默认值),则行必须精确到位才能被视为"唯一".使用其他良好的值thresh缩放机器精度,即thresh=np.spacing(1)*1e3.