【问题标题】:Speeding up the performance of write.table加快 write.table 的性能
【发布时间】:2012-05-17 08:23:13
【问题描述】:

我有一个data.frame,我想把它写出来。我的data.frame 的尺寸是 256 行 x 65536 列。 write.csv 有哪些更快的替代方案?

【问题讨论】:

  • 要么获得更快的硬盘驱动器,要么如果您的数据可以转换为矩阵,请使用write
  • 但是当我第一次将它导入R使用read.table时,它会自动使用dataframe,所以我完成计算后需要使用as.matrix?
  • 您需要将其写为 CSV 还是可以简单地将其保存为 RData 对象或其他压缩形式?
  • 我希望输出的文件看起来像一个矩阵,它将有单独的列和行。
  • @lolibility - 我想我的问题更多是关于为什么你需要它看起来像一个矩阵?您是要在另一个程序中打开它还是将它输入到其他程序中?还是您只需要保存,以便以后可以在 R 中提取它。如下所示,原生 R 对象可以更快地节省和占用更少的空间。对于下面的示例,CSV 文件占用约 275MB,而 RData 对象占用约 80MB。

标签: r


【解决方案1】:

data.table::fwrite() 由 Otto Seiskari 贡献,在 1.9.8+ 版本中可用。 Matt 在顶部进行了额外的增强(包括并行化)并写了an article 关于它。请在tracker 上报告任何问题。

首先,这是上面@chase 使用的相同维度的比较(即非常多的列:65,000 列(!) x 256 行),以及fwritewrite_feather,这样我们就可以在机器之间保持一定的一致性。请注意 compress=FALSE 在基础 R 中的巨大差异。

# -----------------------------------------------------------------------------
# function  | object type |  output type | compress= | Runtime | File size |
# -----------------------------------------------------------------------------
# save      |      matrix |    binary    |   FALSE   |    0.3s |    134MB  |
# save      |  data.frame |    binary    |   FALSE   |    0.4s |    135MB  |
# feather   |  data.frame |    binary    |   FALSE   |    0.4s |    139MB  |
# fwrite    |  data.table |    csv       |   FALSE   |    1.0s |    302MB  |
# save      |      matrix |    binary    |   TRUE    |   17.9s |     89MB  |
# save      |  data.frame |    binary    |   TRUE    |   18.1s |     89MB  |
# write.csv |      matrix |    csv       |   FALSE   |   21.7s |    302MB  |
# write.csv |  data.frame |    csv       |   FALSE   |  121.3s |    302MB  |

请注意,fwrite() 并行运行。此处显示的时间是在 13 英寸 Macbook Pro 上,配备 2 个内核和 1 个线程/内核(通过超线程 +2 个虚拟线程)、512GB SSD、256KB/core L2 缓存和 4MB L4 缓存。根据您的系统规格,YMMV。

我还针对相对更有可能(和更大)的数据重新运行了基准测试:

library(data.table)
NN <- 5e6 # at this number of rows, the .csv output is ~800Mb on my machine
set.seed(51423)
DT <- data.table(
  str1 = sample(sprintf("%010d",1:NN)), #ID field 1
  str2 = sample(sprintf("%09d",1:NN)),  #ID field 2
  # varying length string field--think names/addresses, etc.
  str3 = replicate(NN,paste0(sample(LETTERS,sample(10:30,1),T), collapse="")),
  # factor-like string field with 50 "levels"
  str4 = sprintf("%05d",sample(sample(1e5,50),NN,T)),
  # factor-like string field with 17 levels, varying length
  str5 = sample(replicate(17,paste0(sample(LETTERS, sample(15:25,1),T),
      collapse="")),NN,T),
  # lognormally distributed numeric
  num1 = round(exp(rnorm(NN,mean=6.5,sd=1.5)),2),
  # 3 binary strings
  str6 = sample(c("Y","N"),NN,T),
  str7 = sample(c("M","F"),NN,T),
  str8 = sample(c("B","W"),NN,T),
  # right-skewed (integer type)
  int1 = as.integer(ceiling(rexp(NN))),
  num2 = round(exp(rnorm(NN,mean=6,sd=1.5)),2),
  # lognormal numeric that can be positive or negative
  num3 = (-1)^sample(2,NN,T)*round(exp(rnorm(NN,mean=6,sd=1.5)),2))

