Как filter () в dplyr оценивает, что находится внутри () в настраиваемой функции? - PullRequest
0 голосов
/ 09 ноября 2019

Я пытаюсь написать функцию, которая принимает два имени столбца и верхнюю и / или нижнюю границу для каждого имени столбца, чтобы таким образом я мог подгруппировать данные с именами столбцов и границей по своему выбору. Используя mtcars в качестве примера, если я хочу подмножество данных, говоря, что я хочу только строки с cyl> 4 и mpg> 15, в этом случае моя функция будет принимать два имени столбца cyl и mpg, а также две нижние границы для каждого столбцаname - это 4 и 15. Конечно, в функции у меня есть возможность назначить ей верхнюю границу, чтобы имена столбцов (переменные) находились в определенном диапазоне. Итак, я придумал что-то вроде ниже, что функция принимает две переменные по вашему выбору и верхние и / или нижние границы для каждой переменной. Если бы я дал только верхнюю или нижнюю границу для этой переменной, то это дало бы мне что-то меньшее или большее этой границы, если бы я дал функции как верхнюю, так и нижнюю границу, это вернуло бы мне строки, попадающие в диапазон.

comb_function<-function(df,var1,var2,var1_lower=NULL,var1_upper=NULL,var2_upper=NULL,var2_lower=NULL){
   var1<-enexpr(var1)
   var2<-enexpr(var2)
 #####for var2,if upper boundary are given by user,do this#####{
    filter1<-expr(`$`(df,!!var2))<=var2_upper
    #for var1, if upper boundary are given by user,do this# {
      filter2<-expr(`$`(df,!!var1))<=var1_upper}
    #for var 1,if lower boundary are given by user, do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower}
    #for var1, if both are given by user, do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper}
  }
  #####for var2,if lower boundary are given by user,do this#####{
    filter1<-expr(`$`(df,!!var2))>=var2_lower 
    #for var1,if upper boundary are given by user,do this#{
      filter2<-expr(`$`(df,!!var1))<=var1_upper}
    #for var1,if lower boundary are given by user,do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower}
    #if both are given by the user,do this{
      filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper}
  }
  #####for var2,if both are given by user,do this#####{
    filter1<-expr(`$`(df,!!var2))<=var2_upper&expr(`$`(df,!!var2))>=var2_lower
    #for var1,if upper boundary are given by user,do this#{
      filter2<-expr(`$`(df,!!var1))<=var1_upper}
    #for var1,if lower boundary are given by user,do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower}
    #if both are given by user, do this#{
      filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper}
  }
   output<-df%>%filter(filter1,filter2)%>%summarise(count=n(),avgcyl=mean(cyl,na.rm=TRUE))
    return(output)
}

Когда я вызываю эту функцию на примере mtcars

final1<-comb_function(df=mtcars,var1=mpg,var2=cyl,var1_lower =15,var2_lower=4,var2_upper=6)

Я получаю 0 счетчиков и NaN для avgcrl в final1. Поэтому, когда filter() оценивает, что внутри (), он получает только ЛОЖЬ, не ИСТИНА, я думаю, поэтому строки не возвращаются.

У меня есть теория, почему это происходит. Если я сделаю это:

x<-expr(cyl);eval(expr(expr(`$`(mtcars,!!x))<=6))

Возвращает:

[1]FALSE

, что явно не то, что я ожидал получить. Если я сделаю это:

eval(expr(`$`(mtcars,!!x)))<=6

Возвращает

[1]  TRUE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE FALSE
[23] FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE

, что я и хочу для функции filter() внутри моей функции. Поэтому я предполагаю, что когда filter() оценивает то, что находится внутри (), оно автоматически ставит скобку вокруг всего выражения, как это делал

eval(expr(expr(`$`(mtcars,!!x))<=6))

, что возвращает только один FALSE. Поэтому, если это действительно та причина, на которую я рассчитывал, как я могу сообщить filter(), что я действительно хочу, чтобы он оценил это так:

eval(filter1<-expr(`$`(df,!!var2)))<=var2_upper

не так:

eval(filter1<-expr(`$`(df,!!var2))<=var2_upper)

Если я догадался не о том, что происходит, пожалуйста, помогите мне.

Ответы [ 3 ]

1 голос
/ 11 ноября 2019

В https://stackoverflow.com/a/58793418/1725177 xiahfyj спросил, как вычислить фильтры на отдельном шаге, чем filter(). Как правило, отдельные вычисления могут быть выполнены с transmute(). Эта функция принимает входные данные и возвращает фрейм данных, содержащий один столбец на каждый вход. Входные данные вычисляются в рамках фреймов данных и в группах, если они есть.

filter3 <- function(data, var1, var2, lower1, lower2) {
  filters <- data %>% transmute(
    filter_a = {{ var1 }} > .env$lower1,
    filter_b = {{ var2 }} > .env$lower2
  )

  data %>%
    filter(!!!unname(filters))
}

Фреймы данных оцененных столбцов фильтра могут быть затем объединены в filter(). Оператор принудительного сращивания !!! преобразует свой аргумент в несколько входов в окружающем вызове (здесь вызов filter()).

В случае filter() кадр данных входов должен бытьбезымянный, потому что в filter() есть специальная проверка, чтобы выдать ошибку для именованных входов, чтобы поймать типичную опечатку при записи a = foo вместо a == foo.

