Python - 没有IF语句的在线布尔评估

时间:2016-09-27 13:31:20

标签: python performance if-statement boolean apply

我正在尝试评估数据帧列的值,以确定另一列的值。我通过成功使用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说: &#34; UserWarning:在Python空间进行评估,因为&#39; *&#39;对于bool dtype,numexpr不支持operator,使用&#39;&amp;&#39;代替   不支持[op_str]))&#34;

这是一个问题吗?我应该使用&#34;&amp;&#34;?我觉得使用&#34;&amp;&#34;乘以时会不正确。

谢谢!

1 个答案:

答案 0 :(得分:1)

根据我到目前为止所读到的,性能差距由pandas选择的解析器后端发出。常规的python解析器作为后端,另外还有一个解析后端的pandas 文档说,如果在这里使用普通的旧python而不是pandas,就没有性能提升:Pandas eval Backends

然而,你显然在熊猫后端打了一个白点;即你形成了一个无法用pandas评估的表达式。结果是pandas回退到最初的python解析后端,如结果UserWarning中所述:

  

UserWarning:在Python空间中进行评估,因为对于bool dtype,numexpr不支持'*'运算符,请使用'&amp;'代替     不支持[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. 将表达式评估为字符串
  3. 使用python后端
  4. 评估相同的字符串
  5. 使用pandas
  6. 评估带有外部变量引用的表达式字符串
  7. 使用df.apply()
  8. 解决问题
  9. 使用df.applymap()
  10. 解决问题
  11. 直接提交表达式(无字符串评估)
  12. 我的机器上一列中具有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是完全没问题,只要您知道基础 hows和为什么

    因此:继续前进并让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))