快速检查NumPy中的NaN


120

我正在寻找最快的方法来检查np.nanNumPy数组中NaN()的出现Xnp.isnan(X)毫无疑问,因为它会构建一个shape的布尔数组X.shape,这可能是巨大的。

我试过了np.nan in X,但这似乎不起作用,因为np.nan != np.nan。有没有一种快速且节省内存的方法来做到这一点?

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


在这种情况下验证用户输入是否无效?正如之前插入检查楠
Woot4Moo

@ Woot4Moo:不,该库将NumPy数组或scipy.sparse矩阵作为输入。
弗雷德·富

2
如果你这样做了很多,我听说过瓶颈(好东西pypi.python.org/pypi/Bottleneck
马特

Answers:


160

雷的解决方案很好。但是,在我的机器上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

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

编辑上面的测试是使用单个NaN在阵列中间进行的。

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

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

1
np.min当数组不包含NaN时,速度更快,这是我的预期输入。但我已经决定无论如何都要接受这一个,因为它抓住infneginf为好。
Fred Foo

2
这仅在捕获inf-inf输入包含两者时才捕获,并且如果输入包含较大但有限的值(将它们加在一起时会溢出),则可能会出现问题。
user2357112支持Monica

4
最小和最大不需要在支持sse的x86芯片上分支浮点数据。因此,从numpy开始,1.8分钟不会比sum慢,在我的amd现象上,它甚至快20%。
jtaylor 2014年

1
在OSX上使用numpy 1.9.2的Intel Core i5上,np.sum它的速度仍然比上快30%np.min
马修·布雷特

np.isnan(x).any(0)略快np.sumnp.min我的机器上,虽然可能有一些不必要的缓存。
jsignell '16

28

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


嗯...当可能是O(1)(对于某些数组)时,它始终是O(n)。
user48956 '17

17

即使存在公认的答案,我也想演示以下内容(在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(.)似乎是最稳定的。


1
我怀疑这与操作系统无关,与底层BLAS实现和C编译器无关。谢谢,但是当x包含大值时,点乘积有点可能溢出,我也想检查inf。
Fred Foo

1
好吧,您总是可以将点积与和一起使用isfinite(.)。我只想指出巨大的性能差距。谢谢

在我的机器上也一样。
kawing-chiu

1
聪明,不是吗?正如Fred Foo所建议的那样,基于点积的方法的效率提高几乎可以肯定归功于本地的NumPy安装与优化的BLAS实施(例如ATLAS,MKL或OpenBLAS)相关联。例如,Anaconda就是这种情况。鉴于此,该点积将在所有可用内核之间并行化。对于仅限于单个核心的基于-或- 方法,不能说相同的话。嗯,那是性能差距。minsum
塞西尔·库里

16

这里有两种通用方法:

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

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

在此处输入图片说明

import numpy
import perfplot


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


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


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


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


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


perfplot.show(
    setup=lambda n: numpy.random.rand(n),
    kernels=[min, sum, dot, any, einsum],
    n_range=[2 ** k for k in range(20)],
    logx=True,
    logy=True,
    xlabel="len(a)",
)

4
  1. 使用.any()

    if numpy.isnan(myarray).any()

  2. numpy.isfinite可能比isnan更好

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


3

如果您满意 它允许创建快速短路(找到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功能相同。


1

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

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.