Одновременное объединение нескольких фреймов данных в списке

259

У меня есть список многих data.frames, которые я хочу объединить. Проблема здесь в том, что каждый data.frame отличается количеством строк и столбцов, но все они имеют общие ключевые переменные (которые я вызывал "var1"и "var2"в коде ниже). Если бы data.frames были идентичны с точки зрения столбцов, я мог бы просто rbindсказать, для чего plyr rbind.fill сделает свою работу, но с этими данными дело обстоит иначе .

Поскольку mergeкоманда работает только на 2 data.frames, я обратился к Интернету за идеями. Я получил это один из здесь , который прекрасно работал в R 2.7.2, который является то , что я имел в то время:

merge.rec <- function(.list, ...){
    if(length(.list)==1) return(.list[[1]])
    Recall(c(list(merge(.list[[1]], .list[[2]], ...)), .list[-(1:2)]), ...)
}

И я бы назвал функцию так:

df <- merge.rec(my.list, by.x = c("var1", "var2"), 
                by.y = c("var1", "var2"), all = T, suffixes=c("", ""))

Но в любой версии R после 2.7.2, включая 2.11 и 2.12, этот код завершается ошибкой со следующей ошибкой:

Error in match.names(clabs, names(xi)) : 
  names do not match previous names

(Кстати, я вижу другие ссылки на эту ошибку в другом месте без разрешения).

Есть ли способ решить это?

bshor
источник

Ответы:

183

Конкретно спросил Другой вопрос , как выполнить несколько влево присоединяется с помощью dplyr в R . Этот вопрос был помечен как дубликат этого вопроса, поэтому я отвечаю здесь, используя 3 образца данных ниже:

x <- data.frame(i = c("a","b","c"), j = 1:3, stringsAsFactors=FALSE)
y <- data.frame(i = c("b","c","d"), k = 4:6, stringsAsFactors=FALSE)
z <- data.frame(i = c("c","d","a"), l = 7:9, stringsAsFactors=FALSE)

Обновление от июня 2018 года : я разделил ответ на три части, представляющие три различных способа выполнения слияния. Вы, вероятно, хотите использовать purrrспособ, если вы уже используете пакеты tidyverse . Для сравнения ниже вы найдете базовую версию R, использующую тот же пример набора данных.


1) Присоединяйтесь к ним reduceиз purrrпакета:

purrrПакет предоставляет reduceфункцию , которая имеет синтаксис краткий:

library(tidyverse)
list(x, y, z) %>% reduce(left_join, by = "i")
#  A tibble: 3 x 4
#  i       j     k     l
#  <chr> <int> <int> <int>
# 1 a      1    NA     9
# 2 b      2     4    NA
# 3 c      3     5     7

Вы также можете выполнять другие объединения, такие как full_joinили inner_join:

list(x, y, z) %>% reduce(full_join, by = "i")
# A tibble: 4 x 4
# i       j     k     l
# <chr> <int> <int> <int>
# 1 a     1     NA     9
# 2 b     2     4      NA
# 3 c     3     5      7
# 4 d     NA    6      8

list(x, y, z) %>% reduce(inner_join, by = "i")
# A tibble: 1 x 4
# i       j     k     l
# <chr> <int> <int> <int>
# 1 c     3     5     7

2) dplyr::left_join()с основанием R Reduce():

list(x,y,z) %>%
    Reduce(function(dtf1,dtf2) left_join(dtf1,dtf2,by="i"), .)

#   i j  k  l
# 1 a 1 NA  9
# 2 b 2  4 NA
# 3 c 3  5  7

3) База R merge()с базой R Reduce():

И для сравнения, вот базовая версия R левого соединения

 Reduce(function(dtf1, dtf2) merge(dtf1, dtf2, by = "i", all.x = TRUE),
        list(x,y,z))
