You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用R语言基于DataFrame标识符高效查询90天内既往入院记录?

问题描述

我有两个DataFrame:

  • sample:包含患者入院详情及微生物检测结果,每行字段包括患者ID(hid)、入院ID(SpellID)、标本ID(SpecID)、入院时间(AdmDateTime)、出院时间(DisDateTime)和标本采集日期(dc);
  • wmward_long:包含患者的全部入院记录。

需求:为sample新增Adm_Prior_90d列,标记该患者在标本采集日(dc)前90天是否存在其他入院记录(无论该记录是否关联微生物检测)。

我尝试用自定义函数结合mapply逐行处理,但运行极慢甚至冻结,原代码如下:

# 注:原代码中sample的hid列存在输入错误,已修正为独立患者ID以保证逻辑通顺
sample <- data.frame(
"hid" = c("a123", "B456", "c567", "d890"),
"SpellID" = c(12783, 54462, 92369, 73682), 
"AdmDateTime" = c("2021-04-04 06:17:00","2021-04-04 06:20:00","2021-04-04 06:33:00", "2021-04-04 06:56:00"),
"DisDateTime" = c("2021-04-14 06:17:00","2021-04-24 06:20:00","2021-05-05 06:33:00", "2022-04-04 06:56:00"),
"SpecID" = c("c1893", "c6790", "c8370", "c7908"), 
"dc" = c("2021-04-05 06:17:00","2021-04-06 06:20:00","2021-04-05 06:33:00", "2021-04-06 06:56:00"))

wmward_long <- data.frame(
"hid" = c("a123", "B456", "c567", "d890", "a123", "B456"),
"SpellID" = c(12783, 54462, 92369, 73682, 89097, 54444), 
"AdmDateTime" = c("2021-04-04 06:17:00","2021-04-04 06:20:00","2021-04-04 06:33:00", "2021-04-04 06:56:00", "2021-03-04 06:17:00","2021-02-04 06:20:00"),
"DisDateTime" = c("2021-04-14 06:17:00","2021-04-24 06:20:00","2021-05-05 06:33:00", "2022-04-04 06:56:00", "2021-03-20 06:17:00","2021-02-27 06:20:00"))

adm_prior_90d <- function(culture_date, hospital_ID, spell_ID) {
recent_admissions <- filter(wmward_long,
hid == hospital_ID &
SpellID != spell_ID &
DisDateTime < culture_date & DisDateTime >= culture_date - 90)
return(nrow(recent_admissions) > 0)
}

sample <- sample %>%
mutate(AdmPrior90d = mapply(adm_prior_90d, dc, hid, SpellID))

请问有没有更高效的实现方式?

高效解决方案

逐行循环(如mapply/apply)的效率极低,尤其是数据量大时,因为每次循环都要重新过滤整个数据集。推荐用向量化操作或分组连接的方式,以下是两种高效实现:

方法1:使用dplyr(tidyverse生态)

library(dplyr)
library(lubridate)

# 第一步:转换日期类型(原代码中日期为字符型,必须转为时间格式才能正确计算)
sample <- sample %>%
  mutate(across(c(AdmDateTime, DisDateTime, dc), as.POSIXct))

wmward_long <- wmward_long %>%
  mutate(across(c(AdmDateTime, DisDateTime), as.POSIXct))

# 第二步:连接+分组判断,替代逐行循环
sample <- sample %>%
  # 按患者ID连接所有入院记录
  left_join(wmward_long %>% select(hid, SpellID_other = SpellID, DisDateTime_other = DisDateTime), 
            by = "hid") %>%
  # 筛选符合条件的记录:非当前入院、出院日期在标本采集日前90天内
  mutate(valid = (SpellID_other != SpellID) & 
           (DisDateTime_other < dc) & 
           (DisDateTime_other >= dc - days(90))) %>%
  # 按原sample的行分组,判断是否存在有效记录
  group_by(hid, SpellID, SpecID, AdmDateTime, DisDateTime, dc) %>%
  summarise(Adm_Prior_90d = any(valid, na.rm = TRUE), .groups = "drop")

方法2:使用data.table(适合超大数据集)

data.table的连接和分组操作效率更高,尤其当数据量达到百万级以上时:

library(data.table)

# 转换为data.table并处理日期类型
setDT(sample)[, c("AdmDateTime", "DisDateTime", "dc") := lapply(.SD, as.POSIXct), .SDcols = c("AdmDateTime", "DisDateTime", "dc")]
setDT(wmward_long)[, c("AdmDateTime", "DisDateTime") := lapply(.SD, as.POSIXct), .SDcols = c("AdmDateTime", "DisDateTime")]

# 连接后筛选条件,再分组聚合判断
sample <- wmward_long[sample, on = "hid", allow.cartesian = TRUE][
  SpellID != i.SpellID & DisDateTime < i.dc & DisDateTime >= i.dc - 86400*90, # 86400为一天的秒数
  .(Adm_Prior_90d = .N > 0), 
  by = .(hid, SpellID = i.SpellID, SpecID = i.SpecID, AdmDateTime = i.AdmDateTime, DisDateTime = i.DisDateTime, dc = i.dc)
]

# 补全无匹配记录的行,标记为FALSE
sample <- sample[unique(sample[, .(hid, SpellID, SpecID, AdmDateTime, DisDateTime, dc)]), on = names(sample)[1:6]]
sample[is.na(Adm_Prior_90d), Adm_Prior_90d := FALSE]

关键优化点

  • 避免逐行循环:向量化操作一次性处理整个数据集,效率远高于逐行过滤;
  • 预处理日期类型:确保日期计算正确,同时避免重复转换;
  • 分组聚合替代逐行判断:通过连接后分组统计,减少重复计算。

内容的提问来源于stack exchange,提问作者banananada

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 10:04:52