R 按其他因素对一个因素进行有效计数或制表,并在数据框中进行重塑?

R 按其他因素对一个因素进行有效计数或制表,并在数据框中进行重塑?,r,data.table,performance,R,Data.table,Performance,在使用data.table时,我寻找一种有效的方法来计算向量的所有向量级别的累加和(制表) 问题 dataframe/data.table DT最初由四个变量组成,其中一个变量名为experience。目标是一个向量,它保存了经验条件变量id和cl中因子水平的累积计数。值得注意的是,因子经验的因子水平比数据集中的因子水平多(这是一个必要的属性) 数据看起来像 id trial experience cl 1: 1 1 000A A 2: 1 2

在使用data.table时,我寻找一种有效的方法来计算向量的所有向量级别的累加和(制表)

问题 dataframe/data.table DT最初由四个变量组成,其中一个变量名为experience。目标是一个向量,它保存了经验条件变量id和cl中因子水平的累积计数。值得注意的是,因子经验的因子水平比数据集中的因子水平多(这是一个必要的属性)

数据看起来像

    id trial experience cl
 1:  1     1       000A  A
 2:  1     2       000A  A
 3:  1     3       000B  B
 4:  1     4       111A  A
 5:  1     5       001B  B
 6:  2     1       100B  B
 7:  2     2       111A  A
 8:  2     3       100B  B
 9:  2     4       010A  A
10:  2     5       011B  B
经验的因素水平为16级

levels(DT$experience)
#  [1] "000A" "001A" "010A" "011A" "100A" "101A" "110A" "111A"
#  [9] "000B" "001B" "010B" "011B" "100B" "101B" "110B" "111B"

我们要计算的是对ID和CL有条件的经验的累积计数。考虑前三行:对于ID=1,第一个经验值是00A,所以计数器变量C00A=1。第二个经验值也是000A,因此计数器c000A=2。但现在第三个经验值是000B,因此前一个计数器c000A保持为2,但另一个计数器c000B=1,在此之前为0

按照此逻辑,我们想要的结果如下所示:

    id trial experience cl c000A c001A c010A c011A c100A c101A c110A c111A c000B c001B c010B c011B c100B c101B c110B c111B
 1:  1     1       000A  A     1     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
 2:  1     2       000A  A     2     0     0     0     0     0     0     0     0     0     0     0     0     0     0     0
 3:  1     3       000B  B     2     0     0     0     0     0     0     0     1     0     0     0     0     0     0     0
 4:  1     4       111A  A     2     0     0     0     0     0     0     1     1     0     0     0     0     0     0     0
 5:  1     5       001B  B     2     0     0     0     0     0     0     1     1     1     0     0     0     0     0     0
 6:  2     1       100B  B     0     0     0     0     0     0     0     0     0     0     0     0     1     0     0     0
 7:  2     2       111A  A     0     0     0     0     0     0     0     1     0     0     0     0     1     0     0     0
 8:  2     3       100B  B     0     0     0     0     0     0     0     1     0     0     0     0     2     0     0     0
 9:  2     4       010A  A     0     0     1     0     0     0     0     1     0     0     0     0     2     0     0     0
10:  2     5       011B  B     0     0     1     0     0     0     0     1     0     0     0     1     2     0     0     0
注意:将16个条目c000A、…、c111B分配到单独的列对我来说并不重要。如果结果是一个包含16个条目的向量,按c000A、c001A、…、c110B、c111B排序,并保存累积计数,则这就完全足够了

当前代码和计算速度 我目前使用的代码是以下两步方法。它既不漂亮也不优雅

foo <- function(DT){
   # tabulate experience for each trial
   # store in an auxiliary variables <s000A, s001A, ..., s110B, s111B>
   DT[, paste(sep="","s",levels(DT$experience)) := as.list(table(experience)), by = c("id","cl","trial")]
   # sum each of the s____ variables by id
   DT[, "c000A" := cumsum(s000A), by = id] # this is clumsy
   DT[, "c001A" := cumsum(s001A), by = id]
   DT[, "c010A" := cumsum(s010A), by = id]
   DT[, "c011A" := cumsum(s011A), by = id]
   DT[, "c100A" := cumsum(s100A), by = id]
   DT[, "c101A" := cumsum(s101A), by = id]
   DT[, "c110A" := cumsum(s110A), by = id]
   DT[, "c111A" := cumsum(s111A), by = id]
   DT[, "c000B" := cumsum(s000B), by = id]
   DT[, "c001B" := cumsum(s001B), by = id]
   DT[, "c010B" := cumsum(s010B), by = id]
   DT[, "c011B" := cumsum(s011B), by = id]
   DT[, "c100B" := cumsum(s100B), by = id]
   DT[, "c101B" := cumsum(s101B), by = id]
   DT[, "c110B" := cumsum(s110B), by = id]
   DT[, "c111B" := cumsum(s111B), by = id]
}
创建此示例的代码
库(“data.table”)
图书馆(“R.utils”)
#n=1e+4的数据帧DF示例

