如何按组对变量求和

How to sum a variable by group

提问人:boo-urns 提问时间:11/2/2009 最后编辑:Karolis Koncevičiusboo-urns 更新时间:8/24/2023 访问量:960412

问:

我有一个包含两列的数据框。第一列包含“第一”、“第二”、“第三”等类别,第二列包含数字,表示我从“类别”中看到特定组的次数。

例如:

Category     Frequency
First        10
First        15
First        5
Second       2
Third        14
Third        20
Second       3

我想按类别对数据进行排序,并将所有频率相加:

Category     Frequency
First        30
Second       5
Third        34

我将如何在 R 中做到这一点?

数据帧 聚合 R-FAQ

评论

6赞 Michael M 1/5/2019
以 R 为基数的最快方法是 。rowsum

答:

30赞 Rob Hyndman 11/2/2009 #1

如果是包含数据的数据帧,则以下内容将执行所需的操作:x

require(reshape)
recast(x, Category ~ ., fun.aggregate=sum)
35赞 learnr 11/2/2009 #2
library(plyr)
ddply(tbl, .(Category), summarise, sum = sum(Frequency))
24赞 dalloliogm 11/2/2009 #3

只需添加第三个选项:

require(doBy)
summaryBy(Frequency~Category, data=yourdataframe, FUN=sum)

编辑:这是一个非常古老的答案。现在我建议使用 和 from ,就像@docendo答案一样。group_bysummarisedplyr

511赞 rcs 11/2/2009 #4

用:aggregate

aggregate(x$Frequency, by=list(Category=x$Category), FUN=sum)
  Category  x
1    First 30
2   Second  5
3    Third 34

在上面的示例中,可以在 .可以通过以下方式合并相同数据类型的多个聚合指标:listcbind

