【问题标题】:Fastest way to read large Excel xlsx files? To parallelize or not?读取大型 Excel xlsx 文件的最快方法?并行化与否?
【发布时间】:2019-04-04 10:02:09
【问题描述】:

我的问题是:

  • 将大型(ish).xlsx Excel 文件读入 R 的最快方法是什么? 10 到 200 MB xlsx 文件,多张纸。

  • 可以使用某种并行处理,例如每个核心阅读 多页 Excel 文件的单独表格?

  • 还有其他可以执行的优化吗?

到目前为止我所了解的(以及我尚未了解的):

  • 如果从旋转磁盘读取,我会这样做,并行处理实际上可能会减慢读取速度,因为多个进程尝试从同一个文件中读取。但是,并行过程可能有助于转换和推断数据类型之类的事情?不确定 readxl 从磁盘读取(我假设是 IO 绑定)与转换数据类型(我猜是 CPU 绑定)花费了多少。
  • 这可能与 SSD 驱动器不同。如果有很大的改进,我可能会将数据复制到 SSD 驱动器并从那里读取。
  • data.table::fread 加快了文本文件的读取速度(虽然我不完全理解为什么)但它不能用于 excel 文件 - 或者可以吗?
  • 我从这个answer 了解到readxl 往往比openxlsx

我只对表格数据感兴趣;我对 Excel 格式不感兴趣,对图表、文本标签或任何其他类型的数据也不感兴趣。

我可能希望导入 tidyverse tibbles,但不一定。然后我需要将表导出到 Microsoft SQL Server。

一些背景知识:我主要使用 Python 并且对 R 完全陌生。在 Python 中读取大型 Excel 文件非常缓慢。我已经看到 R 的 readxl 比 Python 的 @987654325 快得多@(在 15 页 xlsx 上,每页有 10,000 行和 32 列:readxl 为 5.6 秒,pandas 为 33 秒),太棒了!但是,我仍然想了解是否有任何方法可以使导入更快。我可以使用 R 读取文件,将它们导出到 SQL,然后使用 Python 从 SQL 读取继续我的工作流程的其余部分。

我不认为转换为 CSV 是最好的选择,尤其是当 readxl 无论如何都比 Python 快得多时;基本上转换为 csv 可能比我通过读取 csv 而不是 excel 节省的时间要长。另外,至少对于 Python(我对 R 的了解还不够,无法用 readxl 彻底测试),xlsx 推断数据类型比 csv 更好。

我的代码(欢迎任何批评或建议):

library(readxl)
library(tidyverse)
library(tictoc)


this.dir <- dirname(parent.frame(2)$ofile)
setwd(this.dir)

tic("readxl")

path <- "myfile.xlsx"
sheetnames <- excel_sheets(path)
mylist <- lapply(excel_sheets(path), read_excel, path = path)

names(mylist) <- sheetnames
toc()

【问题讨论】:

  • 找不到合适的副本。你试过fread吗?!
  • 我认为 fread 仅适用于文本文件,不适用于 xlsx。还是我弄错了?
  • 我还注意到,在 R 中将相同的 58 MB 数据导出到 SQL 比在 Python 中快得多:4.25 秒 vs 18.4 秒

标签: r parallel-processing readxl


【解决方案1】:

您可以尝试使用parallel 包并行运行它,但是如果没有样本数据,估计它的速度会有点困难:

library(parallel)
library(readxl)

excel_path <- ""
sheets <- excel_sheets(excel_path)

创建一个具有指定核数的集群:

cl <- makeCluster(detectCores() - 1)

使用 parLapplyLB 浏览 Excel 工作表并使用负载平衡并行读取它们:

parLapplyLB(cl, sheets, function(sheet, excel_path) {
  readxl::read_excel(excel_path, sheet = sheet)
}, excel_path)

您可以使用包microbenchmark 来测试某些选项的速度:

library(microbenchmark)

microbenchmark(
  lapply = {lapply(sheets, function(sheet) {
    read_excel(excel_path, sheet = sheet)
  })},
  parralel = {parLapplyLB(cl, sheets, function(sheet, excel_path) {
    readxl::read_excel(excel_path, sheet = sheet)
  }, excel_path)},
  times = 10
)

