2012-04-16 2 views
4

他の列のデータを含むdata.tableに新しい列を追加したいとします。ただし、列の選択は、別の列の内容に応じて行ごとに異なります。私は(行ごと)含む新しい列、 '選ばれた'、のいずれかからのデータをみたい別の列のテキストに応じて、data.tableの行ごとに異なる列からデータを照合する

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 
[1,]  55  1    a 
[2,]  56  2    a 
[3,]  57  3    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」だけでなく、これらの種類の列を生成するものもあります。上記の基本的な例に適していると思われる解決策をご紹介します。

ありがとうございました。

答えて

3

を私は今見つけた、適切に1つのライナーをベクトル化私は、それはこの場合にも他の回答よりも高速だと思います。

petesFun2は、petesFunとしてdata.table集約を使用していますが、以前はitemごとではなく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 

編集:私たちは現在、group:=でコメントしています(コメントの中のMatthewによると)。 [:]]、 = column_choiceによって= .SD [[paste0(.BYの$ column_choice、 "_data")、選択]

+1

Doh! column_choice、+1でグループ化するのは非常にいいです。 'cbind()'を避け、時間をさらに短縮する手段が必要です。実装されている場合、グループごとの ':='に対する素晴らしいテストケース。 –

+1

グループで ':='を使って素敵な編集をしました。理想的には、 '.SD'を効率的に使用することを避けたいと思います(' .SD'に各グループに必要でないすべての列を入れるのを避けるため)。たぶん:myDat [、selected:= myDat [[paste0(column_choice、 "_ data")]] [.I]、by = column_choice] 'それが動作すれば、 'myDat'の列の数が増えるにつれて大幅に高速化するはずです。 –

1

私が不器用だと思うと、古い自転車や古い車のようなものが気になるだけでなく、行を繰り返してRでやります。だからあなたの質問に投稿したものよりも控えめに見えることが判明しましたが、私はよりベクトル化された方法だと思っています。以下は、あなたが上に投稿したより洗練されたコードより約10倍速い(同じ結果を返す)ようです。

この提案はreshape2パッケージに依存している:私は物事はもう少し面白くすることが可能にcolumn_choiceとして "C" を追加しました

library(data.table) 
library(reshape2) 

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

P.S.:だから我々はCBINDをドロップすることができますし、簡単に行う

myDatをdata.tableを溶かすことはできませんか?ああ、1.8.1、それは明らかに、修正されます。 – BenBarnes

+0

ありがとうございます。私は融解を理解するために探してきました、そして、それは大きな助けとなりました。私はそのような方法がパフォーマンスが重要になると確信しています。 –

+0

しかし、疑問を尋ねたところでは、別の列をベクター化された方法で選択できる非常に単純なオプション(たぶん1つのエレガントな行)があるのだろうかと思っていました。多分そのようなものは存在しないでしょうか? –

1

我々はforこのため、より多くのループを使用し始めています仕事の種類はdata.tableです。ベンの答えと彼のベンチマークを使用して上に構築、どのように以下について:

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()を削除することができた場合は、それがより速くなると、より大きなデータセットへのより良いスケーリングする必要があります。これをテストするには、非常に大きなテーブルを作成し、1回の実行に要する時間を作成します。

この場合、単純なループforが簡単に従うことができます。

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

i 2列matrixことができれば、その後、塩基でA[B]構文を使用することができる(Bを選択するために、行と列の位置を含む場合)とそれが1つのライナーだ、と述べましたこの取得瞬間

> 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が参照渡しできることはわかりませんでした(ただし、参照がまだどのように機能しているかは完全に理解できませんが、 :=または、私の最初のテストでは?)。 –

+0

@Peterあなたがここで何を求めているかわからない。 '?":= "'、 '?copy'を実行すると、それらのサンプルセクションは役に立ちますか?また、data.tableタグで "reference"または ":="を検索します。 –

+0

私は、 'newDT = DT'の後、newDT [2、col1:= 5]'も 'DT'に影響することを認識しています。しかし、 'newDT $ col2 [2] = 5'のような何か他の操作を行った場合、' newDT'( ':='を使っているものも含めて)の変更は 'DT '。それで 'newDT $ col2 [2] = 5'はどういうわけか参照を破りますか?おそらく、これは別個の質問だったはずです... –

関連する問題