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

我明白, readxl可以用来从工作簿读取多个工作表 。 不过,我正在努力扩展这个方向,并将其引导到许多不同的工作表中,其中有不同的工作表名称以及其中的工作表和数据。

我演示了使用安然电子表格数据 ,这是我下载的一堆.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" 

为了使其易于pipe理,我们抽样了。

 # 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 

检查这些工作簿,并计算其中的工作表数量显示,他们有不同的数量,并包含不同的数据。

 # 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 = "") 

但是,要将工作簿中的所有工作表加载到数据框列表中 ,我们需要:

  • 获取工作表名称作为一个自命名的字符向量(这些名称传播很好)。
  • 使用purrr::map()迭代表单读取。

     books <- dplyr::data_frame(filename = basename(paths), path = paths, sheet_name = purrr::map(paths, readxl::excel_sheets) ) %>% dplyr::mutate(id = as.character(row_number())) books # A tibble: 100 x 4 filename <chr> 1 kenneth_lay__19485__Mlp_1109.xlsx 2 kate_symes__18980__SP 15 pages.xls 3 chris_germany__1821__newpower-purc 4 john_griffith__15991__Forwards Det 5 jane_tholt__13278__bid2001A.xlsx 6 gerald_nemec__11481__EOLfieldnames 7 stacey_white__39009__Power RT Serv 8 eric_saibi__9766__012302.xlsx 9 david_delainey__8083__ENA Status o 10 daren_farmer__5035__HPLN0405.xlsx # ... with 90 more rows, and 3 # more variables: path <chr>, # sheet_name <list>, id <chr> 

在这里,我们在books每个工作簿中都有一行,工作簿的工作表名称存储在列表列中。 我们希望每个工作表有一行,工作表的数据内容存储在列表列中,以便我们可以根据工作表数据添加额外的function(工作表是实验单元)。 问题是它没有像预期的那样vector化,我错过了什么?

这个错误…

 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 

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

 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", "... 

如何将许多工作簿中的许多工作表中的数据读入到一个Tibble的列表中?

我是新来阅读杂乱的电子表格和使用purrr帮助或指针将不胜感激。

既然你提到了purrr软件包,其他一些完整的软件包也值得考虑。

  • 当将purrr::map()应用于dataframe的列并将结果存储为列列时, dplyrmutate()
  • tidyr for unnest() ,它扩展了list-column,使list-column中的每一行成为整个数据框中的一行。
  • tibble地打印嵌套的dataframe

示例文件需要演示。 此代码使用openxlsx包创build一个包含两张表(内置的irismtcars数据集)的文件,以及另一个包含三张表的文件(添加内置的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") 

现在解决scheme。

首先列出文件名,这些文件名将传递给readxl::excel_sheets()作为每个文件中工作表的名称,而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 

在每个文件path上映射readxl::excel_sheets()函数,并将结果存储在一个新的列表中。 sheet_name列的每一行都是表名称的向量。 正如所料,第一个有三个表名,而第二个有两个。

 (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]> 

我们需要将每个文件名和每个表名称传递给readxl::read_excel(path=, sheet=) ,所以下一步是要有一个数据框,每行给出一个path和一个表名。 这是使用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 

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

 (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 

我只是testing了这一个工作簿罚款工作。

 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文件。