【问题标题】:Calculate the mean by group按组计算平均值
【发布时间】:2021-12-11 00:06:25
【问题描述】:

我有一个类似这样的大数据框:

df <- data.frame(dive = factor(sample(c("dive1","dive2"), 10, replace=TRUE)),
                 speed = runif(10)
                 )
> df
    dive      speed
1  dive1 0.80668490
2  dive1 0.53349584
3  dive2 0.07571784
4  dive2 0.39518628
5  dive1 0.84557955
6  dive1 0.69121443
7  dive1 0.38124950
8  dive2 0.22536126
9  dive1 0.04704750
10 dive2 0.93561651

我的目标是在另一列等于某个值时获得一列中值的平均值,并对所有值重复此操作。即在上面的示例中,我想为dive 列的每个唯一值返回speed 列的平均值。所以当dive==dive1 时,speed 的平均值是dive 的每个值的平均值。

【问题讨论】:

标签: r dataframe r-faq


【解决方案1】:

在 R 中有很多方法可以做到这一点。具体来说,byaggregatesplitplyrcasttapplydata.tabledplyr 等等第四次。

从广义上讲,这些问题的形式是拆分-应用-组合。 Hadley Wickham 写了一篇beautiful article,它将让您更深入地了解整个问题类别,非常值得一读。他的plyr 包实现了通用数据结构的策略,而dplyr 是针对数据帧调整的更新的实现性能。它们允许解决相同形式但比这更复杂的问题。作为解决数据操作问题的通用工具,它们非常值得学习。

性能是非常大的数据集的一个问题,因此很难击败基于data.table 的解决方案。但是,如果您只处理中等规模或更小的数据集,那么花时间学习 data.table 可能不值得。 dplyr 也可以很快,所以如果你想加快速度,但不太需要 data.table 的可扩展性,这是一个不错的选择。

以下许多其他解决方案不需要任何额外的软件包。其中一些在中大型数据集上甚至相当快。它们的主要缺点是隐喻或灵活性之一。通过比喻,我的意思是它是一种工具,旨在强迫其他东西以“聪明”的方式解决这种特殊类型的问题。我所说的灵活性是指他们缺乏解决范围广泛的类似问题或轻松产生整洁输出的能力。


示例

base函数

tapply:

tapply(df$speed, df$dive, mean)
#     dive1     dive2 
# 0.5419921 0.5103974

aggregate

aggregate接受data.frames,输出data.frames,使用公式接口。

aggregate( speed ~ dive, df, mean )
#    dive     speed
# 1 dive1 0.5790946
# 2 dive2 0.4864489

by

在其最用户友好的形式中,它接受向量并对它们应用函数。但是,它的输出并不是一种非常易于操作的形式。:

res.by <- by(df$speed, df$dive, mean)
res.by
# df$dive: dive1
# [1] 0.5790946
# ---------------------------------------
# df$dive: dive2
# [1] 0.4864489

为了解决这个问题,对于 by 的简单使用,taRifx 库中的 as.data.frame 方法有效:

library(taRifx)
as.data.frame(res.by)
#    IDX1     value
# 1 dive1 0.6736807
# 2 dive2 0.4051447

split

顾名思义,它只执行拆分-应用-组合策略的“拆分”部分。为了使其余的工作,我将编写一个使用sapply 进行应用组合的小函数。 sapply 自动尽可能简化结果。在我们的例子中,这意味着一个向量而不是一个 data.frame,因为我们只有一维结果。

splitmean <- function(df) {
  s <- split( df, df$dive)
  sapply( s, function(x) mean(x$speed) )
}
splitmean(df)
#     dive1     dive2 
# 0.5790946 0.4864489 

外部包

data.table

library(data.table)
setDT(df)[ , .(mean_speed = mean(speed)), by = dive]
#    dive mean_speed
# 1: dive1  0.5419921
# 2: dive2  0.5103974

dplyr

library(dplyr)
group_by(df, dive) %>% summarize(m = mean(speed))

plyrdplyr 的前身)

以下是official pageplyr 的评价:

使用base R 函数(如splitapply 系列函数),但 plyr 让这一切变得更容易 与:

  • 完全一致的名称、参数和输出
  • 通过foreach 包实现方便的并行化
  • data.frames、矩阵和列表的输入和输出
  • 用于跟踪长期运行操作的进度条
  • 内置错误恢复和信息丰富的错误消息
  • 在所有转换中维护的标签

换句话说,如果您学习一种用于拆分-应用-组合操作的工具,它应该是plyr

library(plyr)
res.plyr <- ddply( df, .(dive), function(x) mean(x$speed) )
res.plyr
#    dive        V1
# 1 dive1 0.5790946
# 2 dive2 0.4864489

重塑2

