Gabungkan dua frame data dengan baris (rbind) ketika mereka memiliki set kolom yang berbeda

232

Mungkinkah untuk mengikat dua bingkai data yang tidak memiliki kumpulan kolom yang sama? Saya berharap dapat mempertahankan kolom yang tidak cocok setelah diikat.

Btibert3
sumber

Jawaban:

223

rbind.filldari paket plyrmungkin apa yang Anda cari.

Jyotirmoy Bhattacharya
sumber
12
rbind.filldan bind_rows()keduanya diam-diam menjatuhkan nama sandi.
MERose
3
@MERose Hadley: "Ya, semua metode dplyr mengabaikan nama pengguna."
zx8754
Berikut ini tautan ke dokumentasi: rdocumentation.org/packages/plyr/versions/1.8.4/topics/…
Gabriel Fair
124

Solusi yang lebih baru adalah dengan menggunakan dplyr's bind_rowsfungsi yang saya asumsikan lebih efisien daripada 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
sumber
Saya mencoba untuk menggabungkan sejumlah besar dataframe (16) dengan nama kolom yang berbeda Ketika saya mencoba ini saya mendapatkan kesalahan Kesalahan: Kolom ABCtidak dapat dikonversi dari karakter ke angka. Apakah ada cara untuk mengonversi kolom terlebih dahulu?
sar
46

Anda dapat menggunakan smartbinddari gtoolspaket.

Contoh:

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
sumber
3
Saya mencoba smartbinddengan dua frame data besar (total sekitar 3 * 10 ^ 6 baris) dan dibatalkan setelah 10 menit.
Joe
2
Banyak yang telah terjadi dalam 9 tahun :) Saya mungkin tidak menggunakan smartbind hari ini. Perhatikan juga bahwa pertanyaan awal tidak menentukan bingkai data besar.
neilfws
42

Jika kolom dalam df1 adalah himpunan bagian dari yang ada di df2 (dengan nama kolom):

df3 <- rbind(df1, df2[, names(df1)])
Aaron Statham
sumber
37

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

rbindjuga akan berfungsi data.tableselama objek dikonversi menjadi data.tableobjek, jadi

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

juga akan bekerja dalam situasi ini. Ini bisa lebih disukai ketika Anda memiliki beberapa data.tabel dan tidak ingin membangun daftar.

kdauria
sumber
Ini adalah solusi paling sederhana, out-of-the-box yang mudah digeneralisasi ke sejumlah kerangka data, karena Anda dapat menyimpan semuanya dalam elemen daftar terpisah. Jawaban lain, seperti intersectpendekatan, hanya berfungsi untuk 2 kerangka data dan tidak mudah digeneralisasi.
Rich Pauloo
35

Sebagian besar jawaban R dasar mengatasi situasi di mana hanya satu data.frame memiliki kolom tambahan atau bahwa data.frame yang dihasilkan akan memiliki persimpangan kolom. Karena OP menulis saya berharap untuk mempertahankan kolom yang tidak cocok setelah bind , jawaban yang menggunakan metode dasar R untuk mengatasi masalah ini mungkin patut diposkan.

Di bawah ini, saya menyajikan dua metode dasar R: Satu yang mengubah data.frame asli, dan yang tidak. Selain itu, saya menawarkan metode yang menggeneralisasi metode non-destruktif ke lebih dari dua data.frame.

Pertama, mari kita ambil beberapa sampel data.

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

Dua data.frame, ubah aslinya
Untuk mempertahankan semua kolom dari kedua data.frame dalam rbind(dan memungkinkan fungsi untuk bekerja tanpa menghasilkan kesalahan), Anda menambahkan kolom NA ke setiap data.frame dengan nama-nama yang hilang yang sesuai diisi menggunakan setdiff.

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

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

Perhatikan bahwa dua baris pertama mengubah data.frame asli, df1 dan df2, menambahkan set kolom lengkap untuk keduanya.