# -------------------------------------------------------------------------------
# function  |   object   | out |        other args         | Runtime  | File size |
# -------------------------------------------------------------------------------
# fwrite    | data.table | csv |      quote = FALSE        |   1.7s   |  523.2MB  |
# fwrite    | data.frame | csv |      quote = FALSE        |   1.7s   |  523.2MB  |
# feather   | data.frame | bin |     no compression        |   3.3s   |  635.3MB  |
# save      | data.frame | bin |     compress = FALSE      |  12.0s   |  795.3MB  |
# write.csv | data.frame | csv |    row.names = FALSE      |  28.7s   |  493.7MB  |
# save      | data.frame | bin |     compress = TRUE       |  48.1s   |  190.3MB  |
# -------------------------------------------------------------------------------

因此,fwrite 在此测试中比 feather 快约 2 倍。这是在上面提到的同一台机器上运行的,fwrite 在 2 个内核上并行运行。

feather 似乎也相当快的二进制格式,但还没有压缩。


这里尝试展示fwrite 在规模方面的比较:

注意:基准测试已通过运行基本 R 的 save()compress = FALSE 进行了更新(因为羽毛也未压缩)。

因此,fwrite 是所有这些数据中最快的(在 2 个内核上运行),而且它创建了一个 .csv,可以轻松查看、检查并传递给 grepsed 等。

复制代码:

require(data.table)
require(microbenchmark)
require(feather)
ns <- as.integer(10^seq(2, 6, length.out = 25))
DTn <- function(nn)
    data.table(
          str1 = sample(sprintf("%010d",1:nn)),
          str2 = sample(sprintf("%09d",1:nn)),
          str3 = replicate(nn,paste0(sample(LETTERS,sample(10:30,1),T), collapse="")),
          str4 = sprintf("%05d",sample(sample(1e5,50),nn,T)),
          str5 = sample(replicate(17,paste0(sample(LETTERS, sample(15:25,1),T), collapse="")),nn,T),
          num1 = round(exp(rnorm(nn,mean=6.5,sd=1.5)),2),
          str6 = sample(c("Y","N"),nn,T),
          str7 = sample(c("M","F"),nn,T),
          str8 = sample(c("B","W"),nn,T),
          int1 = as.integer(ceiling(rexp(nn))),
          num2 = round(exp(rnorm(nn,mean=6,sd=1.5)),2),
          num3 = (-1)^sample(2,nn,T)*round(exp(rnorm(nn,mean=6,sd=1.5)),2))

count <- data.table(n = ns,
                    c = c(rep(1000, 12),
                          rep(100, 6),
                          rep(10, 7)))

mbs <- lapply(ns, function(nn){
  print(nn)
  set.seed(51423)
  DT <- DTn(nn)
  microbenchmark(times = count[n==nn,c],
               write.csv=write.csv(DT, "writecsv.csv", quote=FALSE, row.names=FALSE),
               save=save(DT, file = "save.RData", compress=FALSE),
               fwrite=fwrite(DT, "fwrite_turbo.csv", quote=FALSE, sep=","),
               feather=write_feather(DT, "feather.feather"))})

png("microbenchmark.png", height=600, width=600)
par(las=2, oma = c(1, 0, 0, 0))
matplot(ns, t(sapply(mbs, function(x) {
  y <- summary(x)[,"median"]
  y/y[3]})),
  main = "Relative Speed of fwrite (turbo) vs. rest",
  xlab = "", ylab = "Time Relative to fwrite (turbo)",
  type = "l", lty = 1, lwd = 2, 
  col = c("red", "blue", "black", "magenta"), xaxt = "n", 
  ylim=c(0,25), xlim=c(0, max(ns)))
axis(1, at = ns, labels = prettyNum(ns, ","))
mtext("# Rows", side = 1, las = 1, line = 5)
legend("right", lty = 1, lwd = 3, 
       legend = c("write.csv", "save", "feather"),
       col = c("red", "blue", "magenta"))
