R语言如何基于正则表达式ID与年份字段合并两个数据框
R双键(正则ID+精确年份)合并数据框方案
问题复现
需要合并两个R数据框,匹配规则:
- 双字段共同匹配:
year字段做精确等值匹配,ID字段做正则模式匹配(a表ID中.为正则通配符,代表匹配任意单个字符,对应匹配b表的完整ID) - 匹配无歧义,不存在一个a表ID模式匹配多个b表ID的情况
示例输入数据:
a<- data.frame(x=c(1,4,6,8,1,6,7,2),ID=c("132","14.","732","2..","132","14.","732","2.."),year=c(1,1,1,1,2,2,2,2)) b<- data.frame(y=c(2,7,5,5,1,1,2,3),ID=c("132","144","732","290","132","144","732","290"),year=c(1,1,1,1,2,2,2,2))
期望输出:
output<-data.frame(y=c(2,7,5,5,1,1,2,3),x=c(1,4,6,8,1,6,7,2),ID=c("132","144","732","290","132","144","732","290"), year=c(1,1,1,1,2,2,2,2))
此前方案的报错原因:
- 用
substr截取前缀匹配失败:因为通配符位置不固定(如14.是前2位固定、2..是仅第1位固定),固定长度截取会漏匹配 - 直接调用
regex_inner_join(b,a, by=c("ID","year"))报Coercing pattern to a plain character vector:一是传参逻辑错误,未指定不同字段的匹配规则,函数默认将数值型year也当做正则pattern做类型转换;二是未明确正则匹配的方向,把无通配符的b表ID当做了匹配模式。
可行方案
方案1:fuzzyjoin包实现(代码最简洁)
regex_inner_join的默认逻辑是第二个传入表的匹配列作为正则pattern,匹配第一个传入表的对应列值,需要通过match_fun为每个匹配字段单独指定匹配规则,代码如下:
# 首次使用先安装包 # install.packages("fuzzyjoin") library(fuzzyjoin) # 注意传参顺序:存完整ID的b表在前,存正则pattern的a表在后 merge_res <- regex_inner_join( x = b, y = a, by = c("ID" = "ID", "year" = "year"), # 匹配函数顺序和by字段一一对应:ID走正则检测,year走精确相等匹配 match_fun = list(stringr::str_detect, `==`) ) # 整理列名和列顺序,去掉a表冗余的ID、year列 merge_res <- merge_res[, c("y", "x", "ID.x", "year.x")] colnames(merge_res) <- c("y", "x", "ID", "year")
运行结果和期望输出完全一致。
注意:正则匹配默认会做部分匹配,如果你需要严格匹配整串ID(避免短pattern匹配长ID),可以提前把a表的ID加上首尾锚定符:
a$ID <- paste0("^", a$ID, "$"),再执行上面的合并逻辑即可。
方案2:基础R实现(无第三方包依赖)
如果不想安装额外包,可以按年份分组后逐组做ID匹配:
# 按年份拆分两个表 a_split <- split(a, a$year) b_split <- split(b, b$year) # 逐年份匹配 res_list <- lapply(names(b_split), function(yr){ a_sub <- a_split[[yr]] b_sub <- b_split[[yr]] # 为b表每一行匹配对应的x值 b_sub$x <- vapply(b_sub$ID, function(b_id){ match_flag <- vapply(a_sub$ID, function(a_id) grepl(paste0("^", a_id, "$"), b_id), logical(1)) a_sub$x[match_flag] }, numeric(1)) b_sub }) # 合并分块结果 merge_res <- do.call(rbind, res_list) rownames(merge_res) <- NULL
内容的提问来源于stack exchange,提问作者mclofa
相关产品推荐
相关产品推荐

