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

에 의해 생성 된 "column_choice"의 값에 따라 "a_data"또는 "b_data". 그 결과 데이터 테이블 그러므로 될 것입니다 :

 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'보다 많은 선택 항목이 있으며 생성 할 여러 유형의 열이 있으므로 사용하지 않는 것이 좋습니다. basic ifelse 위의 기본 예제에 적합한 솔루션입니다.

도움 주셔서 감사합니다.

답변

3

내가 지금 제대로 한 라이너를 벡터화 발견했습니다 생각, 즉 빨리이 경우 다른 답변보다 더이기도합니다.

petesFun2는 petesFun으로 data.table 집계를 사용하지만 이전에는 항목별로가 아닌 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 

편집 : =을 (코멘트에 매튜가 언급 한 바와 같이) 난 그냥 우리가 지금 그룹이 나타났습니다. [:]], = column_choice에 의한 = .SD [[paste0 (뒤입니다 $의 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

내가 clunky하다고 생각하면 오래된 자전거 나 오래된 자동차 같은 것이 마음에 떠오르지 만 행을 반복하여 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 

나는 "어설픈는"다른 방법 : 우리는 이것에 대한 더 많은 for 루프를 사용하기 시작하고

+0

추신 : 그래서 우리는 cbind을 놓을 수 있습니다 간단하게 할

myDat을 data.table을 녹일 수 있습니까? 아, 1.8.1로 수정 될 것입니다. – BenBarnes

+0

고맙습니다. 나는 용융을 이해하기 위해 찾고 있었고, 그것은 큰 도움이되었습니다. 성능이 중요 해지면 그러한 방법을 확실히 고려할 것입니다. –

+0

그러나 질문을하면서, 다른 열을 벡터화 된 방식으로 선택할 수있는 아주 간단한 옵션 (아마도 하나의 우아한 라인)이 있는지 궁금합니다. 아마 그런 건 존재하지 않을거야? –

1

을 해석 할 수 있다는 제안 업무 종류는 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()을 제거 할 수 있습니다 더 빨리하고 큰 데이터 세트를 더 확장해야합니다. 이를 테스트하기 위해 아마도 매우 큰 테이블을 만들고 단일 실행 시간이 얼마나 걸릴지를 결정해야합니다.

이 경우 간단한 for 루프를 따르기가 더 쉬울 수 있습니다.

은 2 열 matrix 후 자료 A[B] 구문 ( B 선택하는 행 및 열 위치를 포함하는 경우)을 사용할 수있을 수 i 경우라고 가지며 그것은 하나 라이너 같습니다

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이 참조로 전달 될 수 있다는 것을 알지 못했습니다. 참조가 아직 어떻게 작동하는지 완전히 이해하지 못했지만, 다른 변환을 사용했는지 여부에 달려 있습니다. : = 또는 처음 테스트에서?). –

+0

@Peter 여기서 무엇을 묻고 있는지 잘 모르겠습니다. '? ": ="','? copy' 그리고 예제 섹션이 도움이됩니까? 또한 data.table 태그에서 "reference"또는 ": ="를 검색하십시오. –

+0

newDT = DT 이후 newDT [2, col1 : = 5]는 조작이 참조이므로 DT에 영향을줍니다. 그러나 두 작업 사이에'newDT $ col2 [2] = 5'와 같은 다른 작업을 수행하면'newDT '(': ='를 사용하는 작업도 포함)에 대한 변경 사항이 DT에 더 이상 반영되지 않습니다 '. 그래서'newDT $ col2 [2] = 5'는 어떻게 든 참조를 깨뜨릴까요? 아마도 이것은 별개의 질문이었을 것입니다 ... –

관련 문제