Основной вид нескольких фреймов данных с общими столбцами

#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 неверно; либо есть еще что-то, что повлияет на фильтр после объединения.)