【问题标题】:Numpy 2D array: change all values to the right of NaNsNumpy 2D 数组:将所有值更改为 NaN 的右侧
【发布时间】:2017-06-25 04:36:57
【问题描述】:

情况

我有一个 2D Numpy 数组,其中包含一些 nan 值。简化示例:

arr = np.array([[3, 5, np.nan, 2, 4],
                [9, 1, 3, 5, 1],
                [8, np.nan, 3, np.nan, 7]])

在控制台输出中看起来像这样:

array([[  3.,   5.,  nan,   2.,   4.],
       [  9.,   1.,   3.,   5.,   1.],
       [  8.,  nan,   3.,  nan,   7.]])

问题

我正在寻找一种将现有nan 右侧的所有值也设置为nan 的好方法。换句话说,我需要将示例数组转换为:

array([[  3.,   5.,  nan,  nan,  nan],
       [  9.,   1.,   3.,   5.,   1.],
       [  8.,  nan,  nan,  nan,  nan]]) 

我知道如何使用循环来实现这一点,但我认为只使用 Numpy 矢量化操作的方法会更有效。有没有人可以帮我找到这样的方法?

【问题讨论】:

    标签: python arrays performance numpy vectorization


    【解决方案1】:

    cumsumboolean-indexing 的一种方法-

    arr[np.isnan(arr).cumsum(1)>0] = np.nan
    

    为了性能,最好使用np.maximum.accumulate -

    arr[np.maximum.accumulate(np.isnan(arr),axis=1)] = np.nan
    

    另一种方式,有点扭曲地使用broadcasting -

    n = arr.shape[1]
    mask = np.isnan(arr)
    idx = mask.argmax(1)
    idx[~mask.any(1)] = n
    arr[idx[:,None] <= np.arange(n)] = np.nan
    

    示例运行 -

    In [96]: arr
    Out[96]: 
    array([[  3.,   5.,  nan,   2.,   4.],
           [  9.,   1.,   3.,   5.,   1.],
           [  8.,  nan,   3.,  nan,   7.]])
    
    In [97]: arr[np.maximum.accumulate(np.isnan(arr),axis=1)] = np.nan
    
    In [98]: arr
    Out[98]: 
    array([[  3.,   5.,  nan,  nan,  nan],
           [  9.,   1.,   3.,   5.,   1.],
           [  8.,  nan,  nan,  nan,  nan]])
    

    基准测试

    方法-

    def func1(arr):
        arr[np.isnan(arr).cumsum(1)>0] = np.nan
    
    def func2(arr):
        arr[np.maximum.accumulate(np.isnan(arr),axis=1)] = np.nan
    
    def func3(arr): # @ MSeifert's suggestion
        mask = np.isnan(arr); 
        accmask = np.cumsum(mask, out=mask, axis=1); 
        arr[accmask] = np.nan
    
    def func4(arr):
        mask = np.isnan(arr); 
        np.maximum.accumulate(mask,axis=1, out = mask)
        arr[mask] = np.nan
    
    def func5(arr):
        n = arr.shape[1]
        mask = np.isnan(arr)
        idx = mask.argmax(1)
        idx[~mask.any(1)] = n
        arr[idx[:,None] <= np.arange(n)] = np.nan
    

    时间安排 -

    In [201]: # Setup inputs
         ...: arr = np.random.rand(5000,5000)
         ...: arr.ravel()[np.random.choice(range(arr.size), 10000, replace=0)] = np.nan
         ...: arr1 = arr.copy()
         ...: arr2 = arr.copy()
         ...: arr3 = arr.copy()
         ...: arr4 = arr.copy()
         ...: arr5 = arr.copy()
         ...: 
    
    In [202]: %timeit func1(arr1)
         ...: %timeit func2(arr2)
         ...: %timeit func3(arr3)
         ...: %timeit func4(arr4)
         ...: %timeit func5(arr5)
         ...: 
    10 loops, best of 3: 149 ms per loop
    10 loops, best of 3: 90.5 ms per loop
    10 loops, best of 3: 88.8 ms per loop
    10 loops, best of 3: 88.5 ms per loop
    10 loops, best of 3: 75.3 ms per loop
    

    基于广播的似乎做得很好!

    【讨论】:

    • 如果性能是一个问题,mask = np.isnan(arr); accmask = np.cumsum(mask, out=mask, axis=1); arr[accmask] = np.nan 会更快(并且可能更节省内存):-)
    • @MSeifert 与累积的相当,好建议!考虑将其添加到您的帖子中!虽然里面的代码太多了;)
    • @MSeifert 与其他人一起添加了时间。
    • 在我的电脑上,您的func5func2func3func4 慢。但考虑到这是一个近距离运行,我添加了一个 numba 解决方案,它比所有解决方案都高出 1.5 倍 :)
    • @MSeifert 当然,一个基于 JIT 的会很好。
    【解决方案2】:

    使用布尔索引和某种累加器(我在这里使用了np.cumsum):

    >>> mask = np.cumsum(np.isnan(arr), axis=1).astype(bool)
    
    >>> arr[mask] = np.nan
    
    >>> arr
    array([[  3.,   5.,  nan,  nan,  nan],
           [  9.,   1.,   3.,   5.,   1.],
           [  8.,  nan,  nan,  nan,  nan]])
    

    正如 cmets 中已经指出的那样,使用 out-参数可能会加快速度并避免创建另一个临时数组:

    def put_nans_right_of_nans(arr):
        mask = np.isnan(arr)
        mask = np.cumsum(mask, out=mask, axis=1)
        arr[mask] = np.nan
    

    鉴于我是 的狂热爱好者,我想展示一个易于实施并且在性能和内存使用方面优于所有其他方法的解决方案:

    import numba as nb
    import math
    
    @nb.njit
    def nan_items_rightofnans(arr):
        x, y = arr.shape[0], arr.shape[1]
    
        for row_no in range(x):
            nanfound = False
            for col_no in range(y):
                if nanfound:
                    arr[row_no, col_no] = np.nan
                elif math.isnan(arr[row_no, col_no]):
                    nanfound = True
    
        return arr
    

    【讨论】:

      猜你喜欢
      • 2021-08-26
      • 1970-01-01
      • 2013-06-04
      • 2021-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-08
      相关资源
      最近更新 更多