Numpy 二维数组:将所有 NaN 右侧的值更改

4

情况

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

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向量化操作的方法应该更高效。有没有人能帮助我找到这样的方法?

2个回答

5

使用 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 很类似于累加器,不错的建议!考虑将其添加到您的帖子中!尽管其中有太多的代码 ;) - Divakar
1
@MSeifert 加入了这个计时器以及其他的。 - Divakar
在我的电脑上,你的 func5func2func3func4 运行得慢。但鉴于竞争激烈,我添加了一个 Numba 解决方案,它比它们都要快 1.5 倍 :) - MSeifert
@MSeifert 确定,基于JIT的编译器在这里会很不错。 - Divakar

4
使用布尔索引和某种累加器(我在这里使用了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]])

如评论中所指出,使用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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接