没有IF语句的内联布尔值计算



我试图评估数据框的一列的值,以确定另一列的值。我通过使用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'''
  1. 使用pandas后端将表达式作为字符串求值
  2. 使用python后端计算相同的字符串
  3. 使用pandas
  4. 计算带有外部变量引用的表达式字符串使用df.apply() 解决问题使用df.applymap() 解决问题
  5. 直接提交表达式(无字符串求值)

在我的机器上,对于一个列中有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))

最新更新