使用最后一个非零值填充一维numpy数组中的零值

17

假设我们有一个1维的numpy数组,里面填充了一些int值。并且假设其中一些值为0

是否有任何方法,利用numpy数组的功能,将所有0值填充为最后一个非零值找到的值?

例如:

arr = np.array([1, 0, 0, 2, 0, 4, 6, 8, 0, 0, 0, 0, 2])
fill_zeros_with_last(arr)
print arr

[1 1 1 2 2 4 6 8 8 8 8 8 2]

一种方法是使用以下函数:

def fill_zeros_with_last(arr):
    last_val = None # I don't really care about the initial value
    for i in range(arr.size):
        if arr[i]:
            last_val = arr[i]
        elif last_val is not None:
            arr[i] = last_val

然而,这里使用的是原始的Python for循环,而没有利用numpyscipy的强大功能。
如果我们知道一定数量的连续零是可能存在的,我们可以使用基于numpy.roll的方法。问题在于连续零的数量可能非常大...
有什么想法吗?或者我们应该直接使用Cython
免责声明:
我曾经在stackoverflow上发现过一个类似或非常相似的问题。但我找不到它了。 :-(
也许我错过了正确的搜索词,对于重复的问题我感到抱歉。也许那只是我的想象...

4
如果您不介意使用 pandas,可以查看 ffill 方法(或者查看 fillna 以获取完整信息)。但是,numpy 没有内置"向前填充"类型的功能。 - Joe Kington
2
正如@JoeKington所提到的,pandas中的fillna函数可以实现此功能。Cython源代码中的pad_2d_inplace_函数在这里,特别是底部的内部循环。该代码与您在示例中编写的代码完全相同。 - chrisaycock
1
@JoeKington 谢谢!很棒的功能!但我还是更喜欢避免依赖于 pandas... - mgab
你理论上也可以使用 pandas.groupby() - 不过既然有很多其他解决方案,为什么要费事呢 :) - jtlz2
3个回答

31

这里是使用 np.maximum.accumulate 的解决方案:

def fill_zeros_with_last(arr):
    prev = np.arange(len(arr))
    prev[arr == 0] = 0
    prev = np.maximum.accumulate(prev)
    return arr[prev]

我们构建一个与arr长度相同的数组prev,并且prev [i]是在arr的第i个条目之前的最后一个非零条目的索引。例如,如果:

>>> arr = np.array([1, 0, 0, 2, 0, 4, 6, 8, 0, 0, 0, 0, 2])

那么 prev 看起来应该是:

array([ 0,  0,  0,  3,  3,  5,  6,  7,  7,  7,  7,  7, 12])

然后我们只需使用prev作为索引来访问arr,就可以获得我们的结果。一个测试:

>>> arr = np.array([1, 0, 0, 2, 0, 4, 6, 8, 0, 0, 0, 0, 2])
>>> fill_zeros_with_last(arr)
array([1, 1, 1, 2, 2, 4, 6, 8, 8, 8, 8, 8, 2])

注意:当你的数组第一个元素为零时,要仔细理解此操作的含义:

>>> fill_zeros_with_last(np.array([0,0,1,0,0]))
array([0, 0, 1, 1, 1])

你的答案可能比我的更快 - 除非在我使用原地操作可以节省时间的情况下。 - hpaulj
我自己尝试了一种类似的方法,但是这个方法至少同样好而且更快。:) 谢谢! - mgab

5

受到jme在这里的回答以及Bas Swinckels'(在链接的问题中)的启发,我提出了一种不同的numpy函数组合:

def fill_zeros_with_last(arr, initial=0):
     ind = np.nonzero(arr)[0]
     cnt = np.cumsum(np.array(arr, dtype=bool))
     return np.where(cnt, arr[ind[cnt-1]], initial)

我认为这篇文章简洁实用,非常适合记录。然而,jme的文章同样简洁易懂且速度更快,因此我接受了它 :-)


非常好...还有一个潜在的解决方案,可以使用np.repeat,但似乎更难以正确处理边界。它可能不如我们两个中的任何一个快。 - jme

1
如果0只以1的字符串形式出现,那么这个非零值的使用可能会起作用:
In [266]: arr=np.array([1,0,2,3,0,4,0,5])
In [267]: I=np.nonzero(arr==0)[0]
In [268]: arr[I] = arr[I-1]
In [269]: arr
Out[269]: array([1, 1, 2, 3, 3, 4, 4, 5])

我可以通过反复应用此操作来处理您的arr,直到I为空为止。

In [286]: arr = np.array([1, 0, 0, 2, 0, 4, 6, 8, 0, 0, 0, 0, 2])

In [287]: while True:
   .....:     I=np.nonzero(arr==0)[0]
   .....:     if len(I)==0: break
   .....:     arr[I] = arr[I-1]
   .....:     

In [288]: arr
Out[288]: array([1, 1, 1, 2, 2, 4, 6, 8, 8, 8, 8, 8, 2])

如果0的字符串很长,最好找到这些字符串并将它们作为一个块处理。但是,如果大多数字符串很短,重复应用可能是最快的方法。

1
不幸的是,我确实预计可能会出现许多连续的 0。我考虑过这个问题,但是 for 循环并没有让我信服... :-/ - mgab

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