如何在屏蔽数组上做相当于 block_reduce 的操作?

How to do equivalent of block_reduce on a masked array?

我正在计算 2D numpy 数组中较小块的聚合值。我想以一种有效的方式(而不是 for 和 if 语句)从聚合操作中排除值 0。

我正在使用 skimage.measure.block_reducenumpy.ma.masked_equal,但看起来 block_reduce 忽略了掩码。

import numpy as np
import skimage
a = np.array([[2,4,0,12,5,7],[6,0,8,4,3,9]])
zeros_included = skimage.measure.block_reduce(a,(2,2),np.mean)

包含 0 并(正确)生成

zeros_included
array([[3., 6., 6.]])

我希望

masked = np.ma.masked_equal(a,0)
zeros_excluded = skimage.measure.block_reduce(masked,(2,2),np.mean)

可以解决问题,但仍会产生

zeros_excluded
array([[3., 6., 6.]])

期望的结果是:

array([[4., 8., 6.]])

我正在寻找一种 python 式的方法来获得正确的结果,使用 skimage 是可选的。当然我的实际数组和块比这个例子大得多,因此需要效率。

感谢您的关注。

您可以使用 np.nanmean,但您必须修改原始数组或创建一个新数组:

import numpy as np
import skimage

a = np.array([[2,4,0,12,5,7],[6,0,8,4,3,9]])


b = a.astype("float")
b[b==0] = np.nan
zeros_excluded = skimage.measure.block_reduce(b,(2,2), np.nanmean)
zeros_excluded

# array([[4., 8., 6.]])

block_reduce的核心代码是

blocked = view_as_blocks(image, block_size)
return func(blocked, axis=tuple(range(image.ndim, blocked.ndim)))

view_as_blocks 使用 as_strided 创建数组的不同视图:

In [532]: skimage.util.view_as_blocks(a,(2,2))                                                        
Out[532]: 
array([[[[ 2,  4],
         [ 6,  0]],

        [[ 0, 12],
         [ 8,  4]],

        [[ 5,  7],
         [ 3,  9]]]])

当应用于掩码数组时,它会产生相同的结果。实际上,它适用于 masked.datanp.asarray(masked)。有些动作保留了子类,这个没有。

In [533]: skimage.util.view_as_blocks(masked,(2,2))  
Out[533]: 
array([[[[ 2,  4],
         [ 6,  0]],
         ...

这就是为什么应用于 (2,3) 轴的 np.mean 不响应掩码的原因。

np.mean 应用于屏蔽数组将操作委托给数组自己的方法,因此对屏蔽敏感:

In [544]: np.mean(masked[:,:2])                                                                       
Out[544]: 4.0
In [545]: masked[:,:2].mean()                                                                         
Out[545]: 4.0
In [547]: [masked[:,i:i+2].mean() for i in range(0,6,2)]                                              
Out[547]: [4.0, 8.0, 6.0]

np.nanmeanview_as_blocks 一起工作,因为它不依赖于数组是一个特殊的子类。

我可以定义一个将遮罩应用于块视图的函数:

def foo(arr,axis):
    return np.ma.masked_equal(arr,0).mean(axis)

In [552]: skimage.measure.block_reduce(a,(2,2),foo)                                                   
Out[552]: 
masked_array(data=[[4.0, 8.0, 6.0]],
             mask=[[False, False, False]],
       fill_value=1e+20)

====

由于您的块没有重叠,我创建了具有重塑和交换轴的块。

In [554]: masked.reshape(2,3,2).transpose(1,0,2)                                                      
Out[554]: 
masked_array(
  data=[[[2, 4],
         [6, --]],

        [[--, 12],
         [8, 4]],

        [[5, 7],
         [3, 9]]],
  mask=[[[False, False],
         [False,  True]],

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

        [[False, False],
         [False, False]]],
  fill_value=0)

然后将mean应用于最后两个轴:

In [555]: masked.reshape(2,3,2).transpose(1,0,2).mean((1,2))                                          
Out[555]: 
masked_array(data=[4.0, 8.0, 6.0],
             mask=[False, False, False],
       fill_value=1e+20)