Python 从pandas中的公式动态计算表达式?

Python 从pandas中的公式动态计算表达式?,python,pandas,dataframe,formula,eval,Python,Pandas,Dataframe,Formula,Eval,我想使用pd.eval对一个或多个数据帧列执行算术。具体而言,我想移植以下代码来计算公式: x = 5 df2['D'] = df1['A'] + (df1['B'] * x) …使用pd.eval进行编码。使用pd.eval的原因是我想自动化许多工作流,因此动态创建它们对我很有用 我的两个输入数据帧是: import pandas as pd import numpy as np np.random.seed(0) df1 = pd.DataFrame(np.random.choice(

我想使用
pd.eval
对一个或多个数据帧列执行算术。具体而言,我想移植以下代码来计算公式:

x = 5
df2['D'] = df1['A'] + (df1['B'] * x) 
…使用
pd.eval
进行编码。使用
pd.eval
的原因是我想自动化许多工作流,因此动态创建它们对我很有用

我的两个输入数据帧是:

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
我试图更好地理解
pd.eval
引擎
解析器
参数,以确定如何最好地解决我的问题。我已经经历了这一过程,但我没有弄清楚其中的区别

  • 应该使用哪些参数来确保我的代码以最高性能运行
  • 有没有办法将表达式的结果赋回到
    df2
  • 另外,为了使事情更加复杂,我如何在字符串表达式中传递
    x
    作为参数
  • 您可以使用1)、2)或3)。下面将讨论它们的各种特性和功能

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


    1) 这是熊猫文档应该包含的“缺失手册”。 注意:在讨论的三个功能中,
    pd.eval
    是最重要的
    df.eval
    df.query
    调用
    pd.eval
    发动机罩下。行为和用法或多或少是不同的 在三个功能中保持一致,但有一些次要的语义 稍后将突出显示的变体。本节将 介绍所有三个函数中通用的功能—包括(但不限于)允许的语法、优先规则和关键字参数

    pd.eval
    可以计算由变量和/或文字组成的算术表达式。这些表达式必须作为字符串传递。因此,要回答上述问题,您可以

    x = 5
    pd.eval("df1.A + (df1.B * x)")  
    
    这里需要注意的是:

  • 整个表达式是一个字符串
  • df1
    df2
    x
    引用全局命名空间中的变量,这些变量在解析表达式时由
    eval
    拾取
  • 使用属性访问器索引访问特定列。您也可以使用
    “df1['A']+(df1['B']*x)”
    达到相同的效果
  • 我将在下面解释
    target=…
    属性的部分中讨论重新分配的具体问题。但就目前而言,以下是使用
    pd.eval
    进行有效操作的更简单示例:

    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("[1, 2, 3]")
    array([1, 2, 3], dtype=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")
    
    1a)解析器选择:
    Parser=…
    参数
    pd.eval
    在解析表达式字符串以生成语法树时支持两种不同的解析器选项:
    pandas
    python
    。两者之间的主要区别在于优先级规则略有不同

    使用默认解析器pandas,重载的位运算符
    &
    |
    将实现对pandas对象的矢量化and和OR操作,它们将具有与
    相同的运算符优先级。所以

    pd.eval("(df1 > df2) & (df3 < df4)")
    
    这两种解析器之间的另一个区别是
    ==
    的语义='pandas'
    解析器时,带有列表和元组节点的code>运算符,其语义分别与
    中的
    中的
    相似。比如说,

    pd.eval("df1 == [1, 2, 3]")
    
    是有效的,并将使用与相同的语义运行

    pd.eval("df1 in [1, 2, 3]")
    
    OTOH,
    pd.eval(“df1==[1,2,3]”,parser='python')
    将抛出一个
    NotImplementedError
    错误

    1b)后端选择:
    引擎=…
    参数 有两个选项-
    numexpr
    (默认值)和
    python
    numexpr
    选项使用后端,该后端针对性能进行了优化

    使用
    'python'
    后端,对表达式的求值类似于将表达式传递给python的
    eval
    函数。您可以灵活地执行更多的内部表达式,例如字符串操作

    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
    
    不幸的是,与
    numexpr
    引擎相比,此方法没有提供任何性能优势,而且几乎没有安全措施可确保不评估危险的表达式,因此使用风险自负!除非您知道自己在做什么,否则通常不建议将此选项更改为
    'python'

    1c)
    local_dict
    global_dict
    参数 有时,为表达式中使用但当前未在命名空间中定义的变量提供值很有用。您可以将词典传递给
    local\u dict

    例如:

    pd.eval("df1 > thresh")
    
    UndefinedVariableError: name 'thresh' is not defined
    
    pd.eval('df1.A + df2.A')
    
    0    10
    1    11
    2     7
    3    16
    4    10
    dtype: int32
    
    此操作失败,因为未定义阈值。然而,这是可行的:

    pd.eval("df1 > thresh", local_dict={'thresh': 10})
        
    
    当您需要从字典中提供变量时,这非常有用。或者,使用
    'python'
    引擎,您可以简单地执行以下操作:

    mydict = {'thresh': 5}
    # Dictionary values with *string* keys cannot be accessed without 
    # using the 'python' engine.
    pd.eval('df1 > mydict["thresh"]', engine='python')
    
    但这可能比使用
    'numexpr'
    引擎并将字典传递到
    本地dict
    全局dict
    要慢得多。希望这能为使用这些参数提供令人信服的理由

    1d)
    target
    (+
    inplace
    )参数和赋值表达式 这通常不是一个要求,因为通常有更简单的方法,但是您可以将
    pd.eval
    的结果分配给实现
    \uuuu getitem\uuuuu
    的对象,例如
    dict
    s和(您猜到的)数据帧

    考虑问题中的例子

    要将列“D”分配给
    df2
    ,我们需要

    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
    
    这不是就地修改
    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
    
    pd.eval("df1 > thresh")
    
    UndefinedVariableError: name 'thresh' is not defined
    
    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')
    
    x = 5
    df2['D'] = df1['A'] + (df1['B'] * x)
    
    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
    
    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
    
    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
    
    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]]
    
    pd.eval(["[1]"] * 100, engine='python') # Works
    pd.eval(["[1]"] * 101, engine='python') 
    
    AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'
    
    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)
    pd.eval("df1.A + df1.B")
    
    df1.eval("A + B")
    
    df1.eval("A + index")
    
    A = 5
    df1.eval("A > @A") 
    
    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
    
    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
    
    m = df1.eval("A >= B")
    m
    0     True
    1    False
    2    False
    3     True
    4     True
    dtype: bool
    
    df1[m]
    # df1.loc[m]
    
       A  B  C  D
    0  5  0  3  3
    3  8  8  1  6
    4  7  7  8  1
    
    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
    
    births['decade'] = 10 * (births['year'] // 10)
    
    births.eval("decade = 10 * (year // 10)")
    
    births.eval("decade = 10 * (year // 10)", engine= "python")