美文网首页python入门工作生活
Pandas进阶之高性能函数eval()和query()

Pandas进阶之高性能函数eval()和query()

作者: 惑也 | 来源:发表于2019-07-03 23:29 被阅读0次

    一、说明

    Python数据科学生态环境的强大力量在Numpy和Pandas的基础之上,并通过直观的语法将基本操作转化为c语言:在Numpy里是向量化/广播运算,在pandas里是分组型的运算。虽然这些抽象功能可以简洁高效的解决很多问题,但是他们经常需要创建临时对象,这样会占用很大的计算时间和内存。

    Pandas为了解决性能问题,引入了eval()函数和query()函数,实现了直接运行C语言速度的操作,不需要费力配置中间数组,它们都依赖于Numexpr程序包。

    import numpy as np
    x = np.random.rand(1000000)
    y = np.random.rand(1000000)
    
    # numpy的向量化运算
    %timeit x + y   # timeit模块:准确测量小段代码的执行时间
    
    # python的列表运算
    %timeit np.fromiter([x1+y1 for x1, y1 in zip(x, y)], dtype=np.float)
    
    输出结果
    1.58 ms ± 60.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    177 ms ± 1.75 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    对于上面的numpy向量化运算,其优点很明显:对比普通的python循环或者列表综合运行速度要快很多,但是对于下面的复合代数式问题,numpy的向量化运算效率也比较低。

    mask = (x>0.5) & (x<0.5)
    
    #上式等价于于:
    tmp1 = (x>0.5)
    tmp2 = (y<0.5)
    mask = tmp1 & tmp2
    

    原因是,每段中间过程都需要显式的分配内存。如果x数组和y数组很大,这么运算将会占用大量的时间和内存。Numexpr程序库可以实现不为中间过程分配全部内存的前提下,完成元素到元素的复合代数式运算。Pandas的eval函数()和query()函数就是基于Numexpr实现的。

    二、pandas.eval()函数

    1. 算术运算

    import numpy as np
    df1, df2, df3, df4, df5 = (pd.DataFrame(rng.randint(0, 1000, (100, 3))) for i in range(5))
    
    result1 = -df1 * df2 / (df3 + df4) - df5
    result2 = pd.eval('-df1 * df2 / (df3 + df4) - df5')
    np.allclose(result1, result2)  # np.allclose():,比较两个array的每一元素是否相等
    True
    

    2. 比较运算

    result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
    result2 = pd.eval('df1 < df2 <= df3 != df4')
    
    np.allclose(result1, result2)
    True
    

    3. 位运算

    result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
    result2 = pd.eval('(df1 < 0.5) & (df2 < 0.5) | (df3 < df4)')
    
    np.allclose(result1, result2)
    True
    

    4. 对象属性和索引

    result1 = df2.T[0] + df3.iloc[1]
    result2 = pd.eval('df2.T[0] + df3.iloc[1]')
    
    np.allclose(result1, result2)
    True
    

    三、DataFrame.eval()

    • pandas.eval() 是 Pandas 的顶层函数,因此 DataFrame 也有一个 eval()方法可以做类似的运算;
    • DataFrame.eval()方法的好处:通过列名实现简洁的代数式运算。

    1. 列名作为变量进行代数运算

    df = pd.DataFrame(rng.rand(1000, 3), columns=['A', 'B', 'C'])
    df.head()
            A           B           C
    0   0.401791    0.973228    0.005811
    1   0.453365    0.715901    0.635402
    2   0.171049    0.175610    0.004500
    3   0.254660    0.513748    0.754389
    4   0.897135    0.649130    0.368049
    
    # 三种结果相同的不同写法,第3种 最高效简洁
    result1 = (df['A'] + df['B']) / (df['C'] - 1)
    result2 = pd.eval("(df.A + df.B) / (df.C - 1)")
    result3 = df.eval('(A + B) / (C - 1)')
    
    np.allclose(result1, result2)
    True
    np.allclose(result1, result3)
    True
    

    2. 新增列

    df.eval('D = (A + B) / C', inplace=True)    # 新增D列
    
    df.head()
            A           B           C           D
    0   0.401791    0.973228    0.005811    236.627079
    1   0.453365    0.715901    0.635402    1.840199
    2   0.171049    0.175610    0.004500    77.033882
    3   0.254660    0.513748    0.754389    1.018584
    4   0.897135    0.649130    0.368049    4.201252
    

    3. 局部变量

    • 通过 @ 符号可以使用 Python 的局部变量;
    • @符号表示“这是一个变量名称而不是一个列名称”。从而灵活地使用两个“命名空间”的资源计算代数式(列名称的命名空间、Python 对象的命名空间);
    • 说明:该方法不能在 pandas.eval() 函数中使用,因为 pandas.eval() 函数只能获取一个命名空间的内容。
    column_mean = df.mean(1)
    
    result1 = df['A'] + column_mean
    result2 = df.eval('A + @column_mean')
    
    np.allclose(result1, result2)
    True
    

    四、DataFrame.query()

    1. query()函数和eval()函数一样,是基于DataFrame列计算代数式。通常,过滤操作时,使用query()函数更简洁;
    result1 = pd.eval('df[(df.A < 0.5) & (df.B < 0.5)]')
    result2 = df.query('A < 0.5 and B < 0.5')
    
    np.allclose(result1, result2)
    True
    
    1. query()函数也支持局部变量,同样是通过关键符@进行识别,当存在isin()判断时,需要使用==代替isin()。特别地,因为pandas本身没有isnotin()函数,如果需要按此逻辑进行判断,仅需要把==改为!=即可。
    filter_list = [2, 6, 10]    # 按列表元素筛选
    df = pd.DataFrame({"A": [5, 0, 1, 2, 4, 3], "B": [2, 7, 8, 9, 6, 10]})
    
    # isin()判断
    df.query("B == @ filter_list")  
        A   B
    0   5   2
    4   4   6
    5   3   10
    
    # is not in 判断
    df.query("B != @ filter_list")
        A   B
    1   0   7
    2   1   8
    3   2   9
    

    五、性能

    • 在考虑要不要用这两个函数时,需要思考两个方面:计算时间和内存消耗,而内存消耗是更重要的影响因素;
    • 每个涉及 NumPy 数组或 Pandas 的 DataFrame的复合代数式运算时,都会产生临时数组;
    • 普通方法在处理较小的数组时,反而速度更快! eval()函数和query 函数的优点主要是节省内存,语法也更加简洁。

    相关文章

      网友评论

        本文标题:Pandas进阶之高性能函数eval()和query()

        本文链接:https://www.haomeiwen.com/subject/bymxhctx.html