一个numpy数组中,另一个数组的切片索引。

3
实际问题出现在某些机器学习应用程序中,数据变得有点复杂。因此,以下是捕获问题本质的MWE:
我有两个数组,它们是这样创建的:
L = np.arange(12).reshape(4,3)
M = np.arange(12).reshape(6,2)

现在,我想找到在L中的行R,这样就存在一个由R中除了最后一个元素之外的所有元素组成的行在M中。
从上面的示例代码中,LM看起来像这样:
array([[ 0,  1,  2],  # L
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

array([[ 0,  1],  # M
       [ 2,  3],
       [ 4,  5],
       [ 6,  7],
       [ 8,  9],
       [10, 11]])

我希望从这些标记为L的行中,获取一个numpy数组:

array([[ 0,  1,  2],
       [ 6,  7,  8]])

如果我要用Python列表来表示L和M,我会这样做:
L = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
M = [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11]]
answer = [R for R in L if R[:-1] in M]

现在,我知道我可以在numpy中使用类似的列表推导式,并将结果转换为数组。由于numpy非常棒,可能有更优雅的方法来实现这一点,但我不知道。

我尝试了解 np.where(以获取所需的索引,然后使用它们对L进行下标访问),但这似乎无法满足我的需求。

我感激任何帮助。


如果M的一行包含L的一行的前两个元素,那么它不能包含最后一个元素,因为空间不足。在您的实际应用中,这也是正确的吗? - user2357112
@user2357112:完全正确。这就是为什么我正在测试“M中是否存在一些行,这些行由R中除最后一个元素外的所有元素组成”。L中行的最后一个元素是基于某些额外计算添加的维度。 - inspectorG4dget
当我看到这样的问题时,我希望NumPy的集合操作不仅限于1D。我认为你可以通过组合多个sortin1d调用来解决一些问题。但是现在这个想法还很模糊。 - user2357112
@user2357112:这超出了我的numpy技能范围。我会很感激您提供一个示例。 - inspectorG4dget
4个回答

4

好的,我明白了。诀窍是给M添加另一个维度,然后你可以使用广播:

M.shape += (1,)
E = np.all(L[:,:-1].T == M, 1)

你会得到一个6x4的布尔矩阵E,它给出了将L的每一行与M的每一行进行比较的结果。

从这里开始很容易完成:

result = L[np.any(E,0)]

这样的解决方案更加简洁,不需要使用任何lambda函数或者“隐式循环”(例如np.apply_along_axis())。是的,numpy向量化非常美妙(但有时需要进行相当抽象的思考)...

要么是我没理解,要么就是它不起作用。您能否实现简单的完成方式,以便向我展示如何获得所需的值? - inspectorG4dget
好的,我明确一下 - 我通常尽量不给一个人可以直接复制粘贴的答案。 - Bitwise
太棒了!我更新了我的回答,并详细解释了数组广播的魔力。感谢您增强了我的numpy技能! :) - ddelemeny

3
非常相似于 Bitwise 的回答:
def fn(a):
    return lambda b: np.all(a==b, axis=1)
matches = np.apply_along_axis(fn(M), 1, L[:,:2])
result = L[np.any(matches, axis=1)]

发生在幕后的事情大致如下(我将使用Bitwise的示例来更容易地演示):
>>> M
array([[ 0,  1],
       [ 2,  3],
       [ 4,  5],
       [ 6,  7],
       [ 8,  9],
       [10, 11]])
>>> M.shape+=(1,)
>>> M
array([[[ 0],
        [ 1]],

       [[ 2],
        [ 3]],

       [[ 4],
        [ 5]],

       [[ 6],
        [ 7]],

       [[ 8],
        [ 9]],

       [[10],
        [11]]])

在这里,我们给M数组添加了另一个维度,现在变成了(6,2,1)。

>>> L2 = L[:,:-1].T

我们需要去掉2的最后一列,并将数组转置,以便维度为(2,4)。

现在,这里有一个魔术,M和L2可以广播到(6,2,4)维数组。

正如numpy文档所述:

