2014-09-24 74 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的列表。