4

我想在我的 data.table 中添加一个新列,其中包含来自其他列之一的数据。但是,列的选择因行而异 - 取决于另一列的内容。所以:

对于数据集:

     a_data b_data column_choice
[1,]     55      1             a
[2,]     56      2             a
[3,]     57      3             b

生成者:

dat=data.table(a_data = c(55, 56, 57), 
               b_data = c(1,  2,  3), 
               column_choice = c("a", "a", "b"))

我想要一个新列“选择”,它包含(每行)来自“a_data”或“b_data”的数据,具体取决于“column_choice”的值。因此,生成的数据表将是:

     a_data b_data column_choice chosen
[1,]     55      1             a     55
[2,]     56      2             a     56
[3,]     57      3             b      3

我设法使用以下方法获得了预期的效果:

dat=dat[, data.table(.SD, chosen=.SD[[paste0(.SD$column_choice, "_data")]]),
        by=1:nrow(a)]
dat$nrow = NULL

但是这感觉很笨拙;也许有一种更简单的方法来做到这一点(这无疑也会教会我一些关于 R 的知识)?

在实践中,数据框还有很多其他列需要保留,更多的选择不仅仅是“a或b”,还有几种类型的列要生成,所以我宁愿不使用ifelse可能的基本解决方案适用于上面的基本示例。

非常感谢您的帮助。

4

3 回答 3

4

我想我现在已经找到了一个正确矢量化的班轮,在这种情况下,这也比其他答案更快。

petesFun2 使用 data.table 聚合作为 petesFun,但现在跨 column_choice 向量化(而不是像以前那样按项目)。

虽然 petesFun2 对我来说很好,但它确实以不同的顺序保留了行和列。因此,为了与其他答案进行比较,我添加了 petesFun2Clean ,它与其他答案保持相同的顺序。

petesFun2 <-function(myDat) {
  return(myDat[, cbind(.SD, chosen=.SD[[paste0(.BY$column_choice, "_data")]]),
               by=column_choice])
}

petesFun2Clean <-function(myDat) {
  myDat = copy(myDat) # To prevent reference issues
  myDat[, id := seq_len(nrow(myDat))] # Assign an id
  result = myDat[, cbind(.SD, chosen=.SD[[.BY$choice]]),
                 by=list(column_choice, choice=paste0(column_choice, "_data"))]

  # recover ordering and column order.
  return(result[order(id), 
                list(a_data, b_data, c_data, column_choice, chosen)]) 
}

benchmark(benRes<-   myFun(test.dat),
          petesRes<- petesFun(test.dat),
          dowleRes<- dowleFun(test.dat),
          petesRes2<-petesFun2(test.dat),
          petesRes2Clean<- petesFun2Clean(test.dat),
          replications=25,
          columns=c("test", "replications", "elapsed", "relative"))

#                                         test replications elapsed  relative
# 1                  benRes <- myFun(test.dat)           25   0.337  4.160494
# 3             dowleRes <- dowleFun(test.dat)           25   0.191  2.358025
# 5 petesRes2Clean <- petesFun2Clean(test.dat)           25   0.122  1.506173
# 4           petesRes2 <- petesFun2(test.dat)           25   0.081  1.000000
# 2             petesRes <- petesFun(test.dat)           25   4.018 49.604938

identical(petesRes2, benRes)
# FALSE (due to row and column ordering)
identical(petesRes2Clean, benRes)
# TRUE

编辑:我刚刚注意到(正如 Matthew 在评论中提到的那样)我们现在按组:=。所以我们可以放弃 cbind 并简单地做:

myDat[, selected := .SD[[paste0(.BY$column_choice, "_data")]], by=column_choice]

于 2012-04-18T12:10:46.247 回答
1

我们开始for越来越多地使用循环来完成这种任务data.table。基于 Ben 的回答并使用他的基准,以下内容如何:

dowleFun = function(DT) {
  DT = copy(DT)   # Faster to remove this line to add column by reference, but  
                  # included copy() because benchmark repeats test 25 times and
                  # the other tests use the same input table
  w = match(paste0(DT$column_choice,"_data"),names(DT))
  DT[,chosen:=NA_real_]    # allocate new column (or clear it if already exists)
  j = match("chosen",names(DT))     
  for (i in 1:nrow(DT))
      set(DT,i,j,DT[[w[i]]][i])
  DT
}

