Mesclar simultaneamente vários data.frames em uma lista


259

Eu tenho uma lista de muitos data.frames que quero mesclar. O problema aqui é que cada data.frame difere em termos de número de linhas e colunas, mas todos compartilham as principais variáveis ​​(que eu chamei "var1"e "var2"no código abaixo). Se os data.frames fossem idênticos em termos de colunas, eu poderia apenas rbind, para qual rbind.fill do plyr faria o trabalho, mas esse não é o caso com esses dados.

Como o mergecomando funciona apenas em 2 data.frames, procurei idéias na Internet. Eu peguei esse daqui , que funcionou perfeitamente no R 2.7.2, que era o que eu tinha na época:

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

E eu chamaria a função assim:

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

Mas em qualquer versão R após 2.7.2, incluindo 2.11 e 2.12, esse código falha com o seguinte erro:

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

(Incidentemente, vejo outras referências a esse erro em outro lugar sem resolução).

Existe alguma maneira de resolver isto?

Respostas:


183

Uma outra pergunta feita especificamente como executar múltiplos esquerda junta usando dplyr em R . A pergunta foi marcada como duplicada, então respondo aqui, usando os três quadros de dados de exemplo abaixo:

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)

Atualização junho de 2018 : Dividi a resposta em três seções, representando três maneiras diferentes de executar a mesclagem. Você provavelmente deseja usar o purrrcaminho se já estiver usando os pacotes tidyverse . Para fins de comparação abaixo, você encontrará uma versão R básica usando o mesmo conjunto de dados de amostra.


1) Junte-se a eles reduceno purrrpacote:

O purrrpacote fornece uma reducefunção que possui uma sintaxe concisa:

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

Você também pode executar outras junções, como uma full_joinou 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()com base 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) Base R merge()com base R Reduce():

E para fins de comparação, aqui está uma versão R básica da junção esquerda

 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
A variante full_join funciona perfeitamente e parece muito menos assustadora do que a resposta aceita. Não há muita diferença de velocidade, no entanto.
bshor

1
O @Axeman está certo, mas você pode evitar (visivelmente) retornar uma lista de quadros de dados usando map_dfr()oumap_dfc()
DaveRGP

Embora eu pudesse juntar um número de DFs com base em um padrão usando ´ls (pattern = "DF_name_contains_this") ´, mas não. Usei ´noquote (paste (()) ´, mas ainda estou produzindo um vetor de caractere em vez de uma lista de DF. Acabei digitando os nomes, o que é desagradável.
caneta de George William Russel,

Outra questão fornece uma implementação de Python : lista de quadros de dados pandas dfs = [df1, df2, df3]seguida reduce(pandas.merge, dfs).
Paul Rougieux 18/03/19

222

Reduzir torna isso bastante fácil:

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

Aqui está um exemplo completo usando alguns dados simulados:

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

E aqui está um exemplo usando esses dados para replicar 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>

Nota: Parece que isso é um problema merge. O problema é que não há verificação de que adicionar os sufixos (para lidar com nomes não correspondentes sobrepostos) realmente os torna únicos. Em um certo momento ele usa [.data.frameque faz make.unique os nomes, fazendo com que a rbindfalhar.

# 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.

A maneira mais fácil de corrigir é não deixar o campo renomeando para campos duplicados (dos quais existem muitos aqui) até merge. Por exemplo:

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))

O merge/ Reducefuncionará bem.


Obrigado! Vi essa solução também no link de Ramnath. Parece fácil o suficiente. Mas recebo o seguinte erro: "Erro no match.names (clabs, names (xi)): os nomes não correspondem aos nomes anteriores". As variáveis ​​em que estou correspondendo estão presentes em todos os quadros de dados da lista, portanto não estou entendendo o que esse erro está me dizendo.
bshor

1
Testei esta solução no R2.7.2 e recebo o mesmo erro match.names. Portanto, há um problema mais fundamental com esta solução e com meus dados. Eu usei o código: Reduzir (função (x, y) mesclar (x, y, todos = T, by.x = match.by, by.y = match.by), my.list, acumulate = F)
bshor

1
Estranho, eu adicionei o código que eu testei com o qual funciona bem. Eu acho que há alguma renomeação de campo ocorrendo com base nos argumentos de mesclagem que você está usando? O resultado mesclado ainda deve ter as chaves relevantes para ser mesclado com o quadro de dados subsequente.
Charles

Eu suspeito que algo está acontecendo com quadros de dados vazios. Tentei alguns exemplos como este: empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)e aconteceu algumas coisas estranhas que ainda não descobri.
precisa

