如何使用布尔行的2D数组来过滤另一个2D数组?

时间:2016-09-19 02:06:39

标签: python performance numpy vectorization

我在(3,m)数组中有一些数据。

我有另一个(n,3)形状的面具阵列。此掩码的行是布尔过滤器,需要在执行某些功能之前应用于数据阵列。是否有矢量化方法来应用过滤器并计算函数?

这是一个使用循环的示例,为清晰起见,假设函数是一个mean()。我想使用纯粹的Numpy(没有列表理解)这样做。

(显然,阵列的大小实际上要大得多。)

import numpy as np

data = np.array([
       [ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]
    ])

masks = np.array([
        [True, True, False],
        [False, True, False],
        [False, True, True],
        [True, False, False],
        [True, False, True]
    ])

means = np.array([data[mask].mean(axis=0) for mask in masks])

# means
array([[ 2.,  3.,  4.,  5.],
       [ 4.,  5.,  6.,  7.],
       [ 6.,  7.,  8.,  9.],
       [ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.]])

3 个答案:

答案 0 :(得分:0)

这感觉有点粗糙和混乱,但它确实没有循环。

有两个主要任务:

  • 展开data,以便可以使用masks进行索引 - 从(5,4)到(5,3,4)
  • means应用于行组;我能找到的最近的是np.sum.reduceat

构建reduceat索引:

In [253]: cnt = masks.sum(axis=1)
In [254]: cnt1=np.concatenate(([0],np.cumsum(cnt)[:-1]))
In [255]: cnt
Out[255]: array([2, 1, 2, 1, 2])   # True count per row
In [256]: cnt1
Out[256]: array([0, 2, 3, 5, 6])   # reduceat index positions

展开datamask

In [257]: mdata=data[None,...].repeat(masks.shape[0],0)[masks,:]

add行并除以每个组的行数

In [258]: np.add.reduceat(mdata,cnt1,0)/cnt[:,None]
Out[258]: 
array([[ 2.,  3.,  4.,  5.],
       [ 4.,  5.,  6.,  7.],
       [ 6.,  7.,  8.,  9.],
       [ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.]])

如果有帮助:

In [263]: mdata
Out[263]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 4,  5,  6,  7],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [ 0,  1,  2,  3],
       [ 0,  1,  2,  3],
       [ 8,  9, 10, 11]])

获得此mdata的更好方法是

In [285]: data[np.where(masks)[1],:]
Out[285]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 4,  5,  6,  7],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [ 0,  1,  2,  3],
       [ 0,  1,  2,  3],
       [ 8,  9, 10, 11]])

where(...)[1]masks中True的列位置,这是我们要从data中选择的行。

===========================

@capitalistcuttle也会创建一个(5,3,4)数组,但通过将reduceat行清零来避免需要False。这样,可以在不影响价值的情况下进入meansum。这让我想起了蒙版数组如何执行这样的任务。他们fill屏蔽的值,其值为0或1,不会影响计算。

灵感来自于这是一个MaskedArray解决方案

datamasks展开为(5,3,4)尺寸:

In [322]: data1=data[None,:,:].repeat(5,0)
In [323]: masks1=masks[:,:,None].repeat(4,-1)
In [324]: data1.shape, masks1.shape
Out[324]: ((5, 3, 4), (5, 3, 4))

从中创建蒙面数组:

In [325]: madata=np.ma.MaskedArray(data1,~masks1)
In [326]: madata
Out[326]: 
masked_array(data =
 [[[0 1 2 3]
  [4 5 6 7]
  [-- -- -- --]]

 [[-- -- -- --]
  [4 5 6 7]
  [-- -- -- --]]
 ...
 [[0 1 2 3]
  [-- -- -- --]
  [8 9 10 11]]],
             mask =
 [[[False False False False]
  [False False False False]
  [ True  True  True  True]]

 [[ True  True  True  True]
  [False False False False]
  [ True  True  True  True]]
  ...],
       fill_value = 999999)

现在我们可以简单地使用mean方法,让它处理0填充并调整有效行数。

In [327]: madata.mean(axis=1)
Out[327]: 
masked_array(data =
 [[2.0 3.0 4.0 5.0]
 [4.0 5.0 6.0 7.0]
 [6.0 7.0 8.0 9.0]
 [0.0 1.0 2.0 3.0]
 [4.0 5.0 6.0 7.0]],
             mask =
 [[False False False False]
 [False False False False]
 [False False False False]
 [False False False False]
 [False False False False]],
       fill_value = 1e+20)

要转换回常规数组的.data属性。

这种MaskedArray方法可能更慢,因为它创建了一个更大的数组,但它可能更通用 - 它可以用于操作,只要它们在np.ma或其方法中定义。

答案 1 :(得分:0)

所以,在玩了一段时间之后,似乎这种广播适用于mean()作为具体功能:

means = (masks[:, :, np.newaxis] * data).sum(axis=1) / masks.sum(axis=1)[:, np.newaxis]

# means
array([[ 2.,  3.,  4.,  5.],
       [ 4.,  5.,  6.,  7.],
       [ 6.,  7.,  8.,  9.],
       [ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.]])

对于更普遍的其他功能,您可以使用此格式(其中mean()可以替换为所需的功能):

means = (masks[:, :, np.newaxis] * data).mean(axis=1) * masks.shape[1] / masks.sum(axis=1)[:, np.newaxis]

# means
array([[ 2.,  3.,  4.,  5.],
       [ 4.,  5.,  6.,  7.],
       [ 6.,  7.,  8.,  9.],
       [ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.]])

答案 2 :(得分:0)

matrix-multiplication使用np.dot可以轻松解决该问题,因此必须非常高效。这是实施 -

np.true_divide(masks.dot(data),masks.sum(1)[:,None])