我认为最佳答案是有缺陷的。希望没有人使用来将所有大熊猫大量导入其命名空间from pandas import *
。同样,在将map
方法传递给字典或系列时,应保留该方法的使用时间。它可以带一个函数,但这就是apply
它的用途。
所以,如果您必须使用上述方法,我会这样写
df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
实际上,这里没有理由使用zip。您可以简单地做到这一点:
df["A1"], df["A2"] = calculate(df['a'])
在较大的DataFrame上,第二种方法也快得多
df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})
创建了300,000行的DataFrame
%timeit df["A1"], df["A2"] = calculate(df['a'])
2.65 ms ± 92.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
159 ms ± 5.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
比拉链快60倍
通常,避免使用Apply
Apply通常不会比遍历Python列表快多少。让我们测试一个for循环的性能,以执行与上述相同的操作
%%timeit
A1, A2 = [], []
for val in df['a']:
A1.append(val**2)
A2.append(val**3)
df['A1'] = A1
df['A2'] = A2
298 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
因此,这是缓慢的两倍,这并不是可怕的性能下降,但是如果我们对上述内容进行cythonize,我们将获得更好的性能。假设您正在使用ipython:
%load_ext cython
%%cython
cpdef power(vals):
A1, A2 = [], []
cdef double val
for val in vals:
A1.append(val**2)
A2.append(val**3)
return A1, A2
%timeit df['A1'], df['A2'] = power(df['a'])
72.7 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
直接分配而不适用
如果使用直接矢量化操作,则可以进一步提高速度。
%timeit df['A1'], df['A2'] = df['a'] ** 2, df['a'] ** 3
5.13 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
这利用了NumPy极其快速的矢量化操作的优势,而不是我们的循环。现在,我们的速度比原始速度提高了30倍。
最简单的速度测试 apply
上面的示例应该清楚地显示出速度有多慢apply
,但是正是如此,让我们来看一个最基本的示例。让我们平方一千万个带和不带数字的序列
s = pd.Series(np.random.rand(10000000))
%timeit s.apply(calc)
3.3 s ± 57.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
如果不套用,则速度提高了50倍
%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)