Combine dois quadros de dados por linhas (rbind) quando eles tiverem conjuntos diferentes de colunas

232

É possível vincular linhas a dois quadros de dados que não possuem o mesmo conjunto de colunas? Espero manter as colunas que não coincidem após o vínculo.

Btibert3
fonte

Respostas:

223

rbind.filldo pacote plyrpode ser o que você está procurando.

Jyotirmoy Bhattacharya
fonte
12
rbind.fille bind_rows()ambos silenciosamente eliminam nomes de domínio.
MERose
3
@Mose Joseph Hadley: "Sim, todos os métodos dplyr ignoram nomes de domínio".
Zx8754
Aqui está um link para a documentação: rdocumentation.org/packages/plyr/versions/1.8.4/topics/...
Gabriel Fair
124

Uma solução mais recente é usar dplyra bind_rowsfunção de que suponho ser mais eficiente que smartbind.

df1 <- data.frame(a = c(1:5), b = c(6:10))
df2 <- data.frame(a = c(11:15), b = c(16:20), c = LETTERS[1:5])
dplyr::bind_rows(df1, df2)
    a  b    c
1   1  6 <NA>
2   2  7 <NA>
3   3  8 <NA>
4   4  9 <NA>
5   5 10 <NA>
6  11 16    A
7  12 17    B
8  13 18    C
9  14 19    D
10 15 20    E
xiaodai
fonte
Estou tentando combinar um grande número de quadros de dados (16) com nomes de colunas diferentes. Quando tento isso, recebo um erro Erro: A coluna ABCnão pode ser convertida de caractere para numérico. Existe uma maneira de converter as colunas primeiro?
sar
46

Você pode usar smartbindo gtoolspacote.

Exemplo:

library(gtools)
df1 <- data.frame(a = c(1:5), b = c(6:10))
df2 <- data.frame(a = c(11:15), b = c(16:20), c = LETTERS[1:5])
smartbind(df1, df2)
# result
     a  b    c
1.1  1  6 <NA>
1.2  2  7 <NA>
1.3  3  8 <NA>
1.4  4  9 <NA>
1.5  5 10 <NA>
2.1 11 16    A
2.2 12 17    B
2.3 13 18    C
2.4 14 19    D
2.5 15 20    E
neilfws
fonte
3
Tentei smartbindcom dois grandes quadros de dados (no total aproximadamente 3 * 10 ^ 6 linhas) e abortei-o após 10 minutos.
Joe
2
Muita coisa aconteceu em 9 anos :) Talvez eu não use o smartbind hoje. Observe também que a pergunta original não especificou grandes quadros de dados.
neilfws
42

Se as colunas no df1 forem um subconjunto daquelas no df2 (pelos nomes das colunas):

df3 <- rbind(df1, df2[, names(df1)])
Aaron Statham
fonte
37

Uma alternativa com data.table:

library(data.table)
df1 = data.frame(a = c(1:5), b = c(6:10))
df2 = data.frame(a = c(11:15), b = c(16:20), c = LETTERS[1:5])
rbindlist(list(df1, df2), fill = TRUE)

rbindtambém funcionará data.tableenquanto os objetos forem convertidos em data.tableobjetos, portanto

rbind(setDT(df1), setDT(df2), fill=TRUE)

também funcionará nessa situação. Isso pode ser preferível quando você tem algumas tabelas de dados e não deseja construir uma lista.

kdauria
fonte
Essa é a solução mais simples e pronta para uso que generaliza facilmente para qualquer número de quadros de dados, pois você pode armazená-los todos em elementos de lista separados. Outras respostas, como a intersectabordagem, funcionam apenas para dois quadros de dados e não são facilmente generalizadas.
Ricos Pauloo
35

A maioria das respostas R básicas aborda a situação em que apenas um data.frame possui colunas adicionais ou que o data.frame resultante teria a interseção das colunas. Como o OP escreve , espero manter as colunas que não correspondem após a ligação , provavelmente vale a pena postar uma resposta usando os métodos base R para resolver esse problema.

A seguir, apresento dois métodos R básicos: um que altera o data.frames original e outro que não. Além disso, ofereço um método que generaliza o método não destrutivo para mais de dois data.frames.

Primeiro, vamos obter alguns dados de amostra.

# sample data, variable c is in df1, variable d is in df2
df1 = data.frame(a=1:5, b=6:10, d=month.name[1:5])
df2 = data.frame(a=6:10, b=16:20, c = letters[8:12])

Dois data.frames, altere originais
Para manter todas as colunas de ambos data.frames em um rbind(e permitir que a função funcione sem resultar em erro), adicione colunas NA a cada data.frame com os nomes ausentes apropriados preenchidos usando setdiff.

# fill in non-overlapping columns with NAs
df1[setdiff(names(df2), names(df1))] <- NA
df2[setdiff(names(df1), names(df2))] <- NA

Agora, rbind-em

rbind(df1, df2)
    a  b        d    c
