按ID合并数据框:匹配区间内或最近前置处方记录
解决R数据框按ID+日期匹配处方的问题
我懂你现在的痛点:要把测试记录和处方信息关联起来,既要匹配日期落在处方区间内的情况,还要处理测试日期之前最近的处方,而且得完整保留原始测试数据的每一行——之前用data.table的滚动连接没搞定第三行,还改了testdate,确实不对头。
先把你的原始数据再贴一遍,方便参考:
df1 <- data.frame( ID = c("1", "1", "1", "2", "2", "2"), testdate = as.POSIXct(c("2010-3-20", "2018-04-12","2018-04-25","2011-04-17","2011-09-05","2019-04-16")), testvalue = c(17, 35, 44, 65, 21, 22) ) df2 <- data.frame( ID = c("1", "1", "2", "2", "2"), begindate = as.POSIXct(c("2018-04-10","2018-04-30","2011-04-12","2011-07-15","2018-01-21")), enddate = as.POSIXct(c("2018-04-22","2018-05-12","2011-04-30","2011-07-30","2018-01-29")), Dose = c("2x per day", "1x per day", "1x morning", "2x morning", "3x per day") )
需求拆解
咱们的核心需求可以拆成两点:
- 对每个
testdate,优先匹配同ID下testdate落在begindate和enddate之间的处方 - 如果没有符合区间的,就找同ID下
begindate早于等于testdate的所有处方里,begindate最近的那一个 - 必须完整保留
df1的所有原始行,包括testdate的原始值
解决方案1:用dplyr + fuzzyjoin(直观易读)
这个组合能先处理区间匹配,再处理最近前置处方的情况,逻辑清晰:
library(dplyr) library(fuzzyjoin) # 第一步:先匹配所有testdate落在处方区间内的记录 interval_matches <- df1 %>% fuzzy_inner_join( df2, by = c("ID" = "ID", "testdate" = "begindate", "testdate" = "enddate"), match_fun = list(`==`, `>=`, `<=`) ) %>% select(ID = ID.x, testdate, testvalue, begindate, enddate, Dose) # 第二步:找出df1中没有区间匹配的行,再找最近的前置处方 non_interval_rows <- df1 %>% anti_join(interval_matches, by = c("ID", "testdate")) %>% group_by(ID, testdate, testvalue) %>% mutate( # 筛选同ID下begindate <= testdate的处方,取begindate最大的那个 matched_prescription = list( df2 %>% filter(ID == cur_data()$ID, begindate <= cur_data()$testdate) %>% slice_max(begindate, n = 1, with_ties = FALSE) ) ) %>% unnest(matched_prescription, keep_empty = TRUE) # 第三步:合并两种情况的结果,得到最终数据集 final_result <- bind_rows(interval_matches, non_interval_rows) %>% arrange(ID, testdate)
运行后得到的final_result完全符合需求:
- 第1行(ID=1,testdate=2010-03-20):没有任何前置处方,处方列显示NA
- 第2行(ID=1,testdate=2018-04-12):落在第一个处方区间内,匹配到
2x per day - 第3行(ID=1,testdate=2018-04-25):无区间内处方,匹配到最近的前置处方(2018-04-10的记录)
- 所有行都保留了
df1的原始testdate值
解决方案2:改进data.table方法(高效处理大数据)
如果你习惯用data.table,可以修正之前的逻辑,先处理区间匹配,再用滚动连接补全剩余行:
library(data.table) setDT(df1) setDT(df2) # 先做区间匹配,用i.testdate保留原始测试日期 interval_matches <- df2[df1, on = .(ID = ID, begindate <= testdate, enddate >= testdate), .(ID, testdate = i.testdate, testvalue, begindate, enddate, Dose)] # 找出没有匹配的行,用滚动连接找最近的前置处方 non_interval_rows <- df1[!interval_matches, on = .(ID, testdate)] non_interval_matches <- df2[non_interval_rows, on = .(ID = ID, begindate <= testdate), roll = Inf, .(ID, testdate = i.testdate, testvalue, begindate, enddate, Dose)] # 合并结果并排序 final_result_dt <- rbind(interval_matches, non_interval_matches) %>% setorder(ID, testdate)
这个方法和dplyr的结果完全一致,而且适合处理大规模数据集,关键是通过i.testdate避免了原始testdate被替换的问题。
内容的提问来源于stack exchange,提问作者SorayaG
相关产品推荐
相关产品推荐