#   i j  k  l
# 1 a 1 NA  9
# 2 b 2  4 NA
# 3 c 3  5  7
Поль Ружье
источник
1
Вариант full_join работает отлично и выглядит намного менее страшно, чем принятый ответ. Не большая разница в скорости, хотя.
bshor
1
@Axeman прав, но вы могли бы вообще избежать (явно) возврата списка фреймов данных, используя map_dfr()илиmap_dfc()
DaveRGP
Хотя я мог бы присоединиться к нескольким DF на основе шаблона, используя ´ls (pattern = "DF_name_contains_this") ´, но нет. Использовал «noquote (paste (())», но я все еще создаю символьный вектор вместо списка DF. В итоге я
Ручка Джорджа Уильяма Рассела
Другой вопрос , обеспечивает реализацию питона : список кадров панды данных dfs = [df1, df2, df3]затем reduce(pandas.merge, dfs).
Поль Ружье
222

Уменьшить делает это довольно легко:

merged.data.frame = Reduce(function(...) merge(..., all=T), list.of.data.frames)

Вот полный пример использования некоторых фиктивных данных:

set.seed(1)
list.of.data.frames = list(data.frame(x=1:10, a=1:10), data.frame(x=5:14, b=11:20), data.frame(x=sample(20, 10), y=runif(10)))
merged.data.frame = Reduce(function(...) merge(..., all=T), list.of.data.frames)
tail(merged.data.frame)
#    x  a  b         y
#12 12 NA 18        NA
#13 13 NA 19        NA
#14 14 NA 20 0.4976992
#15 15 NA NA 0.7176185
#16 16 NA NA 0.3841037
#17 19 NA NA 0.3800352

А вот пример использования этих данных для репликации my.list:

merged.data.frame = Reduce(function(...) merge(..., by=match.by, all=T), my.list)
merged.data.frame[, 1:12]

#  matchname party st district chamber senate1993 name.x v2.x v3.x v4.x senate1994 name.y
#1   ALGIERE   200 RI      026       S         NA   <NA>   NA   NA   NA         NA   <NA>
#2     ALVES   100 RI      019       S         NA   <NA>   NA   NA   NA         NA   <NA>
#3    BADEAU   100 RI      032       S         NA   <NA>   NA   NA   NA         NA   <NA>

Примечание: похоже, что это, возможно, ошибка в merge. Проблема в том, что нет проверки, что добавление суффиксов (для обработки перекрывающихся несовпадающих имен) фактически делает их уникальными. В определенный момент он использует, [.data.frameкоторый делает make.unique имена, вызывая rbindсбой.

# first merge will end up with 'name.x' & 'name.y'
merge(my.list[[1]], my.list[[2]], by=match.by, all=T)
# [1] matchname    party        st           district     chamber      senate1993   name.x      
# [8] votes.year.x senate1994   name.y       votes.year.y
#<0 rows> (or 0-length row.names)
# as there is no clash, we retain 'name.x' & 'name.y' and get 'name' again
merge(merge(my.list[[1]], my.list[[2]], by=match.by, all=T), my.list[[3]], by=match.by, all=T)
# [1] matchname    party        st           district     chamber      senate1993   name.x      
# [8] votes.year.x senate1994   name.y       votes.year.y senate1995   name         votes.year  
#<0 rows> (or 0-length row.names)
# the next merge will fail as 'name' will get renamed to a pre-existing field.

Самый простой способ исправить это не оставлять переименование полей для полей дубликатов (которых здесь много) до merge . Например:

my.list2 = Map(function(x, i) setNames(x, ifelse(names(x) %in% match.by,
      names(x), sprintf('%s.%d', names(x), i))), my.list, seq_along(my.list))

merge/ ReduceБудет тогда работать нормально.

Чарльз
источник
Спасибо! Я видел это решение также по ссылке от Рамнатха. Выглядит достаточно просто. Но я получаю следующую ошибку: «Ошибка в match.names (clabs, names (xi)): имена не соответствуют предыдущим именам». Все переменные, с которыми я сопоставляюсь, присутствуют во всех кадрах данных в списке, поэтому я не понимаю, что говорит мне эта ошибка.
bshor
1
Я тестировал это решение на R2.7.2 и получаю ту же ошибку match.names. Таким образом, есть более фундаментальная проблема с этим решением и моими данными. Я использовал код: Reduce (функция (x, y) слияния (x, y, все = T, by.x = match.by, by.y = match.by), my.list, накопление = F)
bshor
1
Странно, я добавил проверенный код, который работает нормально. Я предполагаю, что происходит какое-то переименование полей, основанное на используемых вами аргументах слияния? Результат объединения должен иметь соответствующие ключи, чтобы его можно было объединить с последующим фреймом данных.
Чарльз
Я подозреваю, что что-то происходит с пустыми фреймами данных. Я опробовал несколько примеров, подобных этому: empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)и случилось что-то странное, чего я еще не понял.
Бен Болкер
@ Чарльз Вы на что-то. Ваш код работает отлично для меня. И когда я адаптирую его к своему, он тоже работает нормально - за исключением того, что он выполняет слияние, игнорируя ключевые переменные, которые я хочу. Когда я пытаюсь добавить ключевые переменные, а не пропустить их, я получаю новую ошибку «Ошибка в is.null (x):« x »отсутствует». Строка кода: «test.reduce <- Reduce (function (...) merge (by = match.by, all = T), my.list)», где match.by - вектор имен ключевых переменных, которые я хочу объединить по.
bshor
52

Вы можете сделать это , используя merge_allв reshapeпакете. Вы можете передать параметры, mergeиспользуя ...аргумент

reshape::merge_all(list_of_dataframes, ...)

Вот отличный ресурс по различным методам объединения фреймов данных .

Ramnath
источник
похоже, я только что повторил merge_recurse =) приятно знать, что эта функция уже существует.
SFun28
16
да. всякий раз, когда у меня есть идея, я всегда проверяю, @hadley уже сделал это, и большую часть времени он имеет :-)
Рамнат
1
Я немного запутался; я должен сделать merge_all или merge_recurse? В любом случае, когда я пытаюсь добавить свои дополнительные аргументы к обоим, я получаю сообщение об ошибке «формальный аргумент» «все соответствует нескольким фактическим аргументам».
bshor
2
Я думаю, что я уронил это от reshape2. Уменьшить + объединить так же просто.
Хэдли
2
@Ramnath, ссылка мертва, зеркало есть?
Эдуардо
4

