读取大型 Excel xlsx 文件的最快方法?并行化与否?

Fastest way to read large Excel xlsx files? To parallelize or not?

我的问题是:

到目前为止我已经了解(以及我还没有了解)的内容:

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

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

一些背景:我主要使用 Python 并且是 R 的新手。在 Python 中读取大型 Excel 文件非常慢。 我已经看到 R 的 readxl 比 Python 的 pandas 快得多(在 15-sheet xlsx 上,每个 sheet 有 10,000 行和32 列:readxl 5.6 秒 vs pandas) 33 秒,太棒了!但是,我仍然想了解是否有任何方法可以使导入速度更快。我可以 使用 R 读取文件,将它们导出到 SQL,然后使用 Python 从 SQL.[= 继续我的其余工作流程17=]

我不认为转换为 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()

您可以尝试使用 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 张,每张包含此 table:

    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) 在流程完成后关闭工作人员。

我看到了@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。这些都非常慢,所以不值得考虑。