基于多列条件创建同前缀随访年份列(R语言求助)
问题描述
我有如下包含患者出生日期(DOB)、出生年份(Birth_Year)、死亡日期(DOD)、死亡年份(Death_Year)的数据集:
ID DOB Birth_Year DOD Death_Year 1 2016-10-01 2016 2019-10-15 2019 2 2017-07-01 2019 2022-01-10 2022 3 2017-04-15 2017 2020-08-15 2020
需要为2015至2020年的每一年创建随访时间列(Year_2015至Year_2020),规则如下:
- 若为出生年份,随访时间为DOB到当年年末的时长(按年计算,保留小数)
- 若为死亡年份,随访时间为当年年初到DOD的时长(按年计算,保留小数)
- 年份不在出生年份至死亡年份区间内,随访时间为0
- 年份在区间内且非首尾年份,随访时间为1年
预期输出数据集如下:
ID DOB Birth_Year DOD Death_Year Year_2015 Year_2016 Year_2017 Year_2018 Year_2019 Year_2020 1 2016-10-01 2016 2019-06-30 2019 0 0.25 1 1 0.5 0 2 2017-07-01 2019 2022-01-10 2022 0 0 0.5 1 1 1 3 2017-04-15 2017 2020-08-15 2020 0 0 0.3 1 1 0.8
我尝试用dplyr的case_when函数或带if-else的循环创建这些列,但在循环中处理动态列名和多条件遍历时遇到问题,也考虑过用apply函数,求解决方法。以下是我尝试的循环代码:
for (i in 2015:2020) { FoUp_Year <- paste0('Year_', i) df[, Birth_Year_end] <- make_date(year = Birth_Year, month = 12, day = 31) df[, Death_Year_start] <- make_date(year = Death_Year, month = 1, day = 1) if (i<df[, Birth_Year] | i>df[, Death_Year]) { df[,FoUp_Year] <- 0 } else if(i==df[, Birth_Year] && i<df[, Death_Year]) { df[,FoUp_Year] <- df[, Birth_Year_end]-df[,DOB] } else if(i==df[, Death_Year] && i>df[, Birth_Year]) { df[,FoUp_Year] <- df[, DOD]-df[,Death_Year_start] } else if(i==df[, Birth_Year] && i==df[, Death_Year]) { df[,FoUp_Year] <- df[, DOD]-df[,DOB] } else if(i>df[, Birth_Year] && i<df[, Death_Year]) { df[,FoUp_Year] <- 1 } }
解决方法
方法一:用dplyr+tidyr的向量化操作(推荐)
这种方法避免循环,利用tidyverse的函数实现简洁高效的向量化运算,步骤如下:
- 先将日期列转为Date类型,处理无效日期
- 用
crossing展开每行数据与目标年份的组合 - 按规则计算随访时间,最后转回宽格式
代码示例:
library(dplyr) library(tidyr) library(lubridate) # 读取并预处理数据 df <- tibble( ID = 1:3, DOB = ymd(c("2016-10-01", "2017-07-01", "2017-04-15")), Birth_Year = c(2016, 2019, 2017), DOD = ymd(c("2019-06-30", "2022-01-10", "2020-08-15")), Death_Year = c(2019, 2022, 2020) ) # 生成宽格式随访列 result <- df %>% # 展开每行数据与2015-2020年的组合 crossing(Year = 2015:2020) %>% # 计算随访时间 mutate( Follow_Time = case_when( Year < Birth_Year | Year > Death_Year ~ 0, Year == Birth_Year & Year == Death_Year ~ (DOD - DOB) / dyears(1), Year == Birth_Year ~ (ymd(paste0(Year, "-12-31")) - DOB) / dyears(1), Year == Death_Year ~ (DOD - ymd(paste0(Year, "-01-01"))) / dyears(1), TRUE ~ 1 ) ) %>% # 转回宽格式 pivot_wider(names_from = Year, values_from = Follow_Time, names_prefix = "Year_") %>% # 保持原始列顺序 select(ID, DOB, Birth_Year, DOD, Death_Year, starts_with("Year_")) # 查看结果 print(result, digits = 2)
方法二:修正后的循环方法
你的原始循环存在两个核心问题:一是用标量if处理向量数据(R中if不支持向量运算);二是动态列名赋值方式不规范。以下是修正后的代码:
library(lubridate) # 预处理日期列 df$DOB <- ymd(df$DOB) df$DOD <- ymd(df$DOD) # 预计算年末、年初日期(避免循环内重复计算) df$Birth_Year_end <- ymd(paste0(df$Birth_Year, "-12-31")) df$Death_Year_start <- ymd(paste0(df$Death_Year, "-01-01")) # 循环生成随访列 for (i in 2015:2020) { col_name <- paste0("Year_", i) df[[col_name]] <- case_when( i < df$Birth_Year | i > df$Death_Year ~ 0, i == df$Birth_Year & i == df$Death_Year ~ (df$DOD - df$DOB) / dyears(1), i == df$Birth_Year ~ (df$Birth_Year_end - df$DOB) / dyears(1), i == df$Death_Year ~ (df$DOD - df$Death_Year_start) / dyears(1), TRUE ~ 1 ) } # 可选:删除临时辅助列 df <- df %>% select(-Birth_Year_end, -Death_Year_start) # 查看结果 print(df, digits = 2)
关键修正点:
- 用
case_when替代嵌套if-else,支持向量运算 - 用
df[[col_name]]赋值动态列名,比df[,col_name]更稳定 - 将日期预处理放在循环外,提升效率
- 用
dyears(1)计算年数比例,确保结果为按年的小数(如93天≈0.25年)
内容的提问来源于stack exchange,提问作者Yanting Luo
相关产品推荐
相关产品推荐

