从我目前阅读的内容来看,性能差距是由pandas 选择的解析器 后端发出的。有一个常规的 python 解析器作为后端,另外还有一个 pandas 解析后端。
文档说,如果在 pandas 上使用普通的旧 python 并没有性能提升:Pandas eval Backends
但是,您显然在 pandas 后端遇到了一个白点;即您形成了一个无法使用 pandas 评估的表达式。结果是 pandas 回退到原始的 python 解析后端,如生成的 UserWarning 中所述:
UserWarning:在 Python 空间中进行评估,因为 numexpr 不支持 bool dtype 的 '*' 运算符,请改用 '&'
不支持[op_str]))
(More on this topic)
时间评估
所以,我们现在了解不同的解析后端,是时候检查pandas 提供的适合您所需数据帧操作的一些选项(下面的完整脚本):
expr_a = '''(a < 0.3) * 1 + (a > 0.6) * 3 + (a >= 0.3) * (a <= 0.6) * 2'''
- 使用
pandas 后端将表达式计算为字符串
- 使用
python 后端评估 same 字符串
- 使用
pandas评估带有外部变量引用的表达式字符串
- 使用
df.apply()解决问题
- 使用
df.applymap()解决问题
- 直接提交表达式(无字符串求值)
我的机器上对于一列中有 10,000,000 个随机浮点值的数据框的结果是:
(1) Eval (pd) 0.240498406269
(2) Eval (py) 0.197919774926
(3) Eval @ (pd) 0.200814546686
(4) Apply 3.242620778595
(5) ApplyMap 6.542354086152
(6) Direct 0.140075372736
解释性能差异的要点很可能如下:
- 使用python函数(如
apply()和applymap())(当然!)比使用完全用C实现的功能慢得多
- 字符串求值代价高昂(参见 (6) 与 (2))
- 开销 (1) 超过 (2) 可能是后端选择和回退到也使用
python 后端,因为 pandas 不评估 bool * int。
没什么新意吧?
如何进行
我们基本上只是证明了我们之前的直觉告诉我们的(即:pandas 为一项任务选择了正确的后端)。
因此,我认为完全可以忽略 UserWarning,只要您知道基本的方法和原因。
因此:继续前进,让pandas 使用所有实现中最快的,通常是 C 函数。
测试脚本
from __future__ import print_function
import sys
import random
import pandas as pd
import numpy as np
from timeit import default_timer as timer
def conditional_column(val):
if val < 0.3:
return 1
elif val > 0.6:
return 3
return 2
if __name__ == '__main__':
nr = 10000000
df = pd.DataFrame({
'a': [random.random() for _ in range(nr)]
})
print(nr, 'rows')
expr_a = '''(a < 0.3) * 1 + (a > 0.6) * 3 + (a >= 0.3) * (a <= 0.6) * 2'''
expr_b = '''(@df.a < 0.3) * 1 + (@df.a > 0.6) * 3 + (@df.a >= 0.3) * (@df.a <= 0.6) * 2'''
fmt = '{:16s} {:.12f}'
# Evaluate the string expression using pandas parser
t0 = timer()
b = df.eval(expr_a, parser='pandas')
print(fmt.format('(1) Eval (pd)', timer() - t0))
# Evaluate the string expression using python parser
t0 = timer()
c = df.eval(expr_a, parser='python')
print(fmt.format('(2) Eval (py)', timer() - t0))
# Evaluate the string expression using pandas parser with external variable access (@)
t0 = timer()
d = df.eval(expr_b, parser='pandas')
print(fmt.format('(3) Eval @ (pd)', timer() - t0))
# Use apply to map the if/else function to each row of the df
t0 = timer()
d = df['a'].apply(conditional_column)
print(fmt.format('(4) Apply', timer() - t0))
# Use element-wise apply (WARNING: requires a dataframe and walks ALL cols AND rows)
t0 = timer()
e = df.applymap(conditional_column)
print(fmt.format('(5) ApplyMap', timer() - t0))
# Directly access the pandas series objects returned by boolean expressions on columns
t0 = timer()
f = (df['a'] < 0.3) * 1 + (df['a'] > 0.6) * 3 + (df['a'] >= 0.3) * (df['a'] <= 0.6) * 2
print(fmt.format('(6) Direct', timer() - t0))