根据另一列的文本整理data.table每行不同列的数据

时间:2012-04-16 14:28:46

标签: r data.table

我想在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"))

我想要一个新列'selected',它包含(每行)来自“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解决方案可能适合上述基本示例。

非常感谢你的帮助。

3 个答案:

答案 0 :(得分: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
编辑:我刚刚注意到(正如马修在评论中提到的那样)我们现在有了小组:=。所以我们可以放弃cbind并简单地执行:

myDat [,选择:=。SD [[paste0(.BY $ column_choice,“_ data”)]],       通过= column_choice]

答案 1 :(得分: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更优雅。然而,基准测试结果显示出很大的差异:

制作更大的数据。表格:

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

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

答案 2 :(得分: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列matrix,则可以使用基数A[B]语法(其中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.