有许多方法可以在R中完成此操作。 具体来说,使用
by
、
aggregate
、
split
和
plyr
、
cast
、
tapply
、
data.table
、
dplyr
等等。
广义上说,这些问题的形式是拆分-应用-合并。 Hadley Wickham撰写了一篇
精美的文章,将为您提供更深入的了解整个问题类别,并且值得阅读。他的
plyr
包实现了一般数据结构的策略,而
dplyr
是针对数据框架进行性能调优的较新实现。它们允许解决与此类似但更加复杂的问题。学习它们作为解决数据操作问题的通用工具非常值得。
性能是处理大型数据集时的一个问题,对于这种情况,很难超越基于data.table
的解决方案。但如果你只处理中等大小或更小的数据集,学习data.table
可能不值得花费时间和精力。dplyr
也可以快速处理,因此如果您想加速处理速度但又不需要data.table
的可伸缩性,则是一个不错的选择。
下面许多其他解决方案不需要任何额外的包。其中一些在中等到大型数据集上甚至相当快速。它们的主要缺点要么是隐喻,要么是灵活性。所谓隐喻,就是指将设计用于其他事物的工具强制用于以“聪明”的方式解决特定类型的问题。而灵活性则意味着它们缺乏解决更广泛类似问题的能力或轻松生成整洁的输出。
例子
base
函数
tapply
:
tapply(df$speed, df$dive, mean)
aggregate
:
aggregate
接受数据框作为输入,输出数据框,并使用公式界面。
aggregate( speed ~ dive, df, mean )
by
:
在最用户友好的形式中,它接收向量并对它们应用函数。然而,其输出不是非常易于操作的形式。
res.by <- by(df$speed, df$dive, mean)
res.by
为了解决这个问题,在使用
by
时,可以使用
taRifx
库中的
as.data.frame
方法进行简单操作:
library(taRifx)
as.data.frame(res.by)
split
:
顾名思义,它只执行分离操作,而不是整个分离-应用-合并策略。为了使其余部分正常工作,我将编写一个使用 sapply
进行应用-合并的小函数。sapply
会自动尽可能简化结果。在我们的情况下,这意味着一个向量而不是数据框,因为我们只有一个维度的结果。
splitmean <- function(df) {
s <- split( df, df$dive)
sapply( s, function(x) mean(x$speed) )
}
splitmean(df)
外部包
data.table:
library(data.table)
setDT(df)[ , .(mean_speed = mean(speed)), by = dive]
dplyr
:
library(dplyr)
group_by(df, dive) %>% summarize(m = mean(speed))
plyr
(dplyr
的前身)
以下是官方页面对plyr
的介绍:
使用base
R函数(如split
和apply
函数系列)已经可以实现此功能,但plyr
通过以下方式使所有操作都更加轻松:
- 完全一致的名称、参数和输出
- 通过
foreach
包方便地进行并行处理
- 支持从数据框、矩阵和列表输入和输出
- 进度条可用于跟踪长时间运行的操作
- 内置错误恢复和信息丰富的错误消息
- 标签在所有转换中得以保留
换句话说,如果您只想学习一种分割-应用-合并操作工具,那就应该是plyr
。
library(plyr)
res.plyr <- ddply( df, .(dive), function(x) mean(x$speed) )
res.plyr
reshape2:
reshape2
库的主要目的不是拆分-应用-合并。相反,它使用两部分的融合/重塑策略来执行各种数据重塑任务。然而,由于它允许聚合函数,因此可以用于这个问题。对于拆分-应用-合并操作,这可能不是我的首选,但它的重塑功能非常强大,因此您也应该学习这个包。
library(reshape2)
dcast( melt(df), variable ~ dive, mean)
基准测试
10行,2个组
library(microbenchmark)
m1 <- microbenchmark(
by( df$speed, df$dive, mean),
aggregate( speed ~ dive, df, mean ),
splitmean(df),
ddply( df, .(dive), function(x) mean(x$speed) ),
dcast( melt(df), variable ~ dive, mean),
dt[, mean(speed), by = dive],
summarize( group_by(df, dive), m = mean(speed) ),
summarize( group_by(dt, dive), m = mean(speed) )
)
> print(m1, signif = 3)
Unit: microseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 302 325 343.9 342 362 396 100 b
aggregate(speed ~ dive, df, mean) 904 966 1012.1 1020 1060 1130 100 e
splitmean(df) 191 206 249.9 220 232 1670 100 a
ddply(df, .(dive), function(x) mean(x$speed)) 1220 1310 1358.1 1340 1380 2740 100 f
dcast(melt(df), variable ~ dive, mean) 2150 2330 2440.7 2430 2490 4010 100 h
dt[, mean(speed), by = dive] 599 629 667.1 659 704 771 100 c
summarize(group_by(df, dive), m = mean(speed)) 663 710 774.6 744 782 2140 100 d
summarize(group_by(dt, dive), m = mean(speed)) 1860 1960 2051.0 2020 2090 3430 100 g
autoplot(m1)
![benchmark 10 rows](https://istack.dev59.com/LLwNx.webp)
通常情况下,data.table
的开销略微高一些,因此对于小数据集的平均表现也差不多。尽管如此,这些差异微不足道。这里的任何方法都可以很好地处理,你应该根据以下原则进行选择:
- 你已经熟悉或想熟悉哪种方法(
plyr
值得学习,因为它非常灵活;如果你计划分析大型数据集,则值得学习 data.table
;by
、aggregate
和 split
都是基本 R 函数,因此普遍可用)
- 它返回的输出(数值、数据框或数据表——后者继承自数据框)
1000万行,10个组
但是如果我们有一个大数据集呢?让我们尝试将 10^7 行数据分成十组。
df <- data.frame(dive=factor(sample(letters[1:10],10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)
m2 <- microbenchmark(
by( df$speed, df$dive, mean),
aggregate( speed ~ dive, df, mean ),
splitmean(df),
ddply( df, .(dive), function(x) mean(x$speed) ),
dcast( melt(df), variable ~ dive, mean),
dt[,mean(speed),by=dive],
times=2
)
> print(m2, signif = 3)
Unit: milliseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 720 770 799.1 791 816 958 100 d
aggregate(speed ~ dive, df, mean) 10900 11000 11027.0 11000 11100 11300 100 h
splitmean(df) 974 1040 1074.1 1060 1100 1280 100 e
ddply(df, .(dive), function(x) mean(x$speed)) 1050 1080 1110.4 1100 1130 1260 100 f
dcast(melt(df), variable ~ dive, mean) 2360 2450 2492.8 2490 2520 2620 100 g
dt[, mean(speed), by = dive] 119 120 126.2 120 122 212 100 a
summarize(group_by(df, dive), m = mean(speed)) 517 521 531.0 522 532 620 100 c
summarize(group_by(dt, dive), m = mean(speed)) 154 155 174.0 156 189 321 100 b
autoplot(m2)
![benchmark 1e7 rows, 10 groups](https://istack.dev59.com/evAhu.webp)
如果涉及到使用data.table
或dplyr
对data.table
进行操作,这显然是正确的方法。某些方法(如aggregate
和dcast
)开始变得非常缓慢。
1000万行,1000个组
如果有更多的组,则差异将变得更加明显。对于相同的1000万行和1000个组:
df <- data.frame(dive=factor(sample(seq(1000),10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)
print(m3, signif = 3)
Unit: milliseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 776 791 816.2 810 828 925 100 b
aggregate(speed ~ dive, df, mean) 11200 11400 11460.2 11400 11500 12000 100 f
splitmean(df) 5940 6450 7562.4 7470 8370 11200 100 e
ddply(df, .(dive), function(x) mean(x$speed)) 1220 1250 1279.1 1280 1300 1440 100 c
dcast(melt(df), variable ~ dive, mean) 2110 2190 2267.8 2250 2290 2750 100 d
dt[, mean(speed), by = dive] 110 111 113.5 111 113 143 100 a
summarize(group_by(df, dive), m = mean(speed)) 625 630 637.1 633 644 701 100 b
summarize(group_by(dt, dive), m = mean(speed)) 129 130 137.3 131 142 213 100 a
autoplot(m3)
![enter image description here](https://istack.dev59.com/vscbF.webp)
所以,
data.table
仍然很好地扩展,而在
data.table
上操作的
dplyr
也很好用,而在
data.frame
上使用
dplyr
则慢了一个数量级。
split
/
sapply
策略似乎在组数上扩展性较差(这意味着
split()
可能很慢,而
sapply
很快)。
by
仍然相对高效 - 在5秒内,对于这么大的数据集来说,对用户来说肯定是明显的,但仍不算过分。但是,如果你经常处理这样大小的数据集,
data.table
显然是最佳选择 - 100% 使用
data.table
可以获得最佳性能,或者使用
dplyr
并将
data.table
作为可行的替代方案。