n可能是这样的:

# add some extra variables
DT[, counter := 1:.N]
DT[, dummy := 1]

dcast.data.table(DT, counter+id ~ experience, value.var = 'dummy', fill = 0)[,
  lapply(.SD, cumsum), by = id, .SDcols = c(-1,-2)]
#       id 000A 010A 111A 000B 001B 011B 100B
#    1:  1    1    0    0    0    0    0    0
#    2:  1    2    0    0    0    0    0    0
#    3:  1    2    0    0    1    0    0    0
#    4:  1    2    0    1    1    0    0    0
#    5:  1    2    0    1    1    1    0    0
#   ---                                      
#19996:  2 2000  999 1999 1000 1000  999 1999
#19997:  2 2000  999 2000 1000 1000  999 1999
#19998:  2 2000  999 2000 1000 1000  999 2000
#19999:  2 2000 1000 2000 1000 1000  999 2000
#20000:  2 2000 1000 2000 1000 1000 1000 2000
如果你愿意的话,你可以
cbind
把它找回来。

这个怎么样

首先创建所有列并将其初始化为0L

ex = levels(DT$experience)
DT[, c(ex) := 0L]
现在,按
experience
分组,并在列表中获得与每个
experience
对应的行号,如下所示:

tmp = DT[, list(list(.I)), by=experience]
tmp[, experience := as.character(experience)] ## convert to char
for(i in seq(nrow(tmp))) {
    set(DT, i=tmp$V1[[i]], j=tmp$experience[i], val=1L)
}
然后,您可以循环遍历每个列,并使用
set
tmp
中相应的行(来自列
V1
)和列(来自列
experience
),将
1
分配给
DT
中的相应列,如下所示:

tmp = DT[, list(list(.I)), by=experience]
tmp[, experience := as.character(experience)] ## convert to char
for(i in seq(nrow(tmp))) {
    set(DT, i=tmp$V1[[i]], j=tmp$experience[i], val=1L)
}
最后,
id
在每列上添加一个
cumsum

DT[, c(ex) := lapply(.SD, cumsum), by=id, .SDcols=ex]
总共花了0.013秒(同样不错的
dcast.data.table
解决方案也花了0.027秒)


如果使用
as.character(unique(DT$experience))
而不是最后一行中的
ex
,您可能可以节省更多的时间。。因为有些列的值都是0,您不必
cumsum
它们。即:

ex = as.character(unique(DT$experience)) ## rewrite 'ex'
DT[, c(ex) := lapply(.SD, cumsum), by=id, .SDcols=ex]

哦,抱歉,刚刚编辑了它:
library(“R.utils”)
是的,它可以。您显示的序列是可能的。是的,完全正确。计数是累积的,谢谢。一方面,速度比我的解决方案快得多(哇!)(
用户系统VSTRICHEN | 0.05 0.00 0.05
)。但另一方面,请注意,结果需要显示所有因子级别的计数。此结果显示当前非零因子级别的计数。@JBJ只需手动将其余部分添加为0列,例如:
DT.res[,setdiff(级别(DT$experience),唯一(DT$experience)):=0]
,其中,
DT.res
是上面的最终结果是的,很好!而且,由于我需要按因子经验的顺序对列进行排序,我将使用
setcolorder
对它们进行重新排序。让我将速度比较添加到结果中,看看是否有人获得更快的速度。+1-对于更大的数据量,这比dcast快得多。谢谢。我将在上面添加更多基准。这就是解决方案。当大多数关于速度的建议都指向避免循环的方向时,为什么使用for循环的代码是最快的?我想如果你循环数百万个条目,情况就是这样。如果你把for循环移到C,它就会被执行。这里它只是循环了几次。如果我们实现
set
以能够在C中执行循环并避免这里的for循环,我认为它会更快(虽然这里没有,因为它实际上只有很少的列)。此外,您还可以跳过
tmp
for循环
并执行:
DT[,set(DT,I=.I,j=as.character(experience),value=1L),by=experience]
tmp = DT[, list(list(.I)), by=experience]
tmp[, experience := as.character(experience)] ## convert to char
for(i in seq(nrow(tmp))) {
    set(DT, i=tmp$V1[[i]], j=tmp$experience[i], val=1L)
}
DT[, c(ex) := lapply(.SD, cumsum), by=id, .SDcols=ex]
ex = as.character(unique(DT$experience)) ## rewrite 'ex'
DT[, c(ex) := lapply(.SD, cumsum), by=id, .SDcols=ex]