使用rbind添加新列和data.table

14

我需要将许多大表格添加到现有表格中,因此我使用了优秀的data.table包中的rbind。但是一些后来的表格比原始表格具有更多的列(需要包含)。是否有类似于data.table的rbind.fill的等效方法?

library(data.table)

aa <- c(1,2,3)
bb <- c(2,3,4)
cc <- c(3,4,5)

dt.1 <- data.table(cbind(aa, bb))
dt.2 <- data.table(cbind(aa, bb, cc))

dt.11 <- rbind(dt.1, dt.1)  # Works, but not what I need
dt.12 <- rbind(dt.1, dt.2)  # What I need, doesn't work
dt.12 <- rbind.fill(dt.1, dt.2)  # What I need, doesn't work either

我需要在拥有所有表格之前开始rbinding,因此无法知道未来的新列将被称为什么。缺失数据可以用NA填充。


列的顺序是否始终相同(即在所有表中,第一列是否始终保持为第一列)? - mnel
不,很遗憾不行。 - Chris
列名至少会对应吗? - mnel
是的,在所有表格中大约有20个列具有相同的名称,但有些表格还有一些更多的列(带有新名称)。 - Chris
@Chris,你的列总是整数吗? - Ricardo Saporta
不可以,可以是字符。 - Chris
6个回答

14
v1.9.2 开始,data.table 的 rbind 函数增加了 fill 参数。根据 ?rbind.data.table 文档:

如果为 TRUE,则使用 NA 填充缺少的列。默认为 FALSE。当为 TRUE 时,use.names 必须为 TRUE,并且输入列表的所有项目都必须具有非空列名。

因此,在大约 v1.9.6 之前,您可以执行以下操作:

data.table::rbind(dt.1, dt.2, fill=TRUE) 
#    aa bb cc
# 1:  1  2 NA
# 2:  2  3 NA
# 3:  3  4 NA
# 4:  1  2  3
# 5:  2  3  4
# 6:  3  4  5

v1.9.6更新内容:

现在可以直接使用此功能:

rbind(dt.1, dt.2, fill=TRUE)
#    aa bb cc
# 1:  1  2 NA
# 2:  2  3 NA
# 3:  3  4 NA
# 4:  1  2  3
# 5:  2  3  4
# 6:  3  4  5

2
错误:'rbind'不是来自'namespace:data.table'的导出对象。 - user4458796
rbind现在已经被适应为直接调用data.table方法并且可以直接使用,详见上面的编辑和GitHub - Daniel Krizian
@DanielKrizian 很棒! - Hack-R
@DanielKrizian 我注意到在加载了data.table之后,rbind可以单独使用,但我仍然很惊讶它会抛出那个错误。我猜我还没有完全理解命名空间的使用。例如,p_load可以作为pacman::p_load()或在require(pacman)之后直接使用p_load() - user4458796
这是由于一些自定义重载工作所致。在 GitHub 上研究 .onLoad 很有启发性。 - Daniel Krizian

5
这里有一种方法可以更新缺失的列。
rbind.missing <- function(A, B) { 

  cols.A <- names(A)
  cols.B <- names(B)

  missing.A <- setdiff(cols.B,cols.A)
  # check and define missing columns in A
  if(length(missing.A) > 0L){
   # .. means "look up one level"
   class.missing.A <- lapply(B[, ..missing.A], class)
   nas.A <- lapply(class.missing.A, as, object = NA)
   A[,c(missing.A) := nas.A]
  }
  # check and define missing columns in B
  missing.B <- setdiff(names(A), cols.B)
  if(length(missing.B) > 0L){
    class.missing.B <- lapply(A[, ..missing.B], class)
    nas.B <- lapply(class.missing.B, as, object = NA)
    B[,c(missing.B) := nas.B]
  }
  # reorder so they are the same
  setcolorder(B, names(A))
  rbind(A, B)

}

rbind.missing(dt.1,dt.2)

