Połącz dwie ramki danych według wierszy (rbind), jeśli mają różne zestawy kolumn

232

Czy można powiązać wierszami dwie ramki danych, które nie mają tego samego zestawu kolumn? Mam nadzieję, że zachowam kolumny, które nie pasują po powiązaniu.

Btibert3
źródło

Odpowiedzi:

223

rbind.fillz paczki plyrmoże być tym, czego szukasz.

Jyotirmoy Bhattacharya
źródło
12
rbind.filli bind_rows()oba po cichu upuszczają nazwy.
MERose
3
@MERose Hadley: „Tak, wszystkie metody dplyr ignorują nazwy rown”.
zx8754,
Oto link do dokumentacji: rdocumentation.org/packages/plyr/versions/1.8.4/topics/…
Gabriel Fair
124

Nowsza rozwiązaniem jest użycie dplyr„s bind_rowsfunkcję które zakładam, że jest bardziej wydajny niż 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
źródło
Próbuję połączyć dużą liczbę ramek danych (16) z różnymi nazwami kolumn. Kiedy próbuję, pojawia się błąd Błąd: Kolumny ABCnie można przekonwertować z postaci na cyfrę. Czy istnieje sposób, aby najpierw przekonwertować kolumny?
sar
46

Możesz użyć smartbindz gtoolspakietu.

Przykład:

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
źródło
3
Próbowałem smartbindz dwoma dużymi ramkami danych (w sumie około 3 * 10 ^ 6 wierszy) i przerwałem je po 10 minutach.
Joe
2
Wiele się wydarzyło od 9 lat :) Mogę dziś nie używać smartbind. Należy również zauważyć, że pierwotne pytanie nie określało dużych ramek danych.
neilfws,
42

Jeśli kolumny w df1 są podzbiorem tych w df2 (według nazw kolumn):

df3 <- rbind(df1, df2[, names(df1)])
Aaron Statham
źródło
37

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

rbindbędzie również działać, data.tabledopóki obiekty zostaną przekonwertowane na data.tableobiekty, więc

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

będzie również działać w tej sytuacji. Może to być preferowane, gdy masz kilka tabel data.tab i nie chcesz budować listy.

kdauria
źródło
Jest to najprostsze, gotowe do użycia rozwiązanie, które można łatwo uogólnić na dowolną liczbę ramek danych, ponieważ można je wszystkie przechowywać w osobnych elementach listy. Inne odpowiedzi, takie jak intersectpodejście, działają tylko dla 2 ramek danych i nie dają się łatwo generalizować.
Rich Pauloo,
35

Większość podstawowych odpowiedzi R dotyczy sytuacji, w której tylko jedna ramka danych ma dodatkowe kolumny lub że wynikowa ramka danych ma przecięcie kolumn. Ponieważ OP pisze, że mam nadzieję zachować kolumny, które nie pasują po powiązaniu , prawdopodobnie warto opublikować odpowiedź przy użyciu podstawowych metod R w celu rozwiązania tego problemu.

Poniżej przedstawiam dwie podstawowe metody R. Jedna zmienia oryginalne data.frames, a druga nie. Dodatkowo oferuję metodę, która uogólnia metodę nieniszczącą na więcej niż dwie ramki danych.

Najpierw zdobądźmy przykładowe dane.

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

Dwie data.frame, zmień oryginały
Aby zachować wszystkie kolumny z obu data.frame w rbind(i umożliwić działanie funkcji bez powodowania błędu), dodajesz kolumny NA do każdej data.frame z wypełnionymi odpowiednimi brakującymi nazwami za pomocą setdiff.

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

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

Zauważ, że pierwsze dwa wiersze zmieniają oryginalne dane. Ramki, df1 i df2, dodając pełny zestaw kolumn do obu.


Dwie ramki data.frame, nie zmieniaj oryginałów
Aby pozostawić nienaruszone oryginalne ramki data.frame, najpierw wykonaj pętlę przez nazwy, które się różnią, zwróć nazwany wektor NA, które są połączone w listę za pomocą data.frame c. Następnie data.framekonwertuje wynik na odpowiednią ramkę data.frame dla pliku 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)))
)

Wiele data.frames, nie zmieniaj oryginałów
W przypadku, gdy masz więcej niż dwa data.frames, możesz wykonać następujące czynności.

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

Może nieco milej jest nie widzieć nazw wierszy oryginalnych ramek data.frames? Zrób to.

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
źródło
Mam 16 ramek danych, niektóre z różnymi kolumnami (w sumie około 70-90 kolumn w każdej). Kiedy próbuję tego, utknąłem z pierwszym poleceniem <- mget (ls (pattern = "df \\ d +")). Moje ramki danych mają różne nazwy. Próbowałem stworzyć listę używając mydflist <- c (as, dr, kr, hyt, ed1, of), ale to dało mi ogromną listę.
sar
Link do @GKi
sar
1
wykorzystanie @sar mydflist <- list(as, dr, kr, hyt, ed1, of). To powinno skonstruować obiekt listy, który nie zwiększy wielkości twojego środowiska, ale po prostu wskaże każdy element listy (o ile nie zmienisz żadnej zawartości później). Po operacji usuń obiekt listy, aby być bezpiecznym.
Lmo
20

Możesz także po prostu wyciągnąć wspólne nazwy kolumn.

> cols <- intersect(colnames(df1), colnames(df2))
> rbind(df1[,cols], df2[,cols])
Jonathan Chang
źródło
6

Napisałem funkcję, aby to zrobić, ponieważ podoba mi się mój kod informujący, czy coś jest nie tak. Ta funkcja wyraźnie powie ci, które nazwy kolumn nie pasują i czy masz niezgodność typu. Wtedy i tak dołoży wszelkich starań, aby połączyć dane. Ramki. Ograniczeniem jest to, że możesz łączyć tylko dwie ramki danych jednocześnie.

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

źródło
2

Być może całkowicie źle odczytałem twoje pytanie, ale „Mam nadzieję zachować kolumny, które nie pasują po powiązaniu”, sprawia, że ​​myślę, że szukasz zapytania podobnego do zapytania SQL left joinlub right joinpodobnego. R ma mergefunkcję, która pozwala określić lewe, prawe lub wewnętrzne sprzężenia podobne do łączenia tabel w SQL.

Tutaj jest już świetne pytanie i odpowiedź na ten temat: Jak połączyć (scalić) ramki danych (wewnętrzne, zewnętrzne, lewe, prawe)?

Pościg
źródło
2

gtools / smartbind nie lubił pracować z Dates, prawdopodobnie dlatego, że było tak jak wektory. Oto moje rozwiązanie ...

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
źródło
użycie dplyr :: bind_rows (x, y) zamiast rbind (x, y) utrzymuje kolejność kolumn na podstawie pierwszej ramki danych.
RanonKahn,
2

Tylko dla dokumentacji. Możesz wypróbować Stackbibliotekę i jej funkcję Stackw następującej formie:

Stack(df_1, df_2)

Mam również wrażenie, że jest szybszy niż inne metody dla dużych zbiorów danych.

Cro-Magnon
źródło
1

Możesz również użyć sjmisc::add_rows(), który używa dplyr::bind_rows(), ale w przeciwieństwie do bind_rows(), add_rows()zachowuje atrybuty, a zatem jest użyteczny dla danych z etykietami .

Zobacz następujący przykład z etykietowanym zestawem danych. Funkcja frq()drukuje tabele częstotliwości z etykietami wartości, jeśli dane są oznaczone.

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
źródło
-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
źródło