在一个numpy数组中找到连续重复的nan。

8
什么是在numpy数组中找到连续重复的nan的最大数量的最佳方法?
例子:
from numpy import nan

输入 1:[nan, nan, nan, 0.16, 1, 0.16, 0.9999, 0.0001, 0.16, 0.101, nan, 0.16]

输出 1:3

输入 2:[nan, nan, 2, 1, 1, nan, nan, nan, nan, 0.101, nan, 0.16]

输出 2:4

7个回答

6
这里有一种方法 -
def max_repeatedNaNs(a):
    # Mask of NaNs
    mask = np.concatenate(([False],np.isnan(a),[False]))
    if ~mask.any():
        return 0
    else:
        # Count of NaNs in each NaN group. Then, get max count as o/p.
        c = np.flatnonzero(mask[1:] < mask[:-1]) - \
            np.flatnonzero(mask[1:] > mask[:-1])
        return c.max()

这是改进后的版本 -

def max_repeatedNaNs_v2(a):
    mask = np.concatenate(([False],np.isnan(a),[False]))
    if ~mask.any():
        return 0
    else:
        idx = np.nonzero(mask[1:] != mask[:-1])[0]
        return (idx[1::2] - idx[::2]).max()

针对@pltrdy的评论进行基准测试 -

In [77]: a = np.random.rand(10000)

In [78]: a[np.random.choice(range(len(a)),size=1000,replace=0)] = np.nan

In [79]: %timeit contiguous_NaN(a) #@pltrdy's solution
100 loops, best of 3: 15.8 ms per loop

In [80]: %timeit max_repeatedNaNs(a)
10000 loops, best of 3: 103 µs per loop

In [81]: %timeit max_repeatedNaNs_v2(a)
10000 loops, best of 3: 86.4 µs per loop

4
感谢您对该评论的点赞和反对。这个解决方案旨在提高性能。将添加一些运行时测试来证明其有效性。 - Divakar
1
@pltrdy 好吧,这个人可能是在谈论“最佳”时谈到性能。很快会添加时间。 - Divakar
1
我完全同意Divarkar的观点,这个解决方案可能比必要的更为复杂,而且我不喜欢任何包含np.concatenate的代码,但是这对于更大的数组来说将会有很好的扩展性。 - MSeifert
1
@MSeifert 当然,马上就来! - Divakar
2
@Tagc MSeifert 承担了繁重的工作,并在 他的帖子 中发布了时间。请查看! - Divakar
显示剩余8条评论

4

我不知道你是否有使用Numba,但它对于这样的特殊问题非常方便(而且快速):

import numba as nb
import math

@nb.njit   # also works without but then it's several orders of magnitudes slower
def max_consecutive_nan(arr):
    max_ = 0
    current = 0
    idx = 0
    while idx < arr.size:
        while idx < arr.size and math.isnan(arr[idx]):
            current += 1
            idx += 1
        if current > max_:
            max_ = current
        current = 0
        idx += 1
    return max_

针对你的例子:

>>> from numpy import nan
>>> max_consecutive_nan(np.array([nan, nan, 2, 1, 1, nan, nan, nan, nan, 0.101, nan, 0.16]))
4

>>> max_consecutive_nan(np.array([nan, nan, nan, 0.16, 1, 0.16, 0.9999, 0.0001, 0.16, 0.101, nan, 0.16]))
3

>>> max_consecutive_nan(np.array([0.16, 0.16, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]))
22

使用@Divarkar提出的基准测试,并按性能排序(完整的基准测试代码可以在此gist中找到):

arr = np.random.rand(10000)
arr[np.random.choice(range(len(arr)),size=1000,replace=0)] = np.nan
%timeit mine(arr)         # 10000 loops, best of 3: 67.7 µs per loop
%timeit Divakar_v2(arr)   # 1000 loops, best of 3: 196 µs per loop
%timeit Divakar(arr)      # 1000 loops, best of 3: 252 µs per loop
%timeit Tagc(arr)         # 100 loops, best of 3: 6.92 ms per loop
%timeit Kasramvd(arr)     # 10 loops, best of 3: 38.2 ms per loop
%timeit pltrdy(arr)       # 10 loops, best of 3: 70.9 ms per loop

