对列组应用函数

17
如何使用apply或相关函数创建一个新的数据框,其中包含非常大的数据框中每对列的行平均值的结果?输出仪器会在大量样本上输出n个复制测量值,其中每个单独的测量值都是向量(所有测量值都是相同长度的向量)。我想计算每个样本的所有重复测量值的平均值(和其他统计数据)。这意味着我需要将n个连续的列分组在一起,并进行逐行计算。例如,在两个样本的三个重复测量中,如何获得具有两列(每个样本一个)的数据框,其中一个是dat $ a dat $ b dat $ c 中的每个重复测量行的平均值,另一个是dat $ d dat $ e dat $ f 的每行平均值。下面是一些示例数据:
dat <- data.frame( a = rnorm(16), b = rnorm(16), c = rnorm(16), d = rnorm(16), e = rnorm(16), f = rnorm(16))

            a          b            c          d           e          f
1  -0.9089594 -0.8144765  0.872691548  0.4051094 -0.09705234 -1.5100709
2   0.7993102  0.3243804  0.394560355  0.6646588  0.91033497  2.2504104
3   0.2963102 -0.2911078 -0.243723116  1.0661698 -0.89747522 -0.8455833
4  -0.4311512 -0.5997466 -0.545381175  0.3495578  0.38359390  0.4999425
5  -0.4955802  1.8949285 -0.266580411  1.2773987 -0.79373386 -1.8664651
6   1.0957793 -0.3326867 -1.116623982 -0.8584253  0.83704172  1.8368212
7  -0.2529444  0.5792413 -0.001950741  0.2661068  1.17515099  0.4875377
8   1.2560402  0.1354533  1.440160168 -2.1295397  2.05025701  1.0377283
9   0.8123061  0.4453768  1.598246016  0.7146553 -1.09476532  0.0600665
10  0.1084029 -0.4934862 -0.584671816 -0.8096653  1.54466019 -1.8117459
11 -0.8152812  0.9494620  0.100909570  1.5944528  1.56724269  0.6839954
12  0.3130357  2.6245864  1.750448404 -0.7494403  1.06055267  1.0358267
13  1.1976817 -1.2110708  0.719397607 -0.2690107  0.83364274 -0.6895936
14 -2.1860098 -0.8488031 -0.302743475 -0.7348443  0.34302096 -0.8024803
15  0.2361756  0.6773727  1.279737692  0.8742478 -0.03064782 -0.4874172
16 -1.5634527 -0.8276335  0.753090683  2.0394865  0.79006103  0.5704210
我想要类似这样的东西。
            X1          X2
1  -0.28358147 -0.40067128
2   0.50608365  1.27513471
3  -0.07950691 -0.22562957
4  -0.52542633  0.41103139
5   0.37758930 -0.46093340
6  -0.11784382  0.60514586
7   0.10811540  0.64293184
8   0.94388455  0.31948189
9   0.95197629 -0.10668118
10 -0.32325169 -0.35891702
11  0.07836345  1.28189698
12  1.56269017  0.44897971
13  0.23533617 -0.04165384
14 -1.11251880 -0.39810121
15  0.73109533  0.11872758
16 -0.54599850  1.13332286

我用这个方法做了,但对于我的更大的数据框来说显然不好用...

data.frame(cbind(
apply(cbind(dat$a, dat$b, dat$c), 1, mean),
apply(cbind(dat$d, dat$e, dat$f), 1, mean)
))

我尝试过使用apply和循环,但是总是无法将它们组合在一起。我的实际数据有数百列。


它总是每三列吗?您是提供名称的向量向量还是索引的向量向量?如果用户user1317221_G的答案不是您想要的,也许您需要提供更多信息。 - Tyler Rinker
1
为了后人,上面的问题似乎是这个更近期的关于将函数应用于行组的问题的转置(并且有一些不同的方法):http://stackoverflow.com/q/10837258/1036500 - Ben
6个回答

18

这可能更适用于您的情况,因为您会传递一个索引列表。如果速度是一个问题(数据框很大),我建议使用 lapply 和 do.call 而不是sapply:

<code><code><code>x <- list(1:3, 4:6)
do.call(cbind, lapply(x, function(i) rowMeans(dat[, i])))
</code></code></code>

如果只有列名,则也可以使用此方法:

x <- list(c('a','b','c'), c('d', 'e', 'f'))
do.call(cbind, lapply(x, function(i) rowMeans(dat[, i])))

编辑

我突然想到,也许您想自动化每三列执行此操作。我知道有更好的方法,但是在100列数据集上,这是一种方法:

dat <- data.frame(matrix(rnorm(16*100), ncol=100))

n <- 1:ncol(dat)
ind <- matrix(c(n, rep(NA, 3 - ncol(dat)%%3)), byrow=TRUE, ncol=3)
ind <- data.frame(t(na.omit(ind)))
do.call(cbind, lapply(ind, function(i) rowMeans(dat[, i])))

编辑 2 对于索引仍然不满意。我认为有一种更好/更快的方法来传递索引。这是第二个想法,虽然并不令人满意:

n <- 1:ncol(dat)
ind <- data.frame(matrix(c(n, rep(NA, 3 - ncol(dat)%%3)), byrow=F, nrow=3))
nonna <- sapply(ind, function(x) all(!is.na(x)))
ind <- ind[, nonna]

do.call(cbind, lapply(ind, function(i)rowMeans(dat[, i])))

