#r #dplyr #anti-join
#r #dplyr #защита от объединения
Вопрос:
У меня есть три фрейма данных, как показано ниже:
df3 <- data.frame(col1=c('A','C','E'),col2=c(4,8,2))
df2 <- data.frame(col1=c('A','B','C','E','I'),col2=c(4,6,8,2,9))
df1 <- data.frame(col1=c('A','D','C','E','I'),col2=c(4,7,8,2,9))
Различия между любыми двумя файлами могут быть такими, как показано ниже:
anti_join(df2, df3)
# Joining, by = c("col1", "col2")
# col1 col2
# 1 B 6
# 2 I 9
anti_join(df3, df2)
# Joining, by = c("col1", "col2")
# [1] col1 col2
# <0 rows> (or 0-length row.names)
anti_join(df1, df2)
# Joining, by = c("col1", "col2")
# col1 col2
# 1 D 7
anti_join(df2, df1)
# Joining, by = c("col1", "col2")
# col1 col2
# 1 B 6
Я хотел бы создать основной фрейм данных со всеми значениями в col1
и col2
, специфичными для каждого фрейма данных. Если такого значения нет, оно должно быть заполнено NA
.
col1 df1_col2 df2_col2 df3_col2
1 A 4 4 4
2 B NA 6 NA
3 C 8 8 8
4 E 2 2 2
5 I 9 9 NA
6 D 7 NA NA
Суть вышеуказанного вывода может быть установлена из приведенных выше anti_join
команд. Однако это не дает полной картины сразу. Есть мысли о том, как этого добиться?
Редактировать: для нескольких значений в col2
for col1
вывод немного более запутанный. Например, A
имеет значения 4
, 3
.
df3 <- data.frame(col1=c('A','C','E'),col2=c(4,8,2))
df2 <- data.frame(col1=c('A','A','B','C','E','I'),col2=c(4,3,6,8,2,9))
df1 <- data.frame(col1=c('A','A','D','C','E','I'),col2=c(4,3,7,8,2,9))
lst_of_frames <- list(df1 = df1, df2 = df2, df3 = df3)
lst_of_frames %>%
imap(~ rename_at(.x, -1, function(z) paste(.y, z, sep = "_"))) %>%
reduce(full_join, by = "col1")
Это дает следующий результат.
# col1 df1_col2 df2_col2 df3_col2
# 1 A 4 4 4
# 2 A 4 3 4
# 3 A 3 4 4
# 4 A 3 3 4
# 5 D 7 NA NA
# 6 C 8 8 8
# 7 E 2 2 2
# 8 I 9 9 NA
# 9 B NA 6 NA
Интересная часть выходных данных:
# col1 df1_col2 df2_col2 df3_col2
# 1 A 4 4 4
# 2 A 4 3 4
# 3 A 3 4 4
# 4 A 3 3 4
в то время как ожидаемый результат равен:
# col1 df1_col2 df2_col2 df3_col2
# 1 A 4 4 4
# 2 A 3 3 NA
Ответ №1:
Вы можете использовать full_join
функцию из dplyr
пакета.
df_master <- df1 %>%
full_join(df2, by = "col1") %>%
full_join(df3, by = "col1") %>%
select(col1, df1_col2 = col2.x,
df2_col2 = col2.y,
df3_col2 = col2)
col1 df1_col2 df2_col2 df3_col2
1 A 4 4 4
2 D 7 NA NA
3 C 8 8 8
4 E 2 2 2
5 I 9 9 NA
6 B NA 6 NA
Комментарии:
1. Немного расширяя это, если нужно сделать это для произвольного количества фреймов, то
Reduce(function(a, b) full_join(a, b, by = "col1"), lst_of_frames)
(гдеlst_of_frames <- list(df1, df2, df3)
здесь).2. или
list(df1, df2, df3) %>% reduce(full_join, by = "col1")
тоже!3. Да, очень небольшая разница между базовыми R
Reduce
иpurrr::reduce
. Самый большой (для меня) ввод произвольных аргументов, как вы показали там.
Ответ №2:
Аналогично ответу @ tamtam, но немного программно, если у вас есть динамический список фреймов.
lst_of_frames <- list(df1 = df1, df2 = df2, df3 = df3)
# lst_of_frames <- tibble::lst(df1, df2, df3) # thanks, @user63230
library(dplyr)
library(purrr) # imap, reduce
lst_of_frames %>%
imap(~ rename_at(.x, -1, function(z) paste(.y, z, sep = "_"))) %>%
reduce(full_join, by = "col1")
# col1 df1_col2 df2_col2 df3_col2
# 1 A 4 4 4
# 2 D 7 NA NA
# 3 C 8 8 8
# 4 E 2 2 2
# 5 I 9 9 NA
# 6 B NA 6 NA
Важно (для автоматического переименования столбцов), чтобы список фреймов был именованным списком; я предполагал, что это было имя переменной фрейма list(df1=df1)
, но с таким же успехом можно было list(A=df1)
создать столбец с именем A_col2
в конце.
Комментарии:
1. 1, я хотел использовать
lst
в своем комментарии ниже!lst_of_frames <- tibble::lst(df1, df2, df3)
чтобы избежать необходимости называтьdfs
2. @r2evans Не могли бы вы взглянуть на мою правку? Я обновил его другим сценарием.
3. То, о чем вы просите, выходит за рамки
join
мышления. Поскольку два ваших новых фрейма имеют по два"A"
в каждом, вы получите большее объединение. Смотритеfull_join(df1, df2, by = "col1")
, в чем проблема. С объединениями, когда имеется несколько идентичных ключей (by="col1")
, тогда он становится «мультипликативным». Вам нужно либо лучше определить, как фреймы объединяются (чтобы исключить это), либо придумать дополнительныеfilter
правила редактирования (после всех объединений или после каждого объединения), чтобы этого не произошло. Поскольку ваше сокращение строк с 4 до 2 выглядит несколько произвольно, я не могу посоветовать это.4. Большое спасибо за вашу помощь. Идея сокращения строк с 4 до 2 заключается в том, чтобы избежать комбинаций нескольких идентичных ключей и сопоставлять только их по наборам данных. В этом случае
A
имеет несколько значений (4
,3
), и я не хочу иметь комбинацию этих значений в выходных данных. Вместо этого используйте их только один раз, независимо от того, совпадают ли они с другими, в противном случаеNA
. У меня есть два значения, и в результате должно получиться только две строки. Надеюсь, я здесь ясно выразился 😉5. Единственная логика, которая может здесь сработать, — это фильтр после объединения, но я не вижу логики, указывающей, какую из четырех возможных комбинаций сохранить. Цель вашего объединения — ввести
"col2"
, поэтому вы не можете использовать это в объединении. Что произойдет, если поменять местами строки 1-2 изdf2
? Будут ли строки, которые вам нужны, одинаковыми? (На самом деле, я не знаю, что ответ на этот вопрос меняет мое убеждение: либоmerge
/join
неверно; либо есть еще что-то, что повлияет на фильтр после объединения.)