在R中为每个参与者每日的最后观测值创建新变量
问题描述
我有一份纵向数据,参与者每日多次报告,持续多日。id代表参与者,day代表日期,index代表每个参与者的观测时机。模拟数据如下:
id<-rep(1:20, each=16) index<-rep(1:16, times=20) day<-rep(rep(1:4, each=4), times=20) mock_df<-data.frame(id, index, day)
模拟数据中每个参与者每日提交4次报告,共4天。实际数据为非平衡数据,参与者的报告天数和每日报告次数各不相同。
我需要创建一个新变量last,用于标记每个参与者每日的最后一次观测时机,期望输出示例:
id index day last 1 1 1 0 1 2 1 0 1 3 1 0 1 4 1 1 1 5 2 0 1 6 2 0 1 7 2 0 1 8 2 1 1 9 3 0 ...
我尝试了以下代码,但为所有行生成了值为10的变量(推测是因为实际数据中参与者报告了10天):
mock_df <- mock_df |> group_by(id) |> mutate(last=day[which.max(day)]) |> ungroup()
解决方案
你的核心问题是分组逻辑错误:仅按id分组会定位到每个参与者的最后一天,而非单日的最后一次观测。需要同时按id和day分组,再标记每组内的最后一条记录。
方法1:基于行号的标记(dplyr)
library(dplyr) mock_df <- mock_df |> group_by(id, day) |> mutate(last = ifelse(row_number() == n(), 1, 0)) |> ungroup()
group_by(id, day):将数据拆分到「单个参与者+单日」的分组中row_number() == n():判断当前行是否是分组内的最后一行(n()返回该分组的总记录数)- 通过
ifelse将最后一行标记为1,其余为0
方法2:基于观测索引的标记(更适配非平衡数据)
如果实际数据中index是严格按观测时间递增的,也可以用index判断当日最后一次观测:
mock_df <- mock_df |> group_by(id, day) |> mutate(last = ifelse(index == max(index), 1, 0)) |> ungroup()
这种写法更鲁棒,即使单日观测次数不固定或存在数据缺失,只要index是顺序递增的,就能正确识别当日最后一条记录。
结果验证
运行上述代码后,模拟数据中每个参与者每天的第4条记录(index=4、8、12、16)会被标记为1,其余为0,完全符合预期输出。
内容的提问来源于stack exchange,提问作者Sointu
相关产品推荐
相关产品推荐

