标量值 isnull()/isnan()/isinf()

Scalar-valued isnull()/isnan()/isinf()

在Pandas和Numpy中,有像np.isnannp.isinfpd.isnull这样的向量化函数来检查数组、序列或数据帧的元素是否是各种missing/null/invalid.

他们确实在标量上工作。 pd.isnull(None) 简单地 returns True 而不是 pd.Series([True]),这很方便。

但是假设我想知道 any 对象是否是这些空值之一;你不能用这些功能中的任何一个来做到这一点!那是因为他们会愉快地对各种数据结构进行矢量化。粗心地使用它们将不可避免地导致可怕的 "The truth value of a Series is ambiguous" 错误。

我想要的是这样一个函数:

assert not is_scalar_null(3)
assert not is_scalar_null([1,2])
assert not is_scalar_null([None, 1])
assert not is_scalar_null(pd.Series([None, 1]))
assert not is_scalar_null(pd.Series([None, None]))
assert is_scalar_null(None)
assert is_scalar_null(np.nan)

在内部,Pandas 函数 pandas._lib.missing.checknull 会做正确的事情:

import pandas._libs.missing as libmissing
libmissing.checknull(pd.Series([1,2]))  # correctly returns False

但使用它通常是不好的做法;根据 Python 命名约定,_lib 是私有的。我也不确定 Numpy 的等价物。

是否有 "acceptable" 但官方的方法来使用与 NumPy 和 Pandas 相同的空检查逻辑,但严格用于标量?

你所要做的就是用一种方式包装 pd.isnull ,如果它得到一个可迭代的,它将被迫逐个元素地检查它。这样,您将始终获得标量布尔值作为输出。

from collections import Iterable

def is_scalar_null(value):
    if isinstance(value, Iterable):
        return all(not pd.isnull(v) for v in value)
    return not pd.isnull(value)

assert is_scalar_null(3)
assert is_scalar_null([1, 2])
assert is_scalar_null(pd.Series([1]))
assert not is_scalar_null(None)
assert not is_scalar_null(np.nan)
assert not is_scalar_null([np.nan, 1])
assert not is_scalar_null(pd.Series([np.nan, 1]))

然后您可以修补实际 pd.isnull,但我不能说我建议这样做。

from collections import Iterable

orig_pd_is_null = pd.isnull

def is_scalar_null(value):
    if isinstance(value, Iterable):
        return all(not orig_pd_is_null(v) for v in value)
    return not orig_pd_is_null(value)

pd.isnull = is_scalar_null

assert pd.isnull(3)
assert pd.isnull([1, 2])
assert pd.isnull(pd.Series([1]))
assert not pd.isnull(None)
assert not pd.isnull(np.nan)
assert not pd.isnull([np.nan, 1])
assert not pd.isnull(pd.Series([np.nan, 1]))

在嵌套迭代的情况下,这种方法可能会中断,但这可以通过在 is_scalar_null.

中使用递归来解决

这是对 的扩展。对于 NumPy 数组和扩展的数字 Pandas 系列,您可以利用 numba 对循环进行 JIT 编译。 all / any 使用生成器理解通常效率较低,并且当您的 NaN 值接近数组末尾时通常非常昂贵。

例如,在极端情况下,我们看到约 240 倍的性能差异:

from collections import Iterable
from numba import njit

def any_null(arr):
    for i in range(len(arr)):
        if np.isnan(arr[i]):
            return True
    return False

def is_scalar_null(value, jit_flag=True):
    checker = njit(any_null) if jit_flag else any_null
    if isinstance(value, pd.Series):
        return checker(value.values)
    elif isinstance(value, np.ndarray):
        return checker(value)
    elif isinstance(value, Iterable):
        return all(not pd.isnull(v) for v in value)
    return not pd.isnull(value)

np.random.seed(0)
A = np.random.random(10**7)
A[-1] = np.nan

%timeit is_scalar_null(A, jit_flag=True)  # 74.3 ms per loop
%timeit is_scalar_null(A, jit_flag=False) # 17.6 s per loop

标量值 isinfisnan 可以直接在 math module.

中找到

可以轻松完成基本的标量空值检查:

from math import isnan

def is_scalar_null(x):
    return x is None or (isinstance(x, float) and isnan(x))

这里可能有一些未捕获的边缘情况,但它在我的使用中效果很好。随着 Pandas 开始丰富他们在最新版本 (>= 0.25) 中对 "null" 数据的表示,这也可能会发生变化。