使用R的apply函数之一简化代码

3

我找不到一个令人满意的教程来解释如何使用所有apply函数的可能性。虽然我还是个新手,但这常常很方便,并且可以显著简化我的代码。下面是我的例子……

我有一个看起来像这样的数据框:

> head(p01)
   time key dwell
1   8.13   z  0.00
3   8.13   x  1.25
5   9.38   l  0.87
7  10.25   x  0.15
9  10.40   l  1.13
11 11.53   x  0.45

将其导入R:

p01 <- structure(list(time = c(8.13, 8.13, 9.38, 10.25, 10.4, 11.53), 
key = c("z", "x", "l", "x", "l", "x"), dwell = c(0, 1.25, 
0.869, 0.15, 1.13, 0.45)), .Names = c("time", "key", "dwell"), row.names = c(1L, 3L, 5L, 7L, 9L, 11L), class = "data.frame")

现在我想要统计中每个字母的出现次数,并将它们打印到中,结果应该像这样:
    time key dwell occurences
1   8.13   z  0.00          1
3   8.13   x  1.25          3
5   9.38   l  0.87          2
7  10.25   x  0.15          3
9  10.40   l  1.13          2
11 11.53   x  0.45          3

我现在的做法是:
p01[p01$key == "l", "occurences"] <- table(p01$key)["l"]
p01[p01$key == "x", "occurences"] <- table(p01$key)["x"]
p01[p01$key == "z", "occurences"] <- table(p01$key)["z"]

...这当然不是最好的解决方案。特别是因为真实数据在p01$key中包含更多可能性(16个不同字母之一)。

除此之外,我想要计算每个字母的总dwell,所以现在我正在做的是:

p01[p01$key == "l", "total_dwell"] <- tapply(p01$dwell, p01$key, sum)["l"]
p01[p01$key == "x", "total_dwell"] <- tapply(p01$dwell, p01$key, sum)["x"]
p01[p01$key == "z", "total_dwell"] <- tapply(p01$dwell, p01$key, sum)["z"]

为了获得以下内容:
    time key dwell total_dwell
1   8.13   z  0.00        0.00
3   8.13   x  1.25        1.85
5   9.38   l  0.87        2.00
7  10.25   x  0.15        1.85
9  10.40   l  1.13        2.00
11 11.53   x  0.45        1.85

我已经通过谷歌以及查阅了几本书籍6个小时了,非常希望能够得到一个优雅的解决方案和/或一个综合教程的链接。 我的解决方案显然是有效的,但这不是我第一次像这样绕过问题,而且我的脚本文件开始看起来很荒谬!


3
我相信一定会有人为你提供答案,但是这篇文章是对这种任务相当全面的处理。唯一遗漏的可能是data.table包。 - joran
1
我试图描述如何将循环转换为函数的一般方法:https://github.com/hadley/devtools/wiki/Functionals - hadley
4个回答

10

如果您的数据集很大,请尝试使用data.table。

library(data.table)
DT <- data.table(p01)
DT[,occurences:=.N,by=key]
DT[,total_dwell:=sum(dwell),by=key]

    time key dwell occurences total_dwell
1:  8.13   z 0.000          1       0.000
2:  8.13   x 1.250          3       1.850
3:  9.38   l 0.869          2       1.999
4: 10.25   x 0.150          3       1.850
5: 10.40   l 1.130          2       1.999
6: 11.53   x 0.450          3       1.850
可以将两行引用赋值合并为以下形式:
DT[, `:=`(occurences = .N, total_dwell = sum(dwell)), by=key]

当然,对于小数据集,您也可以使用 data.table :). 但是,对我来说,plyr 语法似乎更容易学习(请注意,我目前主要使用 plyr 而不是 data.table)。 - Paul Hiemstra
5
实际上,当你熟悉了它,data.table 的语法对于这种操作会更容易。 - Roland
易读性是个人口味问题,但data.table看起来像一个很棒的包。 - Paul Hiemstra
你可以同时使用引号中的 :=,并且应该使用 .N 代替 length(time) - eddi
data.table的缺点是它与R中大多数其他类型的对象完全不同,因此您必须学习两种思考方式:常规的R方式和data.table的方式。优势在于这使得data.table非常快速,但劣势是认知负荷增加。 - hadley

6
我会使用plyr
res = ddply(p01, .(key), transform, 
                           occurrences = length(key), 
                           total_dwell = sum(dwell))
res
   time key dwell occurrences total_dwell
1  9.38   l 0.869           2       1.999
2 10.40   l 1.130           2       1.999
3  8.13   x 1.250           3       1.850
4 10.25   x 0.150           3       1.850
5 11.53   x 0.450           3       1.850
6  8.13   z 0.000           1       0.000

请注意,进行此操作后,表格将按照 key 进行字母顺序排序。您可以使用 ordertime 进行重新排序:

res[order(res$time),]
   time key dwell occurrences total_dwell
3  8.13   x 1.250           3       1.850
6  8.13   z 0.000           1       0.000
1  9.38   l 0.869           2       1.999
4 10.25   x 0.150           3       1.850
2 10.40   l 1.130           2       1.999
5 11.53   x 0.450           3       1.850

1
+1 我真的很喜欢 plyr 和 friends 中的这些一行代码。我还在学习如何使用它们来代替基本的 R 语言。 - Simon O'Hanlon
好快啊!你打败了我!+1 ;) - Jilber Urbina
1
Plyr确实很好用,但如果数据变得很大,它会有点慢。这种情况下,data.table就是答案... - Paul Hiemstra
1
只需添加 total_dwell = sum(dwell) 即可将该列包括在内。 - joran
好的,但正如我所说,这篇文章更多地是关于学习如何解决类似问题而不是一次性解决方案,附上的Wickham论文描述了plyr。 - Kuba Krukar
显示剩余8条评论

3

我认为你不应该在这里使用 apply,而是可以尝试使用 table 来获取频率,然后再使用 match 将频率分配给你的表:

freq <- as.data.frame( table(p01$key) )
    # Var1 Freq
#1    l    2
#2    x    3
#3    z    1

p01$occurences <- freq[ match(p01$key , freq[,1] ) , 2 ]
p01
#   time key dwell occurences
#1   8.13   z 0.000          1
#3   8.13   x 1.250          3
#5   9.38   l 0.869          2
#7  10.25   x 0.150          3
#9  10.40   l 1.130          2
#11 11.53   x 0.450          3

据我所知,这种方法与plyr解决方案相比的唯一优点是保留了数据框的原始排序。然而,我不知道在ddply函数中是否可以指定此功能(可能可以!)。

+1 通过分析后进行排序,订单很容易被固定。 - Paul Hiemstra
(+1) @PaulHiemstra,我认为Simon的意思是你无法从plyr中得到“未排序”的解决方案。但是你可以从这一个里面得到两者。 - Arun

2
您可以使用tapply自然地解决这个问题。 请注意,这将创建一个新的对象p01.summary,而不是将其添加到您的对象p01中。 另一行代码可以解决这个问题。
p01.summary = with(p01, cbind(occurences=table(key),total.dwell=tapply(dwell,key,sum)))

或者

p01.summary = with(p01, do.call(rbind,tapply(dwell,key,function(KEY){
   data.frame(occurence=length(KEY),total.dwell= sum(KEY))
}) ))

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