代码之家  ›  专栏  ›  技术社区  ›  cs95 abhishek58g

使用pd.eval()对大熊猫进行动态表达评估

  •  31
  • cs95 abhishek58g  · 技术社区  · 7 年前

    我想使用 pd.eval . 具体而言,我想移植以下代码来计算公式:

    x = 5
    df2['D'] = df1['A'] + (df1['B'] * x) 
    

    …使用 评估 . 使用的原因 我想自动化许多工作流,所以动态创建它们对我很有用。

    import pandas as pd
    import numpy as np
    
    np.random.seed(0)
    df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
    df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
    
    df1
       A  B  C  D
    0  5  0  3  3
    1  7  9  3  5
    2  2  4  7  6
    3  8  8  1  6
    4  7  7  8  1
    
    df2
       A  B  C  D
    0  5  9  8  9
    1  4  3  0  3
    2  5  0  2  3
    3  8  1  3  3
    4  3  7  0  1
    

    我正在努力更好地理解 评估 engine parser 确定如何最好地解决我的问题的参数。我已经通过了考试 documentation 但我并不清楚两者之间的区别。

    1. 有没有办法将表达式的结果重新分配给 df2 ?
    2. x 作为字符串表达式中的参数?
    2 回复  |  直到 5 年前
        1
  •  115
  •   smci    5 年前

    您可以使用1) pd.eval() , 2) df.query() ,或3) df.eval()

    示例将涉及这些数据帧(除非另有规定)。

    np.random.seed(0)
    df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
    df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
    df3 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
    df4 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
    

    1) pandas.eval

    这是熊猫文档应该包含的“缺失手册”。 pd.eval df.eval df.query 呼叫 评估 在三个功能中保持一致,但有一些次要的语义 稍后将突出显示的变体。本节将 介绍所有三个功能中通用的功能-包括(但不限于) 允许的语法、优先规则 关键字参数。

    可以计算由变量和/或文字组成的算术表达式。这些表达式必须作为字符串传递。所以 回答问题

    x = 5
    pd.eval("df1.A + (df1.B * x)")  
    

    这里需要注意的是:

    1. df1 , df2 x 引用全局命名空间中的变量,这些变量由 eval
    2. 使用属性访问器索引访问特定列。你也可以使用 "df1['A'] + (df1['B'] * x)" 同样的效果。

    我将在解释以下内容的章节中讨论重新分配的具体问题: target=... 属性。但就目前而言,这里有一些更简单的有效操作示例 评估 :

    pd.eval("df1.A + df2.A")   # Valid, returns a pd.Series object
    pd.eval("abs(df1) ** .5")  # Valid, returns a pd.DataFrame object
    

    等等条件表达式也以同样的方式得到支持。下面的语句都是有效的表达式,将由引擎计算。

    pd.eval("df1 > df2")        
    pd.eval("df1 > 5")    
    pd.eval("df1 < df2 and df3 < df4")      
    pd.eval("df1 in [1, 2, 3]")
    pd.eval("1 < 2 < 3")
    

    documentation . 总之,

    • 除左移位外的算术运算( << )右移( >> )运营商,例如:。, df + 2 * pi / s ** 4 % 42 -黄金比率
    • 2 < df < df2
    • 布尔运算,例如。, df < df2 and df3 < df4 not df_bool list tuple [1, 2] (1, 2)
    • 属性访问,例如。, df.a
    • 下标表达式,例如。, df[0]
    • 简单变量评估,例如:。, pd.eval('df')
    • 阿尔茨坦2。

    本节文档还指定了不受支持的语法规则,包括 set / dict 文字、if-else语句、循环和理解以及生成器表达式。

    pd.eval('df1.A * (df1.index > 1)')
    

    1a)解析器选择: parser=... 论点

    评估 pandas python . 两者之间的主要区别在于优先级规则略有不同。

    使用默认解析器 ,重载的按位运算符 & | 对pandas对象实现矢量化AND和OR操作的运算符优先级与 and or . 所以

    pd.eval("(df1 > df2) & (df3 < df4)")
    

    pd.eval("df1 > df2 & df3 < df4")
    # pd.eval("df1 > df2 & df3 < df4", parser='pandas')
    

    也和

    pd.eval("df1 > df2 and df3 < df4")
    

    这里,括号是必要的。按照惯例,要做到这一点,将需要paren重写位运算符的更高优先级:

    (df1 > df2) & (df3 < df4)
    

    没有这一点,我们最终会

    df1 > df2 & df3 < df4
    
    ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
    

    parser='python'

    pd.eval("(df1 > df2) & (df3 < df4)", parser='python')
    

    这两种解析器之间的另一个区别是 == != 具有列表和元组节点的运算符,其语义与 in not in 'pandas' 解析器。例如

    pd.eval("df1 == [1, 2, 3]")
    

    是有效的,并将使用与相同的语义运行

    pd.eval("df1 in [1, 2, 3]")
    

    奥托, pd.eval("df1 == [1, 2, 3]", parser='python') NotImplementedError

    1b)后端选择: engine=... 论点

    numexpr (默认)和 python numexpr 选项使用 numexpr 对性能进行了优化的后端。

    具有 'python' 在后端,表达式的计算类似于将表达式传递给python的 评估

    df = pd.DataFrame({'A': ['abc', 'def', 'abacus']})
    pd.eval('df.A.str.contains("ab")', engine='python')
    
    0     True
    1    False
    2     True
    Name: A, dtype: bool
    

    不幸的是,这种方法提供了 过去一年的绩效效益 引擎,并且很少有安全措施来确保不计算危险表达式,所以 使用风险自负 “python”

    1c) local_dict global_dict

    有时,为表达式中使用但当前未在命名空间中定义的变量提供值很有用。你可以把字典递给我 地方法院

    例如:

    pd.eval("df1 > thresh")
    
    UndefinedVariableError: name 'thresh' is not defined
    

    thresh

    pd.eval("df1 > thresh", local_dict={'thresh': 10})
        
    

    当您需要从字典中提供变量时,这非常有用。或者,使用 引擎,您可以简单地执行以下操作:

    mydict = {'thresh': 5}
    # Dictionary values with *string* keys cannot be accessed without 
    # using the 'python' engine.
    pd.eval('df1 > mydict["thresh"]', engine='python')
    

    但这可能是 'numexpr' 引擎并将字典传递给 地方法院 全球规则 . 希望这能为使用这些参数提供令人信服的理由。

    target (+ inplace

    这通常不是一个要求,因为通常有更简单的方法,但您可以指定 评估 __getitem__ 例如 字典

    考虑问题中的例子

    x = 5
    df2['D'] = df1['A'] + (df1['B'] * x)
    

    将列“D”指定给 是的

    pd.eval('D = df1.A + (df1.B * x)', target=df2)
    
       A  B  C   D
    0  5  9  8   5
    1  4  3  0  52
    2  5  0  2  22
    3  8  1  3  48
    4  3  7  0  42
    

    (但它可以……继续读下去)。再举一个例子:

    pd.eval('df1.A + df2.A')
    
    0    10
    1    11
    2     7
    3    16
    4    10
    dtype: int32
    

    目标 论点如下:

    df = pd.DataFrame(columns=list('FBGH'), index=df1.index)
    df
         F    B    G    H
    0  NaN  NaN  NaN  NaN
    1  NaN  NaN  NaN  NaN
    2  NaN  NaN  NaN  NaN
    3  NaN  NaN  NaN  NaN
    4  NaN  NaN  NaN  NaN
    
    df = pd.eval('B = df1.A + df2.A', target=df)
    # Similar to 
    # df = df.assign(B=pd.eval('df1.A + df2.A'))
    
    df
         F   B    G    H
    0  NaN  10  NaN  NaN
    1  NaN  11  NaN  NaN
    2  NaN   7  NaN  NaN
    3  NaN  16  NaN  NaN
    4  NaN  10  NaN  NaN
    

    如果你想对基因进行原位突变 df 设置 inplace=True

    pd.eval('B = df1.A + df2.A', target=df, inplace=True)
    # Similar to 
    # df['B'] = pd.eval('df1.A + df2.A')
    
    df
         F   B    G    H
    0  NaN  10  NaN  NaN
    1  NaN  11  NaN  NaN
    2  NaN   7  NaN  NaN
    3  NaN  16  NaN  NaN
    4  NaN  10  NaN  NaN
    

    如果 是没有目标的,一个 ValueError

    目标

    如果你想这样做的话 评估 ,您将使用包含赋值的表达式:

    df = df.eval("B = @df1.A + @df2.A")
    # df.eval("B = @df1.A + @df2.A", inplace=True)
    df
    
         F   B    G    H
    0  NaN  10  NaN  NaN
    1  NaN  11  NaN  NaN
    2  NaN   7  NaN  NaN
    3  NaN  16  NaN  NaN
    4  NaN  10  NaN  NaN
    

    笔记
    什么之中的一个 评估 的非预期用途是以非常类似的方式解析文本字符串 ast.literal_eval :

    pd.eval("[1, 2, 3]")
    array([1, 2, 3], dtype=object)
    

    它还可以使用 发动机:

    pd.eval("[[1, 2, 3], [4, 5], [10]]", engine='python')
    [[1, 2, 3], [4, 5], [10]]
    

    和字符串列表:

    pd.eval(["[1, 2, 3]", "[4, 5]", "[10]"], engine='python')
    [[1, 2, 3], [4, 5], [10]]
    

    但是,问题在于长度大于100的列表:

    pd.eval(["[1]"] * 100, engine='python') # Works
    pd.eval(["[1]"] * 101, engine='python') 
    
    AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'
    

    here .


    2) DataFrame.eval

    如上所述, 评估 评估 v0.23 source code 这表明:

    def eval(self, expr, inplace=False, **kwargs):
    
        from pandas.core.computation.eval import eval as _eval
    
        inplace = validate_bool_kwarg(inplace, 'inplace')
        resolvers = kwargs.pop('resolvers', None)
        kwargs['level'] = kwargs.pop('level', 0) + 1
        if resolvers is None:
            index_resolvers = self._get_index_resolvers()
            resolvers = dict(self.iteritems()), index_resolvers
        if 'target' not in kwargs:
            kwargs['target'] = self
        kwargs['resolvers'] = kwargs.get('resolvers', ()) + tuple(resolvers)
        return _eval(expr, inplace=inplace, **kwargs)

    评估 创建参数,进行少量验证,并将参数传递给 评估

    有关更多信息,请阅读: when to use DataFrame.eval() versus pandas.eval() or python eval()


    使用差异

    带数据帧的表达式与系列表达式

    对于与整个数据帧关联的动态查询,您应该更喜欢 . 例如,没有简单的方法来指定 pd.eval("df1 + df2") 当你打电话的时候 df1.eval df2.eval .

    2a2) 指定列名

    df1 ,你会打电话吗 使用以下表达式:

    pd.eval("df1.A + df1.B")
    

    使用df.eval,只需提供列名:

    df1.eval("A + B")
    

    因为,在 df1 ,显然“A”和“B”指的是列名。

    您还可以使用引用索引和列 index (除非索引已命名,否则您将使用该名称)。

    df1.eval("A + index")
    

    使用变量的表达式中索引的级别 它代表“ 无损检测 k级 “.IOW,上面的表达式可以写成 df1.eval("A + ilevel_0") .

    这些规则也适用于 .

    访问本地/全局命名空间中的变量

    A = 5
    df1.eval("A > @A") 
    

    同样的道理也适用于 query .

    不用说,列名必须遵循python中有效标识符命名的规则,才能在内部访问 评估 here 有关命名标识符的规则列表。

    2a4)

    一个鲜为人知的事实是 查询 没有)。例如,要在df1中基于对某些列的一些算术运算创建两个新列“E”和“F”,并基于先前创建的“E”和“F”创建第三列“G”,我们可以这样做

    df1.eval("""
    E = A + B
    F = @df2.A + @df2.B
    G = E >= F
    """)
    
       A  B  C  D   E   F      G
    0  5  0  3  3   5  14  False
    1  7  9  3  5  16   7   True
    2  2  4  7  6   6   5   True
    3  8  8  1  6  16   9   True
    4  7  7  8  1  14  10   True
    

    3) vs 查询

    这有助于思考 作为使用 评估 作为一个子程序。

    查询 (顾名思义)用于计算条件表达式(即,导致真/假值的表达式)并返回与 True 后果然后将表达式的结果传递给 loc (在大多数情况下)返回满足表达式的行。根据文件,,

    此表达式的计算结果首先传递给 DataFrame.loc 如果由于多维密钥而失败 (例如,数据帧)然后将结果传递给 DataFrame.__getitem__() .

    此方法使用顶级 pandas.eval() 已通过查询。

    评估

    如上所述,这两者之间的关键区别在于它们如何处理表达式结果。当您实际通过这两个函数运行表达式时,这一点变得很明显。例如,考虑

    df1.A
    
    0    5
    1    7
    2    2
    3    8
    4    7
    Name: A, dtype: int32
    
    df1.B
    
    0    9
    1    3
    2    0
    3    1
    4    7
    Name: B, dtype: int32
    

    获取“A”>所在的所有行中的“B” ,我们会使用 评估

    m = df1.eval("A >= B")
    m
    0     True
    1    False
    2    False
    3     True
    4     True
    dtype: bool
    

    m 表示通过计算表达式“A>=B”生成的中间结果。然后我们使用遮罩进行过滤 df1 :

    df1[m]
    # df1.loc[m]
    
       A  B  C  D
    0  5  0  3  3
    3  8  8  1  6
    4  7  7  8  1
    

    但是, ,中间结果“m”直接传递给 loc ,因此 查询 ,你只需要

    df1.query("A >= B")
    
       A  B  C  D
    0  5  0  3  3
    3  8  8  1  6
    4  7  7  8  1
    

    确切地

    df1_big = pd.concat([df1] * 100000, ignore_index=True)
    
    %timeit df1_big[df1_big.eval("A >= B")]
    %timeit df1_big.query("A >= B")
    
    14.7 ms ± 33.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    14.7 ms ± 24.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    但后者更简洁,在一个步骤中表达相同的操作。

    请注意,你也可以做一些奇怪的事情 查询

    df1.query("index")
    # Same as df1.loc[df1.index] # Pointless,... I know
    
       A  B  C  D
    0  5  0  3  3
    1  7  9  3  5
    2  2  4  7  6
    3  8  8  1  6
    4  7  7  8  1
    

    但是不要。

    底线:请使用 查询 基于条件表达式查询或筛选行时。

        2
  •  6
  •   BhishanPoudel    7 年前

    eval/query 受其简单语法的吸引,如果数据集的行数少于15000行,则会出现严重的性能问题。

    在这种情况下,只需使用 df.loc[mask1, mask2]

    参考: https://pandas.pydata.org/pandas-docs/version/0.22/enhancingperf.html#enhancingperf-eval

    enter image description here

        3
  •  3
  •   TanuAD    5 年前

    Python DataScience Handbook 这里面有一个密码:

    births['decade'] = 10 * (births['year'] // 10)
    

    如果您查看上面的floordiv()(//),我无法使用eval()完成此操作。

    births.eval("decade = 10 * (year // 10)")
    

    这就产生了错误。 然而,在读了你的文章之后,我更换了引擎,它工作得很好。

    births.eval("decade = 10 * (year // 10)", engine= "python")
    

    谢谢@cs95。