Что делает !! среднее значение оператора в R

#r #tidyverse #quote #rlang #quasiquotes

Вопрос:

Кто-нибудь может объяснить, пожалуйста, для чего нам это нужно !! !!! или {{}} от rlang кого ? Я попытался узнать больше о квазиквотации, но ничего не получил.

Я прочитал несколько сообщений об операторе curly-curly в стеке и понял, что мы используем {{ , когда передаем переменные фрейма данных (или другие подобъекты наших объектов) в функцию. Но после прочтения о кавычках/без кавычек я был совершенно сбит с толку всеми этими операторами и их использованием.

Зачем нам это нужно, почему некоторые функции не считывают аргументы без него и, наконец, как они на самом деле работают?

Я буду признателен, если вы изложите ответ самым простым способом, который даже я пойму (может быть, с примерами?).

Комментарии:

1. «Зачем нам это нужно» Они вам нужны, потому что tidyverse в значительной степени использует нестандартную оценку. Как человек, который не использует tidyverse, я никогда не использовал ни один из них.

2. В базе R !! означает двойное (и !!! тройное) отрицание logical операторов. rlang и другие пакеты tidyverse приняли его для использования для оценки переменных NSE.

3. Это, скорее всего, дубликат, но ! бытие ! делает !! в два раза труднее искать.

4. Если вы не поняли их после прочтения главы о квазикавычислении, я не уверен, что еще можно сказать. Возможно, руководство по программированию с помощью dplyr поможет: dplyr.tidyverse.org/articles/programming.html .

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

Ответ №1:

Операторы !! and {{ являются заполнителями, чтобы пометить переменную как заключенную в кавычки. Они обычно нужны только в том случае, если вы собираетесь программировать с tidyverse помощью . tidyverse Любит использовать NSE (нестандартную оценку), чтобы уменьшить количество повторений. Наиболее частое применение относится к "data.frame" классу, в котором выражения/символы оцениваются в контексте data.frame перед поиском в других областях. Для того чтобы это работало, некоторые специальные функции (например, в пакете dplyr ) имеют аргументы, которые приводятся в кавычках. Чтобы привести выражение, нужно сохранить символы, составляющие выражение, и предотвратить вычисление (в контексте tidyverse они используют «запросы», которые похожи на выражение в кавычках, за исключением того, что оно содержит ссылку на среду, в которой было сделано выражение). Хотя NSE отлично подходит для интерактивного использования, с ним значительно сложнее программировать. Давайте рассмотрим dplyr::select

  library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
 
 iris <- as_tibble(iris)
 
 my_select <- function(.data, col) {
   select(.data, col) 
 }
 
 select(iris, Species)
#> # A tibble: 150 × 1
#>    Species
#>    <fct>  
#>  1 setosa 
#>  2 setosa 
#>  3 setosa 
#>  4 setosa 
#>  5 setosa 
#>  6 setosa 
#>  7 setosa 
#>  8 setosa 
#>  9 setosa 
#> 10 setosa 
#> # … with 140 more rows
 my_select(iris, Species)
#> Error: object 'Species' not found
 

мы сталкиваемся с ошибкой, потому что в пределах области действия my_select
col аргумент оценивается с помощью стандартной оценки и
не может найти переменную с именем Species .

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

  Species <- "Sepal.Width"
 my_select(iris, Species)
#> Note: Using an external vector in selections is ambiguous.
#> ℹ Use `all_of(col)` instead of `col` to silence this message.
#> ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
#> This message is displayed once per session.
#> # A tibble: 150 × 1
#>    Sepal.Width
#>          <dbl>
#>  1         3.5
#>  2         3  
#>  3         3.2
#>  4         3.1
#>  5         3.6
#>  6         3.9
#>  7         3.4
#>  8         3.4
#>  9         2.9
#> 10         3.1
#> # … with 140 more rows
 

Чтобы исправить это, нам нужно
предотвратить оценку с enquo() помощью и отменить кавычки !! или просто использовать {{ .

  my_select2 <- function(.data, col) {
   col_quo <- enquo(col)
   select(.data, !!col_quo) #attempting to find whatever symbols were passed to `col` arugment
 }
 #' `{{` enables the user to skip using the `enquo()` step.
 my_select3 <- function(.data, col) {
   select(.data, {{col}}) 
 }
 
 my_select2(iris, Species)
#> # A tibble: 150 × 1
#>    Species
#>    <fct>  
#>  1 setosa 
#>  2 setosa 
#>  3 setosa 
#>  4 setosa 
#>  5 setosa 
#>  6 setosa 
#>  7 setosa 
#>  8 setosa 
#>  9 setosa 
#> 10 setosa 
#> # … with 140 more rows
 my_select3(iris, Species)
#> # A tibble: 150 × 1
#>    Species
#>    <fct>  
#>  1 setosa 
#>  2 setosa 
#>  3 setosa 
#>  4 setosa 
#>  5 setosa 
#>  6 setosa 
#>  7 setosa 
#>  8 setosa 
#>  9 setosa 
#> 10 setosa 
#> # … with 140 more rows
 

Таким образом, вам действительно нужно только !! , и {{ если вы пытаетесь применить NSE программно
или выполнить какой-либо тип программирования на языке.

!!! используется для объединения некоторого списка/вектора в аргументы некоторого цитирующего выражения.

  library(rlang)
 quo_let <- quo(paste(!!!LETTERS))
 quo_let
#> <quosure>
#> expr: ^paste("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L",
#>           "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
#>           "Z")
#> env:  global
 eval_tidy(quo_let)
#> [1] "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
 

Создано 2021-08-30 пакетом reprex (версия v2.0.1)

Комментарии:

1. Джастин, большое тебе спасибо за знакомство с NSE. Я приму ваш ответ на этот вопрос, но это не значит, что он лучше, чем у Артема. Ваши ответы дополняют друг друга, поэтому они рисуют действительно большую и простую картину использования некоторых операторов.

Ответ №2:

Нестандартная оценка (NSE) часто используется вместе с tidyverse/dplyr, но большинство людей сталкиваются с ней ежедневно при загрузке пакетов.

 a <- "rlang"

print(a)               # Standard evaluation: the expression a is replace by its value
# [1] "rlang"

library(a)             # Non-standard evaluation: the expression a is used as-is
# Error in library(a) : there is no package called ‘a’
 

Итак, как вы загружаете динамически заданный пакет? Здесь мы будем использовать квазиквотацию для демонстрации. (В реальном коде я рекомендую делать library(a, character.only=TRUE) это вместо этого.)

В базе R вы можете использовать bquote() для динамического построения выражения, а затем его вычисления.

 myexpr <- bquote(library(.(a)))      # myexpr will now be library("rlang")
eval(myexpr)                         # rlang is now loaded
 

rlang предоставляет дополнительные инструменты для управления выражениями. В целом, они позволяют вам быть более выразительными, чем базовые инструменты R. !! Поведение аналогично описанному выше:

 myexpr <- rlang::expr(library(!!a))  # Same as above, myexpr is now library("rlang")
 

Вы можете использовать rlang::expr с !! для построения любых выражений для будущей оценки.

 x <- rlang::expr(mtcars)
y <- rlang::expr(mpg > 30)
z <- rlang::expr(disp)
rlang::expr(subset(!!x, !!y, !!z))   # Constructs subset(mtcars, mpg > 30, disp)
 

Когда у вас много аргументов, вы можете поместить их в список и использовать !!! ярлык. Приведенное выше выражение может быть воспроизведено с помощью

 l <- rlang::exprs(mtcars, mpg > 30, disp)   # Note the s on exprs
rlang::expr(subset(!!!l))                   # Also builds subset(mtcars, mpg > 30, disp)
 

{{ Оператор является наиболее сложным для объяснения и требует введения ограничений.

Выражения в R являются объектами первого класса, что означает, что они могут быть переданы в функции, возвращены функциями и т. Д. Однако выражения, созданные с rlang::expr помощью, всегда оцениваются в их непосредственном контексте. Считать,

 a <- 10
x <- rlang::expr(a 5)

f <- function(y) {
  a <- 5
  eval(y)
}

f(x)     # What does this return?
 

Даже если выражение x фиксируется a 5 , значение a изменяется непосредственно перед вычислением выражения. Запросы фиксируют выражения И среды, в которых они определены. Эта среда всегда используется для оценки этого выражения.

 a <- 10
x <- rlang::quo(a 5)    # Quosure = expression   environment where a == 10

f <- function(y) {
  a <- 5
  eval_tidy(y)          # Instead of simple eval()
}

f(x)                    # 15 = 10   5
 

Захват выражения или запроса может быть перемещен внутрь функции с помощью en- версий expr и quo :

 f <- function(y) {
  a <- 5
  eval(rlang::enexpr(y))
}

g <- function(y) {
  a <- 5
  eval_tidy(rlang::enquo(y))
}
 

позволяет пользователям передавать выражения непосредственно в функцию

 a <- 10
f(a*4)    # 20 = 5*4,  because f captures expressions, and a is overwritten
g(a*4)    # 40 = 10*4, because g captures quosures
 

И со всем вышесказанным, {{x}} это просто сокращенное обозначение !!enquo(x) .

Комментарии:

1. библиотека(a, символ. только = ВЕРНО)

2. Кое-что прояснилось, спасибо за ваше терпение, Артем!

3. Артем, я принял ответ Джастина, но это не значит, что ты не ответил на вопрос. Если бы я мог принять два из них, я бы принял и твое тоже. Спасибо!

4. Вообще никаких проблем. Я рад, что это было полезно. Я хочу предупредить, что NSE может затруднить чтение и обслуживание кода. Я создал простой пример для демонстрации здесь, но в реальном коде я бы использовал вызов библиотеки (), который @Roland написал в своем комментарии.