如何有效地计算data.table的唯一(数字)列向量?

时间:2018-06-11 13:35:38

标签: r data.table

foo <- data.table(x = 1:5/sum(1:5),
                  y = (-4):0/sum((-4):0),
                 z1 = 2:6/sum(2:6),
                 z2 = 2:6/sum(2:6))

假设我有foo数据表(如上所述):

            x   y   z1   z2
1: 0.06666667 0.4 0.10 0.10
2: 0.13333333 0.3 0.15 0.15
3: 0.20000000 0.2 0.20 0.20
4: 0.26666667 0.1 0.25 0.25
5: 0.33333333 0.0 0.30 0.30

如何有效地计算唯一列?在这种情况下只有3。

请假设一般:

  1. foo始终是数据表,而不是矩阵;虽然列总是数字。
  2. foo实际上很大,但是&gt; 20k和ncol> 100
  3. 是否可以在不制作额外数据副本的情况下执行此操作?

    我目前的方法是apply使用paste的列来获取每列的单个值,然后对结果执行length(unique(.)) ...

    data.table::transpose()data.table::uniqueN和其他朋友有什么魔力吗?

3 个答案:

答案 0 :(得分:5)

另一种可能性:

length(unique(as.list(foo)))

这给出了预期的结果:

> length(unique(as.list(foo)))
[1] 3

注意:必须使用length(unique()),因为uniqueN()会返回错误。

根据@Ryan的评论,你也可以这样做:

length(unique.default(foo))

关于速度,两种方法都具有可比性(当在更大的5M行数据集上测量时):

> fooLarge <- foo[rep(1:nrow(foo),1e6)]
> microbenchmark(length(unique.default(fooLarge)), length(unique(as.list(fooLarge))))
Unit: milliseconds
                              expr     min       lq     mean   median       uq       max neval cld
  length(unique.default(fooLarge)) 94.0433 94.56920 95.24076 95.01492 95.67131 103.15433   100   a
 length(unique(as.list(fooLarge))) 94.0254 94.68187 95.17648 95.02672 95.49857  99.19411   100   a

如果您只想保留唯一列,可以使用:

# option 1
cols <- !duplicated(as.list(foo))
foo[, ..cols]

# option 2 (doesn't retain the column names)
as.data.table(unique.default(foo))

给出(显示输出选项1):

            x   y   z1
1: 0.06666667 0.4 0.10
2: 0.13333333 0.3 0.15
3: 0.20000000 0.2 0.20
4: 0.26666667 0.1 0.25
5: 0.33333333 0.0 0.30

答案 1 :(得分:1)

转置并检查非重复

ncol( foo[ , which( !duplicated( t( foo ) ) ), with = FALSE ])

3

答案 2 :(得分:1)

如果您期望大量重复项,可能会更快的另一种方法:

n_unique_cols <- function(foo) {
  K <- seq_along(foo)
  for (j in seq_along(foo)) {
    if (j %in% K) {
      foo_j <- .subset2(foo, j)
      for (k in K) {
        if (j < k) {
          foo_k <- .subset2(foo, k)
          if (foo_j[1] == foo_k[1] && identical(foo_j, foo_k)) {
            K <- K[K != k]
          }
          rm(foo_k)
        }
      }
    }
  }
  length(K)
}

时序:

library(data.table)
create_foo <- function(row, col) {
  foo <- data.table(x = rnorm(row), 
                    y = seq_len(row) - 2L)

  set.seed(1)
  for (k in seq_len(col %/% 2L)) {
    foo[, (paste0('x', k)) := x + sample(-4:4, size = 1)]
    foo[, (paste0('y', k)) := y + sample(-2:2, size = 1)]
  }
  foo
}

library(bench)
res <- 
  press(rows = c(1e5, 1e6, 1e7), 
        cols = c(10, 50, 100), 
        {

          foorc <- create_foo(rows, cols)
          bench::mark(n_unique_cols(foorc), 
                      length(unique(as.list(foorc))))
        })
plot(res)

对于这个数据系列,此函数的速度是其两倍,但其内存消耗增长速度快于unique(as.list(.))

enter image description here