我想在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
解决方案可能适合上述基本示例。
非常感谢你的帮助。
答案 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.