reshape2 库在设计时并未将拆分-应用-组合作为其主要关注点。相反,它使用两部分熔化/铸造策略来执行m a wide variety of data reshaping tasks。但是,由于它允许聚合函数,因此可以用于此问题。它不是我对拆分应用组合操作的首选,但它的重塑功能非常强大,因此您也应该学习这个包。

library(reshape2)
dcast( melt(df), variable ~ dive, mean)
# Using dive as id variables
#   variable     dive1     dive2
# 1    speed 0.5790946 0.4864489

基准

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)

像往常一样,data.table 的开销要多一些,因此对于小型数据集来说大约是平均水平。不过,这些都是微秒,所以差异是微不足道的。任何一种方法在这里都可以正常工作,您应该根据以下条件进行选择:

  • 您已经熟悉或想要熟悉的内容(plyr 的灵活性始终值得学习;data.table 如果您打算分析大型数据集,则值得学习;byaggregatesplit 都是基础 R 函数,因此普遍可用)
  • 它返回什么输出(数字、data.frame 或 data.table - 后者继承自 data.frame)

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)

那么data.tabledplyr 使用data.tables 上的操作显然是要走的路。某些方法(aggregatedcast)开始看起来很慢。

1000 万行,1000 个组

如果您有更多组,则差异会变得更加明显。 1,000 个组和相同的 10^7 行:

df <- data.frame(dive=factor(sample(seq(1000),10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)

# then run the same microbenchmark as above
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)

所以data.table 继续很好地扩展,dplyrdata.table 上运行也很好,dplyrdata.frame 上运行速度慢了一个数量级。 split/sapply 策略在组数上的扩展性似乎很差(这意味着split() 可能很慢,而sapply 很快)。 by 仍然相对高效——在 5 秒时,用户肯定会注意到它,但对于这么大的数据集来说仍然不是不合理的。尽管如此,如果您经常使用这种大小的数据集,data.table 显然是要走的路 - 100% data.table 以获得最佳性能或dplyrdplyr 一起使用data.table 作为可行的替代方案。

【讨论】:

  • 哇...非常感谢,这是一个巨大的帮助。聚合函数完美运行,微基准库看起来非常适合我的绘图。再次感谢!
  • 另外,microbenchmark 包对事物进行基准测试。 ggplot2 实际上正在那里制作情节(这是一个很棒的包)。
  • +10 好的,很好。这更像是 1000 个组。非常感谢您添加。接下来的两周我将休假,所以你可以好好休息一下我的窃听器,听到你会松一口气:-)
  • @Gregor 不错!非常感谢。看起来很漂亮,早该更新dplyr。显示dplyr 有/没有data.table 的双重荣誉。
  • 天哪。多么出色、准确和全面的答案。荣誉。
【解决方案2】:

2015 年 dplyr 更新:

df %>% group_by(dive) %>% summarise(percentage = mean(speed))
Source: local data frame [2 x 2]

   dive percentage
1 dive1  0.4777462
2 dive2  0.6726483

【讨论】:

    【解决方案3】:
    aggregate(speed~dive,data=df,FUN=mean)
       dive     speed
    1 dive1 0.7059729
    2 dive2 0.5473777
    

    【讨论】:

      【解决方案4】:

      添加替代的基础 R 方法,在各种情况下都保持快速。

      rowsummean <- function(df) {
        rowsum(df$speed, df$dive) / tabulate(df$dive)
      }
      

      借鉴@Ari 的基准:

      10 行,2 组

      1000 万行,10 个组

      1000 万行,1000 个组

      【讨论】:

        【解决方案5】:

        我们已经有很多选项可以按组计算,从mosaic 包中再添加一个。

        mosaic::mean(speed~dive, data = df)
        #dive1 dive2 
        #0.579 0.440 
        

        这会返回一个命名的数字向量,如果需要一个数据框,我们可以将它包装在stack

        stack(mosaic::mean(speed~dive, data = df))
        
        #  values   ind
        #1  0.579 dive1
        #2  0.440 dive2
        

        数据

        set.seed(123)
        df <- data.frame(dive=factor(sample(c("dive1","dive2"),10,replace=TRUE)),
                         speed=runif(10))
        

        【讨论】:

          【解决方案6】:

          使用collapse

          library(collapse)
          library(magrittr)
          df %>% 
             fgroup_by(dive) %>%
             fsummarise(speed = fmean(speed))
          #   dive     speed
          #1 dive1 0.5788479
          #2 dive2 0.4401514
          

          数据

          set.seed(123)
          df <- data.frame(dive=factor(sample(c("dive1","dive2"),10,replace=TRUE)),
                       speed=runif(10))
          

          【讨论】:

            【解决方案7】:

            有了新功能across:

            df %>% 
              group_by(dive) %>% 
              summarise(across(speed, mean, na.rm = TRUE))
            

            【讨论】:

              猜你喜欢
              • 2016-01-10
              • 1970-01-01
              • 2012-04-05
              • 1970-01-01
              相关资源
              最近更新 更多