【问题标题】:How to efficiently count unique (numeric) column vectors of a data.table?如何有效地计算 data.table 的唯一(数字)列向量?
【发布时间】:2018-11-20 18:44:26
【问题描述】:
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实际上很大,nrow > 20k and ncol > 100

是否可以在不制作额外数据副本的情况下做到这一点?

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

data.table::transpose()data.table::uniqueN,也许还有其他一些朋友有什么魔法吗?

【问题讨论】:

  • 快速优化:uniqueN 是一个导出函数,因此请使用它而不是 length(unique())
  • 请不要在微数据集上进行基准测试。微秒基准不会告诉您它如何在真实数据集上扩展。
  • @DavidArenburg:我明白了……

标签: r data.table


【解决方案1】:

转置并检查非重复项

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

3

【讨论】:

  • t( foo ) 在内存中临时创建foo 的副本,不是吗?
  • 真的需要which 吗?
  • 根据 microbenchmark,'which' 似乎(略微)加快了速度。(361 us 对 379 us)不知道为什么......
【解决方案2】:

另一种可能性:

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

【讨论】:

  • as.list 是否制作副本?如果是这样,是否可以在data.table(或data.frame)上调用uniquelist 方法?
  • 据我所知,unique 没有 list 方法,另请参阅 methods('unique')
  • @Jaap:很好,简洁的答案。是否可以控制比较的数值容差?
  • methods('unique'),你可以做length(unique.default(foo))来避开as.list。仅当 as.list 创建一个副本时才值得这样做,我不确定。
  • @plant 应该是可能的,但是你必须用all.equal 编写你自己的函数,它有一个公差参数。
【解决方案3】:

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

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(.))快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-10
    • 2016-11-22
    • 1970-01-01
    • 2018-03-16
    相关资源
    最近更新 更多