【问题标题】:Fastest way to remove all duplicates in R删除 R 中所有重复项的最快方法
【发布时间】:2016-09-06 00:35:00
【问题描述】:

我想删除在向量中多次出现的所有项目。具体来说,这包括字符、数字和整数向量。目前,我正在向前和向后使用duplicated()(使用fromLast 参数)。

在 R 中是否有一种计算效率更高(更快)的方法来执行此操作?下面的解决方案很简单,可以写/读,但是执行两次重复搜索似乎效率低下。也许使用额外数据结构的基于计数的方法会更好?

例子:

d <- c(1,2,3,4,1,5,6,4,2,1)
d[!(duplicated(d) | duplicated(d, fromLast=TRUE))]
#[1] 3 5 6

相关的 SO 帖子 herehere

【问题讨论】:

  • 在我的头顶上:as.integer(names(table(d)[table(d)==1])),不确定这是否真的会更有效
  • 这也有效,但我不确定它的计算效率更高:d[!(d %in% d[duplicated(d)])]
  • duplicated 是一个内部函数并且支持长向量,我认为调用它两次并不像你想象的那么低效。它比使用建议的集合操作解决方案更快
  • 如果担心的话,使用duplicated.default 会缩短大约 50% 的时间
  • @alexis_laz 仅在 d 为严格正整数时有效,否则为 { ud = unique(d); ud[tabulate(match(d, ud)) == 1L] }

标签: r performance duplicates unique


【解决方案1】:

一些时间安排:

set.seed(1001)
d <- sample(1:100000, 100000, replace=T)
d <- c(d, sample(d, 20000, replace=T))  # ensure many duplicates
mb <- microbenchmark::microbenchmark(
  d[!(duplicated(d) | duplicated(d, fromLast=TRUE))],
  setdiff(d, d[duplicated(d)]),
  {tmp <- rle(sort(d)); tmp$values[tmp$lengths == 1]},
  as.integer(names(table(d)[table(d)==1])),
  d[!(duplicated.default(d) | duplicated.default(d, fromLast=TRUE))],
  d[!(d %in% d[duplicated(d)])],
  { ud = unique(d); ud[tabulate(match(d, ud)) == 1L] },
  d[!(.Internal(duplicated(d, F, F, NA)) | .Internal(duplicated(d, F, T, NA)))]
)
summary(mb)[, c(1, 4)]  # in milliseconds
#                                                                                expr      mean
#1                               d[!(duplicated(d) | duplicated(d, fromLast = TRUE))]  18.34692
#2                                                       setdiff(d, d[duplicated(d)])  24.84984
#3                       {     tmp <- rle(sort(d))     tmp$values[tmp$lengths == 1] }   9.53831
#4                                         as.integer(names(table(d)[table(d) == 1])) 255.76300
#5               d[!(duplicated.default(d) | duplicated.default(d, fromLast = TRUE))]  18.35360
#6                                                      d[!(d %in% d[duplicated(d)])]  24.01009
#7                        {     ud = unique(d)     ud[tabulate(match(d, ud)) == 1L] }  32.10166
#8 d[!(.Internal(duplicated(d, F, F, NA)) | .Internal(duplicated(d,      F, T, NA)))]  18.33475

给定cmets,让我们看看它们是否都正确?

 results <- list(d[!(duplicated(d) | duplicated(d, fromLast=TRUE))],
         setdiff(d, d[duplicated(d)]),
         {tmp <- rle(sort(d)); tmp$values[tmp$lengths == 1]},
         as.integer(names(table(d)[table(d)==1])),
         d[!(duplicated.default(d) | duplicated.default(d, fromLast=TRUE))],
         d[!(d %in% d[duplicated(d)])],
         { ud = unique(d); ud[tabulate(match(d, ud)) == 1L] },
         d[!(.Internal(duplicated(d, F, F, NA)) | .Internal(duplicated(d, F, T, NA)))])
 all(sapply(ls, all.equal, c(3, 5, 6)))
 # TRUE

【讨论】:

  • 不如全力以赴d[!(.Internal(duplicated(d, FALSE, FALSE, NA)) | .Internal(duplicated(d, FALSE, TRUE, NA)))]
  • 最好先检查正确性(与测试用例的一致性),然后检查稳健性(字符、整数、数字向量,来自更新的问题;名称(表(...))解决方案失败)和终于有时间了。
  • Yesssss第二名
  • 在更大的向量上进行基准测试不是更干净吗?而且,do.call(all.equal, results) 仅比较“结果”的前两个元素;其他元素作为其他参数传递
  • 看来我的解决方案最后还是先来了)
【解决方案2】:

你可以用rle函数做到这一点:

tmp <- rle(sort(d))
res <- tmp$values[tmp$lengths == 1]

这个想法是找到向量中相同值的计数。

这里有很多选择:Counting the number of elements with the values of x in a vector

编辑

查看基准后,@NBATrends 我开始怀疑。 理论上,与原始 duplicated 逻辑相比,单次通过的计数项目必须快约 2 倍。

我尝试使用data.table

library(data.table)
dt <- data.table(d)
res <-  dt[, count:= .N, by = d][count == 1]$d

以下是三种解决方案的不同样本量的基准(我已将其简化为快速独特的方法):

您可以看到随着样本的增长data.table 开始优于其他方法 (2x)。

这里是重现的代码:

set.seed(1001)
N <- c(3, 4, 5, 6 ,7)
n <- 10^N
res <- lapply(n, function(x) {
d <- sample(1:x/10, 5 * x, replace=T)
d <- c(d, sample(d, x, replace=T))  # ensure many duplicates
dt <- data.table(d)
mb <- microbenchmark::microbenchmark(
  "duplicated(original)" = d[!(duplicated(d) | duplicated(d, fromLast=TRUE))],
  "tabulate" = { ud = unique(d); ud[tabulate(match(d, ud)) == 1L] },
  "data.table" = dt[, count:= .N, by = d][count == 1]$d,
  times = 1,unit = "ms")
sm <- summary(mb)[, c(1, 4, 8)]
sm$size = x
return(sm)

})

res <- do.call("rbind", res)

require(ggplot2)
##The values Year, Value, School_ID are
##inherited by the geoms
ggplot(res, aes(x = res$size, y = res$mean, colour=res$exp)) + 
geom_line() + scale_x_log10() + scale_y_log10() +
geom_point() 

【讨论】:

  • fwiw,标准data.table 语法/这样做的方式是dt[, if(.N == 1) .SD, by = d]
  • 从语法的角度来看很有趣,但在速度方面效率不高。
  • @Bulat 哪个更快取决于重复的分布
  • @eddi,我想不出为什么会这样。
【解决方案3】:

你可以使用set operation:

d <- c(1,2,3,4,1,5,6,4,2,1)
duplicates = d[duplicated(d)]
setdiff(d, duplicates)
[1] 3 5 6

(不确定这是否比上面的代码更有效,但在概念上看起来确实更简洁)

【讨论】:

    猜你喜欢
    • 2017-10-18
    • 2018-05-07
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 2015-11-27
    • 1970-01-01
    • 2015-10-07
    • 2015-07-09
    相关资源
    最近更新 更多