Скалярное isnull () / isnan () / isinf () - PullRequest
0 голосов
/ 01 ноября 2018

В Pandas и Numpy есть векторизованные функции, такие как np.isnan, np.isinf и pd.isnull, чтобы проверить, являются ли элементы массива, серии или фрейма данных различными видами отсутствующими / нулевыми / недействительными.

Они работают на скалярах. pd.isnull(None) просто возвращает True вместо pd.Series([True]), что удобно.

Но скажем, я хочу знать, является ли любой объект одним из этих нулевых значений; Вы не можете сделать это ни с одной из этих функций! Это потому, что они с радостью будут векторизовать различные структуры данных. Неосторожное их использование неизбежно приведет к страшной ошибке «Истина серии неоднозначна».

Мне нужна такая функция:

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.

Есть ли "приемлемый", но официальный способ использовать ту же логику проверки нуля, что и NumPy и Pandas, но строго для скаляров?

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

Это расширение @ решения DeepSpace . Для массивов 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
0 голосов
/ 01 ноября 2018

Все, что вам нужно сделать, это обернуть 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.

...