pandas中的"is in"比numpy中的"in1d"慢很多。

3

pandas中的"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**

isin是索引敏感的 :-) 它们检查值,也可以检查索引,这意味着它们执行不同的任务。 - BENY
@Wen-Ben 在我的例子中,我没有使用索引,那么到底是什么导致了额外的时间? - mclafee
1个回答

8
Numpy和Pandas使用不同的算法来处理isin。对于某些情况,numpy的版本更快,而对于某些情况,pandas的版本更快。在您的测试用例中,numpy似乎更快。
然而,Pandas的版本具有更好的渐进运行时间,在更大的数据集上胜出。
假设数据系列(在您的示例中为df)中有n个元素,查询中有m个元素(在您的示例中为vals)。
通常,Numpy的算法会执行以下操作:
- 使用np.unique(..)查找系列中的所有唯一元素。这是通过排序完成的,即O(n*log(n)),可能有N<=n个唯一元素。 - 对于每个元素,使用二分查找来查找该元素是否在系列中,即总体上是O(m*log(N))
这导致总运行时间为O(n*log(n) + m*log(N))
针对vals只包含少量元素的情况,有一些硬编码优化适用于这种情况,因此numpy真正闪耀。
Pandas的做法略有不同:
- 填充哈希映射(包装的khash功能)以查找所有唯一元素,这需要O(n)的时间。 - 在哈希映射中进行查找对于每个查询都只需要O(1)的时间,即总体上是O(m)
因此,总运行时间为O(n)+O(m),比Numpy要好得多。然而,对于更小的输入,常数因子而不是渐近行为是最重要的,numpy远远优于Pandas。还有其他考虑因素,如内存消耗(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在查询数量增加时逐渐失去优势。可以看出,对于Pandas来说,构建哈希表是瓶颈,而非查询。

最终,仅对一个输入大小进行性能评估并没有太多意义(即使我刚刚这样做了!)- 应该对一系列输入大小进行评估 - 还有一些惊喜等待着我们发现!

例如,有趣的事实:如果你采用

df = pd.DataFrame(np.random.randint(0,10,(10**6+1), dtype='int8'),columns=['A'])

即使是 10^6,也需变成 10^6+1,因为pandas会退回到numpy的算法(我认为不太聪明),这将导致对于小输入更好,但对于大输入更差:

%timeit df['A'].isin(vals)    # 6ms  was 17.0 ms 
%timeit df['A'].isin(vals2)   # 100ms was 16.8 ms

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接