##    aa bb cc
## 1:  1  2 NA
## 2:  2  3 NA
## 3:  3  4 NA
## 4:  1  2  3
## 5:  2  3  4
## 6:  3  4  5

这对许多或大型数据表来说并不高效,因为它一次只能处理两个数据表。


4
答案很棒,但看起来有一些函数建议在这里 链接,例如plyr :: rbind.fill和gtools :: smartbind,对我来说似乎完美地工作。

2

插入缺失的列(有正确类型和NA)的另一种方法是使用merge()函数将第一个数据表A与一个空数据表A2[0]合并,该数据表具有第二个数据表的结构。这样可以避免在用户函数中引入错误(我知道merge()比我自己编写的代码更可靠;))。使用上面mnel的表格,可以像下面的代码一样操作。

此外,处理data.tables时,使用rbindlist()应该会更快。

定义表格(与上面mnel的代码相同):

library(data.table)
A  <- data.table(a=1:3, b=1:3, c=1:3)
A2 <- data.table(a=6:9, b=6:9, c=6:9)
B  <- data.table(b=1:3, c=1:3, d=1:3, m=LETTERS[1:3])
C  <- data.table(n=round(rnorm(3), 2), f=c(T, F, T), c=7:9)

在表A中插入缺失的变量:(请注意使用A2[0]的方式)

A <- merge(x=A, y=A2[0], by=intersect(names(A),names(A2)), all=TRUE)

在表格A2中插入缺失的列:

A2 <- merge(x=A[0], y=A2, by=intersect(names(A),names(A2)), all=TRUE)

现在,AA2应该具有相同的列,并且类型也相同。 为了安全起见(可能不需要,不确定rbindlist()是否跨列名或列位置绑定),请设置列顺序以匹配:
setcolorder(A2, names(A))
DT.ALL <- rbindlist(l=list(A,A2))
DT.ALL

重复进行其他表格...也许最好将其放入函数中,而不是手动重复...

DT.ALL <- merge(x=DT.ALL, y=B[0], by=intersect(names(DT.ALL), names(B)), all=TRUE)
B <- merge(x=DT.ALL[0], y=B, by=intersect(names(DT.ALL), names(B)), all=TRUE)
setcolorder(B, names(DT.ALL))
DT.ALL <- rbindlist(l=list(DT.ALL, B))

DT.ALL <- merge(x=DT.ALL, y=C[0], by=intersect(names(DT.ALL), names(C)), all=TRUE)
C <- merge(x=DT.ALL[0], y=C, by=intersect(names(DT.ALL), names(C)), all=TRUE)
setcolorder(C, names(DT.ALL))
DT.ALL <- rbindlist(l=list(DT.ALL, C))
DT.ALL

结果看起来与mnels的输出相同(除了随机数字和列顺序)。
PS1:原作者没有说明如果有匹配变量应该怎么办--我们真的想做一个rbind()还是考虑一个merge()
PS2:(由于我没有足够的声望来评论)问题的要点似乎是这个问题的重复。对于使用大型数据集进行data.table vs. plyr的基准测试也很重要。

2
基本概念是在两个方向上添加缺失的列:从运行的“master”表到“newTable”,然后反过来。
正如@menl在评论中指出的,简单地分配NA是一个问题,因为这将使整个“class”列成为“logical”。
一种解决方法是强制所有列具有相同的类型(即as.numeric(NA)),但这太过严格。
相反,我们需要分析每个新列的类。然后,我们可以使用as(NA, cc) _(cc为类)作为我们将分配给新列的向量。我们在RHS上包装它在lapply语句中,并在LHS上使用eval(columnName)进行分配。
然后,我们可以将其包装在一个函数中并使用S3方法,以便我们可以简单地调用。
rbindFill(A, B)

