2012-04-16 64 views
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解决方案可能适合上面的基本示例。

非常感谢您的帮助。

回答

3

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

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(。经$ column_choice, “_data”)], 通过= column_choice]

+1

Doh!非常适合按column_choice +1进行分组。必须有一种方法来避免'cbind()'并且进一步减少时间。用于':='按组的大测试用例,实施时。 – 2012-04-18 13:06:16

+1

使用':='按组编辑好的编辑。理想情况下,我们希望避免使用'.SD'来提高效率(以保存为每个组所不需要的所有列填充'.SD')。也许:'myDat [,选择:= myDat [[paste0(column_choice,“_ data”)]] [。I],by = column_choice]'。如果这样做的话,它应该快得多,因为'myDat'的列数增长了。 – 2013-03-27 14:10:32

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 

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

+0

附:我不能融化data.table吗? ahhh,1.8.1会明显解决这个问题。 – BenBarnes 2012-04-16 21:55:08

+0

非常感谢你。我一直在寻找理解融化,这是一个很大的帮助。我肯定会考虑这样一种方法应该表现变得重要。 – 2012-04-17 15:07:02

+0

但是在提问时,我想知道是否有一些非常简单的选项(可能是一条优雅的线条),这将允许以矢量化的方式选择不同的列。也许这样的事情不存在? – 2012-04-17 15:08:08

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. 
+0

谢谢 - 非常有趣的解决方案,我没有意识到data.table可以通过引用传递(虽然我不完全理解引用是如何工作的,但它似乎取决于您是否使用过除了:=或不,在我的初始测试?)。 – 2012-04-18 12:13:24

+0

@Peter不知道你在问什么。做'?“:=”','?copy'和他们的例子部分有帮助吗?同时搜索data.table标签中的“参考”或“:=”。 – 2012-04-18 12:58:14

+0

我现在意识到'newDT = DT'后''newDT [2,col1:= 5]'也会影响'DT',因为这个操作是通过引用的。但是,如果在这两个操作之间我做了其他的操作,比如'newDT $ col2 [2] = 5',那么之后对'newDT'(甚至是那些使用':=')的改变不再反映在'DT '。那么'newDT $ col2 [2] = 5'不知何故打破了参考?也许这应该是一个单独的问题... – 2012-04-18 14:11:04

相关问题