С какой общностью можно использовать оператор тильды для создания анонимных функций?

Внутри некоторых функций как dplyr::mutate_at или purrr::map кажется может использовать оператор тильды ~ для создания анонимных функций.

Например, можно сделать как в связанном вопросе: map(iris, ~length(unique(.)))

Или еще: mtcars %>% mutate_all(~.*2)

Я пытался имитировать это внутри sapply, чтобы избежать sapply(list, function(item) {something_with_item}). Я писал sapply(list, ~ something_with_.), но получаю ошибку

Error in match.fun(FUN) : 
  '~ something_with_.' is not a function, character or symbol

Воспроизводимый пример:

> sapply(names(mtcars),function(item) mean(mtcars[[item]]))
       mpg        cyl       disp         hp       drat         wt       qsec 
 20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750 
        vs         am       gear       carb 
  0.437500   0.406250   3.687500   2.812500 
> sapply(names(mtcars),~mean(mtcars[[.]]))
Error in match.fun(FUN) : 
  '~mean(mtcars[[.]])' is not a function, character or symbol

Почему? Понимание этого синтаксиса как функции — это просто поведение некоторых пакетов, таких как dplyr и purrr? Понимается ли это по базе R для каких-то особых случаев?

Благодарю вас!


person iago    schedule 14.10.2019    source источник
comment
Кто-то еще может углубиться, но, по сути, тильда обозначает объект formula, который tidyverse обычно принимается для нестандартной оценки, в то время как базовое семейство приложений не принимает, поэтому все равно ожидайте, что будет предоставлен function. См. эту виньетку для получения более подробной информации о его реализации в кране tidyverse: .r-project.org/web/packages/lazyeval/vignettes/   -  person caldwellst    schedule 14.10.2019


Ответы (2)


Как сказал Колдуэллст выше, тильда ~ используется для создания объекта формулы, который представляет собой фрагмент кода, не прошедшего оценку, который можно использовать позже. Формулы — это не то же самое, что функции, и функции purrr работают с формулами, потому что они вызывают rlang::as_function (через purrr::as_mapper) в фоновом режиме. Очевидно, что функции *apply этого не делают, хотя вы можете сымитировать такое поведение, вызвав одну из вышеперечисленных функций в вашей собственной модифицированной функции *apply (лучше просто использовать функции map, нижеследующее просто для иллюстрации):

# Write function with `as_mapper()`.
fapply <- function(x, func) {
    sapply(x, purrr::as_mapper(func))
}

# Now we can use formulae.
fapply(names(mtcars), ~ mean(mtcars[[.]]))

#### OUTPUT ####

       mpg        cyl       disp         hp       drat         wt       qsec         vs         am       gear       carb 
 20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750   0.437500   0.406250   3.687500   2.812500 
person gersht    schedule 14.10.2019

Объект формулы не является функцией, и формула будет рассматриваться как функция только в том случае, если вызываемая функция, которой передается формула, была написана для интерпретации аргументов формулы как функций. Многие функции tidyverse написаны таким образом, но в целом формула по умолчанию не является функцией.

fn$

В пакете gusbfn есть fn$, который позволяет почти любой функции, принимающей аргумент функции, принимать формулы. Перед вызовом функции ставится fn$, после чего аргументы формулы интерпретируются как функции (при соблюдении определенных правил).

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

library(gsubfn)

fn$sapply(names(mtcars), item ~ mean(mtcars[[item]]))

давая:

       mpg        cyl       disp         hp       drat         wt       qsec 
 20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750 
        vs         am       gear       carb 
  0.437500   0.406250   3.687500   2.812500 

См. ?fn для получения дополнительной информации и примеров.

match.funfn

Пакет gsubfn также имеет match.funfn, который похож на match.fun в базе R, за исключением того, что он также интерпретирует формулы как функции. Это позволяет писать свои собственные функции, которые принимают аргументы формулы, интерпретируя их как функции.

С точки зрения примера в вопросе:

library(gsubfn)

sapplyfn <- function(X, FUN, ...) {
  FUN <- match.funfn(FUN)
  sapply(X, FUN, ...)
}

sapplyfn(names(mtcars), item ~ mean(mtcars[[item]]))

давая:

       mpg        cyl       disp         hp       drat         wt       qsec 
 20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750 
        vs         am       gear       carb 
  0.437500   0.406250   3.687500   2.812500 

как.формула.функции

В пакете gsubfn также есть as.function.formula, который преобразует формулу в функцию. Его используют fn$ и match.funfn. Например,

library(gsubfn)
as.function(item ~ mean(mtcars[[item]]))

давая:

function (item) 
mean(mtcars[[item]])
person G. Grothendieck    schedule 14.10.2019