检测NumPy数组是否包含至少一个非数值元素?

169 投票
5 回答
197652 浏览
提问于 2025-04-15 11:50

我需要写一个函数,用来检查输入中是否至少有一个值不是数字。如果发现有非数字的值,我会抛出一个错误(因为计算应该只返回数字)。输入数组的维度数量事先是未知的——这个函数应该能正确处理任何维度的输入。作为额外的复杂性,输入可能是一个单独的浮点数,或者是numpy.float64,甚至可能是像零维数组这样的奇怪东西。

解决这个问题的明显方法是写一个递归函数,遍历数组中的每一个可迭代对象,直到找到一个不可迭代的对象。它会对每个不可迭代的对象应用numpy.isnan()函数。如果发现至少一个非数字的值,函数会立即返回False。否则,如果所有可迭代的值都是数字,最终会返回True。

这个方法确实可以工作,但速度比较慢,我觉得NumPy应该有更好的方法来处理这个问题。有没有更快、更符合NumPy风格的替代方案呢?

这是我的初步设计:

def contains_nan( myarray ):
    """
    @param myarray : An n-dimensional array or a single float
    @type myarray : numpy.ndarray, numpy.array, float
    @returns: bool
    Returns true if myarray is numeric or only contains numeric values.
    Returns false if at least one non-numeric value exists
    Not-A-Number is given by the numpy.isnan() function.
    """
    return True

5 个回答

14

哼!微秒算什么!

别用微秒去解决那些可以用纳秒解决的问题。

注意,接受的答案:

  • 会遍历整个数据,不管有没有找到纳米值(nan)
  • 还会创建一个大小为N的临时数组,这其实是多余的。

一个更好的方法是,一旦找到NAN就立刻返回True:

import numba
import numpy as np

NAN = float("nan")

@numba.njit(nogil=True)
def _any_nans(a):
    for x in a:
        if np.isnan(x): return True
    return False

@numba.jit
def any_nans(a):
    if not a.dtype.kind=='f': return False
    return _any_nans(a.flat)

array1M = np.random.rand(1000000)
assert any_nans(array1M)==False
%timeit any_nans(array1M)  # 573us

array1M[0] = NAN
assert any_nans(array1M)==True
%timeit any_nans(array1M)  # 774ns  (!nanoseconds)

而且这个方法适用于多维数据:

array1M_nd = array1M.reshape((len(array1M)/2, 2))
assert any_nans(array1M_nd)==True
%timeit any_nans(array1M_nd)  # 774ns

再来看看numpy的原生解决方案:

def any_nans(a):
    if not a.dtype.kind=='f': return False
    return np.isnan(a).any()

array1M = np.random.rand(1000000)
assert any_nans(array1M)==False
%timeit any_nans(array1M)  # 456us

array1M[0] = NAN
assert any_nans(array1M)==True
%timeit any_nans(array1M)  # 470us

%timeit np.isnan(array1M).any()  # 532us

这种提前退出的方法在某些情况下速度提升了三个数量级。对于一个简单的注释来说,这可不算差。

30

如果无穷大是一个可能的值,我会使用 numpy.isfinite

numpy.isfinite(myarray).all()

如果上面的结果是 True,那么 myarray 里就没有 numpy.nannumpy.inf 或者 -numpy.inf 这些值。

numpy.isnannumpy.inf 的值是可以接受的,比如:

In [11]: import numpy as np

In [12]: b = np.array([[4, np.inf],[np.nan, -np.inf]])

In [13]: np.isnan(b)
Out[13]: 
array([[False, False],
       [ True, False]], dtype=bool)

In [14]: np.isfinite(b)
Out[14]: 
array([[ True, False],
       [False, False]], dtype=bool)
291

这个方法应该比逐个处理要快,而且不管数据的形状如何都能正常工作。

numpy.isnan(myarray).any()

补充说明:速度快了30倍:

import timeit
s = 'import numpy;a = numpy.arange(10000.).reshape((100,100));a[10,10]=numpy.nan'
ms = [
    'numpy.isnan(a).any()',
    'any(numpy.isnan(x) for x in a.flatten())']
for m in ms:
    print "  %.2f s" % timeit.Timer(m, s).timeit(1000), m

结果:

  0.11 s numpy.isnan(a).any()
  3.75 s any(numpy.isnan(x) for x in a.flatten())

额外说明:它对非数组的NumPy类型也能很好地工作:

>>> a = numpy.float64(42.)
>>> numpy.isnan(a).any()
False
>>> a = numpy.float64(numpy.nan)
>>> numpy.isnan(a).any()
True

撰写回答