aggregate(cbind(x$Frequency, x$Metric2, x$Metric3) ...

(嵌入@thelatemail注释),也有一个公式接口aggregate

aggregate(Frequency ~ Category, x, sum)

或者,如果要聚合多个列,可以使用表示法(也适用于一列).

aggregate(. ~ Category, x, sum)

或:tapply

tapply(x$Frequency, x$Category, FUN=sum)
 First Second  Third 
    30      5     34 

使用此数据:

x <- data.frame(Category=factor(c("First", "First", "First", "Second",
                                      "Third", "Third", "Second")), 
                    Frequency=c(10,15,5,2,14,20,3))

评论

4赞 r2evans 12/19/2016
@AndrewMcKinlay,R 使用波浪号来定义符号公式,用于统计和其他函数。它可以解释为“按类别划分的模型频率”或“取决于类别的频率”。并非所有语言都使用特殊运算符来定义符号函数,就像在 R 中所做的那样。也许有了波浪号运算符的“自然语言解释”,它就变得更有意义(甚至更直观)。我个人认为这种符号公式表示比一些更冗长的替代方案更好。
2赞 Dodecaphone 10/28/2018
作为 R 的新手(并且提出与 OP 相同的问题),我将受益于每个替代方案背后的语法的更多细节。例如,如果我有一个较大的源表,并且只想子选择两个维度加上总和指标,我可以采用这些方法中的任何一种吗?很难说。
0赞 QAsena 6/25/2020
有没有维护 ID 列?假设分类是有序的,ID列是,聚合后可以保持每个分类的起始位置吗?因此,ID 列在与聚合折叠后最终将变为 1、3、4、7。就我而言,我喜欢,因为它可以自动处理许多列。1:nrow(df)aggregate
49赞 Shane 11/3/2009 #5

您还可以使用 by() 函数:

x2 <- by(x$Frequency, x$Category, sum)
do.call(rbind,as.list(x2))

其他包(plyr、reshape)具有返回 data.frame 的好处,但值得熟悉 by(),因为它是一个基本函数。

91赞 asieira 9/9/2013 #6

rcs 提供的答案有效且简单。但是,如果您正在处理更大的数据集并需要性能提升,则有更快的替代方法:

library(data.table)
data = data.table(Category=c("First","First","First","Second","Third", "Third", "Second"), 
                  Frequency=c(10,15,5,2,14,20,3))
data[, sum(Frequency), by = Category]
#    Category V1
# 1:    First 30
# 2:   Second  5
# 3:    Third 34
system.time(data[, sum(Frequency), by = Category] )
# user    system   elapsed 
# 0.008     0.001     0.009 

让我们使用 data.frame 和上面的相同内容进行比较:

data = data.frame(Category=c("First","First","First","Second","Third", "Third", "Second"),
                  Frequency=c(10,15,5,2,14,20,3))
system.time(aggregate(data$Frequency, by=list(Category=data$Category), FUN=sum))
# user    system   elapsed 
# 0.008     0.000     0.015 

如果你想保留该列,这是语法:

data[,list(Frequency=sum(Frequency)),by=Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34

对于较大的数据集,差异将变得更加明显,如下面的代码所示:

data = data.table(Category=rep(c("First", "Second", "Third"), 100000),
                  Frequency=rnorm(100000))
system.time( data[,sum(Frequency),by=Category] )
# user    system   elapsed 
# 0.055     0.004     0.059 
data = data.frame(Category=rep(c("First", "Second", "Third"), 100000), 
                  Frequency=rnorm(100000))
system.time( aggregate(data$Frequency, by=list(Category=data$Category), FUN=sum) )
# user    system   elapsed 
# 0.287     0.010     0.296 

对于多个聚合,您可以组合 和 ,如下所示lapply.SD

data[, lapply(.SD, sum), by = Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34

评论

16赞 Matt Dowle 9/9/2013
+1 但 0.296 对 0.059 并不是特别令人印象深刻。数据大小需要远大于 300k 行,并且具有 3 个以上的组,data.table 才能大放异彩。例如,我们将很快尝试支持超过 20 亿行,因为一些 data.table 用户拥有 250GB 的 RAM,而 GNU R 现在支持长度> 2^31。
3赞 asieira 10/24/2013
真。事实证明,我没有那么多的RAM,只是想提供一些data.table卓越性能的证据。我敢肯定,随着数据的增加,差异会更大。
1赞 zazu 11/15/2015
我有 7 mil 的观察结果,dplyr 花了 3 秒,aggregate() 花了 22 秒才能完成操作。我本来打算在这个话题上发布它,你打败了我!
4赞 four-eyes 2/22/2017
还有更短的方法可以写这个.您可以使用哪个来替换该函数。.这是一个有用的备忘单:s3.amazonaws.com/assets.datacamp.com/img/blog/...data[, sum(Frequency), by = Category].Nsum()data[, .N, by = Category]
6赞 asieira 3/1/2017
用。仅当 Frequency 列中的所有值都等于 1 时,N 才等同于 sum(Frequency),因为 .N 计算每个聚合集 (.标清)。但这里的情况并非如此。
413赞 talat 12/3/2014 #7

您也可以将 dplyr 包用于此目的:

library(dplyr)
x %>% 
  group_by(Category) %>% 
  summarise(Frequency = sum(Frequency))

#Source: local data frame [3 x 2]
#
#  Category Frequency
#1    First        30
#2   Second         5
#3    Third        34

或者,对于多个摘要列(也适用于一列):

x %>% 
  group_by(Category) %>% 
  summarise(across(everything(), sum))

以下是有关如何使用内置数据集的 dplyr 函数按组汇总数据的更多示例:mtcars

# several summary columns with arbitrary names
mtcars %>% 
  group_by(cyl, gear) %>%                            # multiple group columns
  summarise(max_hp = max(hp), mean_mpg = mean(mpg))  # multiple summary columns

# summarise all columns except grouping columns using "sum" 
mtcars %>% 
  group_by(cyl) %>% 
  summarise(across(everything(), sum))

# summarise all columns except grouping columns using "sum" and "mean"
mtcars %>% 
  group_by(cyl) %>% 
  summarise(across(everything(), list(mean = mean, sum = sum)))

# multiple grouping columns
mtcars %>% 
  group_by(cyl, gear) %>% 
  summarise(across(everything(), list(mean = mean, sum = sum)))

# summarise specific variables, not all
mtcars %>% 
  group_by(cyl, gear) %>% 
  summarise(across(c(qsec, mpg, wt), list(mean = mean, sum = sum)))

# summarise specific variables (numeric columns except grouping columns)
mtcars %>% 
  group_by(gear) %>% 
  summarise(across(where(is.numeric), list(mean = mean, sum = sum)))

有关详细信息(包括运算符),请参阅 dplyr 简介%>%

评论

1赞 asieira 1/23/2015
与其他答案中提供的 data.table 和聚合替代方案相比,它的速度有多快?
11赞 talat 1/23/2015
@asieira,哪个最快,差异有多大(或者差异是否明显)将始终取决于您的数据大小。通常,对于大型数据集(例如某些 GB),data.table 很可能是最快的。在较小的数据量上,data.table 和 dplyr 通常很接近,这也取决于组的数量。但是,data、table 和 dplyr 都比基本函数快得多(对于某些操作来说,速度可能快 100-1000 倍)。另请参阅此处
1赞 lauren.marietta 10/9/2019
第二个例子中的“乐趣”指的是什么?
1赞 talat 10/9/2019
@lauren.marietta,您可以在 的参数及其相关函数 (,funs()summarise_allsummarise_atsummarise_if)
0赞 user131476 11/2/2020
如果列名包含空格,则为列名。它可能不起作用。使用反勾会有所帮助。参考文献 stackoverflow.com/questions/22842232/...
44赞 David Arenburg 9/10/2015 #8

几年后,只是为了添加另一个由于某种原因不存在的简单基础 R 解决方案——xtabs

xtabs(Frequency ~ Category, df)
# Category
# First Second  Third 
#    30      5     34 

或者如果你想要一个背部data.frame

as.data.frame(xtabs(Frequency ~ Category, df))
#   Category Freq
# 1    First   30
# 2   Second    5
# 3    Third   34
28赞 joemienko 5/17/2016 #9

虽然我最近成为大多数此类操作的转换者,但对于某些事情来说,该软件包仍然非常好(恕我直言更具可读性)。dplyrsqldf

下面是一个示例,说明如何用以下方式回答这个问题sqldf

x <- data.frame(Category=factor(c("First", "First", "First", "Second",
                                  "Third", "Third", "Second")), 
                Frequency=c(10,15,5,2,14,20,3))

sqldf("select 
          Category
          ,sum(Frequency) as Frequency 
       from x 
       group by 
          Category")

##   Category Frequency
## 1    First        30
## 2   Second         5
## 3    Third        34
8赞 Grant Shannon 2/25/2018 #10

使用代替(注意现在是castrecast'Frequency''value')

df  <- data.frame(Category = c("First","First","First","Second","Third","Third","Second")
                  , value = c(10,15,5,2,14,20,3))

install.packages("reshape")

result<-cast(df, Category ~ . ,fun.aggregate=sum)

要获取:

Category (all)
First     30
Second    5
Third     34
9赞 Manos Papadakis 11/18/2018 #11
您可以使用 **package *Rfast*** 中的函数 'group.sum'。
Category <- Rfast::as_integer(Category,result.sort=FALSE) # convert character to numeric. R's as.numeric produce NAs.
result <- Rfast::group.sum(Frequency,Category)
names(result) <- Rfast::Sort(unique(Category)
# 30 5 34
Rfast*** 有许多组函数,“group.sum”就是其中之一。

Rfast 弃用了组函数,并将它们替换为一个名为 .使用参数,您可以选择正确的算法。所以,是.groupmethodgroup.sumgroup(...,method = "sum")

    Category <- as.numeric(Category,result.sort=FALSE) #R has fixed the bug.
    result <- Rfast::group(Frequency,Category, method = "sum")
    names(result) <- Rfast::Sort(unique(Category)
    # 30 5 34
12赞 digEmAll 12/11/2018 #12

当您需要在不同的列上应用不同的聚合函数时,我发现 ave 非常有用(且高效)(并且您必须/想要坚持在基础 R 上):

例如

给定此输入:

DF <-                
data.frame(Categ1=factor(c('A','A','B','B','A','B','A')),
           Categ2=factor(c('X','Y','X','X','X','Y','Y')),
           Samples=c(1,2,4,3,5,6,7),
           Freq=c(10,30,45,55,80,65,50))

> DF
  Categ1 Categ2 Samples Freq
1      A      X       1   10
2      A      Y       2   30
3      B      X       4   45
4      B      X       3   55
5      A      X       5   80
6      B      Y       6   65
7      A      Y       7   50

我们想对 和 进行分组,并计算 的总和均值。
这是一个可能的解决方案,使用:
Categ1Categ2SamplesFreqave

# create a copy of DF (only the grouping columns)
DF2 <- DF[,c('Categ1','Categ2')]

# add sum of Samples by Categ1,Categ2 to DF2 
# (ave repeats the sum of the group for each row in the same group)
DF2$GroupTotSamples <- ave(DF$Samples,DF2,FUN=sum)

# add mean of Freq by Categ1,Categ2 to DF2 
# (ave repeats the mean of the group for each row in the same group)
DF2$GroupAvgFreq <- ave(DF$Freq,DF2,FUN=mean)

# remove the duplicates (keep only one row for each group)
DF2 <- DF2[!duplicated(DF2),]

结果:

> DF2
  Categ1 Categ2 GroupTotSamples GroupAvgFreq
1      A      X               6           45
2      A      Y               9           40
3      B      X               7           50
6      B      Y               6           65
19赞 Karolis Koncevičius 4/28/2020 #13

另一种解决方案,它按矩阵或数据框中的组返回总和,并且简短而快速:

rowsum(x$Frequency, x$Category)

评论

2赞 jay.sf 5/2/2020
很好,而且确实很快。
11赞 tmfmnk 6/14/2020 #14

由于 ,该函数可用于:dplyr 1.0.0across()

df %>%
 group_by(Category) %>%
 summarise(across(Frequency, sum))

  Category Frequency
  <chr>        <int>
1 First           30
2 Second           5
3 Third           34

如果对多个变量感兴趣:

df %>%
 group_by(Category) %>%
 summarise(across(c(Frequency, Frequency2), sum))

  Category Frequency Frequency2
  <chr>        <int>      <int>
1 First           30         55
2 Second           5         29
3 Third           34        190

以及使用 select helpers 选择变量:

df %>%
 group_by(Category) %>%
 summarise(across(starts_with("Freq"), sum))

  Category Frequency Frequency2 Frequency3
  <chr>        <int>      <int>      <dbl>
1 First           30         55        110
2 Second           5         29         58
3 Third           34        190        380

示例数据:

df <- read.table(text = "Category Frequency Frequency2 Frequency3
                 1    First        10         10         20
                 2    First        15         30         60
                 3    First         5         15         30
                 4   Second         2          8         16
                 5    Third        14         70        140
                 6    Third        20        120        240
                 7   Second         3         21         42",
                 header = TRUE,
                 stringsAsFactors = FALSE)
4赞 user12703198 10/22/2020 #15
library(tidyverse)

x <- data.frame(Category= c('First', 'First', 'First', 'Second', 'Third', 'Third', 'Second'), 
           Frequency = c(10, 15, 5, 2, 14, 20, 3))

count(x, Category, wt = Frequency)

4赞 GKi 6/7/2022 #16

按组对变量求和的一个好方法是

rowsum(numericToBeSummedUp, groups)

基地。只有这里,而且速度更快。collapse::fsumRfast::group.sum

关于速度内存消耗

collapse::fsum(numericToBeSummedUp, groups)

在给定的示例中是最好的,在使用分组数据帧时可以加快速度。

GDF <- collapse::fgroup_by(DF, g) #Create a grouped data.frame with group g
#GDF <- collapse::gby(DF, g)      #Alternative

collapse::fsum(GDF)               #Calculate sum per group

这接近于将数据集拆分为每组子数据集的时间。

对不同方法的基准测试表明,对于单个列的求和,比 快 2 倍,比 快 7 倍。紧随其后的是 、 和 。 并且是最慢的。collapse::fsumRfast::group.sumrowsumtapplydata.tablebydplyrxtabsaggregate

聚合两列又是最快的,比 快 3 倍,比 快 5 倍。它们后跟 、 和 。同样,是最慢的。collapse::fsumRfast::group.sumrowsumdata.tabletapplybydplyrxtabsaggregate


基准

set.seed(42)
n <- 1e5
DF <- data.frame(g = as.factor(sample(letters, n, TRUE))
              , x = rnorm(n), y = rnorm(n) )

library(magrittr)

某些方法允许执行可能有助于加快聚合速度的任务。

DT <- data.table::as.data.table(DF)
data.table::setkey(DT, g)

DFG <- collapse::gby(DF, g)
DFG1 <- collapse::gby(DF[c("g", "x")], g)

# Optimized dataset for this aggregation task
# This will also consume time!
DFS <- lapply(split(DF[c("x", "y")], DF["g"]), as.matrix)
DFS1 <- lapply(split(DF["x"], DF["g"]), as.matrix)

总结一列。

bench::mark(check = FALSE
          , "aggregate" = aggregate(DF$x, DF["g"], sum)
          , "tapply" = tapply(DF$x, DF$g, sum)
          , "dplyr" = DF %>% dplyr::group_by(g) %>% dplyr::summarise(sum = sum(x))
          , "data.table" = data.table::as.data.table(DF)[, sum(x), by = g]
          , "data.table2" = DT[, sum(x), by = g]
          , "by" = by(DF$x, DF$g, sum)
          , "xtabs" = xtabs(x ~ g, DF)
          , "rowsum" = rowsum(DF$x, DF$g)
          , "Rfast" = Rfast::group.sum(DF$x, DF$g)
          , "base Split" = lapply(DFS1, colSums)
          , "base Split Rfast" = lapply(DFS1, Rfast::colsums)
          , "collapse"  = collapse::fsum(DF$x, DF$g)
          , "collapse2"  = collapse::fsum(DFG1)
)
#   expression            min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
#   <bch:expr>       <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
# 1 aggregate         20.43ms  21.88ms      45.7   16.07MB    59.4     10    13
# 2 tapply             1.24ms   1.39ms     687.     1.53MB    30.1    228    10
# 3 dplyr              3.28ms   4.81ms     209.     2.42MB    13.1     96     6
# 4 data.table         1.59ms   2.47ms     410.     4.69MB    87.7    145    31
# 5 data.table2        1.52ms   1.93ms     514.     2.38MB    40.5    190    15
# 6 by                 2.15ms   2.31ms     396.     2.29MB    26.7    148    10
# 7 xtabs              7.78ms   8.91ms     111.    10.54MB    50.0     31    14
# 8 rowsum           951.36µs   1.07ms     830.     1.15MB    24.1    378    11
# 9 Rfast            431.06µs 434.53µs    2268.     2.74KB     0     1134     0
#10 base Split       213.42µs 219.66µs    4342.       256B    12.4   2105     6
#11 base Split Rfast  76.88µs  81.48µs   10923.    65.05KB    16.7   5232     8
#12 collapse         121.03µs 122.92µs    7965.       256B     2.01  3961     1
#13 collapse2         85.97µs  88.67µs   10749.       256B     4.03  5328     2

总结两列

bench::mark(check = FALSE
          , "aggregate" = aggregate(DF[c("x", "y")], DF["g"], sum)
          , "tapply" = list2DF(lapply(DF[c("x", "y")], tapply, list(DF$g), sum))
          , "dplyr" = DF %>% dplyr::group_by(g) %>% dplyr::summarise(x = sum(x), y = sum(y))
          , "data.table" = data.table::as.data.table(DF)[,.(sum(x),sum(y)), by = g]
          , "data.table2" = DT[,.(sum(x),sum(y)), by = g]
          , "by" = lapply(DF[c("x", "y")], by, list(DF$g), sum)
          , "xtabs" = xtabs(cbind(x, y) ~ g, DF)
          , "rowsum" = rowsum(DF[c("x", "y")], DF$g)
          , "Rfast" = list2DF(lapply(DF[c("x", "y")], Rfast::group.sum, DF$g))
          , "base Split" = lapply(DFS, colSums)
          , "base Split Rfast" = lapply(DFS, Rfast::colsums)
          , "collapse" = collapse::fsum(DF[c("x", "y")], DF$g)
          , "collapse2" = collapse::fsum(DFG)
            )
#   expression            min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
#   <bch:expr>       <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
# 1 aggregate         25.87ms  26.36ms      37.7   20.89MB   132.       4    14
# 2 tapply             2.65ms   3.23ms     312.     3.06MB    22.5     97     7
# 3 dplyr              4.27ms   6.02ms     164.     3.19MB    13.3     74     6
# 4 data.table         2.33ms   3.19ms     309.     4.72MB    57.0    114    21
# 5 data.table2        2.22ms   2.81ms     355.     2.41MB    19.8    161     9
# 6 by                 4.45ms   5.23ms     190.     4.59MB    22.5     59     7
# 7 xtabs             10.71ms  13.14ms      76.1    19.7MB   145.      11    21
# 8 rowsum             1.02ms   1.07ms     850.     1.15MB    23.8    393    11
# 9 Rfast            841.57µs 846.88µs    1150.     5.48KB     0      575     0
#10 base Split       360.24µs 368.28µs    2652.       256B     8.16  1300     4
#11 base Split Rfast 113.95µs 119.81µs    7540.    65.05KB    10.3   3661     5
#12 collapse         201.31µs 204.83µs    4724.       512B     2.01  2350     1
#13 collapse2        156.95µs 161.79µs    5408.       512B     2.02  2683     1

评论

0赞 Gregor Thomas 6/8/2022
我碰到并重新运行了表现最好的基准测试。大多相同的顺序,是无敌的,排在第二位,紧随其后。在这么大的数据上,实际上胜过基准中的类转换。n1e7rowsumdata.table2dplyrdplyrdata.table
0赞 Henrik 6/8/2022
collapse::fsum速度也很快,至少在具有更多组的较大数据上是这样。set.seed(42); n <- 1e7; DF <- data.frame(g = as.factor(sample(1e4, n, TRUE)), x = rnorm(n), y = rnorm(n)); system.time(group.sum(DF$x, DF$g)); system.time(fsum(DF$x, DF$g))
0赞 Henrik 6/8/2022
对于几个变量: ; .gr = GRP(DF, ~ g)fsum(DF, gr)
0赞 GKi 6/8/2022
感谢您的评论!我已经添加了目前最快的。collapse::fsum
3赞 user2110417 7/27/2022 #17

您可以使用函数来计算频率。rowsum

data("mtcars")
df <- mtcars
df$cyl <- as.factor(df$cyl)

头部外观如下:

               wt    mpg    cyl
              <dbl> <dbl>   <fct>
Mazda RX4     2.620  21.0   6
Mazda RX4 Wag 2.875  21.0   6
Datsun 710    2.320  22.8   4

然后

rowsum(df$mpg, df$cyl) #values , group

4   293.3
6   138.2
8   211.4

评论

3赞 GKi 10/17/2022
两年前,Karolis Koncevičius的回答中是否有新的东西吗?
2赞 Maël 12/2/2022 #18

with 及以上版本,您可以在 中使用 。此快捷方式避免使用并返回 ed 数据帧:dplyr 1.1.0.bysummarisegroup_byungroup

library(dplyr)
x %>%  
  summarise(Frequency = sum(Frequency), .by = Category)