【问题标题】:Is there a %in% operator across multiple columns是否有跨多个列的 %in% 运算符
【发布时间】:2014-05-31 14:26:52
【问题描述】:

假设你有两个数据框

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

您想知道所有变量中哪些观察结果是重复的。

这可以通过将列粘贴在一起然后使用 %in% 来完成:

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

因此,第二个观察是 df2 和 df1 中唯一的观察。

有没有更快的方法来生成这个输出——比如 %IN%,它是跨多个变量的 %in%,还是我们应该满足于 apply(paste) 解决方案?

【问题讨论】:

  • one two 这些回答你的问题了吗?

标签: r unique paste


【解决方案1】:

我会去

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 关于“可怕的低效率”(恕我直言有点极端)的说法仍然成立,但如果您喜欢一个仅使用基本 R 并且快速处理少量数据的简洁解决方案,那么可能就是这样。

【讨论】:

  • 简洁、有效并使用了一个我从未见过的简洁的小功能(交互)。你们R R机器!非常感谢您提供帮助并使 R 成为一种很棒的语言。下一阶段:重写我可怕的代码并传播信息(我正在编写供学者使用的代码,但在考虑创建包之前需要更好地编码)。
【解决方案2】:

data.frame 上调用duplicated 或使用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/2 分钟,而仅 1 个数字列需要 5 分钟,约 8.6 倍。现在谁想向其中添加另一个数字列并重试:)?

IIUC,@flodel 使用interaction 的新解决方案应该不会有太大不同,因为它仍然将它们存储为“因子”,其中因子级别必须是字符..

但是这个居然开始换了……

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

【讨论】:

  • 基本等效项:tail(duplicated(rbind(df1, df2)), nrow(df2))。请注意,它假定 df2 没有重复项。
  • @flodel,除了 base:::duplicated 会将所有列粘贴在一起(这意味着强制转换为字符),这与 OP 所做的相同。
  • 为什么要担心粘贴和强制转换字符?
  • 因为只是浪费时间,把时间花在了强制上。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-29
  • 2014-06-20
  • 2014-08-20
  • 2022-01-17
  • 2011-07-25
  • 2021-12-06
相关资源
最近更新 更多