1   1  6  January <NA>
2   2  7 February <NA>
3   3  8    March <NA>
4   4  9    April <NA>
5   5 10      May <NA>
6   6 16     <NA>    h
7   7 17     <NA>    i
8   8 18     <NA>    j
9   9 19     <NA>    k
10 10 20     <NA>    l

Observe que as duas primeiras linhas alteram os data.frames originais, df1 e df2, adicionando o conjunto completo de colunas a ambos.


Dois data.frames, não alteram os originais
Para deixar os data.frames originais intactos, primeiro faça um loop entre os nomes que diferem, retorne um vetor nomeado de NAs que são concatenados em uma lista com o data.frame usando c. Em seguida, data.frameconverte o resultado em um data.frame apropriado para o rbind.

rbind(
  data.frame(c(df1, sapply(setdiff(names(df2), names(df1)), function(x) NA))),
  data.frame(c(df2, sapply(setdiff(names(df1), names(df2)), function(x) NA)))
)

Muitos data.frames, não alteram os originais
Caso você tenha mais de dois data.frames, faça o seguinte.

# put data.frames into list (dfs named df1, df2, df3, etc)
mydflist <- mget(ls(pattern="df\\d+"))
# get all variable names
allNms <- unique(unlist(lapply(mydflist, names)))

# put em all together
do.call(rbind,
        lapply(mydflist,
               function(x) data.frame(c(x, sapply(setdiff(allNms, names(x)),
                                                  function(y) NA)))))

Talvez um pouco melhor não ver os nomes das linhas dos data.frames originais? Então faça isso.

do.call(rbind,
        c(lapply(mydflist,
                 function(x) data.frame(c(x, sapply(setdiff(allNms, names(x)),
                                                    function(y) NA)))),
          make.row.names=FALSE))
lmo
fonte
Eu tenho 16 quadros de dados, alguns com colunas diferentes (aproximadamente 70-90 colunas no total em cada). Quando tento isso, fico com o primeiro comando <- mget (ls (pattern = "df \\ d +")). Meus quadros de dados têm nomes diferentes. Tentei fazer uma lista usando mydflist <- c (as, dr, kr, hyt, ed1, of), mas isso me deu uma lista enorme.
sar
Apenas ligando para @GKi
sar
1
@sar use mydflist <- list(as, dr, kr, hyt, ed1, of). Isso deve criar um objeto de lista que não aumente o tamanho do seu ambiente, mas apenas aponte para cada elemento da lista (desde que você não altere nenhum conteúdo posteriormente). Após a operação, remova o objeto de lista, apenas para garantir a segurança.
lmo 25/03
20

Você também pode simplesmente retirar os nomes das colunas comuns.

> cols <- intersect(colnames(df1), colnames(df2))
> rbind(df1[,cols], df2[,cols])
Jonathan Chang
fonte
6

Eu escrevi uma função para fazer isso porque eu gosto do meu código para me dizer se algo está errado. Essa função informará explicitamente quais nomes de colunas não correspondem e se você tem uma incompatibilidade de tipo. Em seguida, fará o possível para combinar os data.frames de qualquer maneira. A limitação é que você pode combinar apenas dois data.frames por vez.

### combines data frames (like rbind) but by matching column names
# columns without matches in the other data frame are still combined
# but with NA in the rows corresponding to the data frame without
# the variable
# A warning is issued if there is a type mismatch between columns of
# the same name and an attempt is made to combine the columns
combineByName <- function(A,B) {
    a.names <- names(A)
    b.names <- names(B)
    all.names <- union(a.names,b.names)
    print(paste("Number of columns:",length(all.names)))
    a.type <- NULL
    for (i in 1:ncol(A)) {
        a.type[i] <- typeof(A[,i])
    }
    b.type <- NULL
    for (i in 1:ncol(B)) {
        b.type[i] <- typeof(B[,i])
    }
    a_b.names <- names(A)[!names(A)%in%names(B)]
    b_a.names <- names(B)[!names(B)%in%names(A)]
    if (length(a_b.names)>0 | length(b_a.names)>0){
        print("Columns in data frame A but not in data frame B:")
        print(a_b.names)
        print("Columns in data frame B but not in data frame A:")
        print(b_a.names)
    } else if(a.names==b.names & a.type==b.type){
        C <- rbind(A,B)
        return(C)
    }
    C <- list()
    for(i in 1:length(all.names)) {
        l.a <- all.names[i]%in%a.names
        pos.a <- match(all.names[i],a.names)
        typ.a <- a.type[pos.a]
        l.b <- all.names[i]%in%b.names
        pos.b <- match(all.names[i],b.names)
        typ.b <- b.type[pos.b]
        if(l.a & l.b) {
            if(typ.a==typ.b) {
                vec <- c(A[,pos.a],B[,pos.b])
            } else {
                warning(c("Type mismatch in variable named: ",all.names[i],"\n"))
                vec <- try(c(A[,pos.a],B[,pos.b]))
            }
        } else if (l.a) {
            vec <- c(A[,pos.a],rep(NA,nrow(B)))
        } else {
            vec <- c(rep(NA,nrow(A)),B[,pos.b])
        }
        C[[i]] <- vec
    }
    names(C) <- all.names
    C <- as.data.frame(C)
    return(C)
}

