如何将行附加到R数据框

时间:2013-12-19 19:02:47

标签: r merge append dataframe rows

我查看过StackOverflow,但是我找不到特定于我的问题的解决方案,这涉及将行附加到R数据框。

我正在初始化一个空的2列数据帧,如下所示。

df = data.frame(x = numeric(), y = character())

然后,我的目标是遍历一个值列表,并在每次迭代中将值附加到列表的末尾。我从以下代码开始。

for (i in 1:10) {
    df$x = rbind(df$x, i)
    df$y = rbind(df$y, toString(i))
}

我还尝试了cappendmerge这些功能,但没有成功。如果您有任何建议,请告诉我。

7 个答案:

答案 0 :(得分:102)

更新

我不知道你要做什么,我将再分享一个建议:为每个列预分配所需类型的向量,将值插入到这些向量中,然后在最后创建{{1} }。

继续使用Julian的data.frame(预先分配的f3)作为迄今为止最快的选项,定义为:

data.frame

这是一种类似的方法,但其中# pre-allocate space f3 <- function(n){ df <- data.frame(x = numeric(n), y = character(n), stringsAsFactors = FALSE) for(i in 1:n){ df$x[i] <- i df$y[i] <- toString(i) } df } 被创建为最后一步。

data.frame
来自“microbenchmark”软件包的

# Use preallocated vectors f4 <- function(n) { x <- numeric(n) y <- character(n) for (i in 1:n) { x[i] <- i y[i] <- i } data.frame(x, y, stringsAsFactors=FALSE) } 将为我们提供比microbenchmark更全面的见解:

system.time

library(microbenchmark) microbenchmark(f1(1000), f3(1000), f4(1000), times = 5) # Unit: milliseconds # expr min lq median uq max neval # f1(1000) 1024.539618 1029.693877 1045.972666 1055.25931 1112.769176 5 # f3(1000) 149.417636 150.529011 150.827393 151.02230 160.637845 5 # f4(1000) 7.872647 7.892395 7.901151 7.95077 8.049581 5 (下面的方法)是非常低效的,因为它调用f1()的频率是多少,因为通过这种方式增长的对象在R中通常较慢。data.frame由于预分配而得到很大改善,但f3()结构本身可能是这里瓶颈的一部分。 data.frame试图绕过这个瓶颈,而不会影响你想采取的方法。


原始答案

这真的不是一个好主意,但如果你想这样做,我想你可以试试:

f4()

请注意,在您的代码中,还有一个问题:

  • 如果您希望字符不会转换为因子,则应使用for (i in 1:10) { df <- rbind(df, data.frame(x = i, y = toString(i))) } 。使用:stringsAsFactors

答案 1 :(得分:29)

让我们对提出的三种解决方案进行基准测试:

# use rbind
f1 <- function(n){
  df <- data.frame(x = numeric(), y = character())
  for(i in 1:n){
    df <- rbind(df, data.frame(x = i, y = toString(i)))
  }
  df
}
# use list
f2 <- function(n){
  df <- data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
  for(i in 1:n){
    df[i,] <- list(i, toString(i))
  }
  df
}
# pre-allocate space
f3 <- function(n){
  df <- data.frame(x = numeric(1000), y = character(1000), stringsAsFactors = FALSE)
  for(i in 1:n){
    df$x[i] <- i
    df$y[i] <- toString(i)
  }
  df
}
system.time(f1(1000))
#   user  system elapsed 
#   1.33    0.00    1.32 
system.time(f2(1000))
#   user  system elapsed 
#   0.19    0.00    0.19 
system.time(f3(1000))
#   user  system elapsed 
#   0.14    0.00    0.14

最佳解决方案是预先分配空间(如R中所预期的)。下一个最佳解决方案是使用list,最差的解决方案(至少基于这些时序结果)似乎是rbind

答案 2 :(得分:12)

