Пересылка аргументов в функции с purrr :: map_df

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

person Lief Esbenshade    schedule 04.10.2019    source источник
comment
Можете ли вы попробовать: (1) использовать обычную анонимную функцию в вызове карты function(x) {} вместо обозначения функции lamda ~. Если ошибка по-прежнему отображается (2), опустите вертикальную черту после set_names и используйте вместо нее промежуточную переменную.   -  person TimTeaFan    schedule 05.10.2019
comment
нет кубиков. По-прежнему появляется то же сообщение об ошибке. doc <- path %>% readxl::excel_sheets() %>% rlang::set_names() и purrr::map_df(doc, function(x) {readxl::read_excel(path = path, sheet = .x, !!!args)})} ``   -  person Lief Esbenshade    schedule 05.10.2019
comment
Вы неправильно указали анонимную функцию. Если вы используете function(x), вам нужно заменить .x на x. См. Мой ответ ниже.   -  person TimTeaFan    schedule 05.10.2019


Ответы (2)


Возможное решение - создать именованную функцию, которая принимает только один аргумент, и передать его 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"

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

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

person eipi10    schedule 04.10.2019
comment
Спасибо. Я также хочу точно понимать, что происходит. Из документации для map_df в разделе Arguments: .f If a function, it is used as is. If a formula, e.g. ~ .x + 2, it is converted to a function... я использовал обозначение формулы, и я предполагаю, что с преобразованием в функцию происходило что-то странное. - person Lief Esbenshade; 05.10.2019
comment
Я предлагаю не принимать этот ответ. Мы хотим, чтобы люди посмотрели на ваш вопрос и мой ответ и увидели, смогут ли они объяснить, что происходит, и, возможно, предложить лучший код. Если ответ уже принят, придет меньше людей. - person eipi10; 05.10.2019
comment
~ - это функция цитирования, и ее вывод представляет собой формулу, точки больше не передаются ей, как в строке "hello ... world" или в аргументе функции (с точками формальных или без них) в этом отношении. В принятом решении точка передается в map_df, который затем обрабатывает их внутри, преобразовывая свой аргумент формулы в функцию, если это необходимо, и последовательно передавая ей элементы своего основного аргумента вместе с точками. - person Moody_Mudskipper; 05.10.2019