Combine dos marcos de datos por filas (rbind) cuando tengan diferentes conjuntos de columnas

232

¿Es posible enlazar dos marcos de datos que no tienen el mismo conjunto de columnas? Espero retener las columnas que no coinciden después del enlace.

Btibert3
fuente

Respuestas:

223

rbind.filldel paquete plyrpuede ser lo que estás buscando.

Jyotirmoy Bhattacharya
fuente
12
rbind.filly bind_rows()ambos silenciosamente sueltan nombres de fila.
MERose
3
@MERose Hadley: "Sí, todos los métodos dplyr ignoran los nombres de fila".
zx8754
Aquí hay un enlace a la documentación: rdocumentation.org/packages/plyr/versions/1.8.4/topics/…
Gabriel Fair
124

Una solución más reciente es usar dplyrla bind_rowsfunción que supongo es más 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
fuente
Estoy tratando de combinar una gran cantidad de marcos de datos (16) con diferentes nombres de columna. Cuando intento esto, aparece un error Error: la columna ABCno se puede convertir de carácter a numérico. ¿Hay alguna forma de convertir las columnas primero?
sar
46

Puedes usar smartbinddesde el gtoolspaquete.

Ejemplo:

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
fuente
3
Intenté smartbindcon dos marcos de datos grandes (en total, aproximadamente 3 * 10 ^ 6 filas) y lo aborté después de 10 minutos.
Joe
2
Han pasado muchas cosas en 9 años :) No podría usar smartbind hoy. Tenga en cuenta también que la pregunta original no especificaba grandes marcos de datos.
neilfws
42

Si las columnas en df1 son un subconjunto de las de df2 (por nombres de columna):

df3 <- rbind(df1, df2[, names(df1)])
Aaron Statham
fuente
38

Una alternativa con 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)

rbindtambién funcionará data.tablesiempre que los objetos se conviertan en data.tableobjetos, por lo que

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

También funcionará en esta situación. Esto puede ser preferible cuando tienes un par de data.tables y no quieres construir una lista.

kdauria
fuente
Esta es la solución más simple y lista para usar que se generaliza fácilmente a cualquier cantidad de marcos de datos, ya que puede almacenarlos en elementos de lista separados. Otras respuestas, como el intersectenfoque, solo funcionan para 2 marcos de datos y no se generalizan fácilmente.
Rich Pauloo
35

La mayoría de las respuestas de la base R abordan la situación en la que solo un data.frame tiene columnas adicionales o que el data.frame resultante tendría la intersección de las columnas. Dado que el OP escribe , espero retener las columnas que no coinciden después del enlace , probablemente valga la pena publicar una respuesta que utilice métodos de base R para abordar este problema.

A continuación, presento dos métodos base R: uno que altera los data.frames originales y otro que no. Además, ofrezco un método que generaliza el método no destructivo a más de dos data.frames.

Primero, obtengamos algunos datos de muestra.

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

Dos marcos de datos, alterar originales
Para retener todas las columnas de ambos data.frames en un rbind(y permitir que la función funcione sin provocar un error), agregue columnas NA a cada data.frame con los nombres faltantes correspondientes rellenados utilizando setdiff.

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

Ahora, 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

Tenga en cuenta que las dos primeras líneas alteran los data.frames originales, df1 y df2, agregando el conjunto completo de columnas a ambos.


Dos marcos de datos, no altere los originales
Para dejar intactos los marcos de datos originales, primero recorra los nombres que difieren, devuelva un vector con nombre de NA que se concatenan en una lista con el marco de datos c. Luego, data.frameconvierte el resultado en un data.frame apropiado para 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)))
)

Muchos marcos de datos, no alteran los originales
En el caso de que tenga más de dos data.frames, puede hacer lo siguiente.

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

¿Quizás sea un poco más agradable no ver los nombres de las filas de data.frames originales? Entonces haz esto.

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
fuente
Tengo 16 marcos de datos, algunos con diferentes columnas (aproximadamente 70-90 columnas en total en cada uno). Cuando intento esto, me quedo atascado con el primer comando <- mget (ls (pattern = "df \\ d +")). Mis marcos de datos tienen nombres diferentes. Intenté hacer una lista usando mydflist <- c (como, dr, kr, hyt, ed1, of) pero esto me dio una lista enorme.
sar
Solo enlace a @GKi
sar
1
@sar uso mydflist <- list(as, dr, kr, hyt, ed1, of). Esto debería construir un objeto de lista que no aumente el tamaño de su entorno, sino que solo apunte a cada elemento de la lista (siempre que no altere ninguno de los contenidos después). Después de la operación, elimine el objeto de la lista, solo para estar seguro.
lmo
20

También podría simplemente extraer los nombres de columna comunes.

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

Escribí una función para hacer esto porque me gusta que mi código me diga si algo está mal. Esta función le dirá explícitamente qué nombres de columna no coinciden y si tiene un tipo no coincidente. Entonces hará todo lo posible para combinar data.frames de todos modos. La limitación es que solo puede combinar dos data.frames a la 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)
}

fuente
2

Tal vez leí completamente su pregunta, pero el "Espero retener las columnas que no coinciden después del enlace" me hace pensar que está buscando una consulta SQL left joino right joinsimilar. R tiene elmerge función que le permite especificar uniones izquierdas, derechas o internas similares a unir tablas en SQL.

Ya hay una gran pregunta y respuesta sobre este tema aquí: ¿Cómo unir (fusionar) marcos de datos (interno, externo, izquierdo, derecho)?

Persecución
fuente
2

A gtools / smartbind no le gustaba trabajar con Dates, probablemente porque era un vector. Así que aquí está mi solución ...

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
fuente
El uso de dplyr :: bind_rows (x, y) en lugar de rbind (x, y) mantiene el orden de las columnas en función del primer marco de datos.
RanonKahn
2

Solo por la documentación. Puede probar la Stackbiblioteca y su función Stackde la siguiente forma:

Stack(df_1, df_2)

También tengo la impresión de que es más rápido que otros métodos para grandes conjuntos de datos.

Cro-Magnon
fuente
1

También podría usar sjmisc::add_rows(), que usa dplyr::bind_rows(), pero a diferencia bind_rows(), add_rows()conserva los atributos y, por lo tanto, es útil para los datos etiquetados .

Vea el siguiente ejemplo con un conjunto de datos etiquetado. La frq()función imprime tablas de frecuencia con etiquetas de valor, si los datos están etiquetados.

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