每当我用apply语句替换for循环时,我的R脚本运行得更快,但这是一个例外。我仍然缺乏正确使用apply系列的经验,那么我可以对apply语句做什么比for循环更好(即变得更快)?
示例数据:
vc<-as.character(c("120,129,129,114","103,67,67,67,67,10,10,10,12","2,1,1,1,2,4,3,1,1,1,3,2,1,1","1,3,1,1,1,1,1,4",NA,"5","1,1,99","2,2,2,16,11,11,11,11,11,29,29,26,26,26,26,26,26,26,26,26,26,31,24,29,29,29,29,40,24,23,3,3,3,6,6,4,5,4,4,3,3,4,4,6,8,8,6,6,6,5,3,3,4,4,5,5,4,4,4,4,6,11,10,11,10,14,2,2,22,22,22,22,24,24,24,23,24,24,24,23,24,23,23,23,24,25,27,27,24,24,26,24,25,25,24,25,26,29,31,32,32,32,32,33,32,35,35,35,52,44,37,26","20,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,19,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,19,19,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,1,1,1,12,10","67,63,73,70,75,135,94,94,96,94,95,96,96,97,94,94,94,94,24,24,24,24,24,24,24,24,24,24,24,1,1,1"))
目标是填充数字矩阵m.res,其中每行包含vc中每个元素的top3值。这是for循环:
fx.test1
function(vc)
{
m.res<-matrix(ncol=3, nrow=length(vc))
for (j in 1:length(vc))
{vn<-as.numeric(unlist(strsplit(vc[j], split=",")))
vn[is.na(vn)]<-0; vn2<-rev(sort(vn))
m.res[j,]<-vn2[1:3]
}
}
以下是我的“应用解决方案”。它为什么慢?我怎样才能让它更快?谢谢!
fx.test2
function(vc)
{
m.res<-matrix(ncol=3, nrow=length(vc))
vc[is.na(vc)]<-"0"
ls.vc<-sapply(vc, function(x) tail(sort(as.numeric(unlist(strsplit(x, split=",")))),3), simplify=TRUE)
#names(ls.vc)<-seq(1:length(vc))
ls.vc2<-lapply(ls.vc, function(x) c(as.numeric(x), rep(0, times = 3 - length(x))))
m.res<-as.matrix(t(as.data.frame(ls.vc)))
return(m.res)
}
system.time(m.res<-fx.test1(vc))
# user system elapsed
# 0.001 0.000 0.001
system.time(m.res<-fx.test2(vc))
# user system elapsed
# 0.003 0.000 0.003
更新:我遵循了@John的建议并生成了两个修剪过的&amp;真正等效的功能。实际上,我能够稍微提高一个lapply函数,但它仍然比for循环更低。如果您对如何针对速度优化这些功能有任何想法,请告诉我。谢谢大家。
fx.test3<-function(vc)
{
L<-strsplit(vc,split=",")
m.res<-matrix(ncol=3, nrow=length(vc))
for (j in 1:length(vc))
{
m.res[j,]<-sort(c(as.numeric(L[[j]]),rep(0,3)), decreasing=TRUE)[1:3]
}
return(m.res)
}
fx.test4<-function(vc)
{
L<-strsplit(vc, split=",")
D<-t(as.data.frame(lapply(L, function(X) {sort(c(as.numeric(X),rep(0,3)),decreasing=TRUE)[1:3]})))
row.names(D)<-NULL
m.res<-as.matrix(D)
return(m.res)
}
system.time(fx.test3(vc))
# user system elapsed
# 0.001 0.000 0.001
system.time(fx.test4(vc))
# user system elapsed
# 0.002 0.000 0.002
答案 0 :(得分:2)
UPDATE2&amp;可能的答案:
我现在将fx.test4简化为如下,现在它的速度与for循环相当。因此,正如@John指出的那样,额外的转换步骤使得lapply解决方案变得更慢。另外,正如@Ari B. Friedman和@ SimonO101所讨论的那样,*应用HAD更快的假设可能是错误的!谢谢大家!
fx.test5<-function(vc)
{
L<-strsplit(vc, split=",")
m.res<-t(sapply(seq_along(L), function(X){sort(c(as.numeric(L[[X]]),rep(0,3)),decreasing=TRUE)[1:3]}))
return(m.res)
}
fx.test5(vc)
[,1] [,2] [,3]
[1,] 129 129 120
[2,] 103 67 67
[3,] 4 3 3
[4,] 4 3 1
[5,] 0 0 0
[6,] 5 0 0
[7,] 99 1 1
[8,] 52 44 40
[9,] 20 19 19
[10,] 135 97 96
system.time(fx.test5(vc))
user system elapsed
0.001 0.000 0.001
UPDATE3:事实上,在一个较长的例子中,* apply函数更快(通过头发)。
system.time(fx.test3(vc2))
# user system elapsed
# 3.596 0.006 3.601
system.time(fx.test5(vc2))
# user system elapsed
# 3.355 0.006 3.359
答案 1 :(得分:1)
您可以使用splitstackshape包中的concat.split
函数解决您的问题:
library(splitstackshape)
kk<-data.frame(vc)
nn<-concat.split(kk,split.col="vc",sep=",")
head(nn[1:10,1:4])
vc vc_1 vc_2 vc_3
1 120,129,129,114 120 129 129
2 103,67,67,67,67,10,10,10,12 103 67 67
3 2,1,1,1,2,4,3,1,1,1,3,2,1,1 2 1 1
4 1,3,1,1,1,1,1,4 1 3 1
5 <NA> NA NA NA
6 5 5 NA NA
您可以操作nn数据帧以获取具有最大值的列。
答案 2 :(得分:1)
你在循环中做了很多事情,apply
或for
,这不应该。 apply
的主要特征并不是它比for
更快,而是它鼓励表达式允许你尽可能地保持向量化(即尽可能少地循环)。 R特别慢的是解释一个函数调用,每次通过循环它都需要解释它遇到的每个函数调用。有时循环是不可避免的,但它们应尽可能小。
您的strsplit
可以在第一个食物之外使用。那样你就叫它一次。那么您在unlist
之前也不需要as.numeric
。您还可以使用sort
decreasing = FALSE
而不是另外调用tail
(尽管可能与[1:3]
选择器一样快)。所有这些都可以在循环中反复调用循环中的函数解释。
您不必预先分配矩阵,因为您将同时生成所有值并将其整形为矩阵。
看看是否遵循该建议可以加快速度。