在函数内调用patsy时的命名空间问题

3

我正在尝试为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的方法来处理这种情况,而不需要预先知道函数名或需要多少个函数?
FYI:这是针对Python 3.4.3的。

我不知道细节(对我来说太神奇了),但是statsmodels.base.model.Model.from_formula的文档字符串描述了一个eval_env关键字参数,你可以通过它递增1。from_formula被所有或大多数模型继承。 - Josef
是的,我确实尝试过那个功能;似乎没有生效,但可能是我没有正确调用它。 - chriswhite
你试过将它设置为3吗?在类似的情况下,我使用try..except包装来确定用户函数位于哪个深度。 - Josef
statsmodels.basedata.ModelData.__setstate__ 尝试在反序列化时重新创建公式和设计。我根据一些示例进行了试错编写。 - Josef
@user333700 把这个作为答案发出来,我会接受它;两个注意事项:1.) 我必须设置 eval_env = 2,2.) 这是传递给 logit(..) 而不是 fit(...) 的关键字参数。(虽然你没有暗示它是,但我之前没有意识到)。 - chriswhite
显示剩余2条评论
2个回答

2

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

有关详细信息,请参见http://patsy.readthedocs.org/en/latest/API-reference.html中的eval_env关键字。

from_formula是实现与patsy公式接口的模型的方法。它使用eval_env向patsy提供必要的信息,其默认值为用户调用环境。用户可以使用相应的关键字参数覆盖此设置。

定义eval_env的最简单方法是将其定义为指示patsy应使用的堆栈级别的整数。from_formula会递增它以考虑statsmodels方法中的额外级别。

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

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


如果有几个嵌套的包装器,将其作为参数传递并在递增后传递是否有意义,例如 def f(..., eval_env=1): ... smf.logit(..., eval_env=eval_env+1) - Tadhg McDonald-Jensen
如果我理解你的评论正确的话,那么这就是 from_formula 在做的事情,https://github.com/statsmodels/statsmodels/blob/master/statsmodels/base/model.py#L138 例如,如果提供了增量,则递增并传递。总的来说,在扩展“eval”方法之前,我会先检查安全问题。 - Josef

1
如果您愿意使用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的东西),那么这应该可以实现你想要的效果。

哦,这非常有趣,如果这变得更加复杂,这是我可以考虑未来的事情。谢谢! - chriswhite
有一个问题,我不知道在这种情况下它将如何工作,就是当我们(模型或结果)需要再次使用相同的信息时,例如用于评估或转换predict中的解释变量。据我所知,patsy会保留信息并使用一些eval魔法。 - Josef

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接