รวมสองเฟรมข้อมูลตามแถว (rbind) เมื่อมีชุดคอลัมน์ที่แตกต่างกัน


232

เป็นไปได้หรือไม่ที่จะผูกแถวข้อมูลสองเฟรมที่ไม่มีชุดคอลัมน์เดียวกัน ฉันหวังว่าจะรักษาคอลัมน์ที่ไม่ตรงกันหลังจากการผูกไว้

คำตอบ:


223

rbind.fillจากแพ็คเกจplyrอาจเป็นสิ่งที่คุณกำลังมองหา


12
rbind.fillและbind_rows()ทั้งคู่ก็ปล่อยชื่อแถวอย่างเงียบ ๆ
ลด

3
@Moseose Hadley: "ใช่เมธอด dplyr ทั้งหมดไม่สนใจชื่อแถว"
zx8754

นี่คือลิงค์ไปสู่เอกสาร: rdocumentation.org/packages/plyr/versions/1.8.4/topics/ …
งานกาเบรียลแฟร์

124

วิธีการแก้ปัญหาที่ผ่านมามากขึ้นคือการใช้งานdplyrของฟังก์ชั่นซึ่งผมถือว่าจะมีประสิทธิภาพมากกว่าbind_rowssmartbind

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

ฉันกำลังพยายามรวม dataframes จำนวนมาก (16) กับชื่อคอลัมน์ที่แตกต่างกันเมื่อฉันลองสิ่งนี้ฉันจะได้รับข้อผิดพลาด Error: คอลัมน์ABCไม่สามารถแปลงจากตัวอักษรเป็นตัวเลขได้ มีวิธีแปลงคอลัมน์เป็นอันดับแรกหรือไม่?
sar

46

คุณสามารถใช้smartbindจากgtoolsแพ็คเกจ

ตัวอย่าง:

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
ฉันลองsmartbindกับเฟรมข้อมูลขนาดใหญ่สองเฟรม (รวมประมาณ 3 * 10 ^ 6 แถว) และยกเลิกหลังจากนั้น 10 นาที
Joe

2
เกิดขึ้นมากมายใน 9 ปี :) ฉันอาจไม่ใช้ smartbind วันนี้ โปรดทราบว่าคำถามเดิมไม่ได้ระบุกรอบข้อมูลขนาดใหญ่
neilfws

42

หากคอลัมน์ในdf1เป็นชุดย่อยของคอลัมน์ในdf2 (ตามชื่อคอลัมน์):

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

37

ทางเลือกด้วย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)

rbindจะทำงานในdata.tableตราบเท่าที่วัตถุถูกแปลงเป็นdata.tableวัตถุดังนั้น

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

จะทำงานในสถานการณ์นี้เช่นกัน สิ่งนี้สามารถทำได้ดีกว่าเมื่อคุณมี data.tables สองรายการและไม่ต้องการสร้างรายการ


นี่คือทางออกที่ง่ายที่สุดและไม่ยุ่งยากในกล่องที่วางมาตรฐานของ dataframes ได้อย่างง่ายดายเนื่องจากคุณสามารถเก็บมันไว้ในองค์ประกอบรายการแยกกันได้ คำตอบอื่น ๆ เช่นintersectวิธีใช้งานได้เพียง 2 ดาต้าเฟรมและไม่พูดคุยได้ง่าย
รวย Pauloo

35

คำตอบฐาน R ส่วนใหญ่ตอบโจทย์สถานการณ์ที่ data.frame เพียงอันเดียวมีคอลัมน์เพิ่มเติมหรือว่า data.frame ที่เป็นผลลัพธ์จะมีจุดตัดของคอลัมน์ เนื่องจาก OP เขียนฉันหวังว่าจะรักษาคอลัมน์ที่ไม่ตรงกันหลังจากการผูกคำตอบโดยใช้วิธีการ R ฐานเพื่อแก้ไขปัญหานี้อาจน่าโพสต์

ด้านล่างนี้ฉันนำเสนอวิธีการ R สองวิธี: วิธีหนึ่งที่เปลี่ยนแปลง data.frames ดั้งเดิมและวิธีที่ไม่เปลี่ยนแปลง นอกจากนี้ฉันเสนอวิธีการที่สรุปวิธีการไม่ทำลายข้อมูลมากกว่าสอง data.frames

ก่อนอื่นมารับข้อมูลตัวอย่างกัน

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

สอง data.frames แก้ไขต้นฉบับ
เพื่อเก็บคอลัมน์ทั้งหมดจาก data.frames ทั้งสองในrbind(และอนุญาตให้ฟังก์ชันทำงานได้โดยไม่ทำให้เกิดข้อผิดพลาด) คุณเพิ่มคอลัมน์ NA ในแต่ละ data.frame ด้วยชื่อที่ขาดหายไปที่เหมาะสม setdiffการใช้

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

ตอนนี้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

โปรดทราบว่าสองบรรทัดแรกเปลี่ยน data.frames ดั้งเดิม, df1 และ df2 โดยเพิ่มคอลัมน์ทั้งชุดให้เต็ม