假设您事先并不知道data.frame的大小。它可以是几行,或几百万。你需要有一些动态增长的容器。考虑到我的经验和所有相关答案,我提出了4个不同的解决方案:

  1. rbindlist到data.frame

  2. 使用data.table快速set操作,并在需要时手动将表加倍。

  3. 使用RSQLite并附加到内存中的表格。

  4. data.frame自己增长和使用自定义环境(具有引用语义)来存储data.frame的能力,以便在返回时不会被复制。

  5. 这是对小数量和大量附加行的所有方法的测试。每种方法都有3个与之相关的功能:

    • create(first_element)返回放入first_element的相应支持对象。

    • append(object, element)element追加到表格的末尾(由object表示)。

    • access(object)获取包含所有插入元素的data.frame

    rbindlist到data.frame

    这很简单直接:

    create.1<-function(elems)
    {
      return(as.data.table(elems))
    }
    
    append.1<-function(dt, elems)
    { 
      return(rbindlist(list(dt,  elems),use.names = TRUE))
    }
    
    access.1<-function(dt)
    {
      return(dt)
    }
    

    data.table::set +在需要时手动将表加倍。

    我会将表的真实长度存储在rowcount属性中。

    create.2<-function(elems)
    {
      return(as.data.table(elems))
    }
    
    append.2<-function(dt, elems)
    {
      n<-attr(dt, 'rowcount')
      if (is.null(n))
        n<-nrow(dt)
      if (n==nrow(dt))
      {
        tmp<-elems[1]
        tmp[[1]]<-rep(NA,n)
        dt<-rbindlist(list(dt, tmp), fill=TRUE, use.names=TRUE)
        setattr(dt,'rowcount', n)
      }
      pos<-as.integer(match(names(elems), colnames(dt)))
      for (j in seq_along(pos))
      {
        set(dt, i=as.integer(n+1), pos[[j]], elems[[j]])
      }
      setattr(dt,'rowcount',n+1)
      return(dt)
    }
    
    access.2<-function(elems)
    {
      n<-attr(elems, 'rowcount')
      return(as.data.table(elems[1:n,]))
    }
    

    SQL应该针对快速记录插入进行优化,所以我最初对RSQLite解决方案

    寄予厚望

    这基本上是Karsten W. answer在类似帖子上的复制和粘贴。

    create.3<-function(elems)
    {
      con <- RSQLite::dbConnect(RSQLite::SQLite(), ":memory:")
      RSQLite::dbWriteTable(con, 't', as.data.frame(elems))
      return(con)
    }
    
    append.3<-function(con, elems)
    { 
      RSQLite::dbWriteTable(con, 't', as.data.frame(elems), append=TRUE)
      return(con)
    }
    
    access.3<-function(con)
    {
      return(RSQLite::dbReadTable(con, "t", row.names=NULL))
    }
    

    data.frame自己的行追加+自定义环境。

    create.4<-function(elems)
    {
      env<-new.env()
      env$dt<-as.data.frame(elems)
      return(env)
    }
    
    append.4<-function(env, elems)
    { 
      env$dt[nrow(env$dt)+1,]<-elems
      return(env)
    }
    
    access.4<-function(env)
    {
      return(env$dt)
    }
    

    测试套件:

    为方便起见,我将使用一个测试功能来进行间接调用。 (我检查过:使用do.call代替直接调用函数并不会使代码运行得更长一些。)

    test<-function(id, n=1000)
    {
      n<-n-1
      el<-list(a=1,b=2,c=3,d=4)
      o<-do.call(paste0('create.',id),list(el))
      s<-paste0('append.',id)
      for (i in 1:n)
      {
        o<-do.call(s,list(o,el))
      }
      return(do.call(paste0('access.', id), list(o)))
    }
    

    让我们看看n = 10次插入的表现。

    我还添加了一个安慰剂&#39;函数(带后缀0)不执行任何操作 - 只是为了衡量测试设置的开销。

    r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
    autoplot(r)
    

    Timings for adding n=10 rows

    Timings for n=100 rows Timings for n=1000 rows

    对于1E5行(在Intel(R)Core(TM)i7-4710HQ CPU @ 2.50GHz上进行测量):

    nr  function      time
    4   data.frame    228.251 
    3   sqlite        133.716
    2   data.table      3.059
    1   rbindlist     169.998 
    0   placebo         0.202
    

    看起来基于SQLite的问题虽然在大数据上恢复了一些速度,但远不及数据。手动指数增长。差异几乎是两个数量级!

    摘要

    如果您知道要追加相当少量的行(n <= 100),请继续使用最简单的解决方案:只需使用括号表示法将行分配给data.frame并忽略该事实data.frame没有预先填充。

    其他所有内容都使用data.table::set并以指数方式增长data.table(例如,使用我的代码)。

