如何合并多个DataFrame并筛选参与全部测试的唯一参与者
问题描述
现有4个行数不同的DataFrame,需合并它们,仅保留参与了所有测试的参与者,且每个参与者对应一行,避免重复列名与NA值。
变量定义
- class1Time1:2021年参加VarA和VarB测试的班级1数据
- class2Time1:2021年参加VarA和VarB测试的班级2数据
- class1Time2:2022年参加VarA和VarB测试的班级1数据
- class2Time2:2022年参加VarA和VarB测试的班级2数据
注:"Var_Year"代表对应年份的科目成绩,部分参与者未完成全部测试。
模拟代码
创建模拟数据框:
names1 <- c("Mary","John","Kate", "Bea", "Harry", "Hermione", "Rony", "Dobby") names2 <- c("Harry", "Hermione", "Rony", "Dobby", "Dumbledore", "Snape", "Sirius") class1Time1 <- data.frame(ID = names1[3:8], VarA_21 = sample(1:20, 6), VarB_21 = sample(1:20, 6)) class2Time1 <- data.frame(ID = names2[1:7], VarA_21 = sample(1:20, 7), VarB_21 = sample(1:20, 7)) class1Time2 <- data.frame(ID = names1[2:8], VarA_22 = sample(1:20, 7), VarB_22 = sample(1:20, 7)) class2Time2 <- data.frame(ID = names2[1:4], VarA_22 = sample(1:20, 4), VarB_22 = sample(1:20, 4))
仅Harry、Hermione、Rony、Dobby完成了全部测试
将环境中所有含"class"的DataFrame存入列表:
together <- grep("class",names(.GlobalEnv), value=TRUE) my_list <- do.call("list", mget(together))
已尝试方法
使用Reduce结合full_join、merge、rbind等,但未达预期。使用test <- Reduce(function(...) merge(..., all = TRUE, by="ID"), my_list)时出现列名重复问题。
待解决问题
- 如何合并所有DataFrame,得到仅包含参与全部测试的参与者,且每个参与者一行的结果?
- 若解决问题1,是否可通过filter结合complete.cases筛选无NA的记录?
解决方案
问题1:合并并保留全测试参与者
核心逻辑是先按ID合并所有数据框,合并后处理重复列(取非NA值整合),最后筛选出所有测试都完成的参与者。以下提供两种实现方式:
方法1:dplyr + purrr 实现
library(dplyr) library(purrr) # 逐步合并数据框,处理重复列 merged_data <- my_list %>% reduce(full_join, by = "ID") %>% # 整合重复的年份科目列,取非NA值 mutate( VarA_21 = coalesce(VarA_21.x, VarA_21.y), VarB_21 = coalesce(VarB_21.x, VarB_21.y), .keep = "unused" # 删除原重复列 ) %>% # 筛选所有成绩列无NA的参与者(即完成全部测试) filter(complete.cases(.))
方法2:base R 实现
# 合并所有数据框 merged_base <- Reduce(function(x, y) merge(x, y, by = "ID", all = TRUE), my_list) # 遍历整合重复的年份科目列 for (var in c("VarA", "VarB")) { for (year in c("21", "22")) { target_cols <- grep(paste0(var, "_", year), colnames(merged_base), value = TRUE) # 取每行非NA值(同一参与者同科目同年份只会有一个有效成绩) merged_base[[paste0(var, "_", year)]] <- apply(merged_base[target_cols], 1, function(x) x[!is.na(x)][1]) # 删除多余的重复列 merged_base <- merged_base[, !colnames(merged_base) %in% target_cols[-1]] } } # 筛选完成全部测试的参与者 merged_base <- merged_base[complete.cases(merged_base), ]
两种方法最终都会得到仅包含Harry、Hermione、Rony、Dobby的结果,每个参与者一行,无重复列和NA值。
问题2:关于filter结合complete.cases
完全可以。filter(complete.cases(.))(dplyr语法)或data[complete.cases(data), ](base R语法)是筛选无NA记录的标准方式,也是上面两种方法中筛选全测试参与者的核心步骤——完成所有测试的参与者所有成绩列都不会有NA,因此通过complete.cases可以精准定位目标群体。
内容的提问来源于stack exchange,提问作者Larissa Cury
相关产品推荐
相关产品推荐

