【问题标题】:How can I use a 2D array of boolean rows to filter another 2D array?如何使用布尔行的二维数组过滤另一个二维数组?
【发布时间】:2017-01-26 15:02:35
【问题描述】:

我在 (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.]])

【问题讨论】:

  • 那么您尝试了所有发布的方法吗?有人为你工作吗?实际输入设置的运行时数字是多少?
  • 抱歉,是的,确实试过了。你的方法是最快的,我在下面描述的广播方式适用于其他功能。

标签: python performance numpy vectorization


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    这感觉有点粗糙和混乱,但它确实可以在没有循环的情况下工作。

    有两个主要任务:

    • 扩展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) 数组,但通过清零 False 行来避免对 reduceat 的需要。这样就可以进入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 或其方法中定义。

    【讨论】:

      【解决方案3】:

      所以,玩了一会儿之后,似乎这种广播适用于 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.]])
      

      【讨论】:

      • 这会复制我的tilerepeat 之类的行,但通过清零False 行来避免使用reduceat
      猜你喜欢
      • 2018-02-10
      • 2018-12-28
      • 1970-01-01
      • 2021-06-04
      • 2012-04-10
      • 1970-01-01
      • 2013-06-23
      • 2021-12-31
      相关资源
      最近更新 更多