按列求平均值

6

我希望在数据框的多个列(十几个)上使用ave函数:

ave(df[,the_cols], df[,c('site', 'month')], FUN = mean)

问题在于ave函数会将所有的the_cols列一起运行mean函数。是否有办法分别对每个the_cols列运行该函数?
我尝试查看其他函数。tapplyaggregate是不同的,它们仅返回每个组的一行。我需要ave的行为,即返回与原始df中给定的行数相同的行数。还有一个by函数,但使用它会非常笨拙,因为它返回一个复杂的列表结构,必须以某种方式进行转换。
当然,许多笨拙而丑陋的解决方案存在(如使用& do.call、多个*apply函数调用等),但是否有一些真正简单和优雅的方法?
3个回答

7
也许我漏掉了什么,但这里使用apply()方法会非常有效,并且不会很丑陋或需要任何丑陋的黑客技巧。以下是一些虚拟数据:
df <- data.frame(A = rnorm(20), B = rnorm(20), site = gl(5,4), month = gl(10, 2))

以下是有问题的内容:

sapply(df[, c("A","B")], ave, df$site, df$month)

如果你真的想要这样做,请通过 data.frame() 将其强制转换为数据框。

R> sapply(df[, c("A","B")], ave, df$site, df$month)
            A        B
 [1,]  0.0775  0.04845
 [2,]  0.0775  0.04845
 [3,] -1.5563  0.43443
 [4,] -1.5563  0.43443
 [5,]  0.7193  0.01151
 [6,]  0.7193  0.01151
 [7,] -0.9243 -0.28483
 [8,] -0.9243 -0.28483
 [9,]  0.3316  0.14473
[10,]  0.3316  0.14473
[11,] -0.2539  0.20384
[12,] -0.2539  0.20384
[13,]  0.5558 -0.37239
[14,]  0.5558 -0.37239
[15,]  0.1976 -0.22693
[16,]  0.1976 -0.22693
[17,]  0.2031  1.11041
[18,]  0.2031  1.11041
[19,]  0.3229 -0.53818
[20,]  0.3229 -0.53818

把它整合起来,可以考虑这样做:
AVE <- function(df, cols, ...) {
  dots <- list(...)
  out <- sapply(df[, cols], ave, ...)
  out <- data.frame(as.data.frame(dots), out)
  names(out) <- c(paste0("Fac", seq_along(dots)), cols)
  out
}

R> AVE(df, c("A","B"), df$site, df$month)
   Fac1 Fac2       A        B
1     1    1  0.0775  0.04845
2     1    1  0.0775  0.04845
3     1    2 -1.5563  0.43443
4     1    2 -1.5563  0.43443
5     2    3  0.7193  0.01151
6     2    3  0.7193  0.01151
7     2    4 -0.9243 -0.28483
8     2    4 -0.9243 -0.28483
9     3    5  0.3316  0.14473
10    3    5  0.3316  0.14473
11    3    6 -0.2539  0.20384
12    3    6 -0.2539  0.20384
13    4    7  0.5558 -0.37239
14    4    7  0.5558 -0.37239
15    4    8  0.1976 -0.22693
16    4    8  0.1976 -0.22693
17    5    9  0.2031  1.11041
18    5    9  0.2031  1.11041
19    5   10  0.3229 -0.53818
20    5   10  0.3229 -0.53818

目前我对使用...的细节不是很清楚,但你应该能够为我在这里使用的Fac1等名称得到更好的名称。

我会给你提供另一种表示方法:aggregate(),但使用ave()函数代替mean()

R> aggregate(cbind(A, B) ~ site + month, data = df, ave)
   site month     A.1     A.2      B.1      B.2
1     1     1  0.0775  0.0775  0.04845  0.04845
2     1     2 -1.5563 -1.5563  0.43443  0.43443
3     2     3  0.7193  0.7193  0.01151  0.01151
4     2     4 -0.9243 -0.9243 -0.28483 -0.28483
5     3     5  0.3316  0.3316  0.14473  0.14473
6     3     6 -0.2539 -0.2539  0.20384  0.20384
7     4     7  0.5558  0.5558 -0.37239 -0.37239
8     4     8  0.1976  0.1976 -0.22693 -0.22693
9     5     9  0.2031  0.2031  1.11041  1.11041
10    5    10  0.3229  0.3229 -0.53818 -0.53818

虽然不完全符合所述输出,但如果需要的话,这是可以很容易地重新塑造的。


哇,sapply 的一行代码简单而且运行得很好,谢谢!我不会想到在 sapply 中运行 ave,这是一个惊人的代码高尔夫 :-) 尽管如此,我讨厌你的 AVE 函数。既然有这样一个漂亮的一行代码,为什么还要写它呢? - Tomas
如果你需要获取“site”和“month”信息并且需要频繁地进行此操作,我会快速编写一个类似这样的包装器来帮助你。如果你不需要附加因素,只需使用单行代码即可。 - Gavin Simpson
啊哈,现在我明白了:那只是为了将网站和月份与结果捆绑在一起... 顺便说一句,在你的AVE函数中使用cbind不是更好吗?我猜那样就不需要names()<-这些东西了。 - Tomas
感谢提供备选的“aggregate”解决方案。它是否为每个组内的每条记录创建一列?看起来很麻烦 :) 如果组内的记录数不同怎么办? - Tomas
是的,我想它会弹出一个NA。还没有检查过。试一下然后反馈一下...? - Gavin Simpson

4

如果你想要返回一个数据框

library(plyr)
## assuming that the_cols are string
## if col index just add the index of site and month
the_cols <- c("site", "month", the_cols)
ddply(df, c('site', 'month'), FUN = numcolwise(mean))[,the_cols]

我总是更喜欢仅使用基本库,但感谢plyr的解决方案! - Tomas

3
您可以在colMeans中使用by。
by(df[,the_cols], df[,c('site', 'month')], FUN = colMeans)

您也可以在lapply内使用ave:
res <- lapply(df[,the_cols], function(x) 
                               ave(x, df[,c('site', 'month')], FUN = mean))

data.frame(res) # create data frame

正如我在问题中所述,by 不会返回一个 data.frame!需要大量丑陋的代码才能将其恢复到原始结构!请参阅我的问题。 - Tomas
1
@Tomas 函数ave也不返回数据框。 - Sven Hohenstein

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接