多列中的%运算符是否为%

时间:2014-05-31 14:26:52

标签: r unique paste

想象一下,你有两个数据框

df1 <- data.frame(V1 = c(1, 2, 3), v2 = c("a", "b", "c"))
df2 <- data.frame(V1 = c(1, 2, 2), v2 = c("b", "b", "c"))

这里是他们的样子,并排:

> cbind(df1, df2)
  V1 v2 V1 v2
1  1  a  1  b
2  2  b  2  b
3  3  c  2  c

您希望知道哪些观察结果是重复的,而不是所有变量

这可以通过将cols粘贴在一起然后使用%in%:

来完成
df1Vec <- apply(df1, 1, paste, collapse= "")
df2Vec <- apply(df2, 1, paste, collapse= "")
df2Vec %in% df1Vec
[1] FALSE  TRUE FALSE

第二次观察是df2和df1中唯一的观察。

是否没有更快的方法来生成此输出 - 例如%IN%,多个变量的百分比%,或者我们应该满足应用(粘贴)解决方案?

2 个答案:

答案 0 :(得分:4)

duplicated上调用data.frame或使用paste将所有列强制转换为字符类型,当数据大小变大时,非常效率低下。 duplicated.data.table方法将它们强制转换为字符,因此非常有效且可以很好地扩展。

这是使用data.table的一种方式:

`%dtIN%` <- function(y, x) {
    tmp = rbindlist(list(x,y))
    len_ = nrow(x)
    tmp[, idx := any(.I <= len_) & .N > 1L, by=names(tmp)]
    tail(tmp$idx, nrow(y))
}

# example:
df1 <- data.frame(V1 = c(1, 2, 3), v2 = c("a", "b", "c"))
df2 <- data.frame(V1 = c(1, 2, 1, 2, 1), v2 = c("b", "b", "b", "c", "b"))

df2 %dtIN% df1
# [1] FALSE  TRUE FALSE FALSE FALSE

基准:

@ flodel(早期)的基准测试很好(见历史),但并没有真正展示这种不必要的强制的真实效果,因为整个数据大小是:

print(object.size(df1), units="Kb") # 783.8 Kb

小于1 MB 。让我们构建一个更大的数据集来查看效果。

第一个基准:

set.seed(45L)
df1 <- data.frame(x=sample(paste0("V", 1:1000), 1e7, TRUE), 
                  y = sample(1e2, 1e7, TRUE), stringsAsFactors=FALSE)
df2 <- data.frame(x=sample(paste0("V", 1:700), 1e6, TRUE),
                  y=sample(1e2, 1e6, TRUE), stringsAsFactors=FALSE)


print(object.size(df1), units="Mb") # 114.5Mb

system.time(ans1 <- df2 %dtIN% df1)
#   user  system elapsed 
#  1.896   0.296   2.265 

system.time(ans2 <- df2 %IN% df1)
#   user  system elapsed 
# 13.014   0.510  14.417 

identical(ans1, ans2) # [1] TRUE

Flodel的解决方案在这里慢了6.3倍。

第二个基准:

这是另一个尝试说服它真的非常低效的例子;):

set.seed(1L)
DF1 <- data.frame(x=rnorm(1e7), y=sample(letters, 1e7, TRUE))
DF2 <- data.frame(x=sample(DF1$x, 1e5, TRUE), y=sample(letters, 1e5, TRUE))

require(data.table)
system.time(ans1 <- DF2 %dtIN% DF1)
#    user  system elapsed 
#  35.024   0.884  37.225 

system.time(ans2 <- DF2 %IN% DF1)   ## flodel's earlier answer
#    user  system elapsed 
# 312.931   2.591 319.652 

仅1个数字列,每分钟1/2分钟,相当于5分钟,约为8.6倍。现在谁想为它添加另一个数字列并再试一次:)?

IIUC,@ flodel使用interaction的新解决方案不应该有太大的不同,因为它仍然将它们存储为&#34;因素&#34;,其中因子级别必须是人物..

但是这个实际上开始交换......

system.time(ans3 <- interaction(DF2) %in% interaction(DF1))
## Had to stop after ~3 min because it took 5.5GB and started to SWAP. 

答案 1 :(得分:4)

我会选择

interaction(df2) %in% interaction(df1)
# [1] FALSE  TRUE FALSE

您可以将其包装在二元运算符中:

"%IN%" <- function(x, y) interaction(x) %in% interaction(y)

然后

df2 %IN% df1
# [1] FALSE  TRUE FALSE

rbind(df2, df2) %IN% df1
# [1] FALSE  TRUE FALSE FALSE  TRUE FALSE

免责声明:我对之前使用do.call(paste, ...)而不是interaction(...)的答案进行了一些修改。如果您愿意,请查阅历史记录。我认为Arun声称可怕的低效率&#34; (有点极端恕我直言)仍然坚持,但如果你喜欢一个只使用基数R的简洁解决方案,并且可能是快速的小数据。