Пересылка аргументов в функцию с purrr :: map_df - PullRequest
2 голосов
/ 05 октября 2019

Я пытаюсь создать функцию, которая читает все листы в книге Excel с помощью readxl::read_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)

Ответы [ 2 ]

2 голосов
/ 05 октября 2019

Я столкнулся с подобной проблемой неправильной передачи аргумента с map в совершенно ином контексте (не в функции и не с использованием многоточия, для справки см. Функцию recode_df в конце этого ответа ).

Тогда было решено создать именованную функцию, которая принимает только один аргумент и передает его 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. Чтобы обойти это, я предложил использовать анонимную функцию в комментариях. Очевидно, что приведенный ниже подход также работает.

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"

Теперь возникает вопрос: есть ли другой способ, которым мы должны использовать многоточие в функциях картографирования, или это ошибка.

0 голосов
/ 05 октября 2019

Я бы подумал, что сработает следующее:

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, который является первым, уже был предоставлен. Я действительно не понимаю этого, и это не кажется особенно прозрачным подходом, но я подумал, что я бы применил его на тот случай, если кто-то еще сможет объяснить, что происходит, и предоставить лучший код.

...