在 lapply 中去除循环

3

我希望能够去掉一个循环,但是我不太确定如何做到。比如说我有一个数据框:

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))

我希望您能为Gender和Ethnicity两列的每个级别分别计算平均值,结果如下所示:

我随后要计算Gender和Ethnicity两列中每个级别的平均值,结果如下:

$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)))

显然,这需要使用循环,而我卡在这里了。
可能已经有一个函数可以完成我不知道的这种操作。我看过aggregate,但我认为那不是我想要的。

6
我已经对你发布的代码进行了基准测试,并且在小型和大型测试案例中,这篇文章中的代码实际上是速度最快的,而其他三篇解答则要慢2-3倍。虽然arvi1000的解决方案速度相似,但这确实突显了for循环替换为sapply或类似方法并不一定会提高效率。我建议您使用microbenchmark包来研究所有方法在您的使用情况下的效率。 - josliber
如果您可以不使用print(),那么您的代码将更快。此外,使用Gender和Ethnicity硬编码的2个单独的lapply将避免循环和额外的*apply复杂性。 - ARobertson
我用 data.table 进行了微基准测试,结果表明在这个小数据集上并没有比 josilber 提到的方法更快。 - Vlo
6个回答

3

您可以对tmpnames(除了Score)使用 sapply(),然后使用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
使用 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

2

你可以嵌套使用apply函数。

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

非常完美,谢谢。我之前也有类似的代码,但是一直无法正常运行。当你看到问题所在时,它就显得非常明显了。 - nathaneastwood

2
您可以使用以下代码:
c(tapply(tmp$Score,tmp$Gender,mean),tapply(tmp$Score,tmp$Ethnicity,mean))

我的原始代码在循环中使用了tapply函数,所以看到这个可以被扩展的方法真是太好了。谢谢。 - nathaneastwood

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

这确实是一个非常好的解决方案,谢谢。我真的需要开始更多地使用reshape2... - nathaneastwood

0

你可能需要重新考虑你正在生成的输出。将所有种族和性别变量放在一起的列表可能不是绘制、分析或展示数据的最佳方式。你最好拆分并编写两行代码,而不是使用那个代码行,也许可以使用tapply

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

或者 聚合

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命令更具表现力和反映了最初单独编码这些变量的数据意图。

如果您的真正任务是处理多个变量中的任何一个,那么任何一个都可以放入循环中,但我建议您仍然希望输出不是作为一个单一列表,而是作为向量或数据框的列表。


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