如何在ggplot纵向线图数据中按测试单独过滤非NA的Pre/Post时间点对?
解决方案
核心思路是把每个测试的判断独立开来——你需要的是「受试者针对某一项测试有完整的Pre/Post非NA对」,而非「受试者所有测试必须同时满足完整对」。最简洁的实现方式是先将宽格式数据转成长格式,再按「受试者+测试」分组过滤。
步骤1:宽转长格式
把多个Test列转换为「测试名称」和「测试分数」两列,让每个测试的观测单独成一行:
library(dplyr) library(tidyr) dat_long <- dat_all %>% pivot_longer( cols = starts_with("Test"), # 匹配所有Test开头的列(如Test1到Test5) names_to = "Test", # 新列:存储测试名称 values_to = "Score" # 新列:存储测试分数 )
步骤2:按「受试者+测试」过滤
对每个受试者的每个测试,检查是否同时存在Pre和Post时间点,且两个时间点的分数都不为NA:
dat_filtered <- dat_long %>% group_by(SubjectID, Test) %>% filter( n_distinct(Time) == 2, # 确保该测试包含Pre和Post两个时间点 all(!is.na(Score)) # 两个时间点的分数均非NA ) %>% ungroup()
可选:转回宽格式
如果后续绘图需要宽格式数据,可以再转换回去:
dat_wide <- dat_filtered %>% pivot_wider( names_from = Test, values_from = Score )
为什么之前的方法不生效?
你之前的代码都是仅按SubjectID分组,相当于要求同一个受试者的所有测试必须同时满足完整对,这和你的需求不符。转成长格式后按「SubjectID+Test」分组,就能实现每个测试独立判断,保留那些某一项测试有完整对、其他测试无数据的受试者观测。
替代方案(不转长格式)
如果不想转换数据格式,可以针对每个测试单独过滤后合并:
test_names <- paste0("Test", 1:5) # 逐个处理每个测试 filtered_list <- lapply(test_names, function(test_col) { dat_all %>% group_by(SubjectID) %>% filter( Time %in% c("Pre", "Post"), !is.na(.data[[test_col]]), n() == 2 # 确保该测试有两个非NA时间点 ) %>% ungroup() %>% mutate(Test = test_col) # 标记当前处理的测试 }) # 合并所有结果 dat_filtered <- bind_rows(filtered_list)
内容的提问来源于stack exchange,提问作者shizzle
相关产品推荐
相关产品推荐

