已经报告这里预分配data.frame并逐行赋值非常慢,因为即使仅更改单个单元格也会复制整个data.frame。示例:
# preallocation
df.pre <- function(x) {
df <- data.frame(x=numeric(length(x)), y=logical(length(x)))
for (i in 1:length(x)) {
df[i,] <- data.frame(x=x[i], y=(x[i]>0.5)) # <<<--- entire df is copied
}
return(df)
}
有趣的是,当分配给不存在的索引时,这似乎不会发生,从而自动增加 data.frame:
# automatic extension
df.auto <- function(x) {
df <- data.frame(x=numeric(0), y=logical(0))
for (i in 1:length(x)) {
df[i,] <- data.frame(x=x[i], y=(x[i]>0.5))
}
return(df)
}
x <- runif(100)
library(microbenchmark)
microbenchmark(df.pre(x), df.auto)
产生以下运行时间:
Unit: nanoseconds
expr min lq mean median uq max neval cld
df.pre(x) 5400053 5447409 5889698.24 5827590 5876605.5 18726678 100 b
df.auto 9 11 75.94 82 109.5 1071 100 a
我还注意到,这种填充 data.frame 的方法的运行时间几乎与使用预先分配的矩阵一样快,并且比填充列表并随后在 data.frame 中将它们组合起来要快得多。
这提出了两个问题:
编辑: 正如 jblood94 所指出的,基准测试是错误的。当正确调用时,几乎没有运行时差异,并且分配给未分配的索引与分配给预分配的 data.frame 具有相同的糟糕运行时间。据我所知,最快的方法是使用矩阵(如果所有值都属于同一类型)并稍后将其转换为 data.frame,或者使用列表并稍后将其转换为 data.frame,例如:
df.pre <- function(x) {
df <- data.frame(x=numeric(length(x)), y=logical(length(x)))
for (i in 1:length(x)) {
df[i,] <- data.frame(x=x[i], y=(x[i]>0.5))
}
return(df)
}
ls.pre <- function(x) {
ls <- list(x=numeric(length(x)), y=logical(length(x)))
for (i in 1:length(x)) {
ls$x[i] <- x[i]; ls$y[i] <- (x[i]>0.5)
}
return(as.data.frame(ls))
}
matrix.pre <- function(x) {
mt <- matrix(c(numeric(length(x)*2)), ncol=2)
for (i in 1:length(x)) {
mt[i,] <- c(x[i], as.numeric(x[i]>0.5))
}
return(as.data.frame(mt))
}
x <- runif(100)
library(microbenchmark)
microbenchmark(df.pre(x), ls.pre(x), matrix.pre(x))
产生:
Unit: microseconds
expr min lq mean median uq max neval
df.pre(x) 5745.687 5789.6780 6073.79760 5812.7450 5888.2840 8714.556 100
ls.pre(x) 61.209 65.9720 100.35568 70.9705 74.6935 2981.260 100
matrix.pre(x) 29.641 33.1215 59.85807 38.6615 40.7255 2237.502 100
第三种可能的方法是预先分配向量,填充它们,然后在最后创建
data.frame
。第四种方法是使用 data.table
。
library(data.table)
# preallocation
df.pre <- function(x) {
df <- data.frame(x=numeric(length(x)), y=logical(length(x)))
for (i in 1:length(x)) {
df[i,] <- data.frame(x=x[i], y=(x[i]>0.5)) # <<<--- entire df is copied
}
df
}
df.pre2 <- function(x) {
xx <- numeric(length(x))
y <- logical(length(x))
for (i in 1:length(x)) {
xx[i] <- x[i]
y[i] <- x[i] > 0.5
}
data.frame(x, y)
}
dt.pre <- function(x) {
dt <- data.table(x=numeric(length(x)), y=logical(length(x)))
for (i in 1:length(x)) {
set(dt, i, 1:2, list(x[i], x[i] > 0.5))
}
setDF(dt)
}
# automatic extension
df.auto <- function(x) {
df <- data.frame(x=numeric(0), y=logical(0))
for (i in 1:length(x)) {
df[i,] <- data.frame(x=x[i], y=(x[i]>0.5))
}
df
}
基准:
x <- runif(100)
library(microbenchmark)
microbenchmark(df.pre(x), df.pre2(x), dt.pre(x), df.auto(x), check = "equal")
#> Unit: microseconds
#> expr min lq mean median uq max neval
#> df.pre(x) 11426.6 11757.25 12445.558 12042.35 13093.60 16206.4 100
#> df.pre2(x) 116.5 127.15 134.198 131.75 137.00 284.2 100
#> dt.pre(x) 237.1 266.95 317.664 317.30 341.10 522.7 100
#> df.auto(x) 13337.4 13811.15 14691.654 14468.40 15457.85 20586.7 100