在一个二维的NumPy数组中测试成员资格。

15

我有两个大小相同的二维数组

a = array([[1,2],[3,4],[5,6]])
b = array([[1,2],[3,4],[7,8]])

我想知道在a中与b相同的行。

因此输出应为:

array([ True,  True, False], dtype=bool)

不加任何内容:

array([any(i == a) for i in b])

原因是a和b都非常大。

有一个函数可以做到这一点,但只适用于1D数组: in1d


2
ab的实际数据类型是什么? - unutbu
@unutbu 浮点数(可以转换为整型) - amine23
4个回答

16
我们真正想要做的是使用np.in1d...但是,np.in1d只能用于一维数组。我们的数组是多维的。然而,我们可以将这些数组视为字符串构成的一维数组:
arr.view(np.dtype((np.void, arr.dtype.itemsize * arr.shape[-1])))

例如,
In [15]: arr = np.array([[1, 2], [2, 3], [1, 3]])

In [16]: arr = arr.view(np.dtype((np.void, arr.dtype.itemsize * arr.shape[-1])))

In [30]: arr.dtype
Out[30]: dtype('V16')

In [31]: arr.shape
Out[31]: (3, 1)

In [37]: arr
Out[37]: 
array([[b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00'],
       [b'\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'],
       [b'\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00']],
      dtype='|V16')

这使得arr的每一行都变成了一个字符串。现在只需要将其与np.in1d连接起来即可。
import numpy as np

def asvoid(arr):
    """
    Based on https://dev59.com/mmQn5IYBdhLWcg3wRVRs#16973510 (Jaime, 2013-06)
    View the array as dtype np.void (bytes). The items along the last axis are
    viewed as one value. This allows comparisons to be performed on the entire row.
    """
    arr = np.ascontiguousarray(arr)
    if np.issubdtype(arr.dtype, np.floating):
        """ Care needs to be taken here since
        np.array([-0.]).view(np.void) != np.array([0.]).view(np.void)
        Adding 0. converts -0. to 0.
        """
        arr += 0.
    return arr.view(np.dtype((np.void, arr.dtype.itemsize * arr.shape[-1])))


def inNd(a, b, assume_unique=False):
    a = asvoid(a)
    b = asvoid(b)
    return np.in1d(a, b, assume_unique)


tests = [
    (np.array([[1, 2], [2, 3], [1, 3]]),
     np.array([[2, 2], [3, 3], [4, 4]]),
     np.array([False, False, False])),
    (np.array([[1, 2], [2, 2], [1, 3]]),
     np.array([[2, 2], [3, 3], [4, 4]]),
     np.array([True, False, False])),
    (np.array([[1, 2], [3, 4], [5, 6]]),
     np.array([[1, 2], [3, 4], [7, 8]]),
     np.array([True, True, False])),
    (np.array([[1, 2], [5, 6], [3, 4]]),
     np.array([[1, 2], [5, 6], [7, 8]]),
     np.array([True, True, False])),
    (np.array([[-0.5, 2.5, -2, 100, 2], [5, 6, 7, 8, 9], [3, 4, 5, 6, 7]]),
     np.array([[1.0, 2, 3, 4, 5], [5, 6, 7, 8, 9], [-0.5, 2.5, -2, 100, 2]]),
     np.array([False, True, True]))
]

for a, b, answer in tests:
    result = inNd(b, a)
    try:
        assert np.all(answer == result)
    except AssertionError:
        print('''\
a:
{a}
b:
{b}

answer: {answer}
result: {result}'''.format(**locals()))
        raise
else:
    print('Success!')

收益率

Success!

2
将其视为记录数组,我认为.view(dtype([(´´,a.dtype)*a.shape[1]]))是您需要的,而且对于任何类型都适用相同的技巧。 - Jaime
3
有趣的是归并排序不能处理广义数据类型...虽然此方法仍然无法处理,但我发现在单个数据类型中将许多字段合并的最简单方法是 dtype((np.void, a.dtype.itemsize*a.shape[1])) - Jaime
非常好的解决方案,将其转换为字符串!然而,我最近注意到上面的解决方案只适用于Python2而不是Python3。如果您使用Python 3,则可能会收到以下错误:ValueError:更改为较大的dtype时,其大小必须是数组最后一个轴的总字节数的除数。这是代码片段:https://pastebin.com/XU3kVBP9。有趣的是,如果您将dtype从float32更改为int32,则可以正常工作。 - 0vbb
1
在Python2中,np.str是表示字节的数据类型。在Python3中,np.str表示Unicode字符串。在这里,我们想要将值作为字节而不是Unicode字符串进行比较。np.void在Python2和Python3上都可以实现此目的。 - unutbu
1
@0vbb:我已经更新了上面的代码,采用了Jaime的想法,使用np.void数据类型代替np.str。这样可以避免你看到的ValueError错误。 - unutbu
显示剩余11条评论

4
In [1]: import numpy as np

In [2]: a = np.array([[1,2],[3,4]])

In [3]: b = np.array([[3,4],[1,2]])

In [5]: a = a[a[:,1].argsort(kind='mergesort')]

In [6]: a = a[a[:,0].argsort(kind='mergesort')]

In [7]: b = b[b[:,1].argsort(kind='mergesort')]

In [8]: b = b[b[:,0].argsort(kind='mergesort')]

In [9]: bInA1 = b[:,0] == a[:,0]

In [10]: bInA2 = b[:,1] == a[:,1]

In [11]: bInA = bInA1*bInA2

In [12]: bInA
Out[12]: array([ True,  True], dtype=bool)

应该这样做...不确定是否仍然有效。你需要使用mergesort,因为其他方法是不稳定的。

编辑:

如果你有超过2列,并且行已经排序好了,你可以这样做

In [24]: bInA = np.array([True,]*a.shape[0])

In [25]: bInA
Out[25]: array([ True,  True], dtype=bool)

In [26]: for k in range(a.shape[1]):
    bInAk = b[:,k] == a[:,k]
    bInA = bInAk*bInA
   ....:     

In [27]: bInA
Out[27]: array([ True,  True], dtype=bool)

在迭代过程中,仍有加速的空间,因为您不必检查整个列,而只需检查当前bInATrue的条目。


如果 a = array([[1,2],[2,3],[1,3]])b = array([[2,3],[3,3],[4,4]]) 呢? - amine23
是的,我刚刚检查了一下,然后它失败了... 我正在尝试修复这个问题。 - Jan
编辑/修复:使用 in1d 可能会失败,因为它不检查发生位置... 将其更改为 == - Jan
顺便提一下,由于==in1d快8倍,它现在表现得更好了。 - Jan
这会导致我在Ryan Saxe的答案下发布的测试用例失败。 - amine23
这段代码失败了:a,b = np.array([[1,2],[5,6],[3,4]]), np.array([[1,2],[5,6],[7,8]]) - amine23

3
如果你有类似 a=np.array([[1,2],[3,4],[5,6]])b=np.array([[5,6],[1,2],[7,6]]) 这样的东西,你可以将它们转换成复杂的一维数组:
c=a[:,0]+a[:,1]*1j
d=b[:,0]+b[:,1]*1j

我的解释器中的整个内容如下:

>>> c=a[:,0]+a[:,1]*1j
>>> c
array([ 1.+2.j,  3.+4.j,  5.+6.j])
>>> d=b[:,0]+b[:,1]*1j
>>> d
array([ 5.+6.j,  1.+2.j,  7.+6.j])

现在您已经拥有了一个一维数组,您可以轻松地执行np.in1d(c,d),Python会返回以下结果:

>>> np.in1d(c,d)
array([ True, False,  True], dtype=bool)

使用这种数据类型,您不需要任何循环,至少是在这方面。


0

NumPy 模块实际上可以通过你的数组进行广播,并判断哪些部分与其他部分相同,如果相同则返回 true,否则返回 false:

import numpy as np
a = np.array(([1,2],[3,4],[5,6])) #converting to a numpy array
b = np.array(([1,2],[3,4],[7,8])) #converting to a numpy array
new_array = a == b #creating a new boolean array from comparing a and b

现在 new_array 的样子是这样的:

[[ True  True]
 [ True  True]
 [False False]]

但这并不是你想要的。因此,你可以转置(翻转 x 和 y)数组,然后使用 & 运算符比较两行。现在会创建一个 1-D 数组,仅当行中的两列都为 true 时才返回 true:

new_array = new_array.T #transposing
result = new_array[0] & new_array[1] #comparing rows

当你 print result,你现在得到了你想要的结果:
[ True  True False]

如果 a = array([[1,2],[3,4]])b = array([[3,4],[1,2]]) 呢? - amine23
你的意思不是很清楚,你想要比较所有的内容。你的例子没有清晰地展示这一点...而且你想要检查数组b中的嵌套数组是否在a中,但又不想使用for循环? - Ryan Saxe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接