我认为最佳回答存在缺陷。希望没有人使用from pandas import *
将整个pandas导入其命名空间。此外,应该仅在需要传递字典或Series时才使用map
方法。它可以接受一个函数,但这正是apply
的用途。
因此,如果您必须使用上述方法,我会这样写:
df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
这里实际上没有必要使用zip。你可以简单地这样做:
df["A1"], df["A2"] = calculate(df['a'])
这第二种方法在较大的数据框中也要快得多
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)
比zip快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)
所以这个速度是原来的两倍,虽然性能有所下降,但如果我们将上述内容进行cython化,可以获得更好的性能。假设你正在使用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)
不需要使用apply来直接赋值
如果您使用直接矢量化操作,可以获得更大的速度提升。
%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
有多慢,但为了更加明确,我们来看看最基本的例子。让我们对包含1000万个数字的Series进行平方运算并比较使用和不使用apply
的速度。
s = pd.Series(np.random.rand(10000000))
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)