fonte
2

Talvez eu tenha interpretado mal sua pergunta completamente, mas o "Espero manter as colunas que não coincidem após a ligação" me faz pensar que você está procurando por uma consulta left joinou right joinsemelhante a uma consulta SQL. R possui a mergefunção que permite especificar junções esquerda, direita ou interna semelhantes às junções de tabelas no SQL.

Já existe uma ótima pergunta e resposta sobre este tópico aqui: Como unir (mesclar) quadros de dados (interno, externo, esquerdo, direito)?

correr atrás
fonte
2

O gtools / smartbind não gostava de trabalhar com o Datas, provavelmente porque era como.vector. Então aqui está a minha solução ...

sbind = function(x, y, fill=NA) {
    sbind.fill = function(d, cols){ 
        for(c in cols)
            d[[c]] = fill
        d
    }

    x = sbind.fill(x, setdiff(names(y),names(x)))
    y = sbind.fill(y, setdiff(names(x),names(y)))

    rbind(x, y)
}
Aaron
fonte
o uso de dplyr :: bind_rows (x, y) no lugar de rbind (x, y) mantém a ordem das colunas com base no primeiro quadro de dados.
Re
2

Apenas para a documentação. Você pode tentar a Stackbiblioteca e sua função Stackno seguinte formato:

Stack(df_1, df_2)

Também tenho a impressão de que é mais rápido que outros métodos para grandes conjuntos de dados.

Cro-Magnon
fonte
1

Você também pode usar sjmisc::add_rows(), que usa dplyr::bind_rows(), mas diferentemente bind_rows(), add_rows()preserva atributos e, portanto, é útil para dados rotulados .

Veja o exemplo a seguir com um conjunto de dados rotulado. A frq()função-imprime tabelas de frequência com etiquetas de valor, se os dados estiverem rotulados.

library(sjmisc)
library(dplyr)

data(efc)
# select two subsets, with some identical and else different columns
x1 <- efc %>% select(1:5) %>% slice(1:10)
x2 <- efc %>% select(3:7) %>% slice(11:20)

str(x1)
#> 'data.frame':    10 obs. of  5 variables:
#>  $ c12hour : num  16 148 70 168 168 16 161 110 28 40
#>   ..- attr(*, "label")= chr "average number of hours of care per week"
#>  $ e15relat: num  2 2 1 1 2 2 1 4 2 2
#>   ..- attr(*, "label")= chr "relationship to elder"
#>   ..- attr(*, "labels")= Named num  1 2 3 4 5 6 7 8
#>   .. ..- attr(*, "names")= chr  "spouse/partner" "child" "sibling" "daughter or son -in-law" ...
#>  $ e16sex  : num  2 2 2 2 2 2 1 2 2 2
#>   ..- attr(*, "label")= chr "elder's gender"
#>   ..- attr(*, "labels")= Named num  1 2
#>   .. ..- attr(*, "names")= chr  "male" "female"
#>  $ e17age  : num  83 88 82 67 84 85 74 87 79 83
#>   ..- attr(*, "label")= chr "elder' age"
#>  $ e42dep  : num  3 3 3 4 4 4 4 4 4 4
#>   ..- attr(*, "label")= chr "elder's dependency"
#>   ..- attr(*, "labels")= Named num  1 2 3 4
#>   .. ..- attr(*, "names")= chr  "independent" "slightly dependent" "moderately dependent" "severely dependent"

bind_rows(x1, x1) %>% frq(e42dep)
#> 
#> # e42dep <numeric> 
#> # total N=20  valid N=20  mean=3.70  sd=0.47
#>  
#>   val frq raw.prc valid.prc cum.prc
#>     3   6      30        30      30
#>     4  14      70        70     100
#>  <NA>   0       0        NA      NA

add_rows(x1, x1) %>% frq(e42dep)
#> 
#> # elder's dependency (e42dep) <numeric> 
#> # total N=20  valid N=20  mean=3.70  sd=0.47
#>  
#>  val                label frq raw.prc valid.prc cum.prc
#>    1          independent   0       0         0       0
#>    2   slightly dependent   0       0         0       0
#>    3 moderately dependent   6      30        30      30
#>    4   severely dependent  14      70        70     100
#>   NA                   NA   0       0        NA      NA
Daniel
fonte
-1
rbind.ordered=function(x,y){

  diffCol = setdiff(colnames(x),colnames(y))
  if (length(diffCol)>0){
    cols=colnames(y)
    for (i in 1:length(diffCol)) y=cbind(y,NA)
    colnames(y)=c(cols,diffCol)
  }

  diffCol = setdiff(colnames(y),colnames(x))
  if (length(diffCol)>0){
    cols=colnames(x)
    for (i in 1:length(diffCol)) x=cbind(x,NA)
    colnames(x)=c(cols,diffCol)
  }
  return(rbind(x, y[, colnames(x)]))
}
RockScience
fonte