r语言 - For循环使用自定义匹配函数按所有行比较两个数据帧



这是一个r - for循环的后续,通过所有行比较两个数据帧,但应用不同的匹配函数(stringr::str_detect())。

我正试图将数据框d1的2列与数据框d2的2列逐行比较。为了说明这个问题,我创建了虚拟数据集:

d1 <- data.frame(
a = c("a","b","c", "d"),
b = c("aa", "bbb", "ccc", "d")
)
d2 <- data.frame(
a = c("a", "a", "d", "b"),
b = c("aaa", "bbb", "ddd", "bbb")
)

理想情况下,我想标记d1中的所有行,我可以在d2的至少一行中找到匹配。这就是提供所需结果的方法:

output <- matrix(nrow = nrow(d1), ncol =nrow(d2))
for (i in 1:nrow(d1)) {
for (j in 1:nrow(d2)) {
output[i,j] = ifelse(
d1$a[i] == d2$a[j] & str_detect(d2$b[j], d1$b[i]), 
1, 
0)
}
}
d1$flag <- apply(output,1,max)
d1

由于我的d1表的行数可以变得相当大,因此矩阵的大小也将变得巨大。有没有更好的方法来编写这个循环,而不是矩阵我创建一个向量与最大值每行和内存不爆炸?

高效连接

@Roland暗示了一个好的解决方案:fuzzyjoin::fuzzy_*_join。唯一的问题是,它仍然是一个连接,OP想要避免…坦率地说,大多数merge*_join函数都是相对有效的,因为它们在逐行与逐行展开之前进行了比较;当约束太松且接近直角连接时,就会出现低效率。

接下来,我将修改d2,使我们至少有一个两行匹配:

d2 <- data.frame(
a = c("a", "a", "d", "b", "b"),
b = c("aaa", "bbb", "ddd", "bbb", "bbb")
)

从这里,我们需要添加一个行id,连接,然后在该行id上聚合。

d1$rowid <- seq_len(nrow(d1))
out <- fuzzyjoin::fuzzy_left_join(d1, d2, by = c("a", "b"), match_fun = list(`==`, Vectorize(grepl)))
out
#   a.x b.x rowid  a.y  b.y
# 1   a  aa     1    a  aaa
# 2   b bbb     2    b  bbb
# 3   b bbb     2    b  bbb
# 4   c ccc     3 <NA> <NA>
# 5   d   d     4    d  ddd

注意,row wid 2出现了两次(所以我们需要将它们组合起来),而row wid 3不匹配。(旁注:fuzzyjoin通过设计复制所有"列。减少它们很容易,即使看起来很奇怪。)

out2 <- aggregate(a.y ~ rowid + a.x + b.x, data = out, na.action = na.pass,
FUN = function(z) sum(!is.na(z)))
names(out2)[-1] <- c("a", "b", "flag")
out2
#   rowid a   b flag
# 1     1 a  aa    1
# 2     2 b bbb    2
# 3     3 c ccc    0
# 4     4 d   d    1

由行

如果这会导致内存问题,那么下一步将是对其中一个帧逐行执行此操作。

d1$flag <- mapply(function(A1, B1) sum(A1 == d2$a & grepl(B1, d2$b)), d1$a, d1$b)
d1
#   a   b flag
# 1 a  aa    1
# 2 b bbb    2
# 3 c ccc    0
# 4 d   d    1

想要使用str_detectfor loops创建一个解决方案,但实际上它比这里和您原始帖子中提供的解决方案效率低得多:

d1$flag <- 0
for (i in 1:nrow(d1)) {
flag <- list()
for (j in 1:nrow(d2)) {
flag <- rbind(flag, stringr::str_detect(paste(d1[i,1], d1[i,2]), paste(d2[j,1], d2[j,2])))
}
if (any(flag == TRUE)) {
d1[i, 3] <- 1
}
}

如果你不想使用列索引,你可以用">列名"代替它们,只是要确保在它周围加上引号,即d1[i, "flag"]

最新更新