สอง data.frames ไม่ต้นฉบับเปลี่ยนแปลง
ในการออกจาก data.frames เดิมเหมือนเดิมวงแรกผ่านชื่อที่แตกต่างกันกลับมาเป็นชื่อเวกเตอร์ของ NAS ที่มีการตัดแบ่งออกเป็นรายการที่มี data.frame cที่ใช้ จากนั้นdata.frameแปลงผลเป็น data.frame 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)))
)

data.frames จำนวนมากอย่าดัดแปลงต้นฉบับ
ในกรณีที่คุณมี data.frames มากกว่าสองคุณสามารถทำสิ่งต่อไปนี้

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

อาจจะดีกว่าที่จะไม่เห็นชื่อแถวของ data.frames ดั้งเดิมใช่ไหม จากนั้นทำเช่นนี้

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

ฉันมี 16 dataframes บางตัวที่มีคอลัมน์แตกต่างกัน (ประมาณ 70-90 คอลัมน์ทั้งหมดในแต่ละอัน) เมื่อฉันลองทำสิ่งนี้ฉันจะติดกับคำสั่งแรก <- mget (ls (pattern = "df \\ d +")) dataframes ของฉันมีชื่อแตกต่างกัน ฉันพยายามทำรายการโดยใช้ mydflist <- c (เช่น dr, kr, hyt, ed1, of) แต่นี่ทำให้ฉันมีรายการมหาศาล
sar

เพียงเชื่อมโยงไปยัง @GKi
sar

1
mydflist <- list(as, dr, kr, hyt, ed1, of)ใช้ @sar สิ่งนี้ควรสร้างรายการวัตถุที่ไม่เพิ่มขนาดของสภาพแวดล้อมของคุณ แต่เพียงชี้ไปที่แต่ละองค์ประกอบของรายการ (ตราบใดที่คุณไม่เปลี่ยนแปลงเนื้อหาใด ๆ ในภายหลัง) หลังจากการดำเนินการลบวัตถุรายการเพียงเพื่อความปลอดภัย
lmo

20

คุณสามารถดึงชื่อคอลัมน์ทั่วไปออกมาได้เช่นกัน

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

6

ฉันเขียนฟังก์ชั่นการทำเช่นนี้เพราะฉันชอบรหัสของฉันที่จะบอกฉันว่ามีอะไรผิดปกติ ฟังก์ชันนี้จะบอกคุณอย่างชัดเจนว่าชื่อคอลัมน์ใดไม่ตรงกันและหากคุณมีประเภทไม่ตรงกัน จากนั้นจะทำอย่างดีที่สุดเพื่อรวม data.frames ต่อไป ข้อ จำกัด คือคุณสามารถรวม data.frames สองครั้งเท่านั้น

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

บางทีฉันอาจอ่านคำถามของคุณผิดอย่างสิ้นเชิง แต่ "ฉันหวังว่าจะรักษาคอลัมน์ที่ไม่ตรงกันหลังจากการผูก" ทำให้ฉันคิดว่าคุณกำลังมองหาleft joinหรือright joinคล้ายกับแบบสอบถาม SQL R มีmergeฟังก์ชั่นที่ให้คุณระบุการรวมซ้าย, ขวา, หรือภายในเหมือนกับการเข้าร่วมตารางใน SQL

มีคำถามและคำตอบที่ดีอยู่แล้วในหัวข้อนี้ที่นี่: วิธีเข้าร่วม (รวม) เฟรมข้อมูล (ด้านใน, ด้านนอก, ด้านซ้าย, ด้านขวา)?


2

gtools / smartbind ไม่ชอบทำงานกับวันที่อาจเป็นเพราะมันเป็น นี่คือทางออกของฉัน ...

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

ใช้ dplyr :: bind_rows (x, y) แทน rbind (x, y) เก็บลำดับคอลัมน์ตามเฟรมข้อมูลแรก
RanonKahn

2

สำหรับเอกสารประกอบเท่านั้น คุณสามารถลองใช้Stackไลบรารีและฟังก์ชันStackในรูปแบบต่อไปนี้:

Stack(df_1, df_2)

ฉันยังรู้สึกว่ามันเร็วกว่าวิธีอื่น ๆ สำหรับชุดข้อมูลขนาดใหญ่


1

นอกจากนี้คุณยังสามารถใช้sjmisc::add_rows()ซึ่งใช้dplyr::bind_rows()แต่ไม่เหมือนbind_rows(), add_rows()รักษาคุณลักษณะและด้วยเหตุนี้จะเป็นประโยชน์สำหรับข้อมูลที่มีป้ายกำกับ

ดูตัวอย่างต่อไปนี้ด้วยชุดข้อมูลที่มีข้อความ frq()ฟังก์ชั่พิมพ์ตารางความถี่ที่มีป้ายชื่อค่าถ้าข้อมูลมีข้อความระบุว่า

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)]))
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.