如何高效将宽格式患者信息转换为患者日计数时间序列?
高效实现每日患者计数时间序列
数据说明
给定数据框每行代表一位患者信息,字段说明:
DateStart:患者入院日期DateEnd:患者出院日期Length:DateEnd与DateStart的时间差
示例数据:
| DateStart | DateEnd | Length |
|---|---|---|
| 2020-03-02 | 2020-04-10 | 39 days |
| 2020-03-30 | 2020-04-09 | 10 days |
| 2020-03-15 | 2020-03-28 | 13 days |
| 2020-06-02 | 2020-06-20 | 18 days |
需要转换为每日患者计数的时间序列,示例输出:
| Days | Frequency |
|---|---|
| 2020-03-02 | 1 |
| 2020-03-03 | 1 |
| 2020-03-04 | 1 |
| 2020-03-05 | 1 |
| 2020-03-06 | 1 |
| 2020-03-07 | 1 |
| 2020-03-08 | 1 |
| 2020-03-09 | 1 |
| 2020-03-10 | 1 |
| 2020-03-11 | 1 |
问题描述
当前实现功能的CaseCountGenerator()函数采用多层嵌套循环,运行效率极低,尤其当患者数量或时间跨度较大时性能瓶颈明显,需寻求更高效的实现方案。
现有代码
library(tidyverse) createDays=function(start,end){ Days=seq.Date(from=start,to=end,by=1) return(Days) } CaseCountGenerator=function(Starts,Ends,Length,Days){ Counts=rep(0,length(Days)) for(j in 1:length(Starts)){ for(l in 1:length(Days)){ if(Days[l]==Starts[j]){ for(m in ((1:Length[j])-1)){ Counts[l+m]=Counts[l+m]+1 } break } } } return(Counts) }
示例数据:
tempdf = structure(list(DateStart = structure(c(18323, 18351, 18336, 18415, 18417, 18418, 18428, 18439, 18439, 18438, 18440, 18439, 18444, 18451, 18444, 18452, 18444, 18445, 18450, 18452), tzone = "Asia/Manila", class = "Date"), DateEnd = structure(c(18362, 18361, 18349, 18433, 18470, 18460, 18447, 18460, 18458, 18458, 18459, 18459, 18472, 18463, 18464, 18464, 18458, 18464, 18472, 18470), tzone = "Asia/Manila", class = "Date"), Length = structure(c(39, 10, 13, 18, 53, 42, 19, 21, 19, 20, 19, 20, 28, 12, 20, 12, 14, 19, 22, 18), class = "difftime", units = "days")), row.names = c(NA, -20L), class = c("tbl_df", "tbl", "data.frame"))
原运行方式:
Days=createDays(min(tempdf$DateStart), max(tempdf$DateEnd)) Counts=CaseCountGenerator(tempdf$DateStart, tempdf$DateEnd, tempdf$Length, Days) result=tibble(Days,Counts)
高效实现方案
方案一:利用Tidyverse的行转序列+计数
该方案通过map2生成每个患者的住院日期序列,再展开后按日期计数,补全缺失日期的计数为0,全程使用向量化操作,避免嵌套循环:
library(tidyverse) efficient_case_count <- function(df) { # 生成所有日期范围 full_dates <- tibble(Days = seq.Date(min(df$DateStart), max(df$DateEnd), by = "day")) df %>% # 为每个患者生成住院期间的完整日期序列 mutate(Days = map2(DateStart, DateEnd, ~seq.Date(.x, .y, by = "day"))) %>% unnest(Days) %>% # 按日期统计患者数 count(Days, name = "Frequency") %>% # 关联完整日期范围,补全缺失日期的计数 right_join(full_dates, by = "Days") %>% mutate(Frequency = replace_na(Frequency, 0)) %>% arrange(Days) } # 运行示例 result <- efficient_case_count(tempdf)
方案二:向量化区间赋值
利用日期的数值特性,直接对日期区间进行批量赋值,大幅减少循环次数:
efficient_case_count_vectorized <- function(df) { min_date <- min(df$DateStart) max_date <- max(df$DateEnd) # 将日期转换为数值,便于快速定位索引 date_sequence <- seq.Date(min_date, max_date, by = "day") date_numeric <- as.numeric(date_sequence) start_numeric <- as.numeric(df$DateStart) end_numeric <- as.numeric(df$DateEnd) counts <- integer(length(date_numeric)) # 遍历每个患者,直接对日期区间进行累加 for (i in seq_along(start_numeric)) { start_idx <- which(date_numeric == start_numeric[i]) end_idx <- which(date_numeric == end_numeric[i]) counts[start_idx:end_idx] <- counts[start_idx:end_idx] + 1 } tibble(Days = date_sequence, Frequency = counts) } # 运行示例 result <- efficient_case_count_vectorized(tempdf)
效率对比
原代码使用三层嵌套循环,时间复杂度为O(N*M)(N为患者数,M为天数),数据量增大时性能急剧下降。上述两种方案均通过向量化操作或减少循环层级,将时间复杂度降至O(N+M),在患者数或时间跨度较大时,效率提升可达数十倍甚至上百倍。
内容的提问来源于stack exchange,提问作者Derf
相关产品推荐
相关产品推荐

