Получение фильтра внутри функции для работы с аккуратной оценкой

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

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

df_ex <- data.frame(a = 1:10, b = 11:20)

param <- quo(a)

# returns df_ex with column a, only, as expected
df_ex %>%
dplyr::select(!!param)

# returns expected df
df_ex %>%
dplyr::filter((!!param)==5)

# Now for the function
testfun <- function(test_df, filt_var){
   filt_var_mod <- quo(filt_var)

   test_df %>%
    dplyr::filter((!!filt_var_mod)==5)
}

# returns empty df, not as expected
testfun(df_ex, "a")

Я хотел бы научиться находить для себя ответы на эти типы вопросов о dplyr, поэтому, пожалуйста, направьте меня к соответствующей части виньетка по программированию


person matsuo_basho    schedule 26.10.2017    source источник


Ответы (4)


Если ваша функция принимает имя столбца как символ, то нет необходимости заключать его в кавычки, с другой стороны, вам нужно преобразовать его в символ и немедленно вычислить их в функции filter с помощью UQ или !! в синтаксисе nse:

testfun <- function(test_df, filt_var){
    test_df %>%
        dplyr::filter((!!rlang::sym(filt_var)) == 5)
}

testfun(df_ex, "a")
#  a  b
#1 5 15

Если вы хотите ввести имена столбцов без кавычек, вам понадобится enquo, который

принимает символ, относящийся к аргументу функции, цитирует код R, который был передан этому аргументу, фиксирует среду, в которой была вызвана функция (и, следовательно, где был набран код R), и объединяет их в запрос. < / em>

testfun <- function(test_df, filt_var){
    filt_var_mod <- enquo(filt_var)
    test_df %>%
        dplyr::filter((!!filt_var_mod) == 5)
}

testfun(df_ex, a)
#  a  b
#1 5 15
person Psidom    schedule 26.10.2017

Технически вам не нужны rlang, tidyeval, tibbles или dplyr для такого рода задач, базовый R практически не оставляет священных коров с тем, что вы можете сделать с помощью quote, eval, parse и других инструментов NSE, которые запекаются снизу вверх.

Изменить: гораздо более элегантное решение, предложенное @thelatemail

df_ex <- data.frame(a = 1:10, b = 11:20)

testfun <- function(test_df, filt_var) {
  test_df[test_df[,filt_var] == 5,]
}    

testfun(df_ex, "a")

Возврат

  a  b
5 5 15

Просто для удовольствия, вариант data.table также может работать:

library(data.table)

df_ex <- data.frame(a = 1:10, b = 11:20)

testfun <- function(test_df, filt_var) {
  setDT(test_df,key = filt_var)[.(5)]
}

testfun(df_ex, "a")

Возврат:

   a  b
1: 5 15
person Matt Summersgill    schedule 26.10.2017
comment
Или просто test_df[test_df[,filt_var] == 5,] и покончим со всеми этими eval, parse и т. Д. И т. Д. Использование $ в интерактивном режиме просто вызывает проблемы - fortunes::fortune(312) - person thelatemail; 27.10.2017
comment
Ха! это гораздо более простое решение, даже не знал, что это возможно! - person Matt Summersgill; 27.10.2017
comment
Для тех, кто придет позже, df_ex[which(eval(parse(text = paste0("test_df$",filt_var))) == 5),] - это ерунда, которую я завернул в исходное тело функции base-R. - person Matt Summersgill; 27.10.2017
comment
Да, $ - это просто интерактивный ярлык для [[ по сути. Также здесь уместен fortune(106) - Если ответ - parse (), вам обычно следует переосмыслить вопрос. :-P - person thelatemail; 27.10.2017
comment
Мне действительно очень нравится предложенное элегантное базовое решение R. Однако я удивлен, что dplyr делает это излишне сложным. - person matsuo_basho; 27.10.2017

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

Например, если вы хотите передать имя в виде строки в своей функции, filter_at - это вариант.

Ниже показано, как выглядит использование filter_at с вашей переменной в виде строки. Вы передаете переменные, которые хотите фильтровать, а затем передаете функцию предиката в пределах all_vars или any_vars. При фильтрации с помощью одной переменной не имеет значения, какую из них вы используете.

filter_at(df_ex, "a", all_vars(. == 5) )

 a  b
1 5 15

filter_at можно легко использовать в функции.

testfun = function(test_df, filt_var){

    test_df %>%
        dplyr::filter_at(filt_var, all_vars(. == 5) )
}

testfun(df_ex, "a")

  a  b
1 5 15
person aosmith    schedule 02.11.2017

Базовый NSE, похоже, тоже работает:

testfun2 <- function(test_df, filt_var){
  filt_var_mod <- substitute(filt_var)
  test_df %>% 
    dplyr::filter(eval(filt_var_mod) == 5)
}

testfun2(df_ex, a)


  a  b
1 5 15
person Sebastian Sauer    schedule 02.11.2017