A set of arrays is called “broadcastable” to the same shape if the above rules produce a valid result, i.e., one of the following is true:

The arrays all have exactly the same shape.
The arrays all have the same number of dimensions and the length of each dimensions is either a common length or 1.
The arrays that have too few dimensions can have their shapes prepended with a dimension of length 1 to satisfy property 2.

Example

If a.shape is (5,1), b.shape is (1,6), c.shape is (6,) and d.shape is () so that d is a scalar, then a, b, c, and d are all broadcastable to dimension (5,6); and

a acts like a (5,6) array where a[:,0] is broadcast to the other columns,
b acts like a (5,6) array where b[0,:] is broadcast to the other rows,
c acts like a (1,6) array and therefore like a (5,6) array where c[:] is broadcast to every row, and finally,
d acts like a (5,6) array where the single value is repeated.

M[:,:,0]将重复4次以填充3维数组,并且L2将添加一个新的维度并重复6次以填充它。

>>> B = np.broadcast_arrays(L2,M)
>>> B
[array([[[ 0,  3,  6,  9],
        [ 1,  4,  7, 10]],

       [[ 0,  3,  6,  9],
        [ 1,  4,  7, 10]],

       [[ 0,  3,  6,  9],
        [ 1,  4,  7, 10]],

       [[ 0,  3,  6,  9],
        [ 1,  4,  7, 10]],

       [[ 0,  3,  6,  9],
        [ 1,  4,  7, 10]],

       [[ 0,  3,  6,  9],
        [ 1,  4,  7, 10]]]),


array([[[ 0,  0,  0,  0],
        [ 1,  1,  1,  1]],

       [[ 2,  2,  2,  2],
        [ 3,  3,  3,  3]],

       [[ 4,  4,  4,  4],
        [ 5,  5,  5,  5]],

       [[ 6,  6,  6,  6],
        [ 7,  7,  7,  7]],

       [[ 8,  8,  8,  8],
        [ 9,  9,  9,  9]],

       [[10, 10, 10, 10],
        [11, 11, 11, 11]]])]

现在我们可以逐个元素进行比较:
>>> np.equal(*B)
array([[[ True, False, False, False],
        [ True, False, False, False]],

       [[False, False, False, False],
        [False, False, False, False]],

       [[False, False, False, False],
        [False, False, False, False]],

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

       [[False, False, False, False],
        [False, False, False, False]],

       [[False, False, False, False],
        [False, False, False, False]]], dtype=bool)

按行操作(轴=1):

>>> np.all(np.equal(*B), axis=1)
array([[ True, False, False, False],
       [False, False, False, False],
       [False, False, False, False],
       [False, False,  True, False],
       [False, False, False, False],
       [False, False, False, False]], dtype=bool)

按L的聚合:

>>> C = np.any(np.all(np.equal(*B), axis=1), axis=0)
>>> C
array([ True, False,  True, False], dtype=bool)

这将为您提供适用于L的布尔掩码。

>>> L[C]
array([[0, 1, 2],
       [6, 7, 8]])

apply_along_axis将利用相同的功能,但减少L的维数而不是增加M的维数(从而添加隐式循环)。


1
谢谢您提供如此详细的帖子。我会选择 behzad.nouri 的答案,因为它更简单易懂。但是我真的很喜欢您的回答,因为它包含了更多的细节和教学内容。我会将这个帖子加入书签以备将来参考。 - inspectorG4dget

0
>>> import hashlib
>>> fn = lambda xs: hashlib.sha1(xs).hexdigest()
>>> m = np.apply_along_axis(fn, 1, M)
>>> l = np.apply_along_axis(fn, 1, L[:,:-1])
>>> L[np.in1d(l, m)]
array([[0, 1, 2],
       [6, 7, 8]])

能否加上解释呢?我不太理解这个解决方案。 - inspectorG4dget
@inspectorG4dget 因为 in1d 仅适用于一维数组,所以我正在对行进行哈希处理,然后应用 in1d - behzad.nouri

0
>>> print np.array([row for row in L if row[:-1] in M])
[[0 1 2]
 [6 7 8]]

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