2016-02-22 19 views
4

使用data.table存儲數據。我試圖弄清楚每一行中的某些列是否是唯一的。我想向data.table添加一列,如果有重複的值,它將保存值「重複值」,如果沒有重複值,則爲NA。我想檢查重複的列的名稱存儲在一個字符向量中。例如,我創建我的data.table:確定列值是否唯一在data.table中

tmpdt<-data.table(a=c(1,2,3,4,5), b=c(2,2,3,4,5), c=c(4,2,2,4,4), d=c(3,3,1,4,5)) 
> tmpdt 
    a b c d 
1: 1 2 4 3 
2: 2 2 2 3 
3: 3 3 2 1 
4: 4 4 4 4 
5: 5 5 4 5 

我有另一個變量,指出我需要檢查重複的列。能夠將列名存儲在字符向量中並且不需要「知道」它們是非常重要的(因爲它們將作爲參數傳遞給函數)。

dupcheckcols<-c("a", "c", "d") 

我所要的輸出是:

> tmpdt 
    a b c d  Dups 
1: 1 2 4 3  <NA> 
2: 2 2 2 3 Has Dups 
3: 3 3 2 1  <NA> 
4: 4 4 4 4 Has Dups 
5: 5 5 4 5 Has Dups 

如果我用data.frame,這很容易。我可以簡單地使用:

tmpdt<-data.frame(a=c(1,2,3,4,5), b=c(2,2,3,4,5), c=c(4,2,2,4,4), d=c(3,3,1,4,5)) 
tmpdt$Dups<-NA 
tmpdt$Dups[apply(tmpdt[,dupcheckcols], 1, function(x) {return(sum(duplicated(x))>0)})]<-"Has Dups" 
> tmpdt 
    a b c d  Dups 
1 1 2 4 3  <NA> 
2 2 2 2 3 Has Dups 
3 3 3 2 1  <NA> 
4 4 4 4 4 Has Dups 
5 5 5 4 5 Has Dups 

但我無法弄清楚如何用data.table完成同樣的任務。任何幫助是極大的讚賞。

回答

5

我敢肯定還有其他的方法

tmpdt[, dups := tmpdt[, dupcheckcols, with=FALSE][, apply(.SD, 1, function(x){sum(duplicated(x))>0})] ] 
# a b c d dups 
#1: 1 2 4 3 FALSE 
#2: 2 2 2 3 TRUE 
#3: 3 3 2 1 FALSE 
#4: 4 4 4 4 TRUE 
#5: 5 5 4 5 TRUE 

更令人費解,但稍快(在計算方面)的方法是構建過濾條件中i,然後在j參照

更新
expr <- paste(apply(t(combn(dupcheckcols,2)), 1, FUN=function(x){ paste0(x, collapse="==") }), collapse = "|") 
# [1] "a==c|a==d|c==d" 

expr <- parse(text=expr) 
tmpdt[ eval(expr), dups := TRUE ] 
# a b c d dups 
#1: 1 2 4 3 NA 
#2: 2 2 2 3 TRUE 
#3: 3 3 2 1 NA 
#4: 4 4 4 4 TRUE 
#5: 5 5 4 5 TRUE 

我感興趣的是速度的好處,所以我已經基準這兩個加阿難的解決方案:

library(microbenchmark) 

tmpdt<-data.table(a=c(1,2,3,4,5), b=c(2,2,3,4,5), c=c(4,2,2,4,4), d=c(3,3,1,4,5)) 
t1 <- tmpdt 
t2 <- tmpdt 
t3 <- tmpdt 

expr <- paste(apply(t(combn(dupcheckcols,2)), 1, FUN=function(x){ paste0(x, collapse="==") }), collapse = "|") 
expr <- parse(text=expr) 

microbenchmark(
#Ananda's solution 
t1[, dups := any(duplicated(unlist(.SD))), by = 1:nrow(tmpdt), .SDcols = dupcheckcols], 

t2[, dups := t2[, dupcheckcols, with=FALSE][, apply(.SD, 1, function(x){sum(duplicated(x))>0})] ], 

t3[ eval(expr), dups := TRUE ] 
) 
#  min  lq  mean median  uq  max neval cld 
# 531.416 552.5760 577.0345 565.182 573.2015 1761.863 100 b 
#1277.569 1333.2615 1389.5857 1358.021 1387.9860 2694.951 100 c 
# 265.872 283.3525 293.9362 292.487 301.1640 520.436 100 a 
+1

我一般不是'eval(parse())'方法的粉絲,但是這裏的邏輯很好,它的尺度非常好。 +1 – A5C1D2H2I1M1N2O1R2T1

+1

@AnandaMahto我同意,我一般不喜歡使用它,因爲它可能會變得醜陋,但它確實有它的用處。 – tospig

3

你應該能夠做這樣的事情:

tmpdt[, dups := any(duplicated(unlist(.SD, use.names = FALSE))), 
     by = 1:nrow(tmpdt), .SDcols = dupcheckcols] 
tmpdt 
# a b c d dups 
# 1: 1 2 4 3 FALSE 
# 2: 2 2 2 3 TRUE 
# 3: 3 3 2 1 FALSE 
# 4: 4 4 4 4 TRUE 
# 5: 5 5 4 5 TRUE 

