从多个 Excel 不同结构的工作簿中读取所有工作表(作为数据框)

Read all worksheets (as dataframes) from multiple Excel workbooks of different structure

我明白readxl可以用来read in multiple worksheets from a workbook。但是,我正在努力扩展它并将其矢量化到许多具有不同 sheet 名称和 sheet 数量以及其中数据的工作簿中。

我演示了如何使用 Enron spreadsheet data,这是我下载的一堆 .xlsx 文件。

head(list.files("../data/enron_spreadsheets/"), 3)

[1] "albert_meyers__1__1-25act.xlsx"                           
[2] "albert_meyers__2__1-29act.xlsx"                           
[3] "andrea_ring__10__ENRONGAS(1200).xlsx"  

为了便于管理,我们进行了抽样。

# Set the path to your directory of Enron spreadsheets here
enron_path <- "../data/enron_spreadsheets/"
# Set the sample size for testing here
sample_size <- 100
all_paths <- list.files(enron_path,
                    full.names = TRUE)

# For testing, look at n (sample_size) random workbooks.
set.seed(1337)
sample_paths <- sample(all_paths, sample_size)

paths <- sample_paths

检查这些工作簿并计算其中的工作 sheet 数量表明它们的 sheet 数量不同并且包含不同的数据。

# purr package
# https://jennybc.github.io/purrr-tutorial/index.html
sheet_count <- purrr::map(paths, readxl::excel_sheets) %>%
  purrr::map(length) %>%
  unlist()

hist(sheet_count, main = "")

但是,要将工作簿中的所有 sheet 加载到 list of data frames 中,我们需要:

这里我们在 books 中每个工作簿一行,工作簿的工作sheet 名称存储在列表列中。我们希望每个作品sheet一行,作品sheet的数据内容存储在列表列中,这样我们就可以根据作品sheet的数据添加额外的功能(作品sheet =53=]为实验单位)。问题是它没有按预期矢量化,我是不是漏掉了什么?

这个错误...

sheets <-
  tibble::tibble("sheet_name" = unlist(books$sheet_name),
                 "path" = rep(paths,
                              times = unlist(
                                purrr::map_int(books$sheet_name, length))
                              ),
                 "filename" = basename(path),
                 "sheet_data" = tibble::lst(
                   readxl::read_excel(path = path[], 
                                      sheet = sheet_name[])
                   )
             ) %>% 
  dplyr::mutate(id = as.character(row_number()))

Error in switch(ext, xls = "xls", xlsx = "xlsx", xlsm = "xlsx", if (nzchar(ext)) { : 
  EXPR must be a length 1 vector

代码在未传递工作簿路径向量和 sheet 名称时有效,但显然数据不是来自正确的工作sheet,在下面的示例中:

sheets <-
  tibble::tibble("sheet_name" = unlist(books$sheet_name),
                 "path" = rep(paths,
                              times = unlist(
                                purrr::map_int(books$sheet_name, length))
                              ),
                 "filename" = basename(path),
                 "sheet_data" = tibble::lst(
                   readxl::read_excel(path = path[1], 
                                      sheet = sheet_name[1])
                   )
             ) %>% 
  dplyr::mutate(id = as.character(row_number()))

dplyr::glimpse(sheets)

Observations: 313
Variables: 5
$ sheet_name <chr> "MLP's", "DJ SP15", "newpower-p...
$ path       <chr> "../data/enron_spreadsheets//ke...
$ filename   <chr> "kenneth_lay__19485__Mlp_1109.x...
$ sheet_data <list> [<# A tibble: 57 x 46,        ...
$ id         <chr> "1", "2", "3", "4", "5", "6", "...

如何将许多工作簿中的许多工作sheet的数据读入小标题中的列表列?

我不熟悉阅读杂乱的传播sheets 和使用purrr任何帮助或指示将不胜感激。

既然你提到了 purrr 包,其他一些 tidyverse 包也值得考虑。

  • dplyr for mutate(),当将 purrr::map() 应用于数据框的列并将结果存储为列表列时。
  • tidyr for unnest(),扩展列表列,使列表列中的每一行成为整个数据框中的一行。
  • tibble 用于精美打印的嵌套数据框

需要示例文件进行演示。此代码使用 openxlsx 包创建一个包含两个 sheet 的文件(内置 irismtcars 数据集),另一个文件包含三个 sheet ]s(添加内置 attitude 数据集)。

library(openxlsx)

# Create two spreadsheet files, with different numbers of worksheets
write.xlsx(list(iris, mtcars, attitude), "three_sheets.xlsx")
write.xlsx(list(iris, mtcars),           "two_sheets.xlsx")

现在是一个解决方案。

首先,列出文件名,这些文件名将传递给 readxl::excel_sheets() 每个文件中 sheet 的名称,然后 readxl::read_excel() 导入数据本身。

(paths <- list.files(pattern = "*.xlsx"))
#> [1] "three_sheets.xlsx" "two_sheets.xlsx"

(x <- tibble::data_frame(path = paths))
#> # A tibble: 2 x 1
#>   path             
#>   <chr>            
#> 1 three_sheets.xlsx
#> 2 two_sheets.xlsx

'Map' readxl::excel_sheets() 函数遍历每个文件路径,并将结果存储在新的列表列中。 sheet_name 列的每一行都是 sheet 个名称的向量。正如预期的那样,第一个有三个 sheet 个名字,而第二个有两个。

(x <- dplyr::mutate(x, sheet_name = purrr::map(path, readxl::excel_sheets)))
#> # A tibble: 2 x 2
#>   path              sheet_name
#>   <chr>             <list>    
#> 1 three_sheets.xlsx <chr [3]> 
#> 2 two_sheets.xlsx   <chr [2]>

我们需要将每个文件名和每个 sheet 名称传递给 readxl::read_excel(path=, sheet=),因此下一步是创建一个数据框,其中每一行给出一个路径和一个 sheet 名称.这是使用 tidyr::unnest() 完成的。

(x <- tidyr::unnest(x))
#> # A tibble: 5 x 2
#>   path              sheet_name
#>   <chr>             <chr>     
#> 1 three_sheets.xlsx Sheet 1   
#> 2 three_sheets.xlsx Sheet 2   
#> 3 three_sheets.xlsx Sheet 3   
#> 4 two_sheets.xlsx   Sheet 1   
#> 5 two_sheets.xlsx   Sheet 2

现在每个路径和 sheet 名称都可以传递到 readxl::read_excel(),使用 purrr::map2() 而不是 purrr::map() 因为我们传递了两个参数而不是一个。

(x <- dplyr::mutate(x, data = purrr::map2(path, sheet_name,
                                          ~ readxl::read_excel(.x, .y))))
#> # A tibble: 5 x 3
#>   path              sheet_name data              
#>   <chr>             <chr>      <list>            
#> 1 three_sheets.xlsx Sheet 1    <tibble [150 × 5]>
#> 2 three_sheets.xlsx Sheet 2    <tibble [32 × 11]>
#> 3 three_sheets.xlsx Sheet 3    <tibble [30 × 7]> 
#> 4 two_sheets.xlsx   Sheet 1    <tibble [150 × 5]>
#> 5 two_sheets.xlsx   Sheet 2    <tibble [32 × 11]>

现在每个数据集都位于 data 列的单独行中。我们可以通过对该列进行子集化来查看其中一个数据集。

x$data[3]
#> [[1]]
#> # A tibble: 30 x 7
#>    rating complaints privileges learning raises critical advance
#>     <dbl>      <dbl>      <dbl>    <dbl>  <dbl>    <dbl>   <dbl>
#>  1   43.0       51.0       30.0     39.0   61.0     92.0    45.0
#>  2   63.0       64.0       51.0     54.0   63.0     73.0    47.0
#>  3   71.0       70.0       68.0     69.0   76.0     86.0    48.0
#>  4   61.0       63.0       45.0     47.0   54.0     84.0    35.0
#>  5   81.0       78.0       56.0     66.0   71.0     83.0    47.0
#>  6   43.0       55.0       49.0     44.0   54.0     49.0    34.0
#>  7   58.0       67.0       42.0     56.0   66.0     68.0    35.0
#>  8   71.0       75.0       50.0     55.0   70.0     66.0    41.0
#>  9   72.0       82.0       72.0     67.0   71.0     83.0    31.0
#> 10   67.0       61.0       45.0     47.0   62.0     80.0    41.0
#> # ... with 20 more rows

我刚刚对此进行了测试,它适用于一本工作簿。

library(readxl)    
read_excel_allsheets <- function(filename) {
    sheets <- readxl::excel_sheets(filename)
    x <-    lapply(sheets, function(X) readxl::read_excel(filename, sheet = X))
    names(x) <- sheets
    x
}

可以这样调用:

mysheets <- read_excel_allsheets("foo.xls")

请注意,它适用于 xls 和 xlsx;它不适用于 xlsb 文件。