Numpy и Pandas используют разные алгоритмы для isin
.В некоторых случаях версия Numpy быстрее, а для некоторых панд.Для вашего теста numpy выглядит быстрее.
Версия Pandas имеет, однако, лучшее асимптотическое время выполнения, и выиграет для больших наборов данных.
Предположим, что есть n
элементы в ряду данных (df
в вашем примере) и m
элементы в запросе (vals
в вашем примере).
Обычно алгоритм Numpy выполняет следующие действия:
- Используйте
np.unique(..)
, чтобы найти все уникальные элементы в серии.Таким образом, выполняется сортировка, т. Е. O(n*log(n))
, может быть N<=n
уникальных элементов. - Для каждого элемента используйте бинарный поиск, чтобы определить, входит ли элемент в ряд, то есть
O(m*log(N))
в целом.
Что приводит к общему времени выполнения O(n*log(n) + m*log(N))
.
Существуют некоторые жестко запрограммированные оптимизации для случаев, когда vals
только несколько элементов, а для этих случаев - Numpyдействительно сияет.
Панды делают что-то другое:
- Заполняет хеш-карту (обернутую
khash
-функцию), чтобы найти все уникальные элементы,который занимает O(n)
. - Поиск в хэш-карте в
O(1)
для каждого запроса, т.е. всего O(m)
.
В целом, время выполнения O(n)+O(m)
, что намного лучше, чем у Numpy.
Однако для меньших входных данных постоянные факторы, а не асимптотическое поведение - это то, что имеет значение, и это просто лучше для Numpy.Есть и другие соображения, такие как потребление памяти (которое выше у панд), которое может сыграть роль.
Но если мы возьмем больший набор запросов, ситуация будет совершенно другой:
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randint(0,10,(10**6),dtype='int8'),columns=['A'])
vals = np.array([5,7],dtype='int64')
vals2 = np.random.randint(0,10,(10**6),dtype='int64')
А теперь:
%timeit df['A'].isin(vals) # 17.0 ms
%timeit df['A'].isin(vals2) # 16.8 ms
%timeit pd.np.in1d(df['A'],vals) # 1.36
%timeit pd.np.in1d(df['A'],vals2) # 82.9 ms
Numpy действительно теряет свои позиции, если есть еще запросы.Также можно видеть, что создание хеш-карты является узким местом для Панд, а не запросов.
В конце концов, не имеет смысла (даже если бы я это сделал!) Оцениватьпроизводительность только для одного размера ввода - это должно быть сделано для диапазона размеров ввода - есть некоторые сюрпризы, которые нужно обнаружить!
Например, интересный факт: если вы возьмете
df = pd.DataFrame(np.random.randint(0,10,(10**6+1), dtype='int8'),columns=['A'])
, т.е.10^6+1
вместо 10^6
, панды прибегнут к алгоритму numpy (который, на мой взгляд, не очень умен) и станут лучше для маленьких входов, но хуже для больших:
%timeit df['A'].isin(vals) # 6ms was 17.0 ms
%timeit df['A'].isin(vals2) # 100ms was 16.8 ms