Создание новой формулы типа `~ x + y` с использованием` rlang`

Я пытаюсь написать настраиваемую функцию, в которой я хочу использовать функцию cor.test, но у меня возникают проблемы с отменой цитирования необходимых аргументов для создания рабочей формулы.

Вот то, что у меня сейчас не работает -

library(rlang)

# custom function
tryfn <- function(data, x, y) {
  stats::cor.test(
    formula = rlang::new_formula(NULL, {{ x }} + {{ y }}),
    data = data,
    method = "pearson"
  )
}

# using the function
tryfn(mtcars, wt, mpg)
#> Error in rlang::new_formula(NULL, {: object 'wt' not found

Я пробовал этот способ, потому что, похоже, он работает, если мне не нужно отменять цитирование формулы в среде функции.

# without unquoting inside another function
print(rlang::new_formula(NULL, quote(x + y)))
#> ~x + y

Есть идеи, как это реализовать?


person Indrajeet Patil    schedule 25.07.2019    source источник
comment
Можешь попробовать с tryfn(mtcars, quote(mpg + wt)) и внутри formula = rlang::new_formula(NULL, expr) и function(data, expr)   -  person akrun    schedule 25.07.2019
comment
Это работает, но у меня должна быть функция с аргументами data, x и y, так что это все равно не решит мою проблему.   -  person Indrajeet Patil    schedule 25.07.2019


Ответы (2)


Важно помнить, что rlang::quo - это не то же самое, что base::quote. На практике последний по сути эквивалентен rlang::expr. Интерполяция с {{ создает запросы с соответствующими средами, поэтому это ярлык для случая, подобного следующему:

x <- 0

with_curly <- function(foo) {
  x <- 1
  rlang::eval_tidy({{ foo }})
}

with_curly(x)
# 0

with_enquo <- function(foo) {
  x <- 1
  rlang::eval_tidy(rlang::enquo(foo))
}

with_enquo(x)
# 0

С другой стороны, enexpr действует как quote, но для того, что пользователь ввел:

with_enexpr <- function(foo) {
  x <- 1
  rlang::eval_tidy(rlang::enexpr(foo))
}

with_enexpr(x)
# 1

По моему опыту, quosures плохо сочетаются (или вообще не работают) с какой-либо функцией, которая не поддерживает их явно, и многие функции R ожидают "сырых" выражений. Даже во время печати вы можете увидеть, что они обрабатываются по-разному:

foo <- function(foo) {
  rlang::qq_show({{ foo }})
  rlang::qq_show(!!rlang::enexpr(foo))
  invisible()
}

foo(x)
# ^x
# x

Это означает, что, по крайней мере, на данный момент, нет ярлыка для создания простых выражений, и вам придется делать это долгим путем:

РЕДАКТИРОВАТЬ: не совсем так. Для простых выражений нет ярлыка, но вы все равно можете создавать формулы с кавычками. См. Ответ Moody и комментарии ниже.


Также стоит время от времени делать шаг назад и помнить, что вам не везде нужна нестандартная оценка:

tryfn <- function(data, x, y) {
  stats::cor.test(
    formula = as.formula(glue::glue("~ {x} + {y}")),
    data = data,
    method = "pearson"
  )
}

tryfn(mtcars, "wt", "mpg")
person Alexis    schedule 25.07.2019
comment
Я не согласен с этим, разница между quosures и цитируемыми выражениями здесь не имеет значения, поскольку OP не пытался кормить quosure, где ожидалось цитируемое выражение. rlang::new_formula(NULL, {{ x }} + {{ y }}) будет работать нормально, если new_formula() поддерживает квазицитирование и использует NSE вместо ожидания вызовов или имен. См. Мое мнение в моем собственном ответе. - person Moody_Mudskipper; 08.08.2019
comment
@Moody_Mudskipper Ну, вроде как. Обозначение с {{ действительно создает вопросы. Если вы распечатаете формулу, созданную в вашем решении, вы увидите дополнительные символы ~, добавленные из-за того, что аргументы являются запросами, но это, похоже, не влияет на семантику формулы, поэтому OP действительно может использовать ее в любом случае. - person Alexis; 08.08.2019
comment
Действительно, вы правы, я отредактирую свой ответ, когда у меня будет возможность. - person Moody_Mudskipper; 08.08.2019

Ваши проблемы возникают из-за того, что:

  • new_formula() требует вызова или именования объектов в качестве входных данных, и вы ожидаете, что он будет использовать NSE:
rlang::new_formula(NULL, wt + mpg)
#> Error in rlang::new_formula(NULL, wt + mpg): objet 'wt' introuvable
rlang::new_formula(NULL, quote(wt + mpg))
#> ~wt + mpg
  • new_formula() не поддерживает квазицитирование:
quoted_expr <- quote(wt + mpg)
rlang::new_formula(NULL, !!quote(quoted_expr))
#> Error in !quote(quoted_expr): type de l'argument incorrect

(посмотрите, как !! не распознается)

Способ обойти обе эти проблемы - создать выражение в кавычках из функции, которая поддерживает квазиквотацию. Эта функция expr():


tryfn <- function(data, x, y) {
  stats::cor.test(
    formula = rlang::new_formula(NULL, rlang::expr({{x}} + {{y}})),
    data = data,
    method = "pearson"
  )
}

tryfn(mtcars, wt, mpg)
#> 
#>  Pearson's product-moment correlation
#> 
#> data:  wt and mpg
#> t = -9.559, df = 30, p-value = 1.294e-10
#> alternative hypothesis: true correlation is not equal to 0
#> 95 percent confidence interval:
#>  -0.9338264 -0.7440872
#> sample estimates:
#>        cor 
#> -0.8676594
person Moody_Mudskipper    schedule 08.08.2019
comment
см. комментарии под ответом Алексиса тоже - person Moody_Mudskipper; 08.08.2019
comment
Я думаю, что следует сделать заметку об использовании {{}} и о том, как он появляется только после определенной версии rlang. Любопытно, почему !! не работает, а {{}} работает. Какие принципиальные отличия? Также любопытно, в чем преимущество использования rlang над reformulate и т.п. - person NelsonGon; 19.08.2019
comment
@Moody_Mudskipper Как изменится вызов rlang::new_formula, если формула была не ~x + y, а y ~ x? Я пробовал rlang::new_formula(rlang::expr({{x}}), rlang::expr({{y}})), но это не сработало. - person Indrajeet Patil; 10.06.2020
comment
Я не совсем понимаю, что происходит, но я бы выбрал базу R, в моем ответе можно было бы использовать formula = rlang::new_formula(NULL, substitute(x + y)) или даже formula = eval(substitute(~x + y)), поэтому для вашего варианта вы можете использовать formula = rlang::new_formula(substitute(x), substitute(y)) или formula = eval(substitute(x ~ y)) - person Moody_Mudskipper; 10.06.2020