1
这是因为最后一列没有三列可以绑定在一起。 - Tyler Rinker
是的,您的编辑正好符合我的要求,非常感谢。很抱歉我的问题表述不清,这是由于长时间的徒劳尝试所致... - Ben
1
我将要求一种更好的方法来创建索引并在此处链接回来。 - Tyler Rinker
1
这是一个链接,供未来的搜索者参考 [LINK] (https://dev59.com/vWTVa4cB1Zd3GeqP-wP2)。 - Tyler Rinker
3
另一种索引方法:split(1:n,rep(1:n,each=3,length=n))。这里的n是列数。 - Wojciech Sobala
1
@WojciechSobala,你能把那个答案发布到上面的链接中吗?(尽管你需要删除最后一个列表索引,因为它的长度不是3。) - Tyler Rinker

8

这里有一个类似的问题由@david提出:在r中每16列求平均值(现已关闭),我根据@TylerRinker的答案进行了适应,并遵循了@joran和@Ben的建议进行回答。因为得到的函数可能对OP或未来的读者有所帮助,所以我在此处复制该函数,并提供OP数据的示例。

# Function to apply 'fun' to object 'x' over every 'by' columns
# Alternatively, 'by' may be a vector of groups
byapply <- function(x, by, fun, ...)
{
    # Create index list
    if (length(by) == 1)
    {
        nc <- ncol(x)
        split.index <- rep(1:ceiling(nc / by), each = by, length.out = nc)
    } else # 'by' is a vector of groups
    {
        nc <- length(by)
        split.index <- by
    }
    index.list <- split(seq(from = 1, to = nc), split.index)

    # Pass index list to fun using sapply() and return object
    sapply(index.list, function(i)
            {
                do.call(fun, list(x[, i], ...))
            })
}

然后,为了找到重复实验的平均值:
byapply(dat, 3, rowMeans)

或者,也许是重复数据的标准差:
byapply(dat, 3, apply, 1, sd)

更新

by 也可以指定为群组的矢量:

byapply(dat, c(1,1,1,2,2,2), rowMeans)

7

从向量a、b、c的行中求平均值

 rowMeans(dat[1:3])

从向量d,e,f中提取行的方法

 rowMeans(dat[4:6])

你可以获得的一站式通话
results<-cbind(rowMeans(dat[1:3]),rowMeans(dat[4:6]))

如果您只知道列的名称而不知道顺序,则可以使用以下方法:
rowMeans(cbind(dat["a"],dat["b"],dat["c"]))
rowMeans(cbind(dat["d"],dat["e"],dat["f"]))

#I dont know how much damage this does to speed but should still be quick

1
那么对于有数百列的数据框呢?你如何进行泛化处理? - Ben
@joran,你说得对,我在准备问题时太匆忙了,抱歉造成了歧义。Tyler Rinker的编辑中有我想要的代码。 - Ben

5

rowMeans 方法更快,但是为了完整性,这里演示如何使用 apply:

t(apply(dat,1,function(x){ c(mean(x[1:3]),mean(x[4:6])) }))

1
数据框有几百列,每连续三列的行平均值怎么样? - Ben
2
@Ben 将其简化为您已经解决的问题:(1)转置(2)使用 plyrdata.table,(3)再次转置。 (假设所有内容都是数字。) - joran
我会尝试一下,看看能否想出比Tyler上面的解决方案更有效的东西(可能性不大,但值得一试!) - Ben
谢谢你的建议,我根据你的提示想出了两种方法(虽然可能不完全符合你的意思...),请见上文。 - Ben

2

受@joran建议的启发,我想出了这个方法(实际上与他建议的有些不同,但转置建议尤其有用):

创建一个数据框,其中包含p列示例数据,以模拟真实数据集(根据上面@TylerRinker的答案,与我在问题中提供的差异较大)

p <- 99 # how many columns?
dat <- data.frame(matrix(rnorm(4*p), ncol = p))

将此数据框中的列重命名,以创建一组连续的n列,这样如果我对三列的组感兴趣,我将得到类似于1,1,1,2,2,2,3,3,3等的列名,或者如果我想要四列的组,它将是1,1,1,1,2,2,2,2,3,3,3,3等。我现在选择三个(我猜这是一种索引,适用于像我这样不太了解索引的人)。
n <- 3 # how many consecutive columns in the groups of interest?
names(dat) <- rep(seq(1:(ncol(dat)/n)), each = n, len = (ncol(dat)))

现在使用apply和tapply来获取每个组的行均值。
dat.avs <- data.frame(t(apply(dat, 1, tapply, names(dat), mean)))

主要缺点是原始数据中的列名被替换(但可以通过将分组编号放在新行而不是列名中来克服此问题),并且apply-tapply函数返回的列名顺序不方便。
根据@joran的建议,这里提供了一个data.table解决方案:
p <- 99 # how many columns?
dat <- data.frame(matrix(rnorm(4*p), ncol = p))
dat.t <-  data.frame(t(dat))

n <- 3 # how many consecutive columns in the groups of interest?
dat.t$groups <- as.character(rep(seq(1:(ncol(dat)/n)), each = n, len = (ncol(dat))))

library(data.table)
DT <- data.table(dat.t)
setkey(DT, groups)
dat.av <- DT[, lapply(.SD,mean), by=groups]

感谢大家快速而耐心的付出!

2
只是想指出,由于以下两个原因,lapply(.SD,mean) 这个习惯用法在 v1.8.1 中应该会快得多:i) 在 这个问题 中发现了一些技巧;ii) mean() 函数已经自动进行了 .Internal() 化(不再需要维基第3点)。此外,.SDcols 经常很有用,但在这里并不需要。 - Matt Dowle
@MatthewDowle 谢谢你的留言!很高兴知道 .SDcols 这个函数,这是我不熟悉的一个函数,同时也很棒听到 data.table 的速度一直在提升! - Ben

0

如果你想将一个函数应用于每个列的唯一组合,也就是所谓的组合数学问题,那么有一个非常简单而优美的解决方案。

combinations <- combn(colnames(df),2,function(x) rowMeans(df[x]))

要计算每个三列唯一组合的统计数据等,只需将2更改为3即可。该操作是矢量化的,因此比循环更快,例如上面使用的apply系列函数。如果列的顺序很重要,则需要一个排列算法来复制有序集:combinat::permn


“如果顺序很重要”是什么意思?“combinat::permn”函数又是什么?您能编辑一下代码吗? - user3495945
组合和排列不是同一回事:https://www.youtube.com/watch?v=s2W6Bce_T30 如果输入的顺序很重要,那么您需要的是排列。在这种情况下,“顺序”是指列的顺序。 - Adam Erickson

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