【问题标题】:Determine whether column values are unique in data.table判断 data.table 中的列值是否唯一
【发布时间】:2016-06-03 08:26:48
【问题描述】:

我使用 data.table 来存储数据。我试图弄清楚每一行中的某些列是否是唯一的。我想在 data.table 中添加一列,如果有重复值,它将保存值“重复值”,如果没有重复值,则为 NA。我要检查重复的列的名称存储在字符向量中。例如,我创建了我的 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 完成相同的任务。非常感谢任何帮助。

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    我找到了一种使用 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,这很有趣。

    【讨论】:

    • 一个小问题 - 您是否错过了我的解决方案中的 dups := TRUE?很高兴知道我的限制在哪里。
    • @tospig 我认为(可能是错误的)制作向量(TRUE/FALSE 或位置向量)的成本是主要因素,因此不再将其分配为列。此外,使用变化的对象进行基准测试(因为 := 通过引用修改)很奇怪。关于我在最后一句中提到的限制,我刚刚搜索了一下,结果发现它是由包作者所说的“递归调用过多”引起的:stackoverflow.com/a/25877485我几乎从未见过“堆栈溢出”,所以不要真的很了解它。
    • 另一个小问题,我在运行nc=100 时没有问题,但我在使用nc=500 时遇到了困难(R 3.2.3,x86_64-pc-linux-gnu(64 位),Ubuntu 15.10)
    • @tospig 是的,有 500,你正在查看 choose(500,2) # over 100000 列组合,这是一个需要解决的大问题。
    【解决方案2】:

    另一种方法是将“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
    

    【讨论】:

    • @Frank:谢谢,你是对的;我认为在当前编辑之后似乎还不错。我想我为最少的代码尝试了太多......!
    【解决方案3】:

    具有一些优雅的单线

    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
    

    【讨论】:

      【解决方案4】:

      我确定还有其他方法

      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
      

      我对速度优势感兴趣,因此我对这两个加上 Ananda 的解决方案进行了基准测试:

      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  
      

      【讨论】:

      • 我一般不喜欢eval(parse()) 方法,但这里的逻辑很好,而且可以很好地扩展。 +1
      • @AnandaMahto 我同意,我一般不“喜欢”使用它,因为它会变得丑陋,但它确实有它的用途。
      【解决方案5】:

      你应该可以做这样的事情:

      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
      

      如果你真的想要“有重复”这个词,请相应地进行调整,但请注意,使用逻辑值可能会更容易,就像我在此处的回答一样。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-20
        • 2018-09-29
        • 2017-08-30
        • 2018-08-27
        相关资源
        最近更新 更多