Считая нан в серии панд - когда этот путь потерпит неудачу? - PullRequest
1 голос
/ 08 мая 2019

В нем много постов о том, как считать NaN в списке или серии панд, а также об эффективности времени различных опций. Одно из решений, которое я не видел, это само равенство: если y == np.nan, то (y != y) is True. Таким образом, быстрый способ подсчета NaN в списке:

import pandas as pd
import numpy as np

lst = pd.Series([np.nan, 5, 4, 3, 2, np.nan])
count = sum(1 for x in lst if x != x)

Я не видел такого решения раньше, что заставляет меня задуматься: когда это не сработает так, как я хочу, чтобы оно работало (например, может быть для dtypes, которых нет в моих столбцах - у меня есть числа с плавающей запятой и строки? I Я провел некоторое тестирование с моими собственными данными и обнаружил, что это решение эквивалентно:

count = lst.isnull().sum()
# and
count = len([x for x in lst if x != x])

Я обнаружил, что скорость в этом порядке от самой быстрой до самой низкой: sum, len, .sum()

Ответы [ 2 ]

1 голос
/ 08 мая 2019

Вы можете использовать numpy.isnan(), поэтому ваш код будет выглядеть так:

import pandas as pd
import numpy as np

lst = pd.Series([np.nan, 5, 4, 3, 2, np.nan])
count = len([x for x in lst if np.isnan(x)])

Но если вы хотите быть модным:

count = sum(np.isnan(lst))

Или, если вас беспокоит память:

# Less elegant, but does the job
count = 0
for x in lst:
    if np.isnan(x):
        count += 1
0 голосов
/ 08 мая 2019

Основываясь на комментариях, я провел основной эксперимент по сложности времени между двумя предложенными методами.Обратите внимание, что это не относится ни к каким случаям, когда ваш подход терпит неудачу, поскольку я не нашел ни одного.

Настройка:

def make_col():
    return [np.random.choice([1,2,3,4,5,6,7,8,9,10,np.NaN]) for i in range(100000)]

df=pd.DataFrame({k:make_ops() for k in list('abcdefghijklmnopqrstuvwxyz')})

df.shape
(100000, 26)

Эксперимент (обратите внимание, что я пытался быть явным на каждомшаг, а не оптимизация для общей эффективности, чтобы показать разницу между ними):

%%timeit
n=[]
for col in df.columns:
    count=len([x for x in df[col] if x != x])
    n.append(count)
134 ms

%%timeit
n=[]
for col in df.columns:
    count=count = df[col].isnull().sum()
    n.append(count)
29.7 ms

Для улучшения примерно на 80% с использованием встроенных методов

Интересно, на основе NumPy /Оптимизация Cython / C под капотом: если вы запустите профилировщик %%prun для обеих вышеперечисленных операций, вы получите меньше отдельных вызовов функций с вашим подходом, но больше общее время выполнения.Соответственно:

2013 function calls in 0.248 seconds

против.

8903 function calls in 0.046 seconds

И в отношении использования памяти между ними, похоже, вы правы в том, что оба логически эквивалентны в отношении копирования и хранения.Оба приведенных выше фрагмента примерно

peak memory: 247.20 MiB

с использованием магии %%memit из этого источника (который, как предостережение, я не видел и не использовал ранееи я не в курсе каких-либо ошибок или проблем, связанных с его использованием)

...