我使用的是pandas 0.23.3和Python 3.6,所以我可以看到只有在第二个示例中运行时间的真正区别。
但是让我们研究一下第二个例子的一个稍微不同的版本
2*df[0]
让开)。这是我们在我的机器上的基线:
twice = df[0]*2
mask = df[0] > 0.5
%timeit np.where(mask, twice, df[0])
# 61.4 ms ± 1.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit df[0].mask(mask, twice)
# 143 ms ± 5.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Numpy的速度大约是熊猫的2.3倍。
我在Linux上使用
perf
. 对于numpy的版本,我们得到了(列表见附录A):
>>> perf record python np_where.py
>>> perf report
Overhead Command Shared Object Symbol
68,50% python multiarray.cpython-36m-x86_64-linux-gnu.so [.] PyArray_Where
8,96% python [unknown] [k] 0xffffffff8140290c
1,57% python mtrand.cpython-36m-x86_64-linux-gnu.so [.] rk_random
如我们所见,大部分时间都花在
PyArray_Where
-大约69%。未知符号是一个核心函数(事实上
clear_page
)-我在没有根权限的情况下运行,因此无法解析符号。
对于熊猫,我们得到(代码见附录B):
>>> perf record python pd_mask.py
>>> perf report
Overhead Command Shared Object Symbol
37,12% python interpreter.cpython-36m-x86_64-linux-gnu.so [.] vm_engine_iter_task
23,36% python libc-2.23.so [.] __memmove_ssse3_back
19,78% python [unknown] [k] 0xffffffff8140290c
3,32% python umath.cpython-36m-x86_64-linux-gnu.so [.] DOUBLE_isnan
1,48% python umath.cpython-36m-x86_64-linux-gnu.so [.] BOOL_logical_not
-
熊猫不使用
PyArray_在哪里
在引擎盖下-最突出的时间消费者是
vm_engine_iter_task
,这是
numexpr-functionality
.
-
正在进行一些重内存复制-
__memmove_ssse3_back
使用关于
25
实际上,熊猫-0.19使用
PyArray_在哪里
Overhead Command Shared Object Symbol
32,42% python multiarray.so [.] PyArray_Where
30,25% python libc-2.23.so [.] __memmove_ssse3_back
21,31% python [kernel.kallsyms] [k] clear_page
1,72% python [kernel.kallsyms] [k] __schedule
所以基本上
np.where
在引擎盖下+一些开销(以上所有数据复制,请参见
__回忆往事
)那时候。
我看不出熊猫在熊猫0.19版本中会比numpy快的场景——它只是增加了numpy功能的开销。熊猫的0.23.3版本是一个完全不同的故事-这里使用的是numexpr模块,很有可能在某些情况下熊猫的版本(至少稍微)更快。
我不确定这个内存复制是否真的需要-也许有人甚至可以称之为性能缺陷,但我只是不太确定。
我们可以通过去除一些间接的影响(通过
np.array
而不是
pd.Series
). 例如:
%timeit df[0].mask(mask.values > 0.5, twice.values)
# 75.7 ms ± 1.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
现在,熊猫只慢了25%。表演说:
Overhead Command Shared Object Symbol
50,81% python interpreter.cpython-36m-x86_64-linux-gnu.so [.] vm_engine_iter_task
14,12% python [unknown] [k] 0xffffffff8140290c
9,93% python libc-2.23.so [.] __memmove_ssse3_back
4,61% python umath.cpython-36m-x86_64-linux-gnu.so [.] DOUBLE_isnan
2,01% python umath.cpython-36m-x86_64-linux-gnu.so [.] BOOL_logical_not
我的钥匙被拿走了:
-
熊猫有可能至少比努比快一点(因为它可能更快)。然而,pandas对数据复制的处理有些不透明,因此很难预测何时(不必要的)数据复制会掩盖这一潜力。
-
当
where
/
mask
是瓶颈,我会使用numba/cython来提高性能-请看下面我相当天真的尝试使用numba和cython。
np.where(df[0] > 0.5, df[0]*2, df[0])
版本和消除创建临时-即,
df[0]*2
.
根据@max9111的建议,使用numba:
import numba as nb
@nb.njit
def nb_where(df):
n = len(df)
output = np.empty(n, dtype=np.float64)
for i in range(n):
if df[i]>0.5:
output[i] = 2.0*df[i]
else:
output[i] = df[i]
return output
assert(np.where(df[0] > 0.5, twice, df[0])==nb_where(df[0].values)).all()
%timeit np.where(df[0] > 0.5, df[0]*2, df[0])
# 85.1 ms ± 1.61 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit nb_where(df[0].values)
# 17.4 ms ± 673 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
比纽比版本快5倍!
%%cython -a
cimport numpy as np
import numpy as np
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_where(double[::1] df):
cdef int i
cdef int n = len(df)
cdef np.ndarray[np.float64_t] output = np.empty(n, dtype=np.float64)
for i in range(n):
if df[i]>0.5:
output[i] = 2.0*df[i]
else:
output[i] = df[i]
return output
assert (df[0].mask(df[0] > 0.5, 2*df[0]).values == cy_where(df[0].values)).all()
%timeit cy_where(df[0].values)
# 66.7± 753 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
加速25%。不知道,为什么cython比numba慢得多。
列表:
答:
np_其中.py:
import pandas as pd
import numpy as np
np.random.seed(0)
n = 10000000
df = pd.DataFrame(np.random.random(n))
twice = df[0]*2
for _ in range(50):
np.where(df[0] > 0.5, twice, df[0])
乙:
import pandas as pd
import numpy as np
np.random.seed(0)
n = 10000000
df = pd.DataFrame(np.random.random(n))
twice = df[0]*2
mask = df[0] > 0.5
for _ in range(50):
df[0].mask(mask, twice)