Combina due frame di dati per righe (rbind) quando hanno diversi set di colonne


232

È possibile associare in riga due frame di dati che non hanno lo stesso set di colonne? Spero di conservare le colonne che non corrispondono dopo il bind.

Risposte:



124

Una soluzione più recente è quella di utilizzare dplyrla bind_rowsfunzione che presumo sia più efficiente di 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

Sto provando a combinare un gran numero di frame di dati (16) con nomi di colonne diversi Quando provo questo ottengo un errore Errore: la colonna ABCnon può essere convertita da carattere a numerico. C'è un modo per convertire prima le colonne?
sar

46

Puoi usare smartbinddal gtoolspacchetto.

Esempio:

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
Ho provato smartbindcon due grandi frame di dati (in totale circa 3 * 10 ^ 6 righe) e l'ho interrotto dopo 10 minuti.
Joe,

2
Sono successe molte cose in 9 anni :) Oggi potrei non usare smartbind. Si noti inoltre che la domanda originale non ha specificato frame di dati di grandi dimensioni.
neilfws,

42

Se le colonne in df1 sono un sottoinsieme di quelle in df2 (in base ai nomi delle colonne):

df3 <- rbind(df1, df2[, names(df1)])

37

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

rbindfunzionerà anche data.tablefinché gli oggetti vengono convertiti in data.tableoggetti, quindi

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

funzionerà anche in questa situazione. Questo può essere preferibile quando hai un paio di data.tables e non vuoi costruire un elenco.


Questa è la soluzione più semplice e pronta all'uso che si generalizza facilmente a qualsiasi numero di frame di dati, poiché è possibile memorizzarli tutti in elementi di elenco separati. Altre risposte, come l' intersectapproccio, funzionano solo con 2 frame di dati e non si generalizzano facilmente.
Rich Pauloo,

35

La maggior parte delle risposte di base R affronta la situazione in cui solo un data.frame ha colonne aggiuntive o che il data.frame risultante avrebbe l'intersezione delle colonne. Poiché l'OP scrive spero di conservare le colonne che non corrispondono dopo il bind , probabilmente vale la pena pubblicare una risposta utilizzando i metodi di base R per risolvere questo problema.

Di seguito, presento due metodi R di base: uno che altera i data.frames originali e uno che non lo fa. Inoltre, offro un metodo che generalizza il metodo non distruttivo a più di due data.frames.

Innanzitutto, otteniamo alcuni dati di esempio.

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

Due data.frames, alterano gli originali
Per conservare tutte le colonne di entrambi i data.frames in un rbind(e consentire alla funzione di funzionare senza generare un errore), aggiungi colonne NA a ciascun data.frame con i nomi mancanti appropriati compilati usando setdiff.

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

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

Si noti che le prime due righe modificano i data.frames originali, df1 e df2, aggiungendo il set completo di colonne ad entrambi.


Due data.frames, non alterano gli originali
Per lasciare intatti i data.frames originali, scorrere prima i nomi che differiscono, restituendo un vettore denominato di NA che sono concatenati in un elenco con data.frame usando c. Quindi, data.frameconverte il risultato in un data.frame appropriato per il file 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)))
)

Molti data.frames, non alterano gli originali Nell'istanza in
cui hai più di due data.frames, puoi fare quanto segue.

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

Forse un po 'più bello non vedere i nomi delle righe dei data.frames originali? Quindi fai questo.

do.call(rbind,
        c(lapply(mydflist,
                 function(x) data.frame(c(x, sapply(setdiff(allNms, names(x)),
                                                    function(y) NA)))),
          make.row.names=FALSE))

Ho 16 dataframe alcuni con colonne diverse (circa 70-90 colonne totali in ciascuno). Quando provo questo, rimango bloccato con il primo comando <- mget (ls (pattern = "df \\ d +")). I miei frame di dati hanno nomi diversi. Ho provato a fare una lista usando mydflist <- c (as, dr, kr, hyt, ed1, of) ma questo mi ha dato una lista enorme.
sar

Solo il collegamento a @GKi
sar

1
@sar use mydflist <- list(as, dr, kr, hyt, ed1, of). Questo dovrebbe costruire un oggetto elenco che non aumenti le dimensioni del tuo ambiente, ma punti solo a ciascun elemento dell'elenco (purché non modifichi in seguito alcun contenuto). Dopo l'operazione, rimuovere l'oggetto elenco, solo per sicurezza.
lmo

20

Potresti anche estrarre i nomi delle colonne comuni.

> cols <- intersect(colnames(df1), colnames(df2))
> rbind(df1[,cols], df2[,cols])

6

Ho scritto una funzione per farlo perché mi piace il mio codice per dirmi se qualcosa non va. Questa funzione ti dirà esplicitamente quali nomi di colonna non corrispondono e se hai una mancata corrispondenza del tipo. Quindi farà del suo meglio per combinare i data.frames comunque. Il limite è che puoi combinare solo due data.frames alla volta.

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

Forse ho letto male la tua domanda, ma "Spero di conservare le colonne che non corrispondono dopo il bind" mi fa pensare che stai cercando una left joino right joinsimile a una query SQL. R ha la mergefunzione che consente di specificare join sinistro, destro o interno simili alle tabelle di join in SQL.

C'è già una grande domanda e risposta su questo argomento qui: Come unire (unire) i frame di dati (interno, esterno, sinistro, destro)?


2

A gtools / smartbind non piaceva lavorare con Date, probabilmente perché era as.vectoring. Quindi ecco la mia soluzione ...

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

usando dplyr :: bind_rows (x, y) al posto di rbind (x, y) mantiene l'ordine delle colonne in base al primo frame di dati.
RanonKahn,

2

Solo per la documentazione. Puoi provare la Stacklibreria e la sua funzione Stacknel seguente formato:

Stack(df_1, df_2)

Ho anche l'impressione che sia più veloce di altri metodi per set di dati di grandi dimensioni.


1

È inoltre possibile utilizzare sjmisc::add_rows(), che utilizza dplyr::bind_rows(), ma a differenza bind_rows(), add_rows()conserva gli attributi e quindi è utile per i dati etichettati .

Vedere l'esempio seguente con un set di dati con etichetta. La funzione frq()stampa le tabelle di frequenza con etichette dei valori, se i dati sono etichettati.

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)]))
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.