Pandas DataFrame 性能表现

107
Pandas非常好用,但我真的很惊讶从Pandas.DataFrame中检索值的效率有多低。在下面的玩具示例中,即使使用DataFrame.iloc方法,速度也比字典慢100多倍。
问题是:这里的教训只是字典是查找值的更好方式吗?是的,我知道这正是它们被制作的目的。但我只是想知道是否有关于DataFrame查找性能的一些我所不知道的东西。
我意识到这个问题更多的是“沉思”而不是“询问”,但我将接受提供洞察或观点的答案。谢谢。
import timeit

setup = '''
import numpy, pandas
df = pandas.DataFrame(numpy.zeros(shape=[10, 10]))
dictionary = df.to_dict()
'''

f = ['value = dictionary[5][5]', 'value = df.loc[5, 5]', 'value = df.iloc[5, 5]']

for func in f:
    print func
    print min(timeit.Timer(func, setup).repeat(3, 100000))

value = dictionary[5][5]

0.130625009537

value = df.loc[5, 5]

19.4681699276

value = df.iloc[5, 5]

17.2575249672

这段代码涉及到了三种不同的数据获取方式。第一种是通过字典(dictionary)获取,第二种是通过标签(loc)获取,第三种是通过位置索引(iloc)获取。其中,每个value都代表着一个具体的数值。
5个回答

151

字典对于DataFrame就像自行车对于汽车一样。

你可以在自行车上骑10英尺,比起启动汽车、换挡等等更快。但是如果你需要走一英里,汽车就胜出了。

对于某些小而特定的用途,字典可能更快。如果这正好是你所需要的,请务必使用字典!但是如果你需要/想要DataFrame的强大和豪华,那么字典是不能取代的。如果数据结构不先满足您的需求,比较速度是没有意义的。

现在举个例子——更具体地说——字典很适合访问列,但不太方便访问行。

import timeit

setup = '''
import numpy, pandas
df = pandas.DataFrame(numpy.zeros(shape=[10, 1000]))
dictionary = df.to_dict()
'''

# f = ['value = dictionary[5][5]', 'value = df.loc[5, 5]', 'value = df.iloc[5, 5]']
f = ['value = [val[5] for col,val in dictionary.items()]', 'value = df.loc[5]', 'value = df.iloc[5]']

for func in f:
    print(func)
    print(min(timeit.Timer(func, setup).repeat(3, 100000)))
产出。
value = [val[5] for col,val in dictionary.iteritems()]
25.5416321754
value = df.loc[5]
5.68071913719
value = df.iloc[5]
4.56006002426

使用字典列表在检索行时比df.iloc慢5倍。当列数增加时,速度差距变得更大。(列数就像自行车比喻中的脚的数量一样。距离越长,汽车就越方便...)

这只是字典列表不如DataFrame更方便/更慢的一个例子。

另一个例子是当您拥有日期时间索引以选择特定日期之间的所有行时。使用DataFrame,您可以使用

df.loc['2000-1-1':'2000-3-31']

如果您使用列表字典,则很难找到类似的方法。而且,与DataFrame相比,选择正确行所需的Python循环速度将非常慢。


像这样的答案可以添加到常见问题解答中,参见此处:https://github.com/pydata/pandas/issues/3871 - Jeff
10
感谢您提供这两个具有启发性的例子和比喻,作为一名骑自行车的人,我非常欣赏。 - Owen
10
您的例子使用了 df.to_dict(),但它返回的不是列的字典,而是嵌套字典的字典。如果使用 dic = {x:df[x].values.tolist() for x in df},在我的机器上可以获得25倍的列访问速度提升和1.5倍的行访问速度提升,因此使用字典更快。 - tal
1
如果每次有人发布基准测试结果后都被反驳,我就能得到一分钱了... - MrR

23

现在看起来性能差异要小得多(0.21.1-我忘记原始示例中 Pandas 的版本是什么了)。不仅字典访问和 .loc 之间的性能差距缩小了(从约 335 倍减少到 126 倍更慢),lociloc)现在比 atiat)慢不到两倍。

In [1]: import numpy, pandas
   ...:    ...: df = pandas.DataFrame(numpy.zeros(shape=[10, 10]))
   ...:    ...: dictionary = df.to_dict()
   ...: 

In [2]: %timeit value = dictionary[5][5]
85.5 ns ± 0.336 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: %timeit value = df.loc[5, 5]
10.8 µs ± 137 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: %timeit value = df.at[5, 5]
6.87 µs ± 64.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [5]: %timeit value = df.iloc[5, 5]
14.9 µs ± 114 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [6]: %timeit value = df.iat[5, 5]
9.89 µs ± 54.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: print(pandas.__version__)
0.21.1

在标量运算中,使用atiat是一个很好的做法。以下是一个示例基准测试:

In [1]: import numpy, pandas
   ...: df = pandas.DataFrame(numpy.zeros(shape=[10, 10]))
   ...: dictionary = df.to_dict()

In [2]: %timeit value = dictionary[5][5]
The slowest run took 34.06 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 310 ns per loop

In [4]: %timeit value = df.loc[5, 5]
10000 loops, best of 3: 104 µs per loop

In [5]: %timeit value = df.at[5, 5]
The slowest run took 6.59 times longer than the fastest. This could mean that an intermediate result is being cached 
100000 loops, best of 3: 9.26 µs per loop

In [6]: %timeit value = df.iloc[5, 5]
10000 loops, best of 3: 98.8 µs per loop

In [7]: %timeit value = df.iat[5, 5]
The slowest run took 6.67 times longer than the fastest. This could mean that an intermediate result is being cached 
100000 loops, best of 3: 9.58 µs per loop

使用at (iat) 比使用 loc (iloc) 快约10倍。


8

我遇到过同样的问题。你可以使用at来提高性能。

由于使用[]进行索引必须处理许多情况(单标签访问、切片、布尔索引等),因此需要一定的开销才能弄清楚你要请求什么。如果你只想访问一个标量值,最快的方法是使用atiat方法,这些方法在所有数据结构上都有实现。

请参阅官方参考文献http://pandas.pydata.org/pandas-docs/stable/indexing.html中的章节“快速标量值获取和设置”。


1
这是一个很好的参考,但不如上面的答案详细。 - BCR

3

我在访问数据帧行时遇到了不同的现象。 在一个拥有1000万行的数据帧上测试这个简单的例子。 字典很棒。

def testRow(go):
    go_dict = go.to_dict()
    times = 100000
    ot= time.time()
    for i in range(times):
        go.iloc[100,:]
    nt = time.time()
    print('for iloc {}'.format(nt-ot))
    ot= time.time()
    for i in range(times):
        go.loc[100,2]
    nt = time.time()
    print('for loc {}'.format(nt-ot))
    ot= time.time()
    for i in range(times):
        [val[100] for col,val in go_dict.iteritems()]
    nt = time.time()
    print('for dict {}'.format(nt-ot))

一样的想法。我们应该尽可能避免循环,使用向量化操作。 - Tarik
有人可以运行代码并打印基准数据吗? - Orvar Korvar
Go是一个字典的名称。 - MrR

2
我认为访问单元格的最快方法是:
df.get_value(row,column)
df.set_value(row,column,value) 

这两者速度都比我认为的要快。

df.iat(...) 
df.at(...)

2
似乎at更快 - 4.68微秒(at) vs 5.98微秒(get_values)。而且,at更加灵活,因为您可以使用命名索引。 - joon

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