R语言如何基于字符串匹配合并(连接)含同义词列的数据框
基于同义词列匹配合并两个R数据框
简而言之:我需要基于包含标识符多个同义词的列合并两个数据框。
这是我首次在Stack Overflow提问,我已在这个看似简单的问题上卡顿许久,若能获得帮助将不胜感激。
我手中有两个数据框,二者均包含一个或多个描述行信息的字符列。我需要合并这两个数据框:尽管部分记录可直接匹配,但两表没有统一的标识列,仅能通过主标识或其多个同义词完成匹配。
示例数据
以下是两个数据框的简单可复现示例:
df1 <- data.frame("Name" = c("a", "b", "c")) df2 <- data.frame("Symbol" = c("a", "two", "d"), "Synonym" = c("", "b | 2", "c-four"))
匹配规则:
- 第一行直接匹配("a")
- 第二行通过同义词匹配("b")
- 第三行不匹配("c")
- 未匹配的行("d")可选择是否保留在输出结果中
期望得到的合并后数据框:
goal_df <- data.frame("Name" = c("a", "b", "c", NA), "Symbol" = c("a", "two", NA, "d"), "Synonym" = c("", "b | 2", NA, "c-four"))
预期匹配逻辑
伪代码逻辑如下:
- 在df1中新建名为
match的列,用于存储df1每一行与df2的匹配结果 - 对比
df1$Name与df2$Symbol,若二者完全一致,则将对应df2$Symbol填入df1$match - 若二者不一致,则在
df2$Synonym列中查找精确匹配项,若找到则将对应df2$Symbol填入df1$match - 基于
df1$match与df2$Symbol列完成两个数据框的合并
已尝试的方案与报错
首先将Synonym列拆分为独立的同义词字符串以支持精确对比(每行可能对应多个同义词,拆分后生成列表类型列):
library(dplyr) df2 <- df2 %>% mutate(syns = strsplit(df2$Synonym, split = "|", fixed = T)) df3 <- df1 %>% mutate( match = case_when( Name %in% df2$Symbol ~ Name, Name %in% df2$syns ~ df2$Symbol, # 问题行 TRUE ~ NA_character_)) %>% left_join(df2, by = c("match" = "Symbol"), all.x = T)
针对问题行尝试过的替代写法:
sapply(Name, grep, df2$syns) ~ df2$Symbol, # 问题行 sapply(paste0("\b", Name, "\b"), grep, df2$syns) ~ df2$Symbol, # 问题行
运行后报错信息:
Error in `mutate()`: ! Problem while computing `match = case_when(...)`. Caused by error in `case_when()`: ! LHS of case 2 (`sapply(Name, grep, df2$syns)`) must be a logical vector, not a list. Run `rlang::last_error()` to see where the error occurred.
目前存在两个核心疑问:
- 如何实现精确匹配(例如避免将"c"与"c-four"误判为匹配)
- 字符串对比匹配成功后如何完成两个数据框的合并(case_when中无法直接赋值来自另一个数据框的值)
解决方案
核心思路是先把df2拆成「主标识+所有同义词」的长表,每个匹配键单独占一行,再做连接,既可以保证精确匹配,也不用在case_when里写复杂的跨表判断。
library(dplyr) library(tidyr) # 第一步:把df2处理成长格式,每个匹配键(Symbol本身+拆分后的每个同义词)单独一行 df2_long <- df2 %>% # 按|分隔拆分同义词为多行 separate_longer_delim(Synonym, delim = "|") %>% mutate( # 去掉同义词前后空格,避免" b"这类带空格的内容匹配失败 Synonym = trimws(Synonym), # 统一生成匹配键:空同义词用Symbol本身,非空用同义词 match_key = ifelse(Synonym == "", Symbol, Synonym) ) %>% # 按Symbol分组,还原原表的Synonym列格式 group_by(Symbol) %>% mutate(origin_syn = paste0(Synonym[Synonym != ""], collapse = " | ")) %>% ungroup() %>% # 去重,避免重复匹配键导致连接后出现重复行 distinct(Symbol, match_key, Synonym = origin_syn) # 第二步:全连接保留两边所有未匹配行,和预期输出一致;如果不需要保留df2未匹配行换为left_join即可 result <- df1 %>% full_join(df2_long, by = c("Name" = "match_key")) %>% # 调整列顺序和预期结果一致 select(Name, Symbol, Synonym)
运行后输出结果和给出的goal_df完全一致:
Name Symbol Synonym 1 a a 2 b two b | 2 3 c <NA> <NA> 4 <NA> d c-four
原有写法报错原因说明
df2$syns是列表列,%in%无法直接判断向量元素是否在列表的各个子元素中,所以第二行判断逻辑本身不成立case_when要求等式左边必须是逻辑向量,sapply(grep())返回的是匹配位置的列表,不是TRUE/FALSE的逻辑值,因此触发类型错误- 直接在
case_when里赋值df2$Symbol会出现长度不匹配问题,无法保证匹配到的Symbol和当前df1的行长度一一对应 - 拆分同义词为独立行后使用完全相等判断,不会出现"c"误匹配"c-four"的问题,二者字符串完全不相等,天然满足精确匹配要求
内容的提问来源于stack exchange,提问作者Vanessa Linke
相关产品推荐
相关产品推荐

