4

data.tableに新しい列を追加したいのですが、この列には他の列の1つからのデータが含まれています。ただし、列の選択は、別の列の内容に応じて、行ごとに異なります。それで:

データセットの場合:

     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"))

「column_choice」の値に応じて、「a_data」または「b_data」のデータを(行ごとに)含む新しい列「chosen」が欲しいのですが。したがって、結果のデータテーブルは次のようになります。

     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

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

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 [、chosen:= .SD [[paste0(.BY $ column_choice、 "_data")]]、by = column_choice]

于 2012-04-18T12:10:46.247 に答える
1

不格好だと思うと、古い自転車や古い車のようなものが思い浮かびますが、Rで列を繰り返して何かをすることもあります。したがって、以下はあなたがあなたの質問に投稿したものよりも不格好に見えることが判明しましたが、それは私がよりベクトル化された方法であると思う方法で解決策を追いかけます。以下は、上記で投稿したより洗練されたコードよりも約10倍高速であるように見えます(そして同じ結果を返します)。

reshape2この提案はパッケージに依存しています:

library(data.table)
library(reshape2)

column_choice物事をもう少し面白くするために、可能な限り「c」を追加しました。

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 に答える
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ループをたどるのが簡単になります。

そうは言ってiも、2列のmatrix場合はA[B]、baseの構文を使用でき(B選択する行と列の位置が含まれます)、これは1つのライナーです。

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 に答える