dev.off()

【讨论】:

  • readr::write_csv 呢?将其添加到基准测试中会很好。
  • @DmitriySelivanov 在快速测试中,write_csvwrite.csv 慢...
  • write_csv 在这里超级慢,因为它使用糟糕的策略将双打转换为字符串,请参阅github.com/hadley/readr/issues/387
  • 我还认为save() 正确写入/读取Date 类的列,而fwrite()feather() 目前没有。所以一个公平的比较应该是针对 doublecharinteger 类型......在这一点上。
  • 性能比较可以通过额外的程序进行扩展 (rpubs.com/demydd/170957)。对理论样本进行测试,并根据实际数据进行重新测试。
【解决方案2】:

如果您的所有列都属于同一类,则在写出之前转换为矩阵,可提供近 6 倍的速度提升。此外,您可以考虑使用包MASS 中的write.matrix(),尽管在此示例中并没有证明它更快。也许我没有正确设置一些东西:

#Fake data
m <- matrix(runif(256*65536), nrow = 256)
#AS a data.frame
system.time(write.csv(as.data.frame(m), "dataframe.csv"))
#----------
#   user  system elapsed 
# 319.53   13.65  333.76 

#As a matrix
system.time(write.csv(m, "matrix.csv"))
#----------
#   user  system elapsed 
#  52.43    0.88   53.59 

#Using write.matrix()
require(MASS)
system.time(write.matrix(m, "writematrix.csv"))
#----------
#   user  system elapsed 
# 113.58   59.12  172.75 

编辑

为了解决下面提出的问题,即上述结果对 data.frame 不公平,这里有更多结果和时间,以表明总体信息仍然是“如果可能,请将您的数据对象转换为矩阵。如果不可能,处理它。或者,如果时机至关重要,请重新考虑为什么需要以 CSV 格式写出 200MB 以上的文件”:

#This is a data.frame
m2 <- as.data.frame(matrix(runif(256*65536), nrow = 256))
#This is still 6x slower
system.time(write.csv(m2, "dataframe.csv"))
#   user  system elapsed 
# 317.85   13.95  332.44
#This even includes the overhead in converting to as.matrix in the timing 
system.time(write.csv(as.matrix(m2), "asmatrix.csv"))
#   user  system elapsed 
#  53.67    0.92   54.67 

所以,没有什么真正改变。要确认这是合理的,请考虑as.data.frame() 的相对时间成本:

m3 <- as.matrix(m2)
system.time(as.data.frame(m3))
#   user  system elapsed 
#   0.77    0.00    0.77 

因此,并没有像下面的评论所认为的那样严重或歪曲信息。如果您仍然不相信在大型 data.frames 上使用 write.csv() 在性能方面是一个坏主意,请参阅 Note 下的手册:

write.table can be slow for data frames with large numbers (hundreds or more) of
columns: this is inevitable as each column could be of a different class and so must be
handled separately. If they are all of the same class, consider using a matrix instead.

最后,如果您仍然因为更快地保存内容而失眠,请考虑迁移到本机 RData 对象

system.time(save(m2, file = "thisisfast.RData"))
#   user  system elapsed 
#  21.67    0.12   21.81

【讨论】:

  • 这有点不公平的比较...... as.data.frame 需要相当长的时间。此外,OP 拥有的数据已经在 data.frame 中。
  • @John - 好点,尽管as.data.frame 的相对开销与使用write.csv()data.frame 上的朋友相对于matrix 的开销相比可以忽略不计。
  • 我知道它更少,但最好有可能被接受的答案,而不是把这个问题留给天真的读者。
  • @John - 是的,我完全同意。感谢您朝正确的方向轻推。老实说,我只是马虎,但想提供的不仅仅是RTFM 的回复。而且as.data.frame() 的开销也会随着更小的数据对象而增加......
  • 在最后的system.time(save(...)) 中添加compress=FALSE 要快得多。 14 秒与我的机器上的 0.2 秒。
【解决方案3】:

另一种选择是使用feather 文件格式。

df <- as.data.frame(matrix(runif(256*65536), nrow = 256))

