S3和类的顺序

8

我一直很难理解S3方法的调用文档,这次它又咬了我一口。

我要提前道歉,因为我有超过一个问题,但它们都密切相关。在一组复杂的函数中,我创建了许多glmnet拟合,特别是逻辑回归。现在,glmnet文档指定返回值具有两个类别:“glmnet”和(对于逻辑回归)“lognet”。实际上,它们按照此顺序指定。

然而,在查看glmnet实现末尾时,就在调用(内部函数)lognet后面,将fit的类设置为“lognet”,我在返回之前看到了这行代码:

class(fit) = c(class(fit), "glmnet")

根据这个,我可以得出结论,类的顺序实际上是"lognet"、"glmnet"。

不幸的是,我的拟合结果(如文档所建议):

> class(myfit)
[1] "glmnet" "lognet"

这种方法的问题在于S3方法的分派,特别是predict。这是predict.lognet的代码:
function (object, newx, s = NULL, type = c("link", "response", 
    "coefficients", "class", "nonzero"), exact = FALSE, offset, 
    ...) 
{
    type = match.arg(type)
    nfit = NextMethod("predict") #<- supposed to call predict.glmnet, I think
    switch(type, response = {
        pp = exp(-nfit)
        1/(1 + pp)
    }, class = ifelse(nfit > 0, 2, 1), nfit)
}

我已经添加了一条评论来解释我的推理。现在,当我使用新的datamatrix mydatatype="response"调用此myfit的预测时,就像这样:

predict(myfit, newx=mydata, type="response")

根据文档,我没有得到预测的概率值,而是得到了线性组合,这正是立即调用 predict.glmnet 的结果。

我尝试过颠倒类别的顺序,像这样:

orgclass<-class(myfit)
class(myfit)<-rev(orgclass)

然后再次执行预测调用:喜闻乐见:它可以工作!我得到了概率。
所以,这里有一些问题:
  1. 我对“学习”S3方法会按类的出现顺序分配的理解正确吗?
  2. 如果我假设glmnet中的代码会导致predict的正确派发顺序错误,那么我是正确的吗?
  3. 在我的代码中,没有明显/可见地操作类。 什么可能导致顺序改变?
为了完整起见:这里是一些示例代码供您玩耍(就像我现在正在做的那样):
library(glmnet)
y<-factor(sample(2, 100, replace=TRUE))
xs<-matrix(runif(100), ncol=1)
colnames(xs)<-"x"
myfit<-glmnet(xs, y, family="binomial")
mydata<-matrix(runif(10), ncol=1)
colnames(mydata)<-"x"
class(myfit)
predict(myfit, newx=mydata, type="response")
class(myfit)<-rev(class(myfit))
class(myfit)
predict(myfit, newx=mydata, type="response")
class(myfit)<-rev(class(myfit))#set it back
class(myfit)

根据生成的数据,差异或多或少明显(在我的真实数据集中,我注意到所谓概率值为负数,这就是我发现问题的方式),但确实应该看到差异。感谢任何意见。
编辑:我刚刚发现可怕的真相:glmnet 1.5.2 中的任何顺序都可以使用(该版本存在于我运行实际代码的服务器上,导致分类顺序颠倒的拟合),但是从1.6开始需要按照“lognet”、“glmnet”的顺序进行排序。我还没有检查在1.7中会发生什么。
感谢@Aaron提醒我信息学基础知识(除了“如果一切失败,请重新启动”之外,“检查您的版本”),我错误地认为由统计学习之神创建的程序包将受到此类错误的保护,以及感谢@Gavin确认了我对S3工作方式的重建。

2
当我运行你的代码时,在第一个“class”调用之后,我得到了顺序“lognet”“glmnet”,这与你所说的相反。我有glmnet 1.7;你有什么版本? - Aaron left Stack Overflow
1个回答

6
是的,方法调度的顺序是按照类属性中列出的类的顺序进行的。在简单的日常情况下,是的,第一个声明的类是方法调度首选的类。只有在找不到该类的方法(或调用NextMethod)时,才会转向第二个类,否则将搜索默认方法。
不,我认为您不正确,代码中类的顺序是错误的。文档似乎是错误的。显然意图是首先调用predict.lognet(),使用workhorse predict.glmnet()对glmnet拟合的所有类型的lasso/弹性网络模型进行基本计算,最后对这些通用预测进行一些后处理。也许predict.glmnet()没有从glmnet NAMESPACE导出而其他方法却有,这也许说明了一些问题。
我不确定为什么您认为这样输出的结果:
predict(myfit, newx=mydata, type="response")

有问题吗?我得到了一个10行21列的矩阵,其中列与仅拦截模型预测以及在计算lasso/elastic net路径上的20个lambda值处计算出的模型系数的预测相关。它们似乎不是线性组合,并且按照您的要求在响应比例上。

类别的顺序没有改变。我认为你误解了代码的工作方式。文档中存在错误,因为排序陈述错误。但是我认为代码正在按照我认为的方式工作。


很好的答案,但有一个小问题:您无法在方法分派期间更改类:https://gist.github.com/1043952(好吧,您可以这样做,但它不会影响分派) - hadley
此外,似乎意图是先运行predict.lognet再运行predict.glmnet。但是根据我所读的,OP说他在系统上首先运行了predict.glmnet,因为类的顺序被颠倒了。 - Aaron left Stack Overflow
@hadley 感谢你指出来。我一定记错了。现在已经修复了。 - Gavin Simpson
@Aaron 我的印象是 @Nick Sabbe 故意颠倒对象的类顺序,以展示他认为正确的排序方式。在我的系统上运行此代码,我得到了正确的顺序,包括第一个 predict() 调用。让我们看看 @Nick 有什么要说的。 - Gavin Simpson
嘿,大家好,感谢你们的意见。请看我的编辑。我会接受 @Gavin 的回答,因为它确实回答了我所问的具体问题。 - Nick Sabbe

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