2012-04-16 61 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

相關問題