熊猫“ isin”比numpy“ in1d”要慢得多

时间:2019-03-26 14:52:19

标签: pandas performance numpy benchmarking

从效率方面看,熊猫“ isin”和numpy“ in1d”之间存在巨大差异。经过一些研究,我注意到数据的类型和作为参数传递给“ in”方法的值对运行时间有巨大的影响。无论如何,看起来numpy实现受此问题的影响要小得多。 这是怎么回事?

import timeit
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')
f = lambda: df['A'].isin(vals)
g = lambda: pd.np.in1d(df['A'],vals)
print 'pandas:', timeit.timeit(stmt='f()',setup='from __main__ import f',number=10)/10
print 'numpy :', timeit.timeit(stmt='g()',setup='from __main__ import g',number=10)/10
>>
**pandas: 0.0541711091995
numpy : 0.000645089149475**

1 个答案:

答案 0 :(得分:1)

Numpy和Pandas对isin使用不同的算法。在某些情况下,numpy的版本更快,而在某些情况下,panda的版本更快。对于您的测试用例,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而言,这才是更好的选择。还有其他考虑因素,例如可能会起到一定作用的内存消耗(对于Pandas来说更高)。

但是,如果我们采用更大的查询集,情况将完全不同:

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