如何实现dplyr Join操作中NA匹配任意值?
问题描述
我对本文稍作修改,补充更多上下文,以防从一开始的思路就有误。详见下方「背景」部分,更抽象地解释问题。
我已查阅过讨论tibble中NA匹配的帖子,现有选项为将NA与其他NA匹配,或不与任何值匹配,但我需要相反的行为:能否在Join操作中让NA(或对应场景下的缺失值)匹配任意其他值?
示例代码
library(tidyverse) # 为简洁起见省略输出 tbl1 <- tibble(subj = 1, run = 1, session=1) tbl2 <- tibble(subj = c(1, NA, 2), run = c(NA, 1, 2), session=c(NA, NA, 1), outcomedata = c(NA, NA, NA) ) tbl2$outcomedata[2][[1]] <- list(temperature=30) tbl2$outcomedata[1][[1]] <- list(height=155, weight=80) tbl2$outcomedata[3][[1]] <- list(temperature=20) tbl1 #> # A tibble: 1 x 3 #> subj run session #> <dbl> <dbl> <dbl> #> 1 1 1 1 tbl2 #> # A tibble: 3 x 4 #> subj run session outcomedata #> <dbl> <dbl> <dbl> <list> #> 1 1 NA NA <list [2]> #> 2 NA 1 NA <list [1]> #> 3 2 2 1 <list [1]> left_join(tbl1, tbl2) #> Joining, by = c("subj", "run", "session") #> # A tibble: 1 x 4 #> subj run session outcomedata #> <dbl> <dbl> <dbl> <list> #> 1 1 1 1 <NULL>
期望结果
我期望tbl2的第一行和第二行能匹配tbl1的唯一行,因为它们在所有非NA属性上匹配;第三行因非NA属性不匹配,不应被匹配。最终输出如下:
#> # A tibble: 2 x 4 #> subj run session outcomedata #> <dbl> <dbl> <dbl> <list> #> 1 1 1 1 <list [2]> #> 2 1 1 1 <list [1]>
背景
为避免思路错误,我补充背景信息。我有多个嵌套JSON文件(在R中实例化为列表),需将其中信息关联到数据中的特定实例:比如一个JSON包含受试者1所有实例的信息(对应tbl2第一行),另一个包含运行1所有实例的信息(对应tbl2第二行)。
我希望将数据中每个参数组合的所有相关信息合并到单独列表中(tbl1是其中一个参数组合),计划先让所有相关项匹配,再通过group_by操作(即group_by(subj, run, session))合并列表(打算用rlist::list.merge)。
解决方案
要实现这种"NA匹配任意值"的join逻辑,常规的left_join做不到,因为它默认只在列值完全相等(包括NA只能匹配NA)时才关联。我们可以换个思路:先生成两张表的所有可能组合,再筛选出符合条件的行。
代码实现
library(tidyverse) # 定义匹配函数:检查tbl2的非NA列是否和tbl1对应列一致 match_na_any <- function(row_tbl1, row_tbl2) { # 获取tbl2中非NA的列名(排除outcomedata,只检查匹配键) non_na_cols <- setdiff(names(row_tbl2)[!is.na(row_tbl2)], "outcomedata") # 如果没有非NA列,直接匹配;否则检查对应列是否相等 if (length(non_na_cols) == 0) { TRUE } else { all(row_tbl1[non_na_cols] == row_tbl2[non_na_cols]) } } # 生成笛卡尔积并筛选符合条件的行 result <- cross_join(tbl1, tbl2, suffix = c("_tbl1", "_tbl2")) %>% rowwise() %>% filter(match_na_any(pick(ends_with("_tbl1")), pick(ends_with("_tbl2")))) %>% # 整理成目标格式,保留tbl1的原始键值 mutate( subj = subj_tbl1, run = run_tbl1, session = session_tbl1 ) %>% select(subj, run, session, outcomedata) %>% ungroup() result #> # A tibble: 2 x 4 #> subj run session outcomedata #> <dbl> <dbl> <dbl> <list> #> 1 1 1 1 <list [2]> #> 2 1 1 1 <list [1]>
逻辑解释
cross_join:先生成两张表的所有行组合,这样我们可以逐个检查每对行是否符合匹配规则match_na_any函数:核心逻辑,只关注tbl2中非NA的匹配键列,只要这些列和tbl1对应列完全相等,就算匹配成功- 最后通过
mutate和select整理成我们需要的格式,保留tbl1的原始键值,确保输出结构和期望一致
如果你的tbl1有多行,这个方法同样适用,会自动为每一行tbl1匹配所有符合条件的tbl2行,完全适配你后续分组合并列表的需求。
内容的提问来源于stack exchange,提问作者Granville
相关产品推荐
相关产品推荐