以下是函数代码。
rbindFill.data.table <- function(master, newTable)  {
# Append newTable to master

    # assign to Master
    #-----------------#
      # identify columns missing
      colMisng     <- setdiff(names(newTable), names(master))

      # if there are no columns missing, move on to next part
      if (!identical(colMisng, character(0)))  {
           # identify class of each
            colMisng.cls <- sapply(colMisng, function(x) class(newTable[[x]]))

            # assign to each column value of NA with appropriate class 
            master[ , eval(colMisng) := lapply(colMisng.cls, function(cc) as(NA, cc))]
          }

    # assign to newTable
    #-----------------#
      # identify columns missing
      colMisng     <- setdiff(names(master), names(newTable))

      # if there are no columns missing, move on to next part
      if (!identical(colMisng, character(0)))  {
        # identify class of each
        colMisng.cls <- sapply(colMisng, function(x) class(master[[x]]))

        # assign to each column value of NA with appropriate class 
        newTable[ , eval(colMisng) := lapply(colMisng.cls, function(cc) as(NA, cc))]
      }

    # reorder columns to avoid warning about ordering
    #-----------------#
      colOrdering <- colOrderingByOtherCol(newTable, names(master))
      setcolorder(newTable,  colOrdering)

    # rbind them! 
    #-----------------#
      rbind(master, newTable)
  }

  # implement generic function
  rbindFill <- function(x, y, ...) UseMethod("rbindFill")


示例用法:

    # Sample Data: 
    #--------------------------------------------------#
    A  <- data.table(a=1:3, b=1:3, c=1:3)
    A2 <- data.table(a=6:9, b=6:9, c=6:9)
    B  <- data.table(b=1:3, c=1:3, d=1:3, m=LETTERS[1:3])
    C  <- data.table(n=round(rnorm(3), 2), f=c(T, F, T), c=7:9)
    #--------------------------------------------------#

    # Four iterations of calling rbindFill
    master <- rbindFill(A, B)
    master <- rbindFill(master, A2)
    master <- rbindFill(master, C)

    # Results:
    master
    #      a  b c  d  m     n     f
    #  1:  1  1 1 NA NA    NA    NA
    #  2:  2  2 2 NA NA    NA    NA
    #  3:  3  3 3 NA NA    NA    NA
    #  4: NA  1 1  1  A    NA    NA
    #  5: NA  2 2  2  B    NA    NA
    #  6: NA  3 3  3  C    NA    NA
    #  7:  6  6 6 NA NA    NA    NA
    #  8:  7  7 7 NA NA    NA    NA
    #  9:  8  8 8 NA NA    NA    NA
    # 10:  9  9 9 NA NA    NA    NA
    # 11: NA NA 7 NA NA  0.86  TRUE
    # 12: NA NA 8 NA NA -1.15 FALSE
    # 13: NA NA 9 NA NA  1.10  TRUE

你需要通过引用将NA分配给正确的类(NAlogical),否则在示例数据集中,dt.1[,cc := NA]; rbind(dt.1,dt.2) - mnel
这是一个需要解决的难题,而且需要进行更多的预处理。类似于 foo <- lapply(lapply(dt.1, class), as,object=NA); dt.1[,names(foo) := foo] 这样的代码可以工作,但显然这会将 NA 分配给所有列,所以问题在于只分配你想要的那些列。 - mnel
我已经在答案中模拟了一些东西。虽然还不是完整的方法,但也许更接近了一步。 - mnel
非常感谢你们俩的帮助。我成功实践了mnel的解决方案,意识到它比我自己想出来的要高明得多。谢谢! - Chris

0

dplyr函数的bind_rows可以自动执行此操作。

aa <- c(1,2,3) bb <- c(2,3,4) cc <- c(3,4,5)

dt.1 <- data.table(cbind(aa, bb)) dt.2 <- data.table(cbind(aa, bb, cc))

(dt.11 <- bind_rows(dt.1, dt.2))

aa bb cc
1:  1  2 NA
2:  2  3 NA
3:  3  4 NA
4:  1  2  3
5:  2  3  4
6:  3  4  5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接