【问题标题】:Combine two data frames by rows (rbind) when they have different sets of columns当它们具有不同的列集时,按行组合两个数据帧(rbind)
【发布时间】:2011-03-25 01:28:20
【问题描述】:

是否可以行绑定两个没有相同列集的数据框?我希望保留绑定后不匹配的列。

【问题讨论】:

    标签: r dataframe r-faq


    【解决方案1】:

    大多数基本 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,改变原件
    为了在 rbind 中保留两个 data.frames 中的所有列(并允许函数正常工作而不会导致错误),您将 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 完整,首先遍历不同的名称,返回一个命名的 NA 向量,这些向量使用c 与 data.frame 连接到一个列表中。然后,data.frame 将结果转换为适合rbind 的data.frame。

    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.frame,您可以执行以下操作。

    # 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 个数据框,其中一些具有不同的列(每列大约有 70-90 列)。当我尝试这个时,我被第一个命令
    • 只是链接到@GKi
    • @sar 使用 mydflist &lt;- list(as, dr, kr, hyt, ed1, of)。这应该构造一个不会增加环境大小的列表对象,而只是指向列表的每个元素(只要您之后不更改任何内容)。操作完成后,移除列表对象,以防万一。
    【解决方案2】:

    最近的解决方案是使用dplyrbind_rows 函数,我认为它比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
    

    【讨论】:

    • 我正在尝试将大量数据帧 (16) 与不同的列名组合在一起尝试此操作时出现错误错误:列 ABC 无法从字符转换为数字。有没有办法先转换列?
    • @sar: df$column dplyr.tidyverse.org/reference/mutate_all.html
    • 现代 dplyr 实现将是 ... %>% mutate(across(c(char_column1, char_column2), ~ as.numeric(.x)) %>% ...
    【解决方案3】:

    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 并且不想构建列表时,这可能更可取。

    【讨论】:

    • 这是最简单、开箱即用的解决方案,可以轻松推广到任意数量的数据帧,因为您可以将它们全部存储在单独的列表元素中。其他答案,例如 intersect 方法,仅适用于 2 个数据帧并且不容易泛化。
    【解决方案4】:

    您也可以使用sjmisc::add_rows(),它使用dplyr::bind_rows(),但与bind_rows() 不同的是,add_rows() 保留属性,因此对labelled data 很有用。

    请参见以下带有标记数据集的示例。 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
    

    【讨论】:

      【解决方案5】:

      如果 df1 中的列是 df2 中的列的子集(按列名):

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

      【讨论】:

        【解决方案6】:

        仅用于文档。你可以试试Stack库及其函数Stack,格式如下:

        Stack(df_1, df_2)
        

        我也觉得它比处理大型数据集的其他方法更快。

        【讨论】:

          【解决方案7】:

          您可以使用gtools 包中的smartbind

          例子:

          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*10^6 行)尝试了smartbind,并在 10 分钟后中止了它。
          • 9 年来发生了很多事情 :) 我今天可能不会使用 smartbind。另请注意,原始问题未指定大型数据框。
          【解决方案8】:

          gtools/smartbind 不喜欢使用 Dates,可能是因为它是 as.vectoring。所以这是我的解决方案...

          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) 保持基于第一个数据帧的列顺序。
          【解决方案9】:
          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)]))
          }
          

          【讨论】:

            【解决方案10】:

            我编写了一个函数来执行此操作,因为我喜欢我的代码告诉我是否有问题。此函数将明确告诉您哪些列名不匹配,以及您是否有类型不匹配。然后无论如何它都会尽力组合data.frames。限制是您一次只能组合两个 data.frame。

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

            【讨论】:

              【解决方案11】:

              也许我完全误读了您的问题,但是“我希望保留绑定后不匹配的列”让我认为您正在寻找类似于 SQL 查询的 left joinright join。 R 具有 merge 函数,可让您指定左、右或内连接,类似于 SQL 中的连接表。

              这里已经有关于这个主题的很好的问答:How to join (merge) data frames (inner, outer, left, right)?

              【讨论】:

                【解决方案12】:

                rbind.fill 来自包 plyr 可能就是您要找的东西。

                【讨论】:

                • rbind.fillbind_rows() 都静默删除行名。
                • @MERose Hadley:“是的,所有 dplyr 方法都忽略行名。”
                • 使用 do.call(plyr::rbind.fill, myList) 拯救了我的一天。 myList 是一个表列表
                【解决方案13】:

                您也可以只提取常见的列名。

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

                【讨论】:

                  猜你喜欢
                  • 2014-04-10
                  • 2014-09-09
                  • 2015-04-18
                  • 2015-03-28
                  • 1970-01-01
                  • 2018-01-06
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-12-08
                  相关资源
                  最近更新 更多