R语言高级数据框合并:忽略NA值并处理冲突行的方案问询
背景
我的问题和以下两个R相关问题类似:
- R: 合并相同ID的行
- 如何合并R中具有相同标识符的行?
但我需要在两行存在“冲突”时保留多行。另外,上述两个问题的场景是数据框合并之后,而我的问题可以通过修改合并流程解决,且每个数据框内的ID都是唯一的,也可以提供合并后的通用解决方案。
问题描述
我想要合并两个数据框,合并时忽略NA值,仅当存在冲突(同一列的非NA值不同)时保留多行。
示例
给定数据框:
> my_data1 ID m n o p q 1 A K U <NA> <NA> K 2 B L V Q A L 3 C M W <NA> S M 4 D N X W D N 5 E O Y <NA> <NA> O
和
> my_data2 ID m n o p r 1 A K U E <NA> U 2 B L V <NA> B V 3 C M W R <NA> W 4 D N X <NA> <NA> X 5 E O Y T F Y
直接合并后得到:
> my_data ID m n o p q r 1 A K U E <NA> <NA> U 2 A K U <NA> <NA> K <NA> 3 B L V Q A L <NA> 4 B L V <NA> B <NA> V 5 C M W R <NA> <NA> W 6 C M W <NA> S M <NA> 7 D N X W D N <NA> 8 D N X <NA> <NA> <NA> X 9 E O Y T F <NA> Y 10 E O Y <NA> <NA> O <NA>
但期望的结果是:
> my_data ID m n o p q r 1 A K U E <NA> K U 2 B L V Q A L <NA> 3 B L V <NA> B <NA> V 4 C M W R S M W 5 D N X W D N X 6 E O Y T F O Y
ID == B无法合并,因为p列存在冲突(非NA值分别为A和B)。ID == A较为特殊,p列均为NA值,无冲突可合并。ID为C、D、E的情况无冲突,可直接合并非NA值。
代码
my_data1 <- data.frame(ID = LETTERS[1:5], m = LETTERS[11:15], n = LETTERS[21:25], o = c(NA, 'Q', NA, 'W', NA), p = c(NA, 'A', 'S', 'D', NA), q = LETTERS[11:15]) my_data2 <- data.frame(ID = LETTERS[1:5], m = LETTERS[11:15], n = LETTERS[21:25], o = c('E', NA, 'R', NA, 'T'), p = c(NA, 'B', NA, NA, 'F'), r = LETTERS[21:25]) # 两种基础合并方式 my_data <- merge(my_data1, my_data2, all = TRUE) # 或 my_data <- bind_rows(my_data1, my_data2) |> arrange(ID) # 不完整的解决方案(仅适用于无冲突的ID) my_data <- my_data |> filter(ID != 'A', ID != 'B') |> # 无此行会报错 select(-m, -n) |> # 无此行会报错 group_by(ID) |> summarise(across(.cols = everything(), .fns = ~ na.omit(.x))) my_data |> view()
完整解决方案
以下是基于dplyr包的完整实现,核心逻辑是先判断每个ID下是否存在列冲突,再对无冲突的ID合并非NA值,有冲突的ID保留原行:
library(dplyr) library(tidyr) # 合并两个数据框,保留所有列 combined_df <- bind_rows(my_data1, my_data2) # 定义函数:判断一组值是否存在非NA冲突 has_conflict <- function(x) { non_na <- na.omit(x) length(unique(non_na)) > 1 } # 找出每个ID中存在冲突的列 conflict_check <- combined_df |> group_by(ID) |> summarise(across(-c(ID, m, n), has_conflict)) |> pivot_longer(-ID, names_to = "col", values_to = "conflict") |> group_by(ID) |> summarise(has_any_conflict = any(conflict)) # 拆分数据:有冲突的ID和无冲突的ID conflict_ids <- conflict_check |> filter(has_any_conflict) |> pull(ID) no_conflict_ids <- combined_df |> pull(ID) |> unique() |> setdiff(conflict_ids) # 处理无冲突的ID:合并非NA值 no_conflict_df <- combined_df |> filter(ID %in% no_conflict_ids) |> group_by(ID, m, n) |> summarise(across(everything(), ~ if(all(is.na(.x))) NA else na.omit(.x)[1]), .groups = "drop") # 处理有冲突的ID:去重保留唯一行(原数据框ID唯一,合并后每个ID最多两行) conflict_df <- combined_df |> filter(ID %in% conflict_ids) |> distinct() # 合并结果并排序 final_df <- bind_rows(no_conflict_df, conflict_df) |> arrange(ID) print(final_df)
运行后输出结果与期望一致:
> final_df ID m n o p q r 1 A K U E <NA> K U 2 B L V Q A L <NA> 3 B L V <NA> B <NA> V 4 C M W R S M W 5 D N X W D N X 6 E O Y T F O Y
内容的提问来源于stack exchange,提问作者user1
相关产品推荐
相关产品推荐