Вы можете использовать рекурсию, чтобы сделать это. Я не проверял следующее, но это должно дать вам правильную идею:

MergeListOfDf = function( data , ... )
{
    if ( length( data ) == 2 ) 
    {
        return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) )
    }    
    return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) )
}
SFun28
источник
2

Я буду использовать пример данных из @PaulRougieux

x <- data_frame(i = c("a","b","c"), j = 1:3)
y <- data_frame(i = c("b","c","d"), k = 4:6)
z <- data_frame(i = c("c","d","a"), l = 7:9)

Вот короткое и сладкое решение с использованием purrrиtidyr

library(tidyverse)

 list(x, y, z) %>% 
  map_df(gather, key=key, value=value, -i) %>% 
  spread(key, value)
dmi3kno
источник
1

Функция eatмоего пакета safejoin имеет такую ​​функцию, если вы передадите ей список data.frames в качестве второго входа, он рекурсивно присоединит их к первому входу.

Заимствование и распространение данных принятого ответа:

x <- data_frame(i = c("a","b","c"), j = 1:3)
y <- data_frame(i = c("b","c","d"), k = 4:6)
z <- data_frame(i = c("c","d","a"), l = 7:9)
z2 <- data_frame(i = c("a","b","c"), l = rep(100L,3),l2 = rep(100L,3)) # for later

# devtools::install_github("moodymudskipper/safejoin")
library(safejoin)
eat(x, list(y,z), .by = "i")
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <int>
# 1 a         1    NA     9
# 2 b         2     4    NA
# 3 c         3     5     7

Нам не нужно брать все столбцы, мы можем использовать помощники select из tidyselect и select (поскольку мы начинаем со .xвсех .xсохраненных столбцов):

eat(x, list(y,z), starts_with("l") ,.by = "i")
# # A tibble: 3 x 3
#   i         j     l
#   <chr> <int> <int>
# 1 a         1     9
# 2 b         2    NA
# 3 c         3     7

или удалить конкретные:

eat(x, list(y,z), -starts_with("l") ,.by = "i")
# # A tibble: 3 x 3
#   i         j     k
#   <chr> <int> <int>
# 1 a         1    NA
# 2 b         2     4
# 3 c         3     5

Если список назван, имена будут использоваться в качестве префиксов:

eat(x, dplyr::lst(y,z), .by = "i")
# # A tibble: 3 x 4
#   i         j   y_k   z_l
#   <chr> <int> <int> <int>
# 1 a         1    NA     9
# 2 b         2     4    NA
# 3 c         3     5     7

Если есть конфликты столбцов, .conflictаргумент позволяет вам разрешить его, например, взяв первый / второй, добавив их, объединив их или вложив.

держись первым:

eat(x, list(y, z, z2), .by = "i", .conflict = ~.x)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <int>
# 1 a         1    NA     9
# 2 b         2     4    NA
# 3 c         3     5     7

держать в прошлом:

eat(x, list(y, z, z2), .by = "i", .conflict = ~.y)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA   100
# 2 b         2     4   100
# 3 c         3     5   100

Добавить:

eat(x, list(y, z, z2), .by = "i", .conflict = `+`)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA   109
# 2 b         2     4    NA
# 3 c         3     5   107

COALESCE:

eat(x, list(y, z, z2), .by = "i", .conflict = dplyr::coalesce)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA     9
# 2 b         2     4   100
# 3 c         3     5     7

гнездо:

eat(x, list(y, z, z2), .by = "i", .conflict = ~tibble(first=.x, second=.y))
# # A tibble: 3 x 4
#   i         j     k l$first $second
#   <chr> <int> <int>   <int>   <int>
# 1 a         1    NA       9     100
# 2 b         2     4      NA     100
# 3 c         3     5       7     100

NAзначения могут быть заменены с помощью .fillаргумента.

