R语言按patient_id分组筛选24小时内两类不同就诊记录的方法
高效实现方案
核心思路是使用分组窗口函数替代join操作,避免同组内所有记录两两匹配的O(n²)复杂度,仅需对分组内排序后的记录做一次线性扫描,时间复杂度为O(n),大数据量下性能提升非常明显。
Tidyverse 实现版本
library(tidyverse) df <- df %>% # 先按患者和就诊日期排序 arrange(patient_id, date_visit) %>% group_by(patient_id) %>% mutate( # 计算当前行与上一行、下一行的就诊时间差(单位:天) lag_diff = as.numeric(date_visit - lag(date_visit), units = "days"), lead_diff = as.numeric(lead(date_visit) - date_visit, units = "days"), # 判断上下行的就诊类型是否和当前行不同 lag_type_diff = type_of_visit != lag(type_of_visit), lead_type_diff = type_of_visit != lead(type_of_visit), # 满足任意一种情况即标记为符合条件:和上一行间隔<=1天且类型不同 / 和下一行间隔<=1天且类型不同 is_qualified = (lag_diff <= 1 & lag_type_diff) | (lead_diff <= 1 & lead_type_diff), # 首尾行的空值替换为FALSE is_qualified = replace_na(is_qualified, FALSE) ) %>% ungroup() %>% # 可选:删除中间辅助列 select(-lag_diff, -lead_diff, -lag_type_diff, -lead_type_diff) # 如果需要仅保留符合条件的行,追加以下代码 df_filtered <- df %>% filter(is_qualified)
超大数据量可选 Data.table 版本
如果数据量在百万级以上,用data.table实现速度会更快:
library(data.table) # 转换为data.table格式并排序 setDT(df) setorder(df, patient_id, date_visit) # 分组计算标记列 df[, `:=`( lag_diff = as.numeric(date_visit - shift(date_visit), units = "days"), lead_diff = as.numeric(shift(date_visit, type = "lead") - date_visit, units = "days"), lag_type_diff = type_of_visit != shift(type_of_visit), lead_type_diff = type_of_visit != shift(type_of_visit, type = "lead") ), by = patient_id] df[, is_qualified := fcoalesce((lag_diff <= 1 & lag_type_diff) | (lead_diff <= 1 & lead_type_diff), FALSE)] # 可选:删除辅助列 df[, c("lag_diff", "lead_diff", "lag_type_diff", "lead_type_diff") := NULL] # 可选:筛选符合条件的行 df_filtered <- df[is_qualified == TRUE]
性能说明
原来的left join方案会将同一个患者的所有记录做笛卡尔积匹配,若单个患者有N条就诊记录就会生成N²条关联结果,数据量大时内存和时间消耗都会急剧升高。窗口函数方案仅需要对排序后的分组数据做一次遍历,无论单患者就诊记录多少,都只做线性次数的运算,性能远高于join方案。
内容的提问来源于stack exchange,提问作者bolleke
相关产品推荐
相关产品推荐

