В Python, как правило, членство в хешируемой коллекции лучше всего проверяется через set
. Мы это знаем, потому что использование хэширования дает нам O (1) сложность поиска по сравнению с O (n) для list
или np.ndarray
.
В Пандах мне часто приходится проверять членство в очень больших коллекциях. Я предположил, что это применимо, т.е. Проверка каждого элемента серии для членства в set
более эффективна, чем использование list
или np.ndarray
. Однако, похоже, это не так:
import numpy as np
import pandas as pd
np.random.seed(0)
x_set = {i for i in range(100000)}
x_arr = np.array(list(x_set))
x_list = list(x_set)
arr = np.random.randint(0, 20000, 10000)
ser = pd.Series(arr)
lst = arr.tolist()
%timeit ser.isin(x_set) # 8.9 ms
%timeit ser.isin(x_arr) # 2.17 ms
%timeit ser.isin(x_list) # 7.79 ms
%timeit np.in1d(arr, x_arr) # 5.02 ms
%timeit [i in x_set for i in lst] # 1.1 ms
%timeit [i in x_set for i in ser.values] # 4.61 ms
Версии, используемые для тестирования:
np.__version__ # '1.14.3'
pd.__version__ # '0.23.0'
sys.version # '3.6.5'
Исходный код для pd.Series.isin
, я полагаю, использует numpy.in1d
, который предположительно означает большие накладные расходы для set
на преобразование np.ndarray
.
Отрицая затраты на создание входных данных, последствия для Pandas:
- Если вы знаете, что ваши элементы
x_list
илиx_arr
уникальны, не беспокойтесь о преобразовании вx_set
. Это будет дорогостоящим (как тестирование конверсии, так и членство) для использования с Pandas. - Использование списков - единственный способ извлечь выгоду из поиска O (1).
Мои вопросы:
- Правильно ли мой анализ? Это похоже на очевидный, но недокументированный результат реализации
pd.Series.isin
. - Есть ли обходной путь, не используя понимание списка или
pd.Series.apply
, который использует O (1) набор поиска? Или это неизбежный выбор дизайна и/или следствие наличия NumPy в качестве основы Pandas?
Обновление: при более старой настройке (версии Pandas/NumPy) я вижу x_set
outperform x_arr
с pd.Series.isin
. Поэтому дополнительный вопрос: имеет что - то принципиально изменилось от старого к новому, чтобы вызвать работу с set
ухудшаться?
%timeit ser.isin(x_set) # 10.5 ms
%timeit ser.isin(x_arr) # 15.2 ms
%timeit ser.isin(x_list) # 9.61 ms
%timeit np.in1d(arr, x_arr) # 4.15 ms
%timeit [i in x_set for i in lst] # 1.15 ms
%timeit [i in x_set for i in ser.values] # 2.8 ms
pd.__version__ # '0.19.2'
np.__version__ # '1.11.3'
sys.version # '3.6.0'