【问题标题】:Swap zeros in numpy matrix在numpy矩阵中交换零
【发布时间】:2017-08-09 02:49:25
【问题描述】:

我有一个像这样的 numpy 矩阵:

array([[2,  1, 23, 32],
       [34, 3, 3, 0],
       [3, 33, 0, 0],
       [32, 0, 0, 0]], dtype=int32)

现在我想将所有数字向右移动并将零向左交换,如下所示:

array([[2, 1,  23, 32],
       [0, 34, 3,  3],
       [0, 0,  3,  33],
       [0, 0,  0,  32]], dtype=int32)

是否有一种简短的 Python 方式来执行此操作,可能是使用 numpy、pandas 或 scikit-learn 的 api 方法?

【问题讨论】:

  • 发布的解决方案是否对您有用?
  • 是的!我正在尝试决定接受什么答案,这很难

标签: python pandas numpy matrix scikit-learn


【解决方案1】:

本着@EDChum's pandas 版本的精神,基于行滚动的解决方案:

def rowroll(arr):
    for row in arr:
        row[:] = np.roll(row,-np.count_nonzero(row))
    return arr
In [221]: rowroll(arr.copy())
Out[221]: 
array([[ 2,  1, 23, 32],
       [ 0, 34,  3,  3],
       [ 0,  0,  3, 33],
       [ 0,  0,  0, 32]])

np.count_nonzero 是一种快速编译的查找非零数的方法。 np.where 使用它来查找其返回大小。

但是查看np.roll 代码,我认为它对于任务来说过于复杂,因为它可以与多个轴一起工作。

这看起来更混乱,但我怀疑它和roll 一样快,如果不快的话:

def rowroll(arr):
    for row in arr:
        n = np.count_nonzero(row)
        temp = np.zeros_like(row)
        temp[-n:] = row[:n]
        row[:] = temp
    return arr

roll 解决方案需要原始的尾随 0,而不是分散的 0。

【讨论】:

    【解决方案2】:

    您还可以在 numpy.ma.sort() 的帮助下对掩码数组执行排序,该数组沿最后一个轴 axis=-1 就地排序,如图所示:

    np.ma.array(a, mask=a!=0).sort()
    

    现在a 变为:

    array([[ 2,  1, 23, 32],
           [ 0, 34,  3,  3],
           [ 0,  0,  3, 33],
           [ 0,  0,  0, 32]])
    

    唯一的缺点是它没有上面提到的一些方法那么快,但仍然是一个短的单线。

    【讨论】:

      【解决方案3】:

      这是masking 的矢量化方法-

      valid_mask = a!=0
      flipped_mask = valid_mask.sum(1,keepdims=1) > np.arange(a.shape[1]-1,-1,-1)
      a[flipped_mask] = a[valid_mask]
      a[~flipped_mask] = 0
      

      示例运行 -

      In [90]: a
      Out[90]: 
      array([[ 2,  1, 23, 32],
             [34,  0,  3,  0],  # <== Added a zero in between for variety
             [ 3, 33,  0,  0],
             [32,  0,  0,  0]])
      
      # After code run -
      
      In [92]: a
      Out[92]: 
      array([[ 2,  1, 23, 32],
             [ 0,  0, 34,  3],
             [ 0,  0,  3, 33],
             [ 0,  0,  0, 32]])
      

      更通用的示例运行 -

      In [94]: a
      Out[94]: 
      array([[1, 1, 2, 3, 1, 0, 3, 0, 2, 1],
             [2, 1, 0, 1, 2, 0, 1, 3, 1, 1],
             [1, 2, 0, 3, 0, 3, 2, 0, 2, 2]])
      
      # After code run -
      
      In [96]: a
      Out[96]: 
      array([[0, 0, 1, 1, 2, 3, 1, 3, 2, 1],
             [0, 0, 2, 1, 1, 2, 1, 3, 1, 1],
             [0, 0, 0, 1, 2, 3, 3, 2, 2, 2]])
      

      运行时测试

      适用于一般情况的方法 -

      # Proposed in this post
      def masking_based(a):
          valid_mask = a!=0
          flipped_mask = valid_mask.sum(1,keepdims=1) > np.arange(a.shape[1]-1,-1,-1)
          a[flipped_mask] = a[valid_mask]
          a[~flipped_mask] = 0
          return a
      
      # @Psidom's soln            
      def sort_based(a):
          return a[np.arange(a.shape[0])[:, None], (a != 0).argsort(1, kind="mergesort")]
      

      时间安排 -

      In [205]: a = np.random.randint(0,4,(1000,1000))
      
      In [206]: %timeit sort_based(a)
      10 loops, best of 3: 30.8 ms per loop
      
      In [207]: %timeit masking_based(a)
      100 loops, best of 3: 6.46 ms per loop
      
      In [208]: a = np.random.randint(0,4,(5000,5000))
      
      In [209]: %timeit sort_based(a)
      1 loops, best of 3: 961 ms per loop
      
      In [210]: %timeit masking_based(a)
      1 loops, best of 3: 151 ms per loop
      

      【讨论】:

      • 这是一种更好的通用方法,因为它完全解决了 OP 所说的内容,但 OP 发布的示例数据并不一定需要这个,仍然 +1
      • 时间呢?你能比较解决方案吗?谢谢。
      • @jezrael 为尝试解决一般情况的解决方案添加。
      【解决方案4】:

      您也可以将numpy.argsortadvanced indexing 一起使用:

      arr[np.arange(arr.shape[0])[:, None], (arr != 0).argsort(1, kind="mergesort")]
      
      #array([[ 2,  1, 23, 32],
      #       [ 0, 34,  3,  3],
      #       [ 0,  0,  3, 33],
      #       [ 0,  0,  0, 32]], dtype=int32)
      

      【讨论】:

      • 那不会保持订单。您需要使用'mergesort' 来执行此操作。顺便说一句好主意,问题中提出的简短的一个!
      • @Divakar 你是对的。没注意。
      【解决方案5】:

      在基于非 numpy 的 python 中的微不足道的尝试 -

      >>> arr = [[2,  1, 23, 32],
      ...        [34, 3, 3, 0],
      ...        [3, 33, 0, 0],
      ...        [32, 0, 0, 0]]
      ... 
      >>> t_arr = [[0 for _ in range(cur_list.count(0))]\
                  + [i for i in cur_list if i!=0]\
                  for cur_list in arr]
      >>> t_arr
      [[2, 1, 23, 32], [0, 34, 3, 3], [0, 0, 3, 33], [0, 0, 0, 32]]
      

      【讨论】:

        【解决方案6】:

        熊猫方法:

        In [181]:
        # construct df from array
        df = pd.DataFrame(a)
        # call apply and call np.roll rowise and roll by the number of zeroes
        df.apply(lambda x: np.roll(x, (x == 0).sum()), axis=1).values
        
        Out[181]:
        array([[ 2,  1, 23, 32],
               [ 0, 34,  3,  3],
               [ 0,  0,  3, 33],
               [ 0,  0,  0, 32]])
        

        这使用apply,所以我们可以在每行上调用np.roll,每行中的零个数

        【讨论】:

        • df.apply 不只是遍历行吗?如何将相同的lambda 应用于数组的每一行,而不进行pd 转换?
        • @hpaulj 是的,np.roll 不接受除标量以外的任何东西,这就是我这样做的原因
        猜你喜欢
        • 2012-12-06
        • 1970-01-01
        • 2023-02-09
        • 1970-01-01
        • 1970-01-01
        • 2017-05-09
        • 2019-06-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多