在 NumPy 中快速检查 NaN

我正在寻找检查 NumPy 数组 X中 NaN (np.nan)出现情况的最快方法。np.isnan(X)是不可能的,因为它构建了一个形状为 X.shape的布尔数组,这个数组可能非常庞大。

我试了 np.nan in X,但似乎不工作,因为 np.nan != np.nan。有没有一种既快速又节省内存的方法可以做到这一点呢?

(对于那些会问“有多大”的人: 我不知道,这是库代码的输入验证。)

200328 次浏览

我认为 np.isnan(np.min(X))应该做你想做的。

Ray 的解决方案是好的,但是在我的机器上使用 numpy.sum代替 numpy.min要快2.5倍:

In [13]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 244 us per loop


In [14]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 97.3 us per loop

min不同,sum不需要分支,而分支在现代硬件上往往非常昂贵。这可能是为什么 sum更快的原因。

编辑 上述测试是用位于阵列中间的单个 NaN 进行的。

值得注意的是,有 NaNs 存在时,min比没有 NaNs 时慢。随着 NaN 越来越接近数组的开始,它似乎也变得越来越慢。另一方面,sum的吞吐量似乎是恒定的,无论是否存在 NaNs 以及它们位于何处:

In [40]: x = np.random.rand(100000)


In [41]: %timeit np.isnan(np.min(x))
10000 loops, best of 3: 153 us per loop


In [42]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.9 us per loop


In [43]: x[50000] = np.nan


In [44]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 239 us per loop


In [45]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.8 us per loop


In [46]: x[0] = np.nan


In [47]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 326 us per loop


In [48]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.9 us per loop

即使存在一个公认的答案,我也要演示以下内容(在 Vista 上使用 Python 2.7.2和 Numpy 1.6.0) :

In []: x= rand(1e5)
In []: %timeit isnan(x.min())
10000 loops, best of 3: 200 us per loop
In []: %timeit isnan(x.sum())
10000 loops, best of 3: 169 us per loop
In []: %timeit isnan(dot(x, x))
10000 loops, best of 3: 134 us per loop


In []: x[5e4]= NaN
In []: %timeit isnan(x.min())
100 loops, best of 3: 4.47 ms per loop
In []: %timeit isnan(x.sum())
100 loops, best of 3: 6.44 ms per loop
In []: %timeit isnan(dot(x, x))
10000 loops, best of 3: 138 us per loop

因此,真正有效的方法可能严重依赖于操作系统。无论如何,基于 dot(.)似乎是最稳定的一个。

与此相关的问题是如何找到首次出现的 NaN。据我所知,这是最快的处理方法:

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)

如果你对 感到满意,它允许创建一个快速短路(一旦发现 NaN 就停止)功能:

import numba as nb
import math


@nb.njit
def anynan(array):
array = array.ravel()
for i in range(array.size):
if math.isnan(array[i]):
return True
return False

如果没有 NaN,函数实际上可能比 np.min慢,我认为这是因为 np.min对大型数组使用多处理:

import numpy as np
array = np.random.random(2000000)


%timeit anynan(array)          # 100 loops, best of 3: 2.21 ms per loop
%timeit np.isnan(array.sum())  # 100 loops, best of 3: 4.45 ms per loop
%timeit np.isnan(array.min())  # 1000 loops, best of 3: 1.64 ms per loop

但是,如果数组中有一个 NaN,特别是当它的位置处于低索引时,那么它会快得多:

array = np.random.random(2000000)
array[100] = np.nan


%timeit anynan(array)          # 1000000 loops, best of 3: 1.93 µs per loop
%timeit np.isnan(array.sum())  # 100 loops, best of 3: 4.57 ms per loop
%timeit np.isnan(array.min())  # 1000 loops, best of 3: 1.65 ms per loop

使用 Cython 或 C 扩展也可以获得类似的结果,这些扩展稍微复杂一些(或者像 bottleneck.anynan一样容易获得) ,但最终与我的 anynan函数一样。

这里有两种通用的方法:

  • 检查每个数组项目的 nan并取 any
  • 应用一些保留 nan的累积操作(如 sum)并检查其结果。

虽然第一种方法肯定是最干净的,但是对一些累积操作(特别是在 BLAS 中执行的操作,如 dot)的大量优化可以使这些操作相当快。注意,与其他 BLAS 操作一样,dot在某些条件下也是多线程的。这就解释了不同机器之间的速度差异。

enter image description here

import numpy as np
import perfplot




def min(a):
return np.isnan(np.min(a))




def sum(a):
return np.isnan(np.sum(a))




def dot(a):
return np.isnan(np.dot(a, a))




def any(a):
return np.any(np.isnan(a))




def einsum(a):
return np.isnan(np.einsum("i->", a))




b = perfplot.bench(
setup=np.random.rand,
kernels=[min, sum, dot, any, einsum],
n_range=[2 ** k for k in range(25)],
xlabel="len(a)",
)
b.save("out.png")
b.show()
  1. 使用

    if numpy.isnan(myarray).any()

  2. 是有限的也许比 Isnan 检查起来更好

    if not np.isfinite(prop).all()

添加到@nico-schlömer 和@mseifert 的回答中,我计算了具有提前停止的 numba 测试 has_nan的性能,与一些将解析完整数组的函数相比较。

在我的机器上,对于一个没有 nans 的数组,对于大约10 ^ 4个元素会达到收支平衡。

has_nan_vs_full_parse_methods


import perfplot
import numpy as np
import numba
import math


def min(a):
return np.isnan(np.min(a))


def dot(a):
return np.isnan(np.dot(a, a))


def einsum(a):
return np.isnan(np.einsum("i->", a))


@numba.njit
def has_nan(a):
for i in range(a.size - 1):
if math.isnan(a[i]):
return True
return False




def array_with_missing_values(n, p):
""" Return array of size n,  p : nans ( % of array length )
Ex : n=1e6, p=1 : 1e4 nan assigned at random positions """
a = np.random.rand(n)
p = np.random.randint(0, len(a), int(p*len(a)/100))
a[p] = np.nan
return a




#%%
perfplot.show(
setup=lambda n: array_with_missing_values(n, 0),
kernels=[min, dot, has_nan],
n_range=[2 ** k for k in range(20)],
logx=True,
logy=True,
xlabel="len(a)",
)


如果数组有 nan 会发生什么? 我研究了 nan 覆盖率对数组的影响。

对于长度为1,000,000的数组,如果数组中有约10 ^ -3% 的 n (即约10个 n) ,则 has_nan成为一个更好的选择。

impact of nan-coverage of array


#%%
N = 1000000  # 100000
perfplot.show(
setup=lambda p: array_with_missing_values(N, p),
kernels=[min, dot, has_nan],
n_range=np.array([2 ** k for k in range(20)]) / 2**20 * 0.01,
logy=True,
xlabel=f"% of nan in array (N = {N})",
)

如果在您的应用程序中,大多数数组都有 nan,而您正在寻找没有 nan的数组,那么 has_nan是最好的方法。 否则,dot似乎是最好的选择。