基于(部分)匹配列名计算行均值

3
我将从3个大型数据表(命名为A1、A2、A3)开始。每个表都有4列数据(V1-V4),一个“日期”列跨越所有三个表,以及数千行数据。
这里是一些模拟我的表的虚拟数据。
A1.V1<-c(1,2,3,4)
A1.V2<-c(2,4,6,8)
A1.V3<-c(1,3,5,7)
A1.V4<-c(1,2,3,4)


A2.V1<-c(1,2,3,4)
A2.V2<-c(2,4,6,8)
A2.V3<-c(1,3,5,7)
A2.V4<-c(1,2,3,4)


A3.V1<-c(1,2,3,4)
A3.V2<-c(2,4,6,8)
A3.V3<-c(1,3,5,7)
A3.V4<-c(1,2,3,4)

Date<-c(2001,2002,2003,2004)

DF<-data.frame(Date, A1.V1,A1.V2,A1.V3,A1.V4,A2.V1,A2.V2,A2.V3,A2.V4,A3.V1,A3.V2,A3.V3,A3.V4)

所以这就是我的数据框最终的样子:
  Date A1.V1 A1.V2 A1.V3 A1.V4 A2.V1 A2.V2 A2.V3 A2.V4 A3.V1 A3.V2 A3.V3 A3.V4
1 2001     1     2     1     1     1     2     1     1     1     2     1     1
2 2002     2     4     3     2     2     4     3     2     2     4     3     2
3 2003     3     6     5     3     3     6     5     3     3     6     5     3
4 2004     4     8     7     4     4     8     7     4     4     8     7     4

我的目标是计算每个数据表中匹配列的行均值。因此,在这种情况下,我希望得到所有以V1结尾的列、所有以V2结尾的列、所有以V3结尾的列和所有以V4结尾的列的行均值。

最终结果应该像这样:

      V1  V2  V3  V4
2001   1   2   1   1
2002   2   4   3   2
2003   3   6   5   3
2004   4   8   7   4

所以我的问题是,如何根据列名的部分匹配计算行平均值?
谢谢。

我们可以假设不能依赖于列的位置顺序吗?也就是说,“匹配”的列可能是不规则间隔的? - joran
不,我们不能依赖位置顺序。而且我正在处理的实际数据集有更多的列,因此指定列位置会很麻烦。 - Vinterwoo
4个回答

7
colnames = c("V1", "V2", "V3", "V4")
res <- sapply(colnames, function(x) rowMeans(DF [, grep(x, names(DF))] )  )
rownames(res) <- DF$Date
res
     V1 V2 V3 V4
2001  1  2  1  1
2002  2  4  3  2
2003  3  6  5  3
2004  4  8  7  4

R中的grep函数返回一个整数向量,用于有选择地“提取”包含单个“V”列名称的列,从较大的数据框中。

如果您需要自动生成名称:

> unique(sapply(strsplit(names(DF)[-1], ".", fixed=TRUE), "[", 2) )
[1] "V1" "V2" "V3" "V4"

res 没有被赋予任何值。 - Jason Goal
好的发现。我想我一直在运行它而没有赋值,直到我得到了正确的预期结果,但是后来忘记了进行赋值。现在已经修复了。 - IRTFM

4
library(plyr)
ddply(DF, .(Date), function(x) {
    foo <- melt(x, id.vars = 1)
    foo$variable <- substr(foo$variable, 4, 6)
    return(dcast(foo, Date ~ variable, mean))
    })
Date V1 V2 V3 V4
1 2001  1  2  1  1
2 2002  2  4  3  2
3 2003  3  6  5  3
4 2004  4  8  7  4

2

您可以使用value = Tgrep来获取适当的名称,然后在data.tablej组件中创建一个eval调用。

library(data.table)
# convert to a data.table
DT <- data.table(DF)
 # the indices we wish to group
.index <- paste0('V',1:3)
# a list containing the names
name_list <- mapply(grep, pattern = as.list(.index ), 
                  MoreArgs = list(x= names(DT),value=T ), SIMPLIFY=F)
 # create the expression
.e <- parse(text=sprintf('list( %s)', paste(mapply(sprintf, .index, lapply(name_list, paste, collapse = ', '), 
            MoreArgs = list(fmt = '%s = mean(c(%s), na.rm = T)')), collapse = ',')))

DT[, eval(.e),by=Date]

##    Date V1 V2 V3
## 1: 2001  1  2  1
## 2: 2002  2  4  3
## 3: 2003  3  6  5
## 4: 2004  4  8  7

# what .e looks like
.e 
## expression(list( V1 = mean(c(A1.V1, A2.V1, A3.V1), na.rm = T),V2 = mean(c(A1.V2, A2.V2, A3.V2), na.rm = T),V3 = mean(c(A1.V3, A2.V3, A3.V3), na.rm = T)))

1
这种折磨似乎是由@Vinterwoo将两个分类类型混合到一个列名向量中引起的。在data.table中,我们会将其保留为长格式,然后简单地执行:DT[,mean(var),by="A,V"]。对于其中一些问题,我可能会想回答“为什么?”DWin的方法,但在使用with=FALSE的数据表上可能更简单。 - Matt Dowle

0
我相信有更优雅的方法,但这是一个看起来可行的可能性。
# declare the column names
colnames = c("V1", "V2", "V3", "V4")

# calculate the means
means = lapply(colnames, function(name) { apply(DF[,grep(name, names(DF))], 1, mean) })

# build the result
result = do.call(cbind, means)
result = as.data.frame(t(result))
rownames(result) = DF$Date

我也应该描述一下我所做的。

首先,我声明了要部分匹配的列名。

然后,使用grep命令来部分选择数据框中的列(与特定子字符串匹配的列)。apply命令计算平均值,lapply对所有部分匹配子字符串的列进行计算。

使用do.callcbind(如DWin建议的那样),我们连接单个列。最后,我们从原始数据框的Date列设置列名。

问题可以更优雅、更高效地解决,参见DWin和Maiasaura的解决方案。


1
这是一条相当曲折的完成路径,特别是那个可以被替换为 do.call(cbind, means) 的 for 循环。 - IRTFM
合理的建议,已经相应地更新了帖子。我已经很长时间很少使用R,但我仍然会用比较困难的方式做事:)。顺便说一下,我很喜欢你和@Maiasaura提出的解决方案。 - Timo

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