在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.