如何按组对变量求和
Posted
技术标签:
【中文标题】如何按组对变量求和【英文标题】:How to sum a variable by group 【发布时间】:2021-04-19 07:46:04 【问题描述】:我有一个包含两列的数据框。第一列包含“第一”、“第二”、“第三”等类别,第二列包含代表我从“类别”中看到特定组的次数的数字。
例如:
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 中做到这一点?
【问题讨论】:
base R 中最快的方法是rowsum
。
【参考方案1】:
如果x
是包含您的数据的数据框,那么以下内容将满足您的需求:
require(reshape)
recast(x, Category ~ ., fun.aggregate=sum)
【讨论】:
【参考方案2】:library(plyr)
ddply(tbl, .(Category), summarise, sum = sum(Frequency))
【讨论】:
【参考方案3】:只是添加第三个选项:
require(doBy)
summaryBy(Frequency~Category, data=yourdataframe, FUN=sum)
编辑:这是一个非常古老的答案。现在我建议使用dplyr
中的group_by
和summarise
,就像@docendo 的答案一样。
【讨论】:
【参考方案4】:使用aggregate
:
aggregate(x$Frequency, by=list(Category=x$Category), FUN=sum)
Category x
1 First 30
2 Second 5
3 Third 34
在上面的示例中,可以在list
中指定多个维度。可以通过cbind
合并多个相同数据类型的聚合指标:
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))
【讨论】:
@AndrewMcKinlay,R 使用波浪号来定义符号公式,用于统计和其他功能。它可以解释为“按类别划分的模型频率”或“取决于类别的频率”。并非所有语言都使用特殊运算符来定义符号函数,就像在 R 中所做的那样。也许通过波浪号运算符的“自然语言解释”,它变得更有意义(甚至更直观)。我个人发现这种符号公式表示比一些更冗长的替代方案更好。 作为 R 新手(并提出与 OP 相同的问题),我会从每个替代方案背后的语法的更多细节中受益。例如,如果我有一个更大的源表,并且想只选择两个维度加上汇总指标,我可以调整这些方法中的任何一个吗?很难说。 有没有维护一个 ID 列?假设分类是有序的,ID列是1:nrow(df)
,聚合后是否可以保留每个分类的起始位置?因此,在与聚合折叠后,ID 列最终会变成 1、3、4、7。就我而言,我喜欢aggregate
,因为它会自动处理许多列。【参考方案5】:
你也可以使用by()函数:
x2 <- by(x$Frequency, x$Category, sum)
do.call(rbind,as.list(x2))
其他包(plyr、reshape)有返回data.frame的好处,但值得熟悉by(),因为它是一个基本函数。
【讨论】:
【参考方案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
【讨论】:
+1 但是 0.296 对 0.059 并不是特别令人印象深刻。数据大小需要远大于 300k 行,并且超过 3 个组,data.table 才能发光。例如,我们很快就会尝试支持超过 20 亿行,因为一些 data.table 用户拥有 250GB 的 RAM,而 GNU R 现在支持长度 > 2^31。 是的。事实证明我没有那么多内存,只是想提供一些 data.table 卓越性能的证据。我敢肯定,随着数据的增多,差异会更大。 我有 7 百万次观察 dplyr 花了 0.3 秒,而 aggregate() 花了 22 秒来完成操作。我打算在这个主题上发布它,你打败了我! 写这个data[, sum(Frequency), by = Category]
的方法更短。您可以使用 .N
替换 sum()
函数。 data[, .N, by = Category]
。这是一个有用的备忘单:s3.amazonaws.com/assets.datacamp.com/img/blog/…
仅当频率列中的所有值都等于 1 时,使用 .N 才等效于 sum(Frequency),因为 .N 计算每个聚合集 (.SD) 中的行数。这不是这里的情况。【参考方案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))
这里有更多示例,说明如何使用内置数据集mtcars
的 dplyr 函数按组汇总数据:
# 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)))
有关更多信息,包括%>%
运算符,请参阅introduction to dplyr。
【讨论】:
与其他答案中提供的 data.table 和聚合替代方案相比,它有多快? @asieira,哪个最快以及差异有多大(或者差异是否明显)将始终取决于您的数据大小。通常,对于大型数据集,例如一些 GB,data.table 很可能是最快的。在较小的数据大小上,data.table 和 dplyr 通常很接近,这也取决于组的数量。然而,data、table 和 dplyr 都将比基本函数快很多(对于某些操作来说可能快 100-1000 倍)。另见here 第二个例子中的“乐趣”指的是什么? @lauren.marietta 您可以在summarise_all
的funs()
参数及其相关函数(summarise_at
、summarise_if
)中指定要应用为摘要的函数
如果列名有空格。它可能不起作用。使用反勾号会有所帮助。参考。 ***.com/questions/22842232/…【参考方案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
【讨论】:
【参考方案9】:虽然我最近在大多数这些类型的操作中转换为 dplyr
,但对于某些事情,sqldf
包仍然非常好(恕我直言,更具可读性)。
以下是如何使用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
【讨论】:
【参考方案10】:使用cast
代替recast
(注意'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
【讨论】:
【参考方案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
就是其中之一。
【讨论】:
【参考方案12】:当您需要在不同的列上应用不同的聚合函数(并且您必须/想要坚持使用 base R)时,我发现 ave
非常有用(而且高效):
例如
鉴于此输入:
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
我们想按Categ1
和Categ2
分组,并计算Samples
和Freq
的平均值。
这是使用ave
的可能解决方案:
# 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
【讨论】:
【参考方案13】:另一种在矩阵或数据框中按组返回和的解决方案又短又快:
rowsum(x$Frequency, x$Category)
【讨论】:
很好,而且确实很快。【参考方案14】:由于dplyr 1.0.0
,可以使用across()
函数:
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
以及使用选择助手选择变量:
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)
【讨论】:
【参考方案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)
【讨论】:
以上是关于如何按组对变量求和的主要内容,如果未能解决你的问题,请参考以下文章