首页 文章

在numpy矩阵中交换零

提问于
浏览
3

我有一个像这样的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)

是否有一种简短的pythonic方式来实现这一点,也许是使用来自numpy,pandas或scikit-learn的api方法?

6 回答

  • 0

    这是一个带有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]])
    

    Runtime test

    适用于通用案例的方法 -

    # 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
    
  • 3

    熊猫方法:

    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 每行的零数

  • 1

    你也可以使用 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)
    
  • 0

    基于非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]]
    
  • 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]])
    

    唯一的缺点是,它不如上面提到的一些方法快,但仍然是一个简短的单线程 .

  • 0

    基于行滚动的解决方案,本着 @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 .

相关问题