Dua data.frame, jangan ubah dokumen asli
Untuk membiarkan data.frame asli tetap utuh, pertama-tama periksalah nama-nama yang berbeda, kembalikan vektor bernama NAS yang digabungkan ke dalam daftar dengan menggunakan data.frame c. Kemudian, data.frameubah hasilnya menjadi data.frame yang sesuai untuk 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)))
)

Banyak data.frame, jangan ubah aslinya
Dalam contoh Anda memiliki lebih dari dua data.frame, Anda bisa melakukan hal berikut.

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

Mungkin sedikit lebih baik untuk tidak melihat nama-nama baris data.frame asli? Lalu lakukan ini.

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
sumber
Saya memiliki 16 dataframe beberapa dengan kolom yang berbeda (sekitar 70-90 total kolom di masing-masing). Ketika saya mencoba ini, saya terjebak dengan perintah pertama <- mget (ls (pattern = "df \\ d +")). Dataframe saya memiliki nama yang berbeda. Saya mencoba membuat daftar menggunakan mydflist <- c (seperti, dr, kr, hyt, ed1, of) tetapi ini memberi saya daftar yang sangat besar.
sar
Hanya menghubungkan ke @GKi
sar
1
@sar digunakan mydflist <- list(as, dr, kr, hyt, ed1, of). Ini harus membangun objek daftar yang tidak menumbuhkan ukuran lingkungan Anda, tetapi hanya menunjuk ke setiap elemen daftar (selama Anda tidak mengubah konten apa pun sesudahnya). Setelah operasi, hapus daftar objek, hanya agar aman.
lmo
20

Anda juga bisa mencabut nama kolom yang umum.

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

Saya menulis fungsi untuk melakukan ini karena saya suka kode saya untuk memberi tahu saya jika ada sesuatu yang salah. Fungsi ini secara eksplisit akan memberi tahu Anda nama kolom mana yang tidak cocok dan jika Anda memiliki tipe ketidakcocokan. Maka itu akan melakukan yang terbaik untuk menggabungkan data.frame. Batasannya adalah Anda hanya bisa menggabungkan dua data.frame sekaligus.

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

sumber
2

Mungkin saya benar-benar salah membaca pertanyaan Anda, tetapi "Saya berharap untuk mempertahankan kolom yang tidak cocok setelah ikatan" membuat saya berpikir Anda sedang mencari left joinatau right joinmirip dengan permintaan SQL. R memiliki mergefungsi yang memungkinkan Anda menentukan gabungan kiri, kanan, atau dalam mirip dengan bergabung dengan tabel di SQL.

Sudah ada pertanyaan dan jawaban yang bagus tentang topik ini di sini: Bagaimana cara bergabung (menggabungkan) bingkai data (dalam, luar, kiri, kanan)?

Mengejar
sumber
2

gtools / smartbind tidak suka bekerja dengan Tanggal, mungkin karena as.vectoring. Jadi, inilah solusi saya ...

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)
}
Harun
sumber
menggunakan dplyr :: bind_rows (x, y) sebagai ganti rbind (x, y) menjaga urutan kolom berdasarkan frame data pertama.
RanonKahn
2

Hanya untuk dokumentasi. Anda dapat mencoba Stackperpustakaan dan fungsinya Stackdalam bentuk berikut:

Stack(df_1, df_2)

Saya juga memiliki kesan bahwa itu lebih cepat daripada metode lain untuk set data yang besar.

Cro-Magnon
sumber
1

Anda juga bisa menggunakan sjmisc::add_rows(), yang menggunakan dplyr::bind_rows(), tetapi tidak seperti bind_rows(), add_rows()mempertahankan atribut dan karenanya berguna untuk data berlabel .

Lihat contoh berikut dengan dataset berlabel. The frq()-fungsi mencetak tabel frekuensi dengan label nilai, jika data yang diberi label.

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