如何将行附加到 R 数据框
Posted
技术标签:
【中文标题】如何将行附加到 R 数据框【英文标题】:How to append rows to an R data frame 【发布时间】:2014-01-08 11:47:03 【问题描述】:我查看了 ***,但找不到特定于我的问题的解决方案,其中涉及将行附加到 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))
我还尝试了函数c
、append
和merge
,但均未成功。如果您有任何建议,请告诉我。
评论更新: 我不认为 R 是如何使用的,但我想忽略在每次迭代时更新索引所需的额外代码行,并且我不能轻易地预先分配数据框的大小,因为我不不知道最终会占用多少行。请记住,以上只是一个可重现的玩具示例。不管怎样,谢谢你的建议!
【问题讨论】:
如果你想连接数据帧,this is the answer you want 【参考方案1】:更新
不知道您要做什么,我将再分享一个建议:为每一列预先分配您想要的类型的向量,将值插入这些向量中,然后在最后创建您的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
这是一种类似的方法,但在最后一步创建 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)
“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
f1()
(下面的方法)效率非常低,因为它调用data.frame
的频率非常低,并且因为在 R 中以这种方式增长的对象通常很慢。f3()
由于预分配而得到了很大改善,但是data.frame
结构本身可能是这里瓶颈的一部分。 f4()
试图绕过这个瓶颈,而不会影响您想要采用的方法。
原答案
这确实不是一个好主意,但如果你想这样做,我想你可以试试:
for (i in 1:10)
df <- rbind(df, data.frame(x = i, y = toString(i)))
请注意,在您的代码中,还有另一个问题:
如果您不希望字符转换为因子,您应该使用stringsAsFactors
。使用:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
【讨论】:
谢谢!这解决了我的问题。为什么这“真的不是一个好主意”?在 for 循环中 x 和 y 以什么方式混合? @user2932774,在 R 中以这种方式增长对象的效率非常低。改进(但仍不一定是最好的方法)是预分配您期望的最终大小的data.frame
并添加[
提取/替换中的值。
谢谢,阿难。我通常使用预分配,但我不同意这真的不是一个好主意。这取决于实际情况。就我而言,我正在处理小数据,而替代方案将更耗时编码。此外,与更新数字索引以在每次迭代中填充预分配数据帧的适当部分所需的代码相比,这是更优雅的代码。只是好奇,您认为完成此任务的“最佳方式”是什么?我会认为预分配会是最好的。
@user2932774,这很酷。我也很欣赏你的观点——我也几乎从未真正使用过大数据集。也就是说,如果我要编写一个函数或其他东西,我通常会花费一些额外的精力来尝试调整代码以尽可能提高速度。请参阅我的更新,了解速度差异非常大的示例。
哇,差别太大了!感谢您运行该模拟并教我有关 microbenchmark 包的知识。我绝对同意你的看法,付出额外的努力是件好事。在我的特殊情况下,我想我只是想在一些我可能永远不必再次运行的代码上做一些快速和肮脏的事情。 :)【参考方案2】:
让我们对提出的三个解决方案进行基准测试:
# 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
。
【讨论】:
谢谢!虽然我不同意阿难的建议。我是否希望将字符转换为因子级别取决于我想对输出做什么。虽然我猜你提出的解决方案,有必要将 stringsAsFactors 设置为 FALSE。 感谢模拟。我意识到预分配在处理速度方面是最好的,但这不是我在做出这个编码决定时考虑的唯一因素。 在 f1 中,您通过将字符串分配给数字向量 x 感到困惑。正确的行是:df <- rbind(df, data.frame(x = i, y = toString(i)))
【参考方案3】:
假设您事先根本不知道 data.frame 的大小。它可以是几行,也可以是几百万。你需要有某种容器,它会动态增长。考虑到我在 SO 中的经验和所有相关答案,我提出了 4 种不同的解决方案:
rbindlist
到 data.frame
使用data.table
的快速set
操作并在需要时将其与手动加倍表相结合。
使用RSQLite
并追加到内存中的表格。
data.frame
自己的能力增长和使用自定义环境(具有引用语义)来存储 data.frame,因此它不会在返回时被复制。
这里是对少量和大量附加行的所有方法的测试。每个方法都有 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 插入的性能。
我还添加了一个“安慰剂”函数(后缀为 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)
对于 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 的解决方案虽然在大数据上恢复了一些速度,但远不及 data.table + 手动指数增长。相差几乎是两个数量级!
总结
如果您知道您将追加相当少量的行 (n
对于其他所有内容,请使用 data.table::set
并以指数方式增长 data.table(例如,使用我的代码)。
【讨论】:
SQLite慢的原因是每次INSERT INTO都要REINDEX,也就是O(n),其中n是行数。这意味着一次向 SQL 数据库中插入一行是 O(n^2)。如果您一次插入整个 data.frame,SQLite 可以非常快,但它并不是最好的逐行增长。【参考方案4】:更新 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)) )
这个解决方案简短易读,而且速度相对较快:
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
【讨论】:
您不需要使用add_row
。例如:map_dfr(1:1e5, function(x) tibble(x = x, y = toString(x)) )
.
@user3808394 谢谢,这是一个有趣的选择!如果有人想从头开始创建数据框,那么您的数据框会更短,因此是更好的解决方案。如果您已经有数据框,我的解决方案当然更好。
如果你已经有一个数据框,你会使用bind_rows(df, map_dfr(1:1e5, function(x) tibble(x = x, y = toString(x)) ))
而不是使用add_row
。【参考方案5】:
一个更通用的解决方案可能如下。
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 到 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
接受三个参数:
-
要修改的向量/列。
要包含在修改后向量中的值。
一个下标,在其后附加值。
简单...!! 如有任何抱歉...!
【讨论】:
【参考方案7】:我的解决方案与原始答案几乎相同,但对我不起作用。
所以,我给列起了名字,它起作用了:
painel <- rbind(painel, data.frame("col1" = xtweets$created_at,
"col2" = xtweets$text))
【讨论】:
正是我所需要的,简明扼要的回答!以上是关于如何将行附加到 R 数据框的主要内容,如果未能解决你的问题,请参考以下文章