@ Charles Você está em algo. Seu código corre bem acima para mim. E quando eu o adapto ao meu, ele também funciona bem - exceto que ele faz uma mesclagem ignorando as variáveis-chave que eu quero. Quando tento adicionar variáveis-chave em vez de excluí-las, recebo um novo erro "Erro no is.null (x): 'x' está ausente". A linha de código é "test.reduce <- Reduzir (função (...) mesclar (por = match.by, all = T), my.list)" onde match.by é o vetor dos nomes das variáveis-chave que eu quero mesclar de.
bshor

52

Você pode fazer isso usando merge_alloreshape pacote. Você pode passar parâmetros para mergeusar o ...argumento

reshape::merge_all(list_of_dataframes, ...)

Aqui está um excelente recurso sobre métodos diferentes para mesclar quadros de dados .


parece que acabei de replicar merge_recurse =) bom saber que essa função já existe.
SFun28

16
sim. Sempre que tenho uma idéia, eu sempre verificar se @hadley já fez isso, e na maioria das vezes ele tem :-)
Ramnath

1
Estou um pouco confuso; devo fazer merge_all ou merge_recurse? De qualquer forma, quando tento adicionar meus argumentos adicionais a qualquer um deles, recebo o erro "argumento formal" todos "correspondido por vários argumentos reais".
bshor

2
Eu acho que tirei isso de reformular2. Reduzir + mesclagem é tão simples.
Hadley

2
@ Ramnath, o link está morto, existe um espelho?
Eduardo

4

Você pode usar a recursão para fazer isso. Não verifiquei o seguinte, mas ele deve lhe dar a idéia certa:

MergeListOfDf = function( data , ... )
{
    if ( length( data ) == 2 ) 
    {
        return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) )
    }    
    return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) )
}

2

Vou reutilizar o exemplo de dados de @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)

Aqui está uma solução curta e agradável usando purrretidyr

library(tidyverse)

 list(x, y, z) %>% 
  map_df(gather, key=key, value=value, -i) %>% 
  spread(key, value)

1

A função eatdo meu pacote safejoin possui esse recurso, se você fornecer uma lista de data.frames como segunda entrada, ela os juntará recursivamente à primeira entrada.

Emprestando e estendendo os dados da resposta aceita:

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

Não precisamos pegar todas as colunas, podemos usar auxiliares selecionados de tidyselect e escolher (à medida que partimos de .xtodas as .xcolunas são mantidas):

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

ou remova os específicos:

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

Se a lista for nomeada, os nomes serão usados ​​como prefixos:

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

Se houver conflito de colunas, o .conflict argumento permitirá que você o resolva, por exemplo, pegando o primeiro / segundo, adicionando-os, juntando-os ou aninhando-os.

mantenha primeiro:

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

mantenha por último:

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

adicionar:

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

coalescer:

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

ninho:

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

NAvalores podem ser substituídos usando o .fillargumento

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

Por padrão, é um aprimorado, left_joinmas todas as junções dplyr são suportadas por meio do .modeargumento, junções difusas também são suportadas por meio do match_fun argumento (está agrupado em torno do pacote fuzzyjoin) ou fornecendo uma fórmula como ~ X("var1") > Y("var2") & X("var3") < Y("var4")a do byargumento.


0

Eu tinha uma lista de quadros de dados sem coluna de identificação comum.
Eu tinha dados perdidos em muitos dfs. Havia valores nulos. Os quadros de dados foram produzidos usando a função de tabela. O Reduzir, Mesclar, rbind, rbind.fill e similares não poderiam me ajudar a atingir meu objetivo. Meu objetivo era produzir um quadro de dados mesclado compreensível, irrelevante dos dados ausentes e da coluna de identificação comum.

Portanto, eu fiz a seguinte função. Talvez essa função possa ajudar alguém.

##########################################################
####             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) )

está seguindo a função

##########################################################
####             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

}

Executando o exemplo

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

rbind_null_df_lists ( list_of_df )

0

Quando você possui uma lista de dfs e uma coluna contém o "ID", mas em algumas listas faltam alguns IDs, você pode usar esta versão do Reduce / Merge para associar vários Dfs de IDs ou rótulos de linha ausentes:

Reduce(function(x, y) merge(x=x, y=y, by="V1", all.x=T, all.y=T), list_of_dfs)

0

Aqui está um wrapper genérico que pode ser usado para converter uma função binária em função de vários parâmetros. O benefício desta solução é que ela é muito genérica e pode ser aplicada a qualquer função binária. Você só precisa fazer uma vez e depois pode aplicá-lo em qualquer lugar.

Para demonstrar a ideia, eu uso a recursão simples para implementar. É claro que pode ser implementado de maneira mais elegante que se beneficia do bom suporte de R ao paradigma funcional.

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]))
    })
})}

Em seguida, você pode simplesmente envolver todas as funções binárias com ele e chamar com parâmetros posicionais (geralmente data.frames) nos primeiros parênteses e parâmetros nomeados nos segundos parênteses (como by =ou suffix =). Se nenhum parâmetro nomeado, deixe os segundos parênteses vazios.

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)()
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.