system.time(feather::write_feather(df, "df.feather"))
#>   user  system elapsed 
#>  0.237   0.355   0.617 

Feather 是一种二进制文件格式,旨在提高读写效率。它旨在与多种语言一起使用:目前有 R 和 python 客户端,还有一个 julia 客户端正在开发中。

为了比较,下面是saveRDS 需要多长时间:

system.time(saveRDS(df, "df.rds"))
#>   user  system elapsed 
#> 17.363   0.307  17.856

现在,这是一个有点不公平的比较,因为saveRDS 的默认值是压缩数据,而这里的数据是不可压缩的,因为它是完全随机的。关闭压缩会使saveRDS 明显更快:

system.time(saveRDS(df, "df.rds", compress = FALSE))
#>   user  system elapsed 
#>  0.181   0.247   0.473     

事实上,它现在比羽毛快一点。那么为什么要使用羽毛呢?嗯,它通常比readRDS() 快,而且与读取数据的次数相比,您写入数据的次数通常相对较少。

system.time(readRDS("df.rds"))
#>   user  system elapsed 
#>  0.198   0.090   0.287 

system.time(feather::read_feather("df.feather"))
#>   user  system elapsed 
#>  0.125   0.060   0.185 

【讨论】:

  • 请参阅gist.github.com/markdanese/28b9f5412df55efceba754fee2363444,了解任何想要测试它的人的要点。 FWIW,fwrite 对于 CSV 来说很快,但与羽毛不同。
  • 注意saveRDS需要有compress = FALSE
  • feather 很棒,但与原始问题无关。因为是二进制格式...
  • @DmitriySelivanov 我刚刚重新阅读了原始问题,但看不到它在哪里要求纯文本格式。
【解决方案4】:

fst package

用于快速读取和写入数据文件的最新选项是fst packagefst 生成二进制格式的文件。

使用write.fst(dat, "file.fst", compress=0),其中compress 可以从0(无压缩)到100(最大压缩)。可以使用dat = read.fst("file.fst") 将数据读回 R。根据package website 中列出的时序,它比featherdata.table 和基本R readRDSwriteRDS 更快。

包开发站点警告fst 数据格式仍在发展中,因此fst 不应该用于长期数据存储。

【讨论】:

    【解决方案5】:

    您还可以尝试“readr”包的 read_rds(与 data.table::fread 相比)和 write_rds(与 data.table::fwrite 相比)。

    这是我数据集中的一个简单示例(1133 行和 429499 列):

    写入数据集

    fwrite(rankp2,file="rankp2_429499.txt",col.names=T,row.names=F,quote = F,sep="\t") write_rds(rankp2,"rankp2_429499.rds")

    读取数据集(1133 行 429499 列)

    system.time(fread("rankp2_429499.txt",sep="\t",header=T,fill = TRUE))  user system elapsed 42.391 0.526 42.949

    system.time(read_rds("rankp2_429499.rds")) user system elapsed 2.157 0.388 2.547

    希望对你有帮助。

    【讨论】:

      【解决方案6】:

      我认为你应该使用 fwrite()

      它更快,对我帮助很大:

      fwrite(x, file = "", append = FALSE, quote = "auto",
        sep = ",", sep2 = c("","|",""),
        eol = if (.Platform$OS.type=="windows") "\r\n" else "\n",
        na = "", dec = ".", row.names = FALSE, col.names = TRUE,
        qmethod = c("double","escape"),
        logical01 = getOption("datatable.logical01", FALSE),  # due to change to TRUE; see NEWS
        logicalAsInt = logical01,  # deprecated
        dateTimeAs = c("ISO","squash","epoch","write.csv"),
        buffMB = 8L, nThread = getDTthreads(),
        showProgress = interactive(),
        verbose = getOption("datatable.verbose", FALSE))
      

      https://jangorecki.gitlab.io/data.table/library/data.table/html/fwrite.html

      【讨论】:

        猜你喜欢
        • 2020-05-20
        • 1970-01-01
        • 1970-01-01
        • 2019-06-21
        • 2011-10-28
        • 1970-01-01
        • 2016-03-25
        • 1970-01-01
        • 2018-01-31
        相关资源
        最近更新 更多