benchmark(benRes<-myFun(test.dat),
    petesRes<-petesFun(test.dat),
    dowleRes<-dowleFun(test.dat),
    replications=25,columns=c("test", "replications", "elapsed", "relative"),
    order="elapsed")

#                            test replications elapsed relative
# 3 dowleRes <- dowleFun(test.dat)           25    0.30      1.0
# 1      benRes <- myFun(test.dat)           25    0.39      1.3
# 2 petesRes <- petesFun(test.dat)           25    5.79     19.3

如果可以删除,copy()那么它应该更快并且可以更好地扩展到更大的数据集。为了测试这一点,也许创建一个非常大的表并计算一次运行需要多长时间。

在这种情况下,一个简单的for循环可能更容易遵循。

话虽如此,如果i可以是 2-column matrix,那么A[B]可以使用 base 中的语法(其中B包含要选择的行和列位置)并且它是一个单行:

DT[,chosen:=DT[cbind(1:nrow(DT),paste0(column_choice,"_data"))]]

目前你得到这个:

> DT[cbind(1:3,c(4,4,5))]
Error in `[.data.table`(test.dat, cbind(1:3, c(4, 4, 5))) : 
  i is invalid type (matrix). Perhaps in future a 2 column matrix could return
  a list of elements of DT (in the spirit of A[B] in FAQ 2.14). Please let
  maintainer('data.table') know if you'd like this, or add your comments to
  FR #1611.
于 2012-04-17T18:23:30.787 回答
1

当我想到笨重的时候,我会想到旧自行车或旧汽车之类的东西,但也可以通过迭代行来在 R 中做事。因此,下面的内容看起来比您在问题中发布的内容更笨拙,但它以我认为更加矢量化的方式寻求解决方案。以下似乎比您在上面发布的更时尚的代码快约 10 倍(并返回相同的结果)。

这个建议依赖于reshape2包:

library(data.table)
library(reshape2)

我尽可能地添加了“c”column_choice以使事情变得更有趣:

dat=data.table(a_data = c(55,56,57,65), 
  b_data = c(1,2,3,4),c_data=c(1000,1001,1002,1003),
  column_choice = c("a", "c", "a", "b"))

以下是步骤,包装在一个函数中,为基准测试做准备。

myFun<-function(myDat){
# convert data.table to data.frame for melt()ing
  dat1<-data.frame(myDat)
# add ID variable to keep track of things
  dat1$ID<-seq_len(nrow(dat1))
# melt data - because of this line, it's important to only
# pass those variables that are used to select the appropriate value
# i.e., a_data,b_data,c_data,column_choice
  dat2<-melt(dat1,id.vars=c("ID","column_choice"))
# Determine which value to choose: a, b, or c
  dat2$chosen<-as.numeric(dat2$column_choice==substr(dat2$variable,
    1,1))*dat2$value
# cast the data back into the original form
  dat_cast<-dcast(dat2,ID+column_choice~.,
    fun.aggregate=sum,value.var="chosen")
# rename the last variable
  names(dat_cast)[ncol(dat_cast)]<-"chosen"
# merge data back together and return results as a data.table
  datOUT<-merge(dat1,dat_cast,by=c("ID","column_choice"),sort=FALSE)
  return(data.table(datOUT[,c(names(myDat),"chosen")]))
}

这是您打包到函数中的解决方案:

petesFun<-function(myDat){
  datOUT=myDat[, data.table(.SD,
    chosen=.SD[[paste0(.SD$column_choice, "_data")]]),
    by=1:nrow(myDat)]
  datOUT$nrow = NULL
  return(datOUT)
}

这看起来比myFun. 然而,基准测试结果显示出很大的差异:

制作一个更大的data.table:

test.df<-data.frame(lapply(dat,rep,100))
test.dat<-data.table(test.df)

和基准:

library(rbenchmark)

benchmark(myRes<-myFun(test.dat),petesRes<-petesFun(test.dat),
 replications=25,columns=c("test", "replications", "elapsed", "relative"))
#                             test replications elapsed relative
# 1       myRes <- myFun(test.dat)           25   0.412  1.00000
# 2 petesRes <- petesFun(test.dat)           25   5.429 13.17718

identical(myRes,petesRes)
# [1] TRUE

我建议可以用不同的方式解释“笨重”:)

于 2012-04-16T21:22:25.777 回答