【问题标题】:Namespace issues when calling patsy within a function在函数中调用 patsy 时的命名空间问题
【发布时间】:2016-08-16 09:21:07
【问题描述】:

我正在尝试为 statsmodels 公式 API 编写一个包装器(这是一个简化的版本,功能不止于此):

import statsmodels.formula.api as smf

def wrapper(formula, data, **kwargs):
    return smf.logit(formula, data).fit(**kwargs)

如果我将此功能提供给用户,然后他/她会尝试定义他/她自己的功能:

def square(x):
    return x**2

model = wrapper('y ~ x + square(x)', data=df)

他们将收到NameError,因为patsy 模块正在wrapper 的命名空间中查找函数square。是否有一种安全的 Pythonic 方式来处理这种情况,而无需先验地知道函数名称是什么或需要多少函数?

仅供参考:这是针对 Python 3.4.3 的。

【问题讨论】:

  • 我不知道细节(对我来说太神奇了),但statsmodels.base.model.Model.from_formula 文档字符串描述了 **kwarg 中的 eval_env kwd,您可以将其增加 1。 from_formula 被所有或大多数模型继承。
  • 是的,我确实尝试过;似乎没有用,但也许我没有正确调用它。
  • 您是否尝试将其设置为 3?在类似的情况下,我使用 try..except 包装来确定用户函数的深度。
  • 示例:statsmodels.basedata.ModelData.__setstate__ 尝试在 unpickling 期间重新创建公式和设计。我是根据几个例子反复试验写的。
  • @user333700 将此作为答案发布,我会接受;两个注意事项:1.)我必须设置 eval_env = 2 和 2.)这是 logit(..) 的关键字,而不是 fit(...)。 (不是你暗示它是,但我没有意识到这一点)。

标签: python python-3.x namespaces statsmodels patsy


【解决方案1】:

statsmodels 使用 patsy 包来解析公式并创建设计矩阵。 patsy 允许用户函数作为公式的一部分,并在用户命名空间或环境中获取或评估用户函数。

作为参考,请参阅http://patsy.readthedocs.org/en/latest/API-reference.html 中的 eval_env 关键字

from_formula是models实现patsy公式接口的方法。它使用eval_env 向patsy 提供必要的信息,默认情况下是用户的调用环境。这可以由用户使用相应的关键字参数覆盖。

定义 eval_env 的最简单方法是用一个整数表示 patsy 应该使用的堆栈级别。 from_formula 正在递增它以考虑 statsmodels 方法中的额外级别。

根据 cmets,eval_env = 2 将使用创建模型的级别的下一个更高级别,例如model = smf.logit(..., eval_env=2)

这将创建模型,调用 patsy 并创建设计矩阵,model.fit() 将对其进行估计并返回结果实例。

【讨论】:

  • 如果有多个嵌套的包装器,将其作为参数并在递增后传递它是否有意义。喜欢def f(..., eval_env=1): ... smf.logit(..., eval_env=eval_env+1)
  • 如果我正确理解您的评论,那么这就是 from_formula 正在做的事情,github.com/statsmodels/statsmodels/blob/master/statsmodels/base/… 例如如果提供,则增加并继续执行。总的来说,我会在大量扩展 eval 方法之前检查安全问题。
【解决方案2】:

如果您愿意使用eval 来完成您的函数的繁重工作,您可以从wrapper 的参数和外部框架的局部变量构造一个命名空间:

wrapper_code = compile("smf.logit(formula, data).fit(**kwargs)",
                       "<WrapperFunction>","eval")
def wrapper(formula,data,**kwargs):
    outer_frame = sys._getframe(1)
    namespace = dict(outer_frame.f_locals)
    namespace.update(formula=formula, data=data, kwargs=kwargs, smf=smf)
    return eval(wrapper_code,namespace)

我真的不认为这是作弊,因为它似乎是 logit 无论如何都会为它引发 NameError 所做的事情,并且只要 wrapper_code 没有被修改并且没有名称冲突(比如使用名为 data) 的东西,这应该可以满足您的需求。

【讨论】:

  • 哦,这非常有趣,如果这变得更复杂,我可以考虑继续前进。谢谢!
  • 一个问题,我不知道在这种情况下它会如何工作,是当我们(模型或结果)再次需要相同的信息时,例如用于评估或转换predict 中的解释变量。 patsy 保留信息并使用一些 eval 魔术,AFAIK。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-24
  • 2014-07-15
  • 2012-10-13
  • 1970-01-01
  • 1970-01-01
  • 2012-12-20
相关资源
最近更新 更多