在我看来,最佳答案是有缺陷的。希望没有人使用from pandas import * 将所有 pandas 大量导入到他们的命名空间中。此外,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)
比 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)
所以这慢了一倍,这并不是一个糟糕的性能回归,但如果我们对上述内容进行 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 的速度有多慢,但是为了更加清楚,让我们看一下最基本的例子。让我们对包含和不包含 apply 的 1000 万个数字进行平方
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)