dplyr tidyeval эквивалент версий функции подчеркивания

Rencent версии dplyr не рекомендуют версии функций с подчеркиванием, такие как filter_, в пользу аккуратная оценка.

Какая ожидается новая форма подчеркивания при новом способе? Как мне написать, избегая неопределенных символов с помощью проверки R CMD?

library(dplyr)

df <- data_frame(id = rep(c("a","b"), 3), val = 1:6)
df %>% filter_(~id == "a")

# want to avoid this, because it references column id in a variable-style
df %>% filter( id == "a" )

# option A
df %>% filter( UQ(rlang::sym("id")) == "a" )
# option B
df %>% filter( UQ(as.name("id")) == "a" )
# option C
df %>% filter( .data$id == "a" )

Есть ли предпочтительная или дополнительная форма рассмотрения? Вариант C является самым коротким, но он медленнее для некоторых из моих реальных больших наборов данных и более сложных конструкций dplyr:

microbenchmark(
sym = dsPClosest %>%
  group_by(!!sym(dateVarName), !!sym("depth")) %>%
  summarise(temperature = mean(!!sym("temperature"), na.rm = TRUE)
            , moisture = mean(!!sym("moisture"), na.rm = TRUE)) %>%
  ungroup()
,data = dsPClosest %>%
    group_by(!!sym(dateVarName), .data$depth ) %>%
    summarise(temperature = mean(.data$temperature , na.rm = TRUE)
              , moisture = mean(.data$moisture , na.rm = TRUE)) %>%
    ungroup()  
,times=10
)
#Unit: milliseconds
# expr        min         lq      mean     median        uq       max neval
#  sym   80.05512   84.97267  122.7513   94.79805  100.9679  392.1375    10
# data 4652.83104 4741.99165 5371.5448 5039.63307 5471.9261 7926.7648    10

Существует еще один ответ для mutate_, использующий еще более сложный синтаксис.


person Thomas Wutzler    schedule 05.12.2017    source источник
comment
Я бы просто использовал utils::globalVariables() для регистрации всех имен переменных, которые вы используете в выражениях dplyr.   -  person Lionel Henry    schedule 06.12.2017
comment
Взгляните на пакет seplyr.   -  person G. Grothendieck    schedule 06.12.2017


Ответы (3)


Основываясь на вашем комментарии, я предполагаю, что это будет:

df %>% filter(!!as.name("id") == "a")

rlang не требуется, так как вы можете сделать это с помощью !! и as.name вместо UQ и sym.

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

df %>% filter_at(vars("id"), all_vars(. == "a"))

В приведенном выше коде vars() определяет, к каким столбцам мы будем применять оператор фильтрации (в справке для filter_at оператор фильтрации называется «предикатом». В этом случае vars("id") означает, что оператор фильтрации применяется только к id столбец. Оператор фильтрации может быть оператором all_vars() или any_vars(), хотя в данном случае они эквивалентны. all_vars(. == "a") означает, что все столбцы в vars("id") должны равняться "a". Да, это немного сбивает с толку.

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

set.seed(2)
df <- data_frame(group = sample(1:100,1e4*52,replace=TRUE), 
                 id = rep(c(letters,LETTERS), 1e4), 
                 val = sample(1:50,1e4*52,replace=TRUE))

microbenchmark(
quosure=df %>% group_by(!!as.name("group"), !!as.name("id")) %>% 
  summarise(val = mean(!!as.name("val"))),
data=df %>% group_by(.data$group, .data$id) %>% 
  summarise(val = mean(.data$val)),
scoped_group_by = df %>% group_by_at(vars("group","id")) %>% 
  summarise_at("val", mean), times=10)
Unit: milliseconds
            expr       min        lq      mean    median        uq       max neval cld
         quosure  59.29157  61.03928  64.39405  62.60126  67.93810  72.47615    10  a 
            data 391.22784 394.65636 419.24201 413.74683 425.11709 498.42660    10   b
 scoped_group_by  69.57573  71.21068  78.26388  76.67216  82.89914  91.45061    10  a

Исходный ответ

Я думаю, что это тот случай, когда вы должны ввести переменную фильтра как простое имя, а затем использовать enquo и !! (эквивалент UQ) для использования переменной фильтра. Например:

library(dplyr)

fnc = function(data, filter_var, filter_value) {
  filter_var=enquo(filter_var)
  data %>% filter(!!filter_var == filter_value)
}

fnc(df, id, "a")
     id   val
1     a     1
2     a     3
3     a     5
fnc(mtcars, carb, 3)
   mpg cyl  disp  hp drat   wt qsec vs am gear carb 
1 16.4   8 275.8 180 3.07 4.07 17.4  0  0    3    3 
2 17.3   8 275.8 180 3.07 3.73 17.6  0  0    3    3 
3 15.2   8 275.8 180 3.07 3.78 18.0  0  0    3    3 
person eipi10    schedule 06.12.2017
comment
спасибо @ eipi10, однако это, похоже, другая проблема. Меня не интересует передача параметров, а скорее краткий синтаксис, как указать фиксированное имя столбца в виде строки или формулы вместо (казалось бы, глобального) символа. - person Thomas Wutzler; 06.12.2017
comment
спасибо @ eipi10. Ваш обновленный ответ аналогичен варианту B, если вы не используете !! вместо UQ. С фильтром мне нужно было использовать UQ из-за недостаточного приоритета !!. В моей настройке фильтра !! вариант не сработал. - person Thomas Wutzler; 06.12.2017
comment
Мне не нравится второе решение, использующее filter_at слишком много, потому что очень трудно понять, что оно на самом деле делает. - person Thomas Wutzler; 06.12.2017
comment
Я добавил некоторые пояснения. Также см. Справку для filter_at, - person eipi10; 06.12.2017

# option D: uses formula instead of string
df %>% filter( UQ(f_rhs(~id)) == "a" )

По-прежнему довольно многословен, но без двойных кавычек.

Микробенчмарк равен (или на небольшую отметку быстрее), чем вариант B, то есть решение as.name.

person Thomas Wutzler    schedule 06.12.2017

# option F: using the dot .
df %>% filter( .$id == "a" )

# slow in progtw's real-world problem:
microbenchmark(
  sym = dsPClosest %>%
    group_by(!!sym(dateVarName), !!sym("depth")) %>%
    summarise(temperature = mean(!!sym("temperature"), na.rm = TRUE)
              , moisture = mean(!!sym("moisture"), na.rm = TRUE)) %>%
    ungroup()
  ,dot = dsPClosest %>%
    group_by(!!sym(dateVarName), .$depth ) %>%
    summarise(temperature = mean(.$temperature , na.rm = TRUE)
              , moisture = mean(.$moisture , na.rm = TRUE)) %>%
    ungroup()  
  ,times=10
)
#Unit: milliseconds
# expr          min           lq         mean       median           uq         max neval
#  sym     75.37921     78.86365     90.72871     81.22674     90.77943    163.2081    10
#  dot 115452.88945 116260.32703 128314.44451 125162.46876 136578.09888 149193.9751    10

Аналогичен варианту c (.data $), но короче. Однако в моем реальном приложении он показал низкую производительность.

Более того, я не нашел документации о том, когда это можно использовать.

person Thomas Wutzler    schedule 08.12.2017