问题 “循环”data.table来计算条件平均值


我想“遍历”data.table的行并计算每行的平均值。平均值应根据以下机制计算:

  1. 在第i行中查找标识符ID(ID(i))
  2. 在第i行中查找T2的值(T2(i))
  3. 计算平均值 Data1 所有行中的值 j,符合这两个标准: ID(j) = ID(i) 和 T1(j) = T2(i)
  4. 在第i行的Data2列中输入计算的平均值

     DF = data.frame(ID=rep(c("a","b"),each=6), 
                 T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12))
     DT = data.table(DF)
     DT[ , Data2:=NA_real_]
         ID T1 T2  Data1 Data2
    [1,]  a  1  1     1    NA
    [2,]  a  1  2     2    NA
    [3,]  a  1  3     3    NA
    [4,]  a  2  1     4    NA
    [5,]  a  2  2     5    NA
    [6,]  a  2  3     6    NA
    [7,]  b  1  1     7    NA
    [8,]  b  1  2     8    NA
    [9,]  b  1  3     9    NA
    [10,] b  2  1    10    NA
    [11,] b  2  2    11    NA
    [12,] b  2  3    12    NA
    

对于这个简单的示例,结果应如下所示:

      ID T1 T2  Data1 Data2
[1,]  a  1  1     1    2
[2,]  a  1  2     2    5
[3,]  a  1  3     3    NA
[4,]  a  2  1     4    2
[5,]  a  2  2     5    5
[6,]  a  2  3     6    NA
[7,]  b  1  1     7    8
[8,]  b  1  2     8    11
[9,]  b  1  3     9    NA
[10,] b  2  1    10    8
[11,] b  2  2    11    11
[12,] b  2  3    12    NA

我认为这样做的一种方法是遍历行,但我认为这是低效的。我看过了 apply() 功能,但我敢肯定它是否能解决我的问题。我也可以用 data.frame 代替 data.table 如果这会使它更有效或更容易。真实数据集包含大约100万行。


9611
2018-03-23 17:27


起源

您编写的规范似乎难以操作,但您的示例表明,在每个ID组中,您需要某些值的平均值,其中T2的值在T1的范围内。但是,当试图弄清楚为什么第二行中的Data2应该是5时,即使这种解释也会分崩离析。????? - 42-
@DWin那是因为平均值是在 Data1 柱。 Data2[2] 等于5,因为5是平均值 (4, 5, 6)。 - ulidtko


答案:


经验法则是首先聚合,然后加入到那里。

agg = DT[,mean(Data1),by=list(ID,T1)]
setkey(agg,ID,T1)
DT[,Data2:={JT=J(ID,T2);agg[JT,V1][[3]]}]
      ID T1 T2 Data1 Data2
 [1,]  a  1  1     1     2
 [2,]  a  1  2     2     5
 [3,]  a  1  3     3    NA
 [4,]  a  2  1     4     2
 [5,]  a  2  2     5     5
 [6,]  a  2  3     6    NA
 [7,]  b  1  1     7     8
 [8,]  b  1  2     8    11
 [9,]  b  1  3     9    NA
[10,]  b  2  1    10     8
[11,]  b  2  2    11    11
[12,]  b  2  3    12    NA

你可以看到它在这种情况下有点难看(但会很快)。它计划添加 drop 这将避免 [[3]] 有点,也许我们可以提供一种说法 [.data.table 评估 i 在调用范围(即没有自我连接),这将避免 JT= 这里需要的是因为 ID 在两者中 agg 和 DT

keyby 已经添加到R-Forge的v1.8.0中,因此无需使用 setkey也是。


10
2018-03-23 18:10



谢谢马修。这非常快。是否有可能给予 V1 专栏 agg 在创建时自定义名称以避免对列名称产生任何混淆? - Cake
尝试 DT[,list(myname=mean(Data1)),by=list(ID,T1)]。另见 data.table wiki 第3点,在这种情况下进一步加速。 - Matt Dowle
我替换了你的第三行 DT[,Data2:={agg[J(ID, T2)][[3]]}],并得到相同的结果。即,我 没有 避免 JT= 位(以及... ,V1)。对我来说,这些都是不好的做法吗? - Josh O'Brien
@Josh嗨。我试过那条线,但似乎没有相同的结果。该 ID 在 - 的里面 J() 来自 agg,再循环以匹配的长度 T2 (从 DT 以来 T2 不在 agg)所以它混合了 a和 b的。但是可以避免这种情况 V1 在这种情况下,可能更有效率。 - Matt Dowle


迭代行的更快的替代方案将是采用向量化的解决方案。

R> d <- data.frame(ID=rep(c("a","b"),each=6), T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12)) 
R> d
   ID T1 T2 Data1
1   a  1  1     1
2   a  1  2     2
3   a  1  3     3
4   a  2  1     4
5   a  2  2     5
6   a  2  3     6
7   b  1  1     7
8   b  1  2     8
9   b  1  3     9
10  b  2  1    10
11  b  2  2    11
12  b  2  3    12

R> rowfunction <- function(i) with(d, mean(Data1[which(T1==T2[i] & ID==ID[i])]))
R> d$Data2 <- sapply(1:nrow(d), rowfunction)
R> d
   ID T1 T2 Data1 Data2
1   a  1  1     1     2
2   a  1  2     2     5
3   a  1  3     3   NaN
4   a  2  1     4     2
5   a  2  2     5     5
6   a  2  3     6   NaN
7   b  1  1     7     8
8   b  1  2     8    11
9   b  1  3     9   NaN
10  b  2  1    10     8
11  b  2  2    11    11
12  b  2  3    12   NaN

此外,我更喜欢预处理数据 之前 把它变成R. I.e.如果要从SQL服务器检索数据,那么让服务器计算平均值可能是更好的选择,因为它很可能在这方面做得更好。

由于几个原因,R实际上并不擅长数字运算。但在对已经预处理的数据进行统计时,它非常出色。


2
2018-03-23 17:55





使用tapply和另一篇近期帖子的一部分:

DF = data.frame(ID=rep(c("a","b"),each=6), T1=rep(1:2,each=3), T2=c(1,2,3), Data1=c(1:12))

编辑:实际上,大多数原始功能都是多余的,并且用于其他目的。在这里,简化:

ansMat <- tapply(DF$Data1, DF[, c("ID", "T1")], mean)

i <- cbind(match(DF$ID, rownames(ansMat)), match(DF$T2, colnames(ansMat)))

DF<-cbind(DF,Data2 = ansMat[i])


# ansMat<-tapply(seq_len(nrow(DF)), DF[, c("ID", "T1")], function(x) {
#   curSub <- DF[x, ]
#   myIndex <- which(DF$T2 == curSub$T1 & DF$ID == curSub$ID)
#   meanData1 <- mean(curSub$Data1)
#   return(meanData1 = meanData1)
# })

诀窍是对ID和T1进行tapply而不是ID和T2。还有什么比这更快的?


1
2018-03-23 17:57