Мы планируем поддерживать фрейм данныхвходные данные в следующей основной версии dplyr и их автоматическое соединение. В этом случае последний шаг станет таким простым:

  data %>%
    filter(filters)
1 голос
/ 09 ноября 2019

В целом, я очень рекомендую держаться подальше от всех этих цитирований и оценок. Среда Tidy eval предоставляет альтернативные инструменты, с которыми намного проще работать.

Используя mtcars в качестве примера, если я хочу поместить данные в подгруппу, говоря, что я хочу только строки, которые имеют cyl > 4 и mpg > 15

Типичная функция-обертка будет выглядеть следующим образом:

filter2 <- function(data, var1, var2, lower1, lower2) {
  data %>%
    filter(
      {{ var1 }} > .env$lower1,
      {{ var2 }} > .env$lower2
    )
}
  • С помощью оператора {{ мы интерполируем входные выражения внутриконтекст данных. Это означает, что вы можете предоставить код R, который напрямую ссылается на имена столбцов.

  • С .env$ мы запрашиваем переменные lower внутри функциональной среды. Это означает, что если фрейм данных содержит столбцы lower1 и lower2, они не будут мешать. Другой способ заставить оценку в среде - использовать !!.

mtcars %>% filter2(cyl, mpg, 4, 15) %>% head()
#>   mpg cyl disp  hp drat  wt qsec vs am gear carb
#> 1  21   6  160 110  3.9 2.6   16  0  1    4    4
#> 2  21   6  160 110  3.9 2.9   17  0  1    4    4
#> 3  21   6  258 110  3.1 3.2   19  1  0    3    1
#> 4  19   8  360 175  3.1 3.4   17  0  0    3    2
#> 5  18   6  225 105  2.8 3.5   20  1  0    3    1
#> 6  19   6  168 123  3.9 3.4   18  1  0    4    4

===============================

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

Давайте возьмем:

x<-expr(cyl);eval(expr(expr(`$`(mtcars,!!x))<=6))
#> [1] FALSE

Переформатированиенемного:

x <- expr(cyl)
eval(expr(expr(`$`(mtcars,!!x)) <= 6))

Удаление ненужных сложностей:

eval(expr(expr(mtcars$cyl) <= 6))

Давайте посмотрим на промежуточный результат:

expr(expr(mtcars$cyl) <= 6)
#> expr(mtcars$cyl) <= 6

Внешний expr() возвращает выражениепоручив R:

  1. Создать новое выражение (с внутренним expr())
  2. Сравните это выражение с 6

К сожалению, Rвыражения сопоставимы, хотя это не имеет никакого смысла. В идеальном мире это было бы ошибкой:

quote(foo) < 10
#> [1] FALSE

Вероятно, вам нужно сначала вычислить подмножество столбцов, описанное в выражении, а затем сравнить с <=:

eval(expr(mtcars$cyl)) <= 6
#>  [1]  TRUE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE  TRUE
#> [11]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE
#> [21]  TRUE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE  TRUE
#> [31] FALSE  TRUE

Еще одна заметка. Вы пишете:

eval(filter1<-expr(`$`(df,!!var2)))

Переформатирование и упрощение:

eval(filter1 <- expr(mtcars$cyl)))

Вот что происходит, когда вы оцениваете это:

  1. eval() спрашивает Rвернуть свой первый аргумент, чтобы он мог его оценить.

  2. R видит, что аргумент eval() является вызовом <-. Затем он начинает его оценивать.

  3. RHS - это выражение с выражением "defused", описывающее, как поднабор mtcars. Эта RHS присваивается LHS filter1.

  4. <- возвращает RHS незаметно. Это то, что eval() получает в качестве аргумента.

  5. eval() продолжает вычислять подмножество mtcars.

0 голосов
/ 11 ноября 2019

@ Лайонел Генри Спасибо! У меня есть дополнительный вопрос по вашему примеру.

filter2 <- function(data, var1, var2, lower1, lower2) {
  data %>%
    filter(
      {{ var1 }} > .env$lower1,
      {{ var2 }} > .env$lower2
    )
}

Что если я захочу что-то вроде блока ниже? По сути, я хочу извлечь из него то, что у вас есть внутри filter (), и заранее сохранить их в некоторых переменных. Я знаю, что ниже функция не работает. Интересно, как мне отредактировать его, чтобы он работал.

filter2 <- function(data, var1, var2, lower1, lower2) {
filter_a<-{{ var1 }} > .env$lower1
filter_b<-{{ var2 }} > .env$lower2
  data %>%
    filter(filter_a,filter_b)
}

Причина, по которой я этого хочу, заключается в том, что для целей моей функции то, что находится внутри filter (), будет динамическим. Например, мне нужно что-то вроде этого:

###if both lower and upper boundary for var1 are given by the user,do below:
   filter_a<-{{ var1 }} > .env$lower1&{{ var1 }} < .env$upper1
###if only upper are given.do below:
   filter_a<-{{ var1 }} < .env$upper1
###if only lower are given, do below:
   filter_a<-{{ var1 }} > .env$lower1

Именно поэтому у меня было так много утверждений if в моем оригинальном длинном и «трудно читаемом» вопросе.

...