rpart:分类与连续回归量的计算时间
Posted
技术标签:
【中文标题】rpart:分类与连续回归量的计算时间【英文标题】:rpart: Computational time for categorical vs continuous regressors 【发布时间】:2013-06-19 15:17:31 【问题描述】:我目前正在使用rpart
包将回归树拟合到具有相对较少的观察和数千个具有两个可能值的分类预测变量的数据。
通过在较小的数据上测试包,我知道在这种情况下,我是否将回归变量声明为分类(即因子)或保持原样(它们被编码为 +/-1)并不重要。
但是,我仍然想了解为什么将我的解释变量作为因素传递会显着减慢算法速度(尤其是因为我很快就会得到新数据,其中响应采用 3 个不同的值并将它们视为连续值将不再是一种选择)。应该是反过来吧?
这是一个模拟我的数据的示例代码:
library(rpart)
x <- as.data.frame(matrix(sample(c(-1, +1), 50 * 3000, replace = T), nrow = 50))
y <- rnorm(50)
x.fac <- as.data.frame(lapply(x, factor))
现在比较:
system.time(rpart( y ~ ., data = x, method = 'anova'))
user system elapsed
1.62 0.21 1.85
system.time(rpart( y ~ ., data = x.fac, method = 'anova'))
user system elapsed
246.87 165.91 412.92
处理每个变量(因子)只有一个潜在的拆分可能性比处理整个范围的潜在拆分(对于连续变量)更简单、更快捷,所以我对rpart
的行为感到最困惑。任何澄清/建议都会非常感激。
【问题讨论】:
【参考方案1】:您需要对代码进行分析以确定,但如果时间差异不是来自 R 在准备模型矩阵时必须将每个因子变量转换为两个二进制变量,我会感到惊讶.
试试
Rprof("rpartProfile.Rprof")
rpart( y ~ ., data = x.fac, method = 'anova')
Rprof()
summaryRprof("rpartProfile.Rprof")
并查看时间都花在了哪里。我现在已经完成了:
> summaryRprof("rpartProfile.Rprof")
$by.self
self.time self.pct total.time total.pct
"[[<-.data.frame" 786.46 72.45 786.56 72.46
"rpart.matrix" 294.26 27.11 1081.78 99.66
"model.frame.default" 1.04 0.10 3.00 0.28
"terms.formula" 0.96 0.09 0.96 0.09
"as.list.data.frame" 0.46 0.04 0.46 0.04
"makepredictcall.default" 0.46 0.04 0.46 0.04
"rpart" 0.44 0.04 1085.38 99.99
"[[.data.frame" 0.16 0.01 0.42 0.04
"<Anonymous>" 0.16 0.01 0.18 0.02
"match" 0.14 0.01 0.22 0.02
"print" 0.12 0.01 0.12 0.01
"model.matrix.default" 0.10 0.01 0.44 0.04
....
$by.total
total.time total.pct self.time self.pct
"rpart" 1085.38 99.99 0.44 0.04
"rpart.matrix" 1081.78 99.66 294.26 27.11
"[[<-" 786.62 72.47 0.06 0.01
"[[<-.data.frame" 786.56 72.46 786.46 72.45
"model.frame.default" 3.00 0.28 1.04 0.10
"eval" 3.00 0.28 0.04 0.00
"eval.parent" 3.00 0.28 0.00 0.00
"model.frame" 3.00 0.28 0.00 0.00
"terms.formula" 0.96 0.09 0.96 0.09
"terms" 0.96 0.09 0.00 0.00
"makepredictcall" 0.50 0.05 0.04 0.00
"as.list.data.frame" 0.46 0.04 0.46 0.04
"makepredictcall.default" 0.46 0.04 0.46 0.04
"as.list" 0.46 0.04 0.00 0.00
"vapply" 0.46 0.04 0.00 0.00
"model.matrix.default" 0.44 0.04 0.10 0.01
"[[" 0.44 0.04 0.02 0.00
"model.matrix" 0.44 0.04 0.00 0.00
....
$sample.interval
[1] 0.02
$sampling.time
[1] 1085.5
请注意,大部分时间都花在函数 rpart.matrix
上:
> rpart:::rpart.matrix
function (frame)
if (!inherits(frame, "data.frame") || is.null(attr(frame,
"terms")))
return(as.matrix(frame))
for (i in 1:ncol(frame))
if (is.character(frame[[i]]))
frame[[i]] <- as.numeric(factor(frame[[i]]))
else if (!is.numeric(frame[[i]]))
frame[[i]] <- as.numeric(frame[[i]])
X <- model.matrix(attr(frame, "terms"), frame)[, -1L, drop = FALSE]
colnames(X) <- sub("^`(.*)`", "\\1", colnames(X))
class(X) <- c("rpart.matrix", class(X))
X
但大部分时间都花在该函数中的 for
循环上,本质上是转换每一列并将它们添加回数据框。
【讨论】:
看起来它本身不是model.matrix
,而是rpart:::rpart.matrix
,它必须在for循环中扩展模型简写y~.
。可能是同样的事情导致人们反对随机森林中的公式界面。
+1 @joran 确实 - 我正在分析它,但在我的电脑上花了一段时间。现在已添加这些详细信息。
@GavinSimpson 谢谢你,加文。是的,Rprof 也为我提供了类似的结果,因此将罪魁祸首确定为 rpart.matrix。
@joran 我不确定这是否是一个微不足道的问题,但如果函数参数需要它,实际上是否可以放弃公式语法?
@stasg 在这种情况下(显然)没有rpart
的非公式接口,我实际上有点惊讶。我将在下面调查 Hong 的工作。【参考方案2】:
只是在上面@gavin simpson 的发现的基础上构建...我决定尝试使用rpart.matrix
,看看我是否可以对过多的执行时间做点什么。
问题归结为for
循环的使用。通常我不知道for
与[sl]apply
相比;后者通常被认为更优雅,但我不会在 for
工作正常时替换它,只是为了这个。特别是我认为*apply
的性能优势有时被夸大了。与旧的 S-Plus 时代相比,for
在速度和内存使用方面有了显着提高。
但在这种情况下不是。只需将 for
替换为 lapply
即可将此示例的运行时间缩短 2 个数量级以上。很高兴看看其他人是否可以确认这一点。
m <- model.frame(x.fac)
# call rpart.matrix
system.time(mm <- rpart:::rpart.matrix(m))
user system elapsed
208.25 88.03 296.99
# exactly the same as rpart.matrix, but with for replaced by lapply
f <- function(frame)
if (!inherits(frame, "data.frame") || is.null(attr(frame,
"terms")))
return(as.matrix(frame))
frame[] <- lapply(frame, function(x)
if (is.character(x))
as.numeric(factor(x))
else if(!is.numeric(x))
as.numeric(x)
else x
)
X <- model.matrix(attr(frame, "terms"), frame)[, -1L, drop = FALSE]
colnames(X) <- sub("^`(.*)`", "\\1", colnames(X))
class(X) <- c("rpart.matrix", class(X))
X
system.time(mm2 <- f(m))
user system elapsed
0.65 0.04 0.70
identical(mm, mm2)
[1] TRUE
【讨论】:
谢谢你!我已经在我的机器上运行了这个,对于原始循环函数,248.64 170.68 421.89
与重写 lapply 函数的0.81 0.20 1.57
的结果相似。我认为,这绝不是一个例外,这证明了 apply 系列比循环快得多。最重要的是,它们利用了 R 语言的向量化特性。我发现调用model.frame
本身需要的时间太长了。并且看到 rpart.matrix
是一个隐藏函数,我不知道这真的让我离开了哪里!
@stasg 小心得出 for 循环很慢的结论!事实并非如此。问题是 for 循环中的代码,而不是循环本身。这是循环中发生的所有任务。 lapply
的匿名函数回避了很多引发的对象复制,仅此而已。我可能可以构造一个写法稍有不同但速度更快的 for 循环。
我敢于将它作为对包维护者的改进建议提交! ;)
@stasg 这里的问题不是循环的速度(尽管lapply()
将比for()
更快地运行循环基础结构[稍微],这只有在循环是微不足道的,否则该组件占主导地位)但正如 Joran 提到的循环内函数调用的计算成本。 lapply
调用正在吞噬所有,除了对[[<.data.frame()
的调用之一(剩下一个对[<-.data.frame()
的R 端调用)。众所周知,数据框处理起来很慢,我认为这个问题很好地突出了这个问题。
+1 这是一个很好的修改。在你接受@joran 他的勇气之前,我会确定代码没有做任何奇怪的事情,你已经针对 rpart 包中的所有代码示例和测试(如果有的话)运行它并且不依赖于 [<-.data.frame
的未记录行为 - 我对 ?[<-.data.frame
的阅读在我看来表明您的用法是正确的并且更重要的是记录正确。以上是关于rpart:分类与连续回归量的计算时间的主要内容,如果未能解决你的问题,请参考以下文章