【问题标题】:Python - In-line boolean evaluation without IF statementsPython - 没有 IF 语句的内联布尔评估
【发布时间】:2016-09-27 13:31:19
【问题描述】:

我正在尝试评估数据框的一列的值以确定另一列的值。我通过使用if 语句和.apply() 函数成功地做到了这一点。 IE。

if Col x < 0.3:
    return y
elif Col x > 0.6:
    return z

等等。问题是这需要很长时间才能运行大量数据。相反,我尝试使用以下逻辑来确定新的列值:

(x0.6)*z

所以 Python 评估 TRUE/FALSE 并应用正确的值。这似乎工作得更快,唯一的事情是 Python 说: “用户警告:在 Python 空间中进行评估,因为 numexpr 不支持 bool dtype 的 '*' 运算符,请改用 '&' 不支持[op_str]))"

这是个问题吗?我应该使用“&”吗?我觉得乘法时使用“&”会不正确。

谢谢!

【问题讨论】:

  • 什么数据框? pandas, pyspark, ...?
  • 您确定if 语句需要时间吗?如果您没有在其中做任何其他事情,if 语句应该基本上是即时的。
  • 我认为 apply() 让它慢得多?
  • @jbndlr 它是一个 PANDAS 数据框。我的布尔过程在 4 分钟内运行,而多个 apply() if 语句过程需要 30 分钟。我只是担心这个“用户警告”。有什么想法吗?
  • 你能展示你的实际代码吗?

标签: python performance if-statement boolean apply


【解决方案1】:

从我目前阅读的内容来看,性能差距是由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'''
  1. 使用pandas 后端将表达式计算为字符串
  2. 使用 python 后端评估 same 字符串
  3. 使用pandas评估带有外部变量引用的表达式字符串
  4. 使用df.apply()解决问题
  5. 使用df.applymap()解决问题
  6. 直接提交表达式(无字符串求值)

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

【讨论】:

  • 感谢您的详细回复!它验证了我的想法 - Apply 比在线布尔值要慢得多!我设法找到了一种导致用户警告的方法。我使用了函数“.astype('int')”,它将所有 TRUE 和 FALSE 语句转换为 1 和 0 - 因此没有错误消息!干杯
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-13
  • 2017-08-02
  • 2013-09-04
  • 2013-06-16
  • 2017-10-28
  • 1970-01-01
相关资源
最近更新 更多