答案 3 :(得分:2)

让我们取一个矢量'点',其数字从1到5

point = c(1,2,3,4,5)

如果我们想在向量中的任意位置附加数字6,那么在命令下方可能会派上用场

i)向量

new_var = append(point, 6 ,after = length(point))

ii)表格的

new_var = append(point, 6 ,after = length(mtcars$mpg))

命令append有三个参数:

  1. 要修改的矢量/列。
  2. 要包含在修改后的矢量中的值。
  3. 下标,之后将附加值。
  4. ...简单!! 在任何情况下道歉...!

答案 4 :(得分:2)

使用purrr,tidyr和dplyr更新

由于问题已经过时(6年),答案缺少使用较新软件包tidyr和purrr的解决方案。因此,对于使用这些软件包的人们,我想为先前的答案添加解决方案-所有这些都非常有趣,尤其是。

purrr和tidyr的最大优势是更好的可读性IMHO。 purrr用更灵活的map()系列代替了lapply, tidyr提供了超直观的方法add_row-就像它所说的一样:)

map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })

此解决方案简短易懂,并且相对较快:

system.time(
   map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
   user  system elapsed 
   0.756   0.006   0.766

它几乎线性缩放,因此对于1e5行,性能为:

system.time(
  map_df(1:100000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
   user  system elapsed 
 76.035   0.259  76.489 

这将使其在@Adam Ryczkowski的基准测试中排在data.table(如果您忽略了安慰剂)之后,排在第二位:

nr  function      time
4   data.frame    228.251 
3   sqlite        133.716
2   data.table      3.059
1   rbindlist     169.998 
0   placebo         0.202

答案 5 :(得分:1)

更通用的解决方案可能如下。

    extendDf <- function (df, n) {
    withFactors <- sum(sapply (df, function(X) (is.factor(X)) )) > 0
    nr          <- nrow (df)
    colNames    <- names(df)
    for (c in 1:length(colNames)) {
        if (is.factor(df[,c])) {
            col         <- vector (mode='character', length = nr+n) 
            col[1:nr]   <- as.character(df[,c])
            col[(nr+1):(n+nr)]<- rep(col[1], n)  # to avoid extra levels
            col         <- as.factor(col)
        } else {
            col         <- vector (mode=mode(df[1,c]), length = nr+n)
            class(col)  <- class (df[1,c])
            col[1:nr]   <- df[,c] 
        }
        if (c==1) {
            newDf       <- data.frame (col ,stringsAsFactors=withFactors)
        } else {
            newDf[,c]   <- col 
        }
    }
    names(newDf) <- colNames
    newDf
}

函数extendDf()扩展了一个包含n行的数据框。

举个例子:

aDf <- data.frame (l=TRUE, i=1L, n=1, c='a', t=Sys.time(), stringsAsFactors = TRUE)
extendDf (aDf, 2)
#      l i n c                   t
# 1  TRUE 1 1 a 2016-07-06 17:12:30
# 2 FALSE 0 0 a 1970-01-01 01:00:00
# 3 FALSE 0 0 a 1970-01-01 01:00:00

system.time (eDf <- extendDf (aDf, 100000))
#    user  system elapsed 
#   0.009   0.002   0.010
system.time (eDf <- extendDf (eDf, 100000))
#    user  system elapsed 
#   0.068   0.002   0.070

答案 6 :(得分:1)

我的解决方案与原始答案几乎相同,但不适用于我。

因此,我为各列命名,并且有效:

painel <- rbind(painel, data.frame("col1" = xtweets$created_at,
                                   "col2" = xtweets$text))