首先 ,我要说的是Pandas和NumPy数组的功能是从对数字数组的高性能 矢量化
计算中获得的。1向量化计算的全部目的是通过将计算移至高度优化的C代码并利用连续的内存块来避免Python级循环。2
Python级循环
现在我们来看一些时间。以下是 所有
的Python级环,其任一产生
pd.Series,
np.ndarray或
list包含相同值的对象。为了分配给数据框内的序列,结果是可比较的。
# Python 3.6.5, NumPy 1.14.3, Pandas 0.23.0np.random.seed(0)N = 10**5%timeit list(map(divide, df['A'], df['B'])) # 43.9 ms%timeit np.vectorize(divide)(df['A'], df['B']) # 48.1 ms%timeit [divide(a, b) for a, b in zip(df['A'], df['B'])]# 49.4 ms%timeit [divide(a, b) for a, b in df[['A', 'B']].itertuples(index=False)] # 112 ms%timeit df.apply(lambda row: divide(*row), axis=1, raw=True) # 760 ms%timeit df.apply(lambda row: divide(row['A'], row['B']), axis=1) # 4.83 s%timeit [divide(row['A'], row['B']) for _, row in df[['A', 'B']].iterrows()] # 11.6 s
一些要点:
- 的
tuple
基的方法(第一4)是一个因素比更有效的pd.Series
基于方法(最后3)。 np.vectorize
,列表理解+zip
和map
方法(即前3名)的性能大致相同。这是因为它们使用tuple
并 绕过了熊猫的一些开销pd.Dataframe.itertuples
。- 使用
raw=True
withpd.Dataframe.apply
和不使用时,速度显着提高。此选项将NumPy数组而不是pd.Series
对象提供给自定义函数。
pd.Dataframe.apply
:只是另一个循环
要 确切地 查看Pandas传递的对象,可以对函数进行微不足道的修改:
def foo(row): print(type(row)) assert False # because you only need to see this oncedf.apply(lambda row: foo(row), axis=1)
输出:
<class'pandas.core.series.Series'>。相对于NumPy数组,创建,传递和查询Pandas系列对象会带来大量开销。这不足为奇:Pandas系列包含相当数量的脚手架,用于存放索引,值,属性等。
再次进行相同的练习
raw=True,您会看到
<class 'numpy.ndarray'>。所有这些都在文档中进行了描述,但是看到它更具说服力。
np.vectorize
:假向量化
的文档
np.vectorize有以下注意事项:
pyfunc除了使用numpy的广播规则外,矢量化函数像python map函数一样对输入数组的连续元组求值。
这里的“广播规则”是无关紧要的,因为输入数组具有相同的尺寸。与之并行
map是有启发性的,因为上述
map版本具有几乎相同的性能。该源代码显示发生的事情:
np.vectorize你的输入函数转换成通用的功能通过(“ufunc”)
np.frompyfunc。有些优化,例如缓存,可以带来一些性能改进。
总之,
np.vectorize做一个Python级环路什么 应该
做,但
pd.Dataframe.apply增加了一个矮胖的开销。您不会看到任何JIT编译
numba(请参见下文)。这只是一种方便。
真正的向量化:您 应该 使用什么
为什么在任何地方都没有提到上述差异?因为真正矢量化计算的性能使它们无关紧要:
%timeit np.where(df['B'] == 0, 0, df['A'] / df['B']) # 1.17 ms%timeit (df['A'] / df['B']).replace([np.inf, -np.inf], 0) # 1.96 ms
是的,这比上述循环式解决方案中最快的速度快40倍。这些都可以接受。我认为,第一个是简洁,可读和高效的。
numba如果性能至关重要,这只是瓶颈,请仅查看其他方法,例如下面的方法。
numba.njit
:更高的效率
当循环 被 认为可行时,通常可以通过
numba底层的NumPy数组对其进行优化,以尽可能多地移至C。
实际上,将
numba性能提高到了 微秒 。没有一些繁琐的工作,将很难获得比这更高的效率。
from numba import njit@njitdef divide(a, b): res = np.empty(a.shape) for i in range(len(a)): if b[i] != 0: res[i] = a[i] / b[i] else: res[i] = 0 return res%timeit divide(df['A'].values, df['B'].values) # 717 µs
使用
@njit(parallel=True)可以为更大的阵列提供进一步的提升。
1种数字类型包括:
int,
float,
datetime,
bool,
category。它们 不包含
objectdtype,可以保存在连续的内存块中。
2 NumPy操作相对于Python高效的原因至少有两个:
- Python中的所有内容都是一个对象。与C不同,这包括数字。因此,Python类型具有本机C类型不存在的开销。
- NumPy方法通常基于C。另外,在可能的情况下使用优化算法。