哦,我没有访问“numba”的权限!你能用我的时间测试吗? - Divakar
@Divakar 好的,我已经包含了它。只有很少的人拥有numba,这太糟糕了 :( - MSeifert
太棒了!谢谢。是啊..我想更多的人应该去探索/使用它。 - Divakar
有趣的是,最快的解决方案比最慢的解决方案快了1000多倍。 - Tagc
如果可以再麻烦您一次 - 我添加了一个更好的版本,我认为它比之前的版本提高了15%以上。所以,如果不是太麻烦的话,您能否将其加入到您的时间计算中呢? :) - Divakar
显示剩余3条评论

1

我发布了另一个基于 itertools 的答案,但我认为这个更好:

from itertools import groupby

from numpy import nan


def longest_nan_run(sequence):
    return max((sum(1 for _ in group) for key, group in groupby(sequence) if key is nan), default=0)


if __name__ == '__main__':
    array1 = [nan, nan, nan, 0.16, 1, 0.16, 0.9999, 0.0001, 0.16, 0.101, nan, 0.16]
    array2 = [nan, nan, 2, 1, 1, nan, nan, nan, nan, 0.101, nan, 0.16]

    print(longest_nan_run(array1))  # 3
    print(longest_nan_run(array2))  # 4
    print(longest_nan_run([]))      # 0
    print(longest_nan_run([1, 2]))  # 0

编辑:现在处理了没有存在nan值的情况(感谢MSeifert指出)。


就性能而言,我猜@ Divakar的方案会更好 - pltrdy
@pltrdy 可能可以 - 让我们试试 :) - Tagc
@Tagc 当我进行基准测试时,我注意到你应该指定一个“default”来捕获没有“nan”的情况:max((sum(1 for _ in group) for key, group in groupby(sequence) if key is nan), default=0) - MSeifert
@MSeifert 谢谢!已更新。同时感谢您发布时间表。 - Tagc

1

性能提升是可能的,特别是当存在长的nan序列时。在这种情况下,没有必要测试所有的值。

使用@MSeifert的方法和符号,如果在max_长度块中出现任何空洞,则可以通过步长为max_来扫描数组:

@nb.njit
def max_consecutive_nan2(arr):
    max_ = 0
    idx = 0
    while idx < arr.size:
        while idx < arr.size and math.isnan(arr[idx]): # amelioration
            max_ += 1
            idx  += 1
        while idx < arr.size - max_:
            idx2 = idx + max_
            while idx2>idx and math.isnan(arr[idx2]):
                idx2 -=1
            if idx2==idx: # record reached.
              idx = idx + max_ +1
              break # goto amelioration
            idx=idx2 # skip unuseful tests
        else : return max_         
    return max_ #case record at end. 

结果:

arr = np.random.rand(10000)
arr[np.random.choice(range(len(arr)),size=4000,replace=0)] = np.nan

In [25]: max_consecutive_nan(arr)
Out[25]: 14

In [26]: max_consecutive_nan2(arr)
Out[26]: 14

表演:

In [27]: %timeit max_consecutive_nan2(arr)
100000 loops, best of 3: 3.29 µs per loop

In [28]: %timeit max_consecutive_nan(arr) # MSeifert
10000 loops, best of 3: 68.5 µs per loop

0

这是我的解决方案。
计算复杂度为O(n),其中n = len(arr),空间复杂度为O(1)

def contiguous_NaN(arr):
     count, max_count = 0, 0
     for e in arr:
         if np.isnan(e):
             count += 1
             max_count = max(max_count, count)
         else:
             count = 0

     return max_count

编辑:请记住你的代码要达到以下目的:

  1. 能够运行
  2. 使用合理的资源(时间和空间)
  3. 易于阅读和理解。

3
迭代NumPy数组绝对违反了第二点:因为这是处理数组最低效的方法之一。 - MSeifert
感谢 @pltrdy 的回答,但当数组的最后一个元素是 'nan' 时,此函数会返回 0。例如:"[0.16, 0.16, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]" 返回了 0。 - volt

0
另一种易于阅读和理解的方法是使用字符串,然后使用str.split函数:
array2 = [nan, nan, 2, 1, 1, nan, nan, nan, nan, 0.101, nan, 0.16]
thestring=isnan(array2).tobytes().decode()
#'\x01\x01\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00'
m=max(len(c) for c in thestring.split('\x00'))
# 4

0

在NumPy中,可以高效地完成此操作,而无需使用任何循环。

如果我们将序列称为x,则可以使用以下方法找到连续的nan的最大数量:

np.max(np.diff(np.concatenate(([-1], np.where(-np.isnan(x))[0], [len(x)]))) - 1)

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