相應的調整,如果你真的想要的話「有複本」,但要注意,它可能會更容易使用邏輯值,如我在這裏回答。

3

我發現了一種與RCPP做到這一點,following an example by hadley (under "Sets")

// [[Rcpp::plugins(cpp11)]] 
#include <Rcpp.h> 
#include <unordered_set> 
using namespace Rcpp; 

// [[Rcpp::export]] 
LogicalVector anyDupCols(IntegerMatrix x) { 
    int nr = x.nrow(); 
    int nc = x.ncol(); 
    LogicalVector out(nr, false); 

    std::unordered_set<int> seen; 
    for (int i = 0; i < nr; i++) { 
     seen.clear(); 
     for (int j = 0; j < nc; j++){ 
      int xij = x(i,j); 
      if (seen.count(xij)){ out[i] = true; break; } 
      else seen.insert(xij); 
     } 
    } 

    return out; 
} 

要使用它,把它放在一個CPP文件並運行

library(Rcpp) 
sourceCpp("anyDupCols.cpp") 
anyDupCols(as.matrix(DT)) 

它確實很好的基準:

nc = 30 
nv = nc^2 
n = 1e4 

set.seed(1) 
DT = setDT(replicate(nc, sample(nv, n, replace = TRUE), simplify=FALSE)) 

library(microbenchmark) 
microbenchmark(
    ananda = DT[, any(duplicated(unlist(.SD, use.names = FALSE))), by = 1:nrow(DT)]$V1, 
    tospig = { 
     expr = parse(text=paste(apply(t(combn(names(DT),2)),1,FUN = 
      function(x){ paste0(x, collapse="==") }), collapse = "|")) 
     DT[, eval(expr)] 
    }, 
    cpp = anyDupCols(as.matrix(DT)), 
    alex = ff(DT), 
    tscharf = apply(DT,1,function(row) any(duplicated(row))), 
    unit = "relative", times = 10 
) 

Unit: relative 
    expr  min  lq  mean median  uq  max neval cld 
    ananda 2.462739 2.596990 2.774660 2.659898 2.869048 3.352547 10 c 
    tospig 3.118158 3.253102 3.606263 3.424598 3.885561 4.583268 10 d 
    cpp 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 10 a 
    alex 1.295415 1.927802 1.914883 1.982580 2.029868 2.538143 10 b 
tscharf 2.112286 2.204654 2.385318 2.234963 2.322206 2.978047 10 bc 

如果我去nc = 50,@ tospig的expr變得太長了R處理,我得到node stack overflow,這很有趣。

+0

小問題 - 您是否錯過了我的解決方案中的'dups:= TRUE'? 很高興知道我的限制在哪裏。 – tospig

+0

@tospig我想(可能是錯誤的),製作矢量的成本(無論是真/假還是矢量的位置)是主要的東西,因此將其分配爲列。此外,基準與更改對象(因爲':='修改引用)是奇怪的。關於我在最後一句中提到的限制,我剛剛搜索了它,結果發現它是由一個包作者根據「遞歸調用太多」引起的:http://stackoverflow.com/a/25877485我幾乎沒有看到「堆棧溢出「,所以不太瞭解它。 – Frank

+0

另一個小問題,我運行'nc = 100'沒有問題,但是很難與'nc = 500'(R 3.2.3,x86_64-pc-linux-gnu(64位),Ubuntu 15.10) – tospig

1

一個班輪與一些優雅

  1. 定義列

  2. 循環下來的行

  3. 看看是否有任何愚弄

tmpdt[,dups:=apply(.SD,1,function(row) any(duplicated(row))),.SDcols = dupcheckcols] 

> tmpdt 
    a b c d dups 
1: 1 2 4 3 FALSE 
2: 2 2 2 3 TRUE 
3: 3 3 2 1 FALSE 
4: 4 4 4 4 TRUE 
5: 5 5 4 5 TRUE 
1

另一種方式是製表「tmpdt」沿其行,並找到該行有一個元素的不止一個:

tmpdt2 = tmpdt[, dupcheckcols, with = FALSE] # subset tmpdt 
colSums(table(unlist(tmpdt2), row(tmpdt2)) > 1L) > 0L 
# 1  2  3  4  5 
#FALSE TRUE FALSE TRUE TRUE 

table偷看我們可以用類似顯著加快步伐:

ff = function(x) 
{ 
    lvs = Reduce(union, lapply(x, function(X) if(is.factor(X)) levels(X) else unique(X))) 
    x = lapply(x, function(X) match(X, lvs)) 
    nr = length(lvs); nc = length(x[[1L]]) 
    tabs = "dim<-"(tabulate(unlist(x, use.names = FALSE) + (0:(nc - 1L)) * nr, nr * nc), 
        c(nr, nc)) 
    colSums(tabs > 1L) > 0L 
} 
ff(tmpdt2) 
#[1] FALSE TRUE FALSE TRUE TRUE 
+1

@Frank:謝謝,你是對的;我認爲目前的編輯看起來沒問題。我想我爲了最小的代碼嘗試了太多..! –

相關問題