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.

Odpowiedzi:



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

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

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

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.


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

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

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

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


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

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.


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

-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)]))
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.