使用R tidyverse计算长格式数据中测量2与各测量的天数差
分组计算个体重复测量的日期差值问题
问题说明
现有长格式重复测量数据集,字段包括:
obs_id:个体标识time:测量序号(1、2、3)date:测量1/2的日期(若存在)date_3:测量3的单独日期(若存在)
需要生成days_2_to_time字段:
- 存在测量2的个体,每行存储该行日期与测量2日期的天数差(测量1/2用
date,测量3用date_3) - 无测量2的个体,该字段为缺失值
示例数据集:
library(tidyverse) library(lubridate) sample_data <- tribble( ~obs_id, ~time, ~date, ~date_3, "a", 1, ymd("2017-10-28"), NA_Date_, "a", 2, ymd("2017-11-09"), NA_Date_, "a", 3, NA_Date_, ymd("2017-11-12"), "b", 1, ymd("2017-10-28"), NA_Date_, "b", 2, ymd("2017-11-09"), NA_Date_, "b", 3, NA_Date_, NA_Date_, "c", 1, ymd("2017-10-05"), NA_Date_, "c", 3, NA_Date_, ymd("2017-10-29"), )
预期结果:
expected_result <- tribble( ~obs_id, ~time, ~date, ~date_3, ~days_2_to_time, "a", 1, ymd("2017-10-28"), NA_Date_, -12, "a", 2, ymd("2017-11-09"), NA_Date_, 0, "a", 3, NA_Date_, ymd("2017-11-12"), 3, "b", 1, ymd("2017-10-28"), NA_Date_, -12, "b", 2, ymd("2017-11-09"), NA_Date_, 0, "b", 3, NA_Date_, NA_Date_, NA_integer_, "c", 1, ymd("2017-10-05"), NA_Date_, NA_integer_, "c", 3, NA_Date_, ymd("2017-10-29"), NA_integer_ )
原代码错误分析
尝试的代码报错:
sample_data <- sample_data |> group_by(obs_id) |> mutate( days_2_to_time = case_when( 2 %in% time & time != 3 ~ as.duration(date[time == 2] %--% date)/ddays(1), 2 %in% time & time == 3 ~ as.duration(date[time == 2] %--% date_3)/ddays(1), ! 2 %in% time ~ NA_integer_ ) ) |> ungroup()
错误信息:
Error in `mutate()`: ℹ In argument: `days_2_to_time = case_when(...)`. ℹ In group 3: `obs_id = "c"`. Caused by error in `case_when()`: ! Can't recycle `..1 (left)` (size 2) to match `..1 (right)` (size 0). Run `rlang::last_trace()` to see where the error occurred.
核心问题:case_when是逐行评估条件的,2 %in% time逐行判断时,每个行的time是单个值,导致:
- 无测量2的组中,
date[time==2]返回空向量,与逐行计算的结果长度不匹配 !2 %in% time会被逐行触发(比如time=1或3的行都会满足),而非针对整个组判断是否存在测量2
解决方案
方法1:先提取组内测量2的日期,再计算差值
先在分组内统一提取测量2的日期,再基于该日期计算每行的差值,同时通过组内整体判断决定是否返回NA:
sample_data <- sample_data |> group_by(obs_id) |> mutate( # 提取当前组中测量2的日期,无则为NA time2_date = first(date[time == 2]), # 确定当前行使用的日期字段 current_date = case_when( time != 3 ~ date, time == 3 ~ date_3 ), # 计算天数差:仅当存在测量2时计算,否则返回NA days_2_to_time = ifelse(is.na(time2_date), NA_integer_, as.integer(current_date - time2_date)) ) |> # 可选:移除中间临时变量 select(-time2_date, -current_date) |> ungroup()
方法2:简化版,无需中间变量
直接在ifelse中先判断组内是否存在测量2,再计算差值:
sample_data <- sample_data |> group_by(obs_id) |> mutate( days_2_to_time = ifelse( # 针对整个组判断是否存在测量2 !any(time == 2), NA_integer_, # 计算当前行日期与测量2日期的天数差 as.integer( case_when( time != 3 ~ date, time == 3 ~ date_3 ) - first(date[time == 2]) ) ) ) |> ungroup()
两种方法都能得到符合预期的结果,核心是用any(time == 2)实现针对整个分组的条件判断,而非逐行判断,同时统一提取组内测量2的日期,避免长度不匹配问题。
内容的提问来源于stack exchange,提问作者torwart
相关产品推荐
相关产品推荐

