如何将行附加到R数据框

Gya*_*eda 109 merge r append rows dataframe

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

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

df = data.frame(x = numeric(), y = character())
Run Code Online (Sandbox Code Playgroud)

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

for (i in 1:10) {
    df$x = rbind(df$x, i)
    df$y = rbind(df$y, toString(i))
}
Run Code Online (Sandbox Code Playgroud)

我也试图功能c,append以及merge没有成功.如果您有任何建议,请告诉我.

A5C*_*2T1 107

更新

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

继续使用Julian 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
}
Run Code Online (Sandbox Code Playgroud)

这是一种类似的方法,但是将data.frame其作为最后一步创建.

# 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)
}
Run Code Online (Sandbox Code Playgroud)

microbenchmark来自"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
Run Code Online (Sandbox Code Playgroud)

f1()(下面的方法)是非常低效的,因为它调用的频率data.frame和由于f3()预分配而在R中通常缓慢增长的对象大大改善,但data.frame结构本身可能是这里瓶颈的一部分.f4()试图绕过这个瓶颈而不影响你想采取的方法.


原始答案

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

for (i in 1:10) {
  df <- rbind(df, data.frame(x = i, y = toString(i)))
}
Run Code Online (Sandbox Code Playgroud)

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

  • stringsAsFactors如果您希望字符不转换为因子,则应使用.使用:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)

  • 谢谢!这解决了我的问题.为什么这"真的不是个好主意"?在for循环中,x和y以什么方式混合? (6认同)
  • @ user2932774,在R中以这种方式增长对象是非常低效的.改进(但仍然不一定是最好的方法)是预分配你期望的最终大小的`data.frame`并用`[添加值] `提取/替换. (5认同)
  • @ user2932774,很酷.我也很欣赏你的观点 - 我几乎从未真正使用大数据集.也就是说,如果我要编写函数或其他东西,我通常会花费一点额外的努力来尝试调整代码以尽可能提高速度.有关相当大的速度差异的示例,请参阅我的更新. (2认同)

Jul*_*ano 31

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

# 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
Run Code Online (Sandbox Code Playgroud)

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


Ada*_*ski 13

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

  1. rbindlist 到data.frame

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

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

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

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

  • create(first_element)返回带有first_elementput 的适当后备对象.

  • 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)
}
Run Code Online (Sandbox Code Playgroud)

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,]))
}
Run Code Online (Sandbox Code Playgroud)

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

这基本上是复制和粘贴Karsten W.回答类似的主题.

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))
}
Run Code Online (Sandbox Code Playgroud)

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)
}
Run Code Online (Sandbox Code Playgroud)

测试套件:

为方便起见,我将使用一个测试功能通过间接调用覆盖它们.(我检查过:使用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)))
}
Run Code Online (Sandbox Code Playgroud)

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

我还添加了一个"安慰剂"功能(带后缀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)
Run Code Online (Sandbox Code Playgroud)

添加n = 10行的计时

n = 100行的计时 n = 1000行的计时

对于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
Run Code Online (Sandbox Code Playgroud)

它看起来像基于SQLite的问题,尽管在大数据上恢复了一些速度,但远不及data.table +手动指数式增长.差异几乎是两个数量级!

摘要

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

对于其他一切使用data.table::set和增长data.table指数(例如使用我的代码).

  • SQLite之所以慢是因为在每个INSERT INTO上,它都必须重新索引,即O(n),其中n是行数。这意味着一次插入一行到SQL数据库是O(n ^ 2)。如果您一次插入整个data.frame,SQLite可能会非常快,但是逐行增长并不是最好的选择。 (2认同)

Agi*_*ean 7

使用 purrr、tidyr 和 dplyr 进行更新

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

purrr 和 tidyr 的最大优点是更好的可读性恕我直言。purrr 用更灵活的 map() 系列替换了 lapply,tidyr 提供了超级直观的方法 add_row - 只是按照它说的做:)

map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })
Run Code Online (Sandbox Code Playgroud)

此解决方案简短易读,而且速度相对较快:

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
Run Code Online (Sandbox Code Playgroud)

它几乎呈线性扩展,因此对于 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 
Run Code Online (Sandbox Code Playgroud)

这将使其在@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
Run Code Online (Sandbox Code Playgroud)