【问题标题】:Forwarding arguments in a function with purrr::map_df使用 purrr::map_df 在函数中转发参数
【发布时间】:2020-02-03 05:22:09
【问题描述】:

我正在尝试创建一个函数,该函数使用 readxl::read_excel 读取 excel 工作簿中的所有工作表并将它们绑定到单个数据框中,并允许我将其他参数传递给 read_excel。我可以很好地完成第一部分,但不能完成第二部分。

library(magrittr)

# example excel workbook with multiple sheets
path <- readxl::readxl_example("datasets.xlsx")

# function with simple forwarding
read_all <- function(path, ...) {

  path %>%
    readxl::excel_sheets() %>%
    rlang::set_names() %>%
    purrr::map_df(~ readxl::read_excel(path = path, sheet = .x, ...))

}

# errors with and without additional arguments
read_all(path)
read_all(path, skip = 5)

我应该取回一个文件,但我得到一个错误:

Error: Can't guess format of this cell reference: iris
In addition: Warning message: Cell reference follows neither the A1 nor R1C1 format. Example: iris NAs generated.

没有参数传递函数可以正常工作:

# Function works without passing extra params
read_all_0 <- function(path) {

  path %>%
    readxl::excel_sheets() %>%
    rlang::set_names() %>%
    purrr::map_df(~ readxl::read_excel(path = path, sheet = .x))

}

read_all_0(path)

在没有purrr::map_df 的简单函数中,参数传递可以正常工作

read_test <- function(path, ...) {

  path %>% readxl::read_excel(...)
}
read_test(path, skip = 10)

【问题讨论】:

  • 您可以尝试:(1) 在您的地图调用中使用普通匿名函数function(x) {},而不是lamda 函数符号~。如果错误仍然显示 (2) 在set_names 之后省略管道并使用中间变量。
  • 没有骰子。仍然收到相同的错误消息。 doc &lt;- path %&gt;% readxl::excel_sheets() %&gt;% rlang::set_names()purrr::map_df(doc, function(x) {readxl::read_excel(path = path, sheet = .x, !!!args)}) }```
  • 您错误地指定了匿名函数。如果您使用function(x),则必须将.x 替换为x。请参阅下面的回复。

标签: r purrr readxl


【解决方案1】:

一种可能的解决方案是创建一个命名函数,它只接受一个参数并将其传递给map,这样唯一的参数就是您正在循环的向量/列表。

应用于您的问题的解决方案如下所示:

# function with forwarding
read_all <- function(path, ...) {

  # function within function that sets the arguments path and ellipsis as given and only leaves sheet to be determined
  read_xl <- function(sheet) {
    readxl::read_excel(path = path, sheet, ...)
  }

  path %>%
    readxl::excel_sheets() %>%
    rlang::set_names() %>%
    purrr::map_df(read_xl)

}

# this allows you to pass along arguments in the ellipsis correctly
read_all(path)
read_all(path, col_names = FALSE)

这个问题似乎源于对 purrr::as_mapper 函数的不当环境处理。为了避免这种情况,我建议在 cmets 中使用匿名函数。显然,下面的方法也有效。

read_all <- function(path, ...) {

  path %>%
    readxl::excel_sheets() %>%
    rlang::set_names() %>%
    purrr::map_df(function(x) {
                      readxl::read_excel(path = path, sheet = x, ...)
                   })

}

要验证确实是 as_mapper 函数导致了问题,我们可以使用 as_mapper 从上面重写命名的函数中函数。这同样会在省略号中有或没有其他参数时产生错误。

# function with forwarding
read_all <- function(path, ...) {

  # named mapper function
  read_xl <- purrr::as_mapper(~ readxl::read_excel(path = path, sheet = .x, ...))

  path %>%
    readxl::excel_sheets() %>%
    rlang::set_names() %>%
    purrr::map_df(read_xl)

} 

更新 知道as_mapper 导致了这个问题,我们可以更深入地研究这个问题。现在我们可以在 RStudio 调试器中检查运行 read_excel 的简单映射器版本时发生了什么:

read_xl <- purrr::as_mapper(~ readxl::read_excel(path = .x, sheet = .y, ...))
debugonce(read_xl) 
read_xl(path, 1)

似乎当省略号包含在映射器函数中时,as_mapper 不仅将第一个参数映射到 .x,而且自动映射到省略号 ...。我们可以通过创建一个简单的映射函数paster 来验证这一点,该函数采用两个参数.x...

paster <- purrr::as_mapper(~ paste0(.x, ...))
paster(1)
> [1] "11"
paster(2)
> [1] "22"

现在的问题是:我们是否应该在映射器函数中使用省略号,或者这是一个错误。

【讨论】:

    【解决方案2】:

    我原以为以下方法会起作用:

    read_all <- function(path, ...) {
    
      path %>%
        readxl::excel_sheets() %>%
        purrr::set_names() %>%
        map_df(~readxl::read_excel(path=path, sheet=.x), ...)
    
    }
    

    因为map 系列有一个... 参数,用于将附加参数传递给映射函数。但是,以下代码忽略了n_max 参数,仍然返回各种数据帧的所有行,而不是一个有 8 行的数据帧(四张表各有 2 行):

    p <- readxl_example("datasets.xlsx")
    read_all(p, n_max=2)
    

    但是,这是可行的:

    read_all <- function(path, ...) {
    
      path %>% 
        excel_sheets() %>% 
        set_names() %>%
        map_df(read_excel, path=path, ...)
    
    }
    
    p <- readxl_example("datasets.xlsx")
    read_all(path=p, n_max=2)
    

    在上面,path... 中的任何附加参数都被传递给read_excel,并且(显然)工作表名称(如果我们显式使用它将是.x)被隐式传递给sheet 参数,我猜是因为第一个参数 path 已经提供。我真的不明白,这似乎不是一种特别透明的方法,但我想我会把它放在那里,以防其他人可以解释发生了什么并提供更好的代码。

    【讨论】:

    • 谢谢。我也想确切地了解发生了什么。在Arguments: .f If a function, it is used as is. If a formula, e.g. ~ .x + 2, it is converted to a function...map_df 的文档中,我使用的是公式表示法,我的猜测是转换为函数时发生了一些奇怪的事情。
    • 我建议“不接受”这个答案。我们希望人们看看你的问题和我的回答,看看他们是否能解释正在发生的事情,或许还能提出更好的代码。如果答案已经被接受,那么来的人就会更少。
    • ~ 是一个引用函数,它的输出是一个公式,点不再传递给它,它们将传递给字符串 "hello ... world" 或函数参数(带点正式与否)就此而言。在公认的解决方案中,点被传递给map_df,然后它在内部处理它们,方法是将其公式参数转换为相关的函数,并按顺序将其主要参数的元素与点一起提供给它。跨度>
    猜你喜欢
    • 2023-02-19
    • 1970-01-01
    • 1970-01-01
    • 2021-10-09
    • 2018-07-08
    • 2018-07-03
    • 1970-01-01
    • 1970-01-01
    • 2021-06-21
    相关资源
    最近更新 更多