就我而言,并行版本更快:

Unit: milliseconds
     expr       min        lq     mean    median        uq      max neval
   lapply 133.44857 167.61801 179.0888 179.84616 194.35048 226.6890    10
 parralel  58.94018  64.96452 118.5969  71.42688  80.48588 316.9914    10

测试文件包含 6 张表,每张都包含此表:

    test test1 test3 test4 test5
 1     1     1     1     1     1
 2     2     2     2     2     2
 3     3     3     3     3     3
 4     4     4     4     4     4
 5     5     5     5     5     5
 6     6     6     6     6     6
 7     7     7     7     7     7
 8     8     8     8     8     8
 9     9     9     9     9     9
10    10    10    10    10    10
11    11    11    11    11    11
12    12    12    12    12    12
13    13    13    13    13    13
14    14    14    14    14    14
15    15    15    15    15    15

注意: 当进程完成时,您可以使用stopCluster(cl) 关闭工作人员。

【讨论】:

  • 谢谢!文件是在 SSD 还是旋转磁盘上?
  • 负载均衡有什么作用,我们为什么需要它?我知道parLapplyLB 进行负载平衡而parLapply 没有;使用后者会产生关于关闭未使用连接的警告,但仍会在似乎是同一时间读取数据。
  • 如果你有 4 个核心并且想读 8 张,这都需要不同的时间,你会开始读 4 张,等到全部完成,然后继续其他 4. 通过负载平衡,进程不会相互等待,一个核心将在第一张纸上工作,而其他核心继续处理下一张。
  • 非常感谢,你是明星!所以在我的情况下,它可能需要相同的时间,因为所有的床单都有相同的尺寸;在其他情况下,负载平衡可以发挥作用。
【解决方案2】:

我看到了@clemens 的回答,但是因为我已经准备了一些东西,所以我还是发布了它。除了@clemens 的回答,我使用更大的测试数据,并使用furrr::future_map() 运行更简单的多核选项,这最终不会带来任何性能提升......

数据生成

这将创建 10 张 10000 * 15 的数据,其中包含浮点、整数和字符的混合。在我的磁盘上,文件大小为 13.2MB。

library(writexl)
library(tidyverse)

n <- 1e4
sample_data <- map(seq(10), function(x) {
  sample_data <-
    map(1:5, function(x){
      data_frame(
        num_var = rnorm(n),
        int_var = as.integer(sample(1e5:9e5, n, replace = T)),
        char_var = sample(letters, n, replace = T)
      ) %>% rename_all(funs(paste0(., x)))
    }) %>% bind_cols()
  return(sample_data)
})
fn <- tempfile(tmpdir = "~/Desktop/temp",fileext = ".xlsx")
write_xlsx(sample_data, path = fn)

基准测试

parallel 部分是从@clemens 借来的。

library(parallel)
library(readxl)
library(purrr)

sheets <- excel_sheets(fn)

cl <- makeCluster(detectCores() - 1)
excel_path <- fn

microbenchmark::microbenchmark(
  map = map(sheets, function(x) read_xlsx(fn, sheet = x)) ,
  future_map = furrr::future_map(sheets, function(x) read_xlsx(fn, sheet = x)),
  parLapplyLB = {parLapplyLB(cl, sheets, function(sheet, excel_path) {
    readxl::read_xlsx(excel_path, sheet = sheet)
  }, excel_path)},
  times = 10
)

基准测试结果如下所示:

Unit: milliseconds
        expr       min        lq      mean    median       uq      max neval
         map 1258.2643 1272.2354 1351.8371 1291.2474 1430.211 1556.992    10
  future_map 1276.4125 1302.2022 1469.8349 1436.5356 1616.146 1702.494    10
 parLapplyLB  809.2697  863.3299  951.1041  914.9503 1014.907 1189.897    10

我的CPU比较弱,所以在其他环境下增益一定很大,但最后可能写SQL部分可能是一个瓶颈,因为read_xlsx的阅读速度真的很快。

注意

我还尝试了其他软件包,例如 gdataxlsx。这些非常缓慢,因此不值得考虑。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2017-11-29
  • 1970-01-01
  • 2015-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-22
  • 1970-01-01
相关资源
最近更新 更多