为data.frame中的多个变量按组计算平均值和标准差
Posted
技术标签:
【中文标题】为data.frame中的多个变量按组计算平均值和标准差【英文标题】:Compute mean and standard deviation by group for multiple variables in a data.frame 【发布时间】:2013-04-28 08:38:51 【问题描述】:编辑 -- 这个问题最初的标题是>
我只是在学习 R,并试图找到应用它来帮助我生活中的其他人的方法。作为一个测试用例,我正在努力重塑一些数据,但我无法按照我在网上找到的示例进行操作。我开始的内容如下所示:
ID Obs 1 Obs 2 Obs 3
1 43 48 37
1 27 29 22
1 36 32 40
2 33 38 36
2 29 32 27
2 32 31 35
2 25 28 24
3 45 47 42
3 38 40 36
而我最终想要的结果是这样的:
ID Obs 1 mean Obs 1 std dev Obs 2 mean Obs 2 std dev
1 x x x x
2 x x x x
3 x x x x
等等。我不确定的是我是否需要在我的长格式数据中提供其他信息,或者什么。我想数学部分(找到平均值和标准差)将是简单的部分,但我一直无法找到一种似乎可以正确重塑数据以开始该过程的方法。
非常感谢您的帮助。
【问题讨论】:
只是评论:我不认为这就是人们通常所说的从长格式转向宽格式的意思。 很多人发表了评论,但我很惊讶没有人愿意修复这样一个误导性的标题(现在完成了。) 【参考方案1】:这是一个聚合问题,而不是最初建议的问题重塑问题——我们希望将每一列按 ID 聚合为均值和标准差。有许多软件包可以处理此类问题。在 R 的基础上,可以像这样使用aggregate
来完成(假设DF
是输入数据框):
ag <- aggregate(. ~ ID, DF, function(x) c(mean = mean(x), sd = sd(x)))
注 1: 一位评论者指出,ag
是一个数据框,其中一些列是矩阵。虽然最初这可能看起来很奇怪,但实际上它简化了访问。 ag
的列数与输入 DF
的列数相同。它的第一列ag[[1]]
是ID
,余数的第i 列ag[[i+1]]
(或等价的ag[-1][[i]]
)是第i 个输入观察列的统计矩阵。如果希望访问第 i 个观察的第 j 个统计量,则它是 ag[[i+1]][, j]
,也可以写为 ag[-1][[i]][, j]
。
另一方面,假设输入中的每个观察值都有 k
统计列(其中问题中的 k=2)。然后,如果我们将输出展平,那么要访问第 i 个观察列的第 j 个统计量,我们必须使用更复杂的 ag[[k*(i-1)+j+1]]
或等效的 ag[-1][[k*(i-1)+j]]
。
例如,比较第一个表达式与第二个表达式的简单性:
ag[-1][[2]]
## mean sd
## [1,] 36.333 10.2144
## [2,] 32.250 4.1932
## [3,] 43.500 4.9497
ag_flat <- do.call("data.frame", ag) # flatten
ag_flat[-1][, 2 * (2-1) + 1:2]
## Obs_2.mean Obs_2.sd
## 1 36.333 10.2144
## 2 32.250 4.1932
## 3 43.500 4.9497
注2:可重现形式的输入为:
Lines <- "ID Obs_1 Obs_2 Obs_3
1 43 48 37
1 27 29 22
1 36 32 40
2 33 38 36
2 29 32 27
2 32 31 35
2 25 28 24
3 45 47 42
3 38 40 36"
DF <- read.table(text = Lines, header = TRUE)
【讨论】:
也许需要注意:虽然此输出将显示为data.frame
,但如果您查看结构,则每列聚合两列(示例数据有 7 列) ,你会看到它实际上只有四列,聚合列是矩阵。您可以使用 do.call(data.frame, aggregate(. ~ ID, DF, function(x) c(mean = mean(x), sd = sd(x))))
解决此问题。
@Ananda Mahto,好点。我添加了一些对此进行详细说明的评论。【参考方案2】:
有几种不同的方法可以解决这个问题。 reshape2
是一个有用的软件包。
就个人而言,我喜欢使用data.table
下面是一步一步
如果myDF
是你的data.frame
:
library(data.table)
DT <- data.table(myDF)
DT
# this will get you your mean and SD's for each column
DT[, sapply(.SD, function(x) list(mean=mean(x), sd=sd(x)))]
# adding a `by` argument will give you the groupings
DT[, sapply(.SD, function(x) list(mean=mean(x), sd=sd(x))), by=ID]
# If you would like to round the values:
DT[, sapply(.SD, function(x) list(mean=round(mean(x), 3), sd=round(sd(x), 3))), by=ID]
# If we want to add names to the columns
wide <- setnames(DT[, sapply(.SD, function(x) list(mean=round(mean(x), 3), sd=round(sd(x), 3))), by=ID], c("ID", sapply(names(DT)[-1], paste0, c(".men", ".SD"))))
wide
ID Obs.1.men Obs.1.SD Obs.2.men Obs.2.SD Obs.3.men Obs.3.SD
1: 1 35.333 8.021 36.333 10.214 33.0 9.644
2: 2 29.750 3.594 32.250 4.193 30.5 5.916
3: 3 41.500 4.950 43.500 4.950 39.0 4.243
另外,这可能有用也可能没有帮助
> DT[, sapply(.SD, summary), .SDcols=names(DT)[-1]]
Obs.1 Obs.2 Obs.3
Min. 25.00 28.00 22.00
1st Qu. 29.00 31.00 27.00
Median 33.00 32.00 36.00
Mean 34.22 36.11 33.22
3rd Qu. 38.00 40.00 37.00
Max. 45.00 48.00 42.00
【讨论】:
我试过了,得到Error in var(if (is.vector(x) || is.factor(x)) x else as.double(x), na.rm = na.rm) : Calling var(x) on a factor x is defunct. Use something like 'all(duplicated(x)[-1L])' to test for a constant vector.
Traceback 表明问题出在sapply
的调用形式上。
是否可以使用相同的方法按多个因素分组?例如,by=c("ID", "factor2")?【参考方案3】:
这可能是最简单的方法(使用reproducible example):
library(plyr)
df <- data.frame(ID=rep(1:3, 3), Obs_1=rnorm(9), Obs_2=rnorm(9), Obs_3=rnorm(9))
ddply(df, .(ID), summarize, Obs_1_mean=mean(Obs_1), Obs_1_std_dev=sd(Obs_1),
Obs_2_mean=mean(Obs_2), Obs_2_std_dev=sd(Obs_2))
ID Obs_1_mean Obs_1_std_dev Obs_2_mean Obs_2_std_dev
1 1 -0.13994642 0.8258445 -0.15186380 0.4251405
2 2 1.49982393 0.2282299 0.50816036 0.5812907
3 3 -0.09269806 0.6115075 -0.01943867 1.3348792
编辑:在处理许多列时,以下方法可以为您节省大量输入。
ddply(df, .(ID), colwise(mean))
ID Obs_1 Obs_2 Obs_3
1 1 -0.3748831 0.1787371 1.0749142
2 2 -1.0363973 0.0157575 -0.8826969
3 3 1.0721708 -1.1339571 -0.5983944
ddply(df, .(ID), colwise(sd))
ID Obs_1 Obs_2 Obs_3
1 1 0.8732498 0.4853133 0.5945867
2 2 0.2978193 1.0451626 0.5235572
3 3 0.4796820 0.7563216 1.4404602
【讨论】:
您还漏掉了一个观察结果。虽然这是减少列的方法,但我认为它很快就会变得丑陋。 我们可以使用这种方法计算行的平均值吗?【参考方案4】:我添加了dplyr
解决方案。
set.seed(1)
df <- data.frame(ID=rep(1:3, 3), Obs_1=rnorm(9), Obs_2=rnorm(9), Obs_3=rnorm(9))
library(dplyr)
df %>% group_by(ID) %>% summarise_each(funs(mean, sd))
# ID Obs_1_mean Obs_2_mean Obs_3_mean Obs_1_sd Obs_2_sd Obs_3_sd
# (int) (dbl) (dbl) (dbl) (dbl) (dbl) (dbl)
# 1 1 0.4854187 -0.3238542 0.7410611 1.1108687 0.2885969 0.1067961
# 2 2 0.4171586 -0.2397030 0.2041125 0.2875411 1.8732682 0.3438338
# 3 3 -0.3601052 0.8195368 -0.4087233 0.8105370 0.3829833 1.4705692
【讨论】:
【参考方案5】:这是对data.table
答案的另一种看法,使用@Carson 的数据,可读性更强(而且速度也更快,因为使用lapply
而不是sapply
):
library(data.table)
set.seed(1)
dt = data.table(ID=c(1:3), Obs_1=rnorm(9), Obs_2=rnorm(9), Obs_3=rnorm(9))
dt[, c(mean = lapply(.SD, mean), sd = lapply(.SD, sd)), by = ID]
# ID mean.Obs_1 mean.Obs_2 mean.Obs_3 sd.Obs_1 sd.Obs_2 sd.Obs_3
#1: 1 0.4854187 -0.3238542 0.7410611 1.1108687 0.2885969 0.1067961
#2: 2 0.4171586 -0.2397030 0.2041125 0.2875411 1.8732682 0.3438338
#3: 3 -0.3601052 0.8195368 -0.4087233 0.8105370 0.3829833 1.4705692
【讨论】:
第二个应该使用sd
而你使用.SD
两次.. 是否存在性能问题?有什么想法吗?
@Arun,谢谢,修复了 sd
位。我不知道是否会因此而影响性能,让我检查一下
@Arun 看起来性能下降了约 10%,但好消息是它不会随着类别的增加而增加
此外,您还会看到一条关于为每个 by
创建名称(平均值、标准差)的优化消息(这对于大量数据而言效率低下。我在 1e6 data.table 上进行基准测试。将尽快发布结果。
这对我有用,但是生成的列都具有相同的名称,即Obs_1
,Obs_2
,Obs_3
,Obs_1
,Obs_2
,Obs_3
。不是mean.Obs_1
...任何想法为什么会这样?【参考方案6】:
更新的 dplyr 解决方案,至于 2020
1:summarise_each_()
自 dplyr 0.7.0 起已弃用。
和
2:funs()
自 dplyr 0.8.0 起已弃用。
ag.dplyr <- DF %>% group_by(ID) %>% summarise(across(.cols = everything(),list(mean = mean, sd = sd)))
【讨论】:
【参考方案7】:psych
包中有一个有用的功能。
您应该尝试以下实现:
psych::describeBy(data$dependentvariable, group = data$groupingvariable)
【讨论】:
以上是关于为data.frame中的多个变量按组计算平均值和标准差的主要内容,如果未能解决你的问题,请参考以下文章