Python Pandas:为什么map函数更快?

13

在pandas的手册中,有一个关于索引的例子:

In [653]: criterion = df2['a'].map(lambda x: x.startswith('t'))
In [654]: df2[criterion]

然后Wes写道:
**# equivalent but slower**
In [655]: df2[[x.startswith('t') for x in df2['a']]]

有人可以解释一下为什么使用地图方法更快吗?这是Python的特性还是Pandas的特性?


1
我敢打赌,from operator import methodcaller\\df2['a'].map(methodcaller("startswith", "t")) 会快得多。 - Veedrac
@TimPietzcker;这不使用内置的 map 函数(在这种情况下速度较慢)。 - Veedrac
@Veedrac:我明白了,我只是想知道map的第二个参数在哪里 :) - Tim Pietzcker
1个回答

23

关于为什么在Python中用某种方式“应该”更快的争论不能太认真地对待,因为您经常测量的是在某些情况下可能表现不同的实现细节。 因此,当人们猜测什么应该更快时,他们通常(或者说总是?)是错误的。 例如,我发现map实际上可能会更慢。 使用以下安装代码:

import numpy as np, pandas as pd
import random, string

def make_test(num, width):
    s = [''.join(random.sample(string.ascii_lowercase, width)) for i in range(num)]
    df = pd.DataFrame({"a": s})
    return df

让我们比较一下制作索引对象所需的时间——无论是 Series 还是 list ——以及使用该对象对 DataFrame 进行索引所需的时间。例如,制作列表可能很快,但在将其用作索引之前,它需要被内部转换为 Seriesndarray 等,因此会增加额外的时间。
首先,对于一个小框架:
>>> df = make_test(10, 10)
>>> %timeit df['a'].map(lambda x: x.startswith('t'))
10000 loops, best of 3: 85.8 µs per loop
>>> %timeit [x.startswith('t') for x in df['a']]
100000 loops, best of 3: 15.6 µs per loop
>>> %timeit df['a'].str.startswith("t")
10000 loops, best of 3: 118 µs per loop
>>> %timeit df[df['a'].map(lambda x: x.startswith('t'))]
1000 loops, best of 3: 304 µs per loop
>>> %timeit df[[x.startswith('t') for x in df['a']]]
10000 loops, best of 3: 194 µs per loop
>>> %timeit df[df['a'].str.startswith("t")]
1000 loops, best of 3: 348 µs per loop

在这种情况下,列表理解是最快的。 说实话,这并不让我太惊讶,因为通过lambda进行操作可能比直接使用str.startswith要慢,但真的很难猜测。10足够小,我们可能仍然在测量Series的设置成本; 在更大的框架中会发生什么?

>>> df = make_test(10**5, 10)
>>> %timeit df['a'].map(lambda x: x.startswith('t'))
10 loops, best of 3: 46.6 ms per loop
>>> %timeit [x.startswith('t') for x in df['a']]
10 loops, best of 3: 27.8 ms per loop
>>> %timeit df['a'].str.startswith("t")
10 loops, best of 3: 48.5 ms per loop
>>> %timeit df[df['a'].map(lambda x: x.startswith('t'))]
10 loops, best of 3: 47.1 ms per loop
>>> %timeit df[[x.startswith('t') for x in df['a']]]
10 loops, best of 3: 52.8 ms per loop
>>> %timeit df[df['a'].str.startswith("t")]
10 loops, best of 3: 49.6 ms per loop

现在看起来,当作为索引使用时,`map`似乎更胜一筹,尽管差距很小。但是不要太快下结论:如果我们手动将列表生成式转换为`array`或`Series`呢?
>>> %timeit df[np.array([x.startswith('t') for x in df['a']])]
10 loops, best of 3: 40.7 ms per loop
>>> %timeit df[pd.Series([x.startswith('t') for x in df['a']])]
10 loops, best of 3: 37.5 ms per loop

现在列表推导式再次获胜!

结论:谁知道呢?但是不要相信没有timeit结果的任何东西,即使有结果,你也必须问自己是否在测试你想要测试的内容。


你能否将这个提交为文档的PR:https://github.com/pydata/pandas/issues/3871,尝试创建一个新的章节。 - Jeff
这可能也是文档中Wes指出startswith比切片慢的部分! - Andy Hayden

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