在pandas中,非唯一索引会对性能产生何种影响?

61

根据pandas的文档,我得出结论:唯一值索引可以使某些操作更加高效,并且偶尔会容忍非唯一索引。

从外部来看,似乎没有利用非唯一索引的任何方式。例如,下面的ix查询速度很慢,似乎正在扫描整个数据框。

In [23]: import numpy as np
In [24]: import pandas as pd
In [25]: x = np.random.randint(0, 10**7, 10**7)
In [26]: df1 = pd.DataFrame({'x':x})
In [27]: df2 = df1.set_index('x', drop=False)
In [28]: %timeit df2.ix[0]
1 loops, best of 3: 402 ms per loop
In [29]: %timeit df1.ix[0]
10000 loops, best of 3: 123 us per loop

我知道这两个ix查询返回的结果不同--这只是一个例子,调用非唯一索引上的ix似乎要慢得多。

有没有办法诱使pandas在非唯一和/或排序索引上使用更快的查找方法,比如二分查找?

2个回答

114

当索引是唯一的时候,Pandas使用哈希表将键映射到值O(1)。当索引是非唯一且已排序时,Pandas使用二分搜索O(logN),当索引是随机排序时,Pandas需要检查索引中的所有键O(N)。

您可以调用sort_index方法:

import numpy as np
import pandas as pd
x = np.random.randint(0, 200, 10**6)
df1 = pd.DataFrame({'x':x})
df2 = df1.set_index('x', drop=False)
df3 = df2.sort_index()
%timeit df1.loc[100]
%timeit df2.loc[100]
%timeit df3.loc[100]

结果:

10000 loops, best of 3: 71.2 µs per loop
10 loops, best of 3: 38.9 ms per loop
10000 loops, best of 3: 134 µs per loop

4
我不理解最后的时间安排。 df3 应该更快吗? - lucid_dreamer
7
我也感到困惑,但是df1使用默认索引,该索引从0到len(df1)-1并且唯一,因此df1.loc[]使用哈希表。df2将索引设置为“ x”,它不是唯一的也不是排序的,因此它执行线性扫描,O(N)。df3与df2相同,但已排序且仍然不唯一,因此它执行二进制搜索。 - Max Taggart
2
这个时间比较实际上非常误导人,因为第一个语句 df1.loc[100] 做的事情与其他两个语句完全不同,即使用隐式创建的 RangeIndex 检索第100行,而其他两个语句检索所有 x == 100 的行。 - Ernesto Elsäßer
5
虽然已经晚了4年,但是df2并不更快。速度是以毫秒为单位衡量的,而df1和df3的速度是以微秒为单位衡量的,很容易被忽视。 - s_wheels
1
哎呀,世界又恢复正常了。谢谢@s_wheels。 - lucid_dreamer
显示剩余4条评论

45

@HYRY说得很好,但没有什么比一个有着计时的彩色图更能说明问题了。

enter image description here

这些图是使用perfplot生成的。以下是相关代码:

import pandas as pd
import perfplot

_rnd = np.random.RandomState(42)

def make_data(n):    
    x = _rnd.randint(0, 200, n)
    df1 = pd.DataFrame({'x':x})
    df2 = df1.set_index('x', drop=False)
    df3 = df2.sort_index()

    return df1, df2, df3

perfplot.show(
    setup=lambda n: make_data(n),
    kernels=[
        lambda dfs: dfs[0].loc[100],
        lambda dfs: dfs[1].loc[100],        
        lambda dfs: dfs[2].loc[100],
    ],
    labels=['Unique index', 'Non-unique, unsorted index', 'Non-unique, sorted index'],
    n_range=[2 ** k for k in range(8, 23)],
    xlabel='N',
    logx=True,
    logy=True,
    equality_check=False)

1
我没有看到你实际计时操作的地方,而且总体上在计时pandas操作方面遇到了麻烦。 - young_souvlaki
@young_souvlaki 我不明白,代码是嵌入在图表下面的答案中,你需要安装perfplot库。要查看正在测试的实际方法,请检查make_data函数,然后检查perfplot.showkernels参数。 - cs95
1
啊,perfplot正在计时。 - young_souvlaki

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