逐行填充data.frame的快速方法

问题描述 投票:0回答:1

已经报告这里预分配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 中将它们组合起来要快得多。

这提出了两个问题:

  1. 这种行为(无副本)有保证吗?
  2. 这是逐行填充 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
r performance
1个回答
0
投票

第三种可能的方法是预先分配向量,填充它们,然后在最后创建

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
© www.soinside.com 2019 - 2024. All rights reserved.