布尔掩码切片的意外结果

Unexpected result from boolean mask slicing

我对下面示例中 numpy 数组切片的工作方式感到困惑。我不知道切片究竟是如何工作的,希望得到解释。

import numpy as np
arr = np.array([
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12],
    [13,14,15,16]
    ])
m = [False,True,True,False]

# Test 1 - Expected behaviour
print(arr[m])
Out: 
array([[ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])

# Test 2 - Expected behaviour
print(arr[m,:])
Out:
array([[ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])

# Test 3 - Expected behaviour
print(arr[:,m])
Out:
array([[ 2,  3],
       [ 6,  7],
       [10, 11],
       [14, 15]])

### What's going on here? ###
# Test 4
print(arr[m,m])
Out:
array([ 6, 11]) # <--- diagonal components. I expected [[6,7],[10,11]].

我发现 arr[:,m][m] 可以达到我想要的结果。但我仍然很好奇这是如何工作的。

这就是 numpy 数组的索引工作方式。通常,如果您有特定的 "slices" 行和列,您想要 select 您只需执行:

import numpy as np
arr = np.array([
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12],
    [13,14,15,16]
    ])

# You want to check out rows 2-3 cols 2-3
print(arr[2:4,2:4])
Out:
[[11 12]
 [15 16]]

现在假设您想要 select 特定行和列索引的任意组合,例如您想要 row0-col2 和 row2-col3

print(arr[[0, 2], [2, 3]])
Out:
[ 3 12]

你做的和上面的一样。 [m,m] 等同于:

[m,m] == [[False,True,True,False], [False,True,True,False]]

这相当于说你想要 row1-col1 和 row2-col2

print(arr[[1, 2], [1, 2]])
Out:
[ 6 11]

我不知道为什么,但这是 numpy 处理一维 boolean 数组元组切片的方式:

arr = np.array([
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12]
    ])
m1 = [True, False, True]
m2 = [False, False, True, True]

# Pseudocode for what NumPy does
#def arr[m1,m2]:
#  intm1 = np.transpose(np.argwhere(m1)) # [True, False, True] -> [0,2]
#  intm2 = np.transpose(np.argwhere(m2)) # [False, False, True, True] -> [2,3]
#  return arr[intm1,intm2] # arr[[0,2],[2,3]]

print(arr[m1,m2]) # --> [3 12]

我所期待的是对数组的非连续段进行切片行为;选择行和列的交集,可以通过以下方式实现:

arr = np.array([
    [1,2,3,4],
    [5,6,7,8],
    [9,10,11,12]
    ])
m1 = [True, False, True]
m2 = [False, False, True, True]

def row_col_select(arr, *ms): 
   n = arr.ndim 
   assert(len(ms) == n) 
   # Accumulate a full boolean mask which will have the shape of `arr`
   accum_mask = np.reshape(True, (1,) * n) 
   for i in range(n): 
     shape = tuple([1]*i + [arr.shape[i]] + [1]*(n-i-1)) 
     m = np.reshape(ms[i], shape) 
     accum_mask = np.logical_and(accum_mask, m) 
   # Select `arr` according to full boolean mask
   # The boolean mask is the multiplication of the boolean arrays across each corresponding dimension. E.g. for m1 and m2 above it is:
   # m1:   | m2: False False  True  True
   #       |       
   # True  |   [[False False  True  True]
   # False |    [False False False False]
   # True  |    [False False  True  True]]
   return arr[accum_mask]

print(row_col_select(arr,m1,m2)) # --> [ 3  4 11 12]
In [55]: arr = np.array([ 
    ...:     [1,2,3,4], 
    ...:     [5,6,7,8], 
    ...:     [9,10,11,12], 
    ...:     [13,14,15,16] 
    ...:     ]) 
    ...: m = [False,True,True,False] 

在您的所有示例中,我们可以使用此 m1 而不是布尔列表:

In [58]: m1 = np.where(m)[0]                                                    
In [59]: m1                                                                     
Out[59]: array([1, 2])

如果 m 是一个类似于 arr 的二维数组,那么我们可以将它用于 arr 中的 select 个元素——但它们会被分解;但是当沿一维使用 select 时,等效数组索引更清晰。是的,我们可以以不同的顺序甚至多次使用 np.array([2,1])np.array([2,1,1,2]) 到 select 行。但是用 m1 代替 m 不会丢失任何信息或控制。

Select 行或列:

In [60]: arr[m1]                                                                
Out[60]: 
array([[ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])
In [61]: arr[:,m1]                                                              
Out[61]: 
array([[ 2,  3],
       [ 6,  7],
       [10, 11],
       [14, 15]])

使用 2 个数组,我们得到 2 个元素,arr[1,1]arr[2,2]

In [62]: arr[m1, m1]                                                            
Out[62]: array([ 6, 11])

请注意,在 MATLAB 中我们必须使用 sub2ind 来做同样的事情。在 numpy 中容易的事情在 MATLAB 中有点难;对于块,它是另一种方式。

为了得到一个块,我们必须创建一个列数组来广播第一行:

In [63]: arr[m1[:,None], m1]                                                    
Out[63]: 
array([[ 6,  7],
       [10, 11]])

如果这太难记了,np.ix_可以帮我们做:

In [64]: np.ix_(m1,m1)                                                          
Out[64]: 
(array([[1],
        [2]]),
 array([[1, 2]]))

[63] 与 [62] 做同样的事情;不同之处在于 2 个阵列的广播方式不同。这与在这些添加中所做的广播相同:

In [65]: m1+m1                                                                  
Out[65]: array([2, 4])
In [66]: m1[:,None]+m1                                                          
Out[66]: 
array([[2, 3],
       [3, 4]])

这种索引行为是完全一致的——前提是我们不从其他语言导入期望。


我使用了m1,因为布尔数组不广播,如下所示:

In [67]: np.array(m)                                                            
Out[67]: array([False,  True,  True, False])
In [68]: np.array(m)[:,None]                                                    
Out[68]: 
array([[False],
       [ True],
       [ True],
       [False]])
In [69]: arr[np.array(m)[:,None], np.array(m)]                                  
...
IndexError: too many indices for array

实际上 'column' 布尔值也不起作用:

In [70]: arr[np.array(m)[:,None]]                                               
...
IndexError: boolean index did not match indexed array along dimension 1; dimension is 4 but corresponding boolean dimension is 1

我们可以使用 logical_and 针对行布尔值广播列布尔值:

In [72]: mb = np.array(m)                                                       
In [73]: mb[:,None]&mb                                                          
Out[73]: 
array([[False, False, False, False],
       [False,  True,  True, False],
       [False,  True,  True, False],
       [False, False, False, False]])
In [74]: arr[_]                                                                 
Out[74]: array([ 6,  7, 10, 11])      # 1d result

你引用的是这种情况:"If obj.ndim == x.ndim, x[obj] returns a 1-dimensional array filled with the elements of x corresponding to the True values of obj"

你的其他引述:

*"Advanced indexing always returns a copy of the data (contrast with basic slicing that returns a view)."*

表示如果arr1 = arr[m,:]arr1是一个副本,对arr1的任何修改都不会影响arr。但是我可以使用 arr[m,:]=10 来修改 arr。副本的替代方法是 view,如在基本索引中一样,arr2=arr[0::2,:]arr2 的修改也会修改 arr

您可以使用矩阵乘法创建二维蒙版。

import numpy as np
arr = np.array([
[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16]
])
m = [False,True,True,False]

mask2d = np.array([m]).T * m
print(arr[mask2d])

输出:

[ 6  7 10 11]

或者,您可以使用矩阵格式输出。

print(np.ma.masked_array(arr, ~mask2d))