我试图评估数据框的一列的值,以确定另一列的值。我通过使用if
语句和.apply()
函数成功地做到了这一点。例如
if Col x < 0.3:
return y
elif Col x > 0.6:
return z
等。问题是,这需要相当长的时间来运行大量的数据。相反,我尝试使用以下逻辑来确定新的列值:
(x<0.3)*y + (x>0.6)*z
所以Python计算TRUE/FALSE并应用正确的值。这似乎工作得更快,唯一的事情是Python说:"UserWarning:在Python空间中求值,因为numexpr不支持bool类型的'*'运算符,请使用'&'代替不支持(op_str)))"
这有问题吗?我应该使用"&"吗?我觉得用"&"做乘法是不正确的。
谢谢!
从我读到目前为止,性能差距是由pandas
选择的解析器后端发出的。有一个普通的python解析器作为后台,另外,还有一个pandas解析后端。
文档说,如果在这里使用普通的旧python而不是pandas,则没有性能提升:pandas eval Backends
然而,你显然击中了熊猫后台的一个白点;例如,您形成了一个不能使用pandas求值的表达式。结果是pandas返回到原始的python解析后端,如结果UserWarning:
所述。UserWarning:在Python空间中求值,因为numexpr不支持bool类型的'*'操作符,请使用'&'代替不支持的[op_str]))
(关于这个主题的更多信息)
时间评估所以,既然我们现在知道了不同的解析后端,现在是时候检查pandas
提供的一些适合您所需的数据框架操作的选项了(下面是完整的脚本):
expr_a = '''(a < 0.3) * 1 + (a > 0.6) * 3 + (a >= 0.3) * (a <= 0.6) * 2'''
- 使用
pandas
后端将表达式作为字符串求值 - 使用
python
后端计算相同的字符串 - 使用
pandas
计算带有外部变量引用的表达式字符串使用df.apply()
解决问题使用df.applymap()
解决问题- 直接提交表达式(无字符串求值)
expr_a = '''(a < 0.3) * 1 + (a > 0.6) * 3 + (a >= 0.3) * (a <= 0.6) * 2'''
pandas
后端将表达式作为字符串求值python
后端计算相同的字符串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)vs (2))
- 开销(1)超过(2)可能是后端选择和回退到也使用
python
后端,因为pandas
不评估bool * int
。
没什么新鲜事,是吗?
如何继续
我们基本上只是证明了我们的直觉告诉我们之前(即:熊猫选择正确的后端任务)。
因此,我认为完全可以忽略 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))