代码之家  ›  专栏  ›  技术社区  ›  mammykins

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

  •  2
  • mammykins  · 技术社区  · 7 年前

    我理解 readxl 可用于 read in multiple worksheets from a workbook . 然而,我正在努力扩展这一点,并将其矢量化到许多具有不同工作表名称、工作表数量和数据的工作簿中。

    我演示如何使用 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
    

    检查这些工作簿并计算其中的工作表数量,可以发现它们的工作表数量不同,包含的数据也不同。

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

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

    • 使用 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 工作簿的工作表名称存储在列表列中。我们希望每个工作表一行,工作表的数据内容存储在列表列中,以便我们可以根据工作表数据添加额外的功能(工作表是实验单元)。问题是它没有像预期的那样矢量化,我遗漏了什么吗?

    此错误。。。

    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
    

    如果没有为工作簿路径和工作表名称传递向量,但在下面的示例中,数据显然不是来自正确的工作表,则代码可以工作:

    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 任何帮助或指点都将不胜感激。

    2 回复  |  直到 7 年前
        1
  •  4
  •   nacnudus JJ Allaire    7 年前

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

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

    需要示例文件进行演示。此代码使用 openxlsx 包创建一个包含两张图纸的文件(内置 iris mtcars 数据集)和另一个包含三张图纸的文件(添加内置 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() 每个文件中图纸的名称,以及 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
    

    “映射” 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=) ,因此下一步是创建一个数据框,其中每行提供一个路径和一个图纸名称。这是通过使用 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
    

    现在可以将每个路径和图纸名称传递到 readxl::read\u 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
    
        2
  •  0
  •   ASH    7 年前

    我刚刚测试了这个,它在一个工作簿中运行良好。

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