2014-09-24 78 views
3

我有一个我想摆脱的循环,我只是不太明白。说我有一个数据帧:在lapply中删除循环

tmp = data.frame(Gender = rep(c("Male", "Female"), each = 6), 
       Ethnicity = rep(c("White", "Asian", "Other"), 4), 
       Score = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)) 

然后我要计算的均值在两个性别和种族列每个级别,这将使:

$Female 
[1] 9.5 

$Male 
[1] 3.5 

$Asian 
[1] 6.5 

$Other 
[1] 7.5 

$White 
[1] 5.5 

这是很容易做到,但我不想使用循环 - 我正在追求速度。所以我目前有以下几种:

for(i in c("Gender", "Ethnicity")) 
    print(lapply(split(tmp$Score, tmp[, i]), function(x) mean(x))) 

显然,这使用了一个循环,并且是我卡住的地方。

可能有一个功能已经做了这种事情,我不知道。我看过聚合,但我不认为这就是我想要的。

+6

我基于您发布的代码与当前发布的三个答案进行了基准测试,并且您在此帖子中的代码实际上是小型和大型测试用例中最快的代码。 arvi1000的解决方案具有相似的速度,其他两种解决方案的速度要慢2-3倍。这真的强调,用'sapply'或类似的词替换'for'不一定会提高效率。我鼓励你使用'microbenchmark'包来调查你的用例的所有方法的效率。 – josliber 2014-09-24 15:24:42

+0

如果您可以不使用print(),您将拥有更快的代码。此外,2个独立的乐团与性别和种族硬编码将避免循环和额外的*复杂性。 – ARobertson 2014-09-24 16:31:10

+0

Fyi,我用'data.table'运行了一个microbenchmark。与josilber提到的方法相比,不会提高这个小数据集的速度。 – Vlo 2014-09-24 17:36:26

回答

2

你可以ne st应用函数。

sapply(c("Gender", "Ethnicity"), 
     function(i) { 
     print(lapply(split(tmp$Score, tmp[, i]), function(x) mean(x))) 
     }) 
+0

完美的谢谢。我有类似的东西,但它不工作。当你看到它时很明显。 – nathaneastwood 2014-09-24 15:06:29

2

使用dplyr

library(dplyr) 
library(tidyr) 
tmp[,1:2] <- lapply(tmp[,1:2], as.character) 
tmp %>% 
    gather(Var1, Var2, Gender:Ethnicity) %>% 
    unite(Var, Var1, Var2) %>% 
    group_by(Var) %>% 
    summarise(Score=mean(Score)) 

    #    Var Score 
    #1 Ethnicity_Asian 6.5 
    #2 Ethnicity_Other 7.5 
    #3 Ethnicity_White 5.5 
    #4 Gender_Female 9.5 
    #5  Gender_Male 3.5 
3

可以sapply()超过tmpnames,除了Score,然后用by()(或aggregate()):

> sapply(setdiff(names(tmp),"Score"),function(xx)by(tmp$Score,tmp[,xx],mean)) 
$Gender 
tmp[, xx]: Female 
[1] 9.5 
------------------------------------------------------------ 
tmp[, xx]: Male 
[1] 3.5 

$Ethnicity 
tmp[, xx]: Asian 
[1] 6.5 
------------------------------------------------------------ 
tmp[, xx]: Other 
[1] 7.5 
------------------------------------------------------------ 
tmp[, xx]: White 
[1] 5.5 

然而,这在内部使用一个循环,所以它不会加速很多...

2

您可以使用代码:

c(tapply(tmp$Score,tmp$Gender,mean),tapply(tmp$Score,tmp$Ethnicity,mean)) 
+0

我原来的代码是在一个循环内使用tapply,所以很高兴看到这可以扩展。谢谢 – nathaneastwood 2014-09-24 15:07:40

1

尝试reshape2包。

require(reshape2) 

#demo 
melted<-melt(tmp) 
casted.gender<-dcast(melted,Gender~variable,mean) #for mean of each gender 
casted.eth<-dcast(melted,Ethnicity~variable,mean) #for mean of each ethnicity 

#now, combining to do for all variables at once 
variables<-colnames(tmp)[-length(colnames(tmp))] 

casting<-function(var.name){ 
    return(dcast(melted,melted[,var.name]~melted$variable,mean)) 
} 

lapply(variables, FUN=casting) 

输出:

[[1]] 
    melted[, var.name] Score 
1    Female 9.5 
2    Male 3.5 

[[2]] 
    melted[, var.name] Score 
1    Asian 6.5 
2    Other 7.5 
3    White 5.5 
+0

这真的是一个非常好的解决方案,谢谢。我真的需要开始使用reshape2更多... – nathaneastwood 2014-09-24 15:26:34

0

你或许应该重新考虑你正在生成的输出。包含所有种族和性别变量的列表可能不是绘制,分析或呈现数据的最佳方式。你可能是最好关闭打破和使用或许tapply

tapply(tmp$Score, tmp$Gender, mean) 
tapply(tmp$Score, tmp$Ethnicity, mean) 

aggregate

aggregate(Score ~ Gender, tmp, mean) 
aggregate(Score ~ Ethnicity, tmp, mean) 

编写的代码,而不是一个假两行,然后,也许你会想在你的互动,看起来更虽然你建议聚合不会做你真正想要的。

with(tmp, tapply(Score, list(Gender, Ethnicity), mean)) 
aggregate(Score ~ Gender + Ethnicity, tmp, mean) 

不仅这些导致你的这些变量所提出的基本思想更好的分离和介绍,但你的R命令更有表现力和反射中的首位单独编码这些变量数据的意图。

如果你真正的任务是去一些变量,其中任何一个都可以放入一个循环,但我建议你仍然希望输出不是作为一个单独的列表,而是作为向量或data.frames的列表。