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.

Respuestas:



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

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

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

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.


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

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

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


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

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.


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

-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)]))
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.