eat(x, list(y, z), .by = "i", .fill = 0)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <dbl> <dbl>
# 1 a         1     0     9
# 2 b         2     4     0
# 3 c         3     5     7

По умолчанию это усовершенствованный , left_joinно весь dplyr присоединяется поддерживается с помощью .modeаргумента, нечеткий присоединяется также поддерживаются через match_fun аргумент (это обернуто вокруг пакета fuzzyjoin) или дают формулу , например , как ~ X("var1") > Y("var2") & X("var3") < Y("var4")к byаргументу.

Moody_Mudskipper
источник
0

У меня был список фреймов данных без общего столбца идентификатора.
У меня отсутствовали данные о многих DFS. Были нулевые значения. Кадры данных были созданы с использованием табличной функции. Снижение, объединение, rbind, rbind.fill и тому подобное не могли помочь мне достичь цели. Моя цель состояла в том, чтобы создать понятный объединенный фрейм данных, не имеющий отношения к отсутствующим данным и общему столбцу идентификаторов.

Поэтому я сделал следующую функцию. Может быть, эта функция может кому-то помочь.

##########################################################
####             Dependencies                        #####
##########################################################

# Depends on Base R only

##########################################################
####             Example DF                          #####
##########################################################

# Example df
ex_df           <- cbind(c( seq(1, 10, 1), rep("NA", 0), seq(1,10, 1) ), 
                         c( seq(1, 7, 1),  rep("NA", 3), seq(1, 12, 1) ), 
                         c( seq(1, 3, 1),  rep("NA", 7), seq(1, 5, 1), rep("NA", 5) ))

# Making colnames and rownames
colnames(ex_df) <- 1:dim(ex_df)[2]
rownames(ex_df) <- 1:dim(ex_df)[1]

# Making an unequal list of dfs, 
# without a common id column
list_of_df      <- apply(ex_df=="NA", 2, ( table) )

это следует за функцией

##########################################################
####             The function                        #####
##########################################################


# The function to rbind it
rbind_null_df_lists <- function ( list_of_dfs ) {
  length_df     <- do.call(rbind, (lapply( list_of_dfs, function(x) length(x))))
  max_no        <- max(length_df[,1])
  max_df        <- length_df[max(length_df),]
  name_df       <- names(length_df[length_df== max_no,][1])
  names_list    <- names(list_of_dfs[ name_df][[1]])

  df_dfs <- list()
  for (i in 1:max_no ) {

    df_dfs[[i]]            <- do.call(rbind, lapply(1:length(list_of_dfs), function(x) list_of_dfs[[x]][i]))

  }

  df_cbind               <- do.call( cbind, df_dfs )
  rownames( df_cbind )   <- rownames (length_df)
  colnames( df_cbind )   <- names_list

  df_cbind

}

Запуск примера

##########################################################
####             Running the example                 #####
##########################################################

rbind_null_df_lists ( list_of_df )
Элиас ЭстатистиксЕУ
источник
0

Если у вас есть список dfs, а столбец содержит «ID», но в некоторых списках некоторые идентификаторы отсутствуют, вы можете использовать эту версию Reduce / Merge для объединения нескольких Dfs с отсутствующими идентификаторами строк или метками:

Reduce(function(x, y) merge(x=x, y=y, by="V1", all.x=T, all.y=T), list_of_dfs)
Элиас ЭстатистиксЕУ
источник
0

Вот общая оболочка, которая может быть использована для преобразования двоичной функции в функцию с несколькими параметрами. Преимущество этого решения в том, что оно очень универсально и может применяться к любым двоичным функциям. Вам просто нужно сделать это один раз, а затем вы можете применить его в любом месте.

Чтобы продемонстрировать идею, я использую простую рекурсию для реализации. Конечно, это может быть реализовано более элегантным способом, который выигрывает от хорошей поддержки функциональной парадигмы R.

fold_left <- function(f) {
return(function(...) {
    args <- list(...)
    return(function(...){
    iter <- function(result,rest) {
        if (length(rest) == 0) {
            return(result)
        } else {
            return(iter(f(result, rest[[1]], ...), rest[-1]))
        }
    }
    return(iter(args[[1]], args[-1]))
    })
})}

Затем вы можете просто обернуть в нее любые двоичные функции и вызвать их с позиционными параметрами (обычно data.frames) в первых скобках, а именованные параметры - во вторых скобках (например, by =or suffix =). Если именованных параметров нет, оставьте вторые скобки пустыми.

merge_all <- fold_left(merge)
merge_all(df1, df2, df3, df4, df5)(by.x = c("var1", "var2"), by.y = c("var1", "var2"))

left_join_all <- fold_left(left_join)
left_join_all(df1, df2, df3, df4, df5)(c("var1", "var2"))
left_join_all(df1, df2, df3, df4, df5)()
englealuze
источник