Создать функцию R с помощью задачи dplyr :: filter - PullRequest
0 голосов
/ 28 сентября 2018

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

Проблема в том, что часть функции filter не работает.Однако это работает, если я уберу фильтр с функции и добавлю его как mydiff("a") %>% filter(a.x != a.y)

Любое предложение полезно.

Обратите внимание, что я функция ввода в кавычках

library(dplyr)

# fake data
df1<- tibble(id = seq(4,19,2), 
             a = c("a","b","c","d","e","f","g","h"), 
             b = c(rep("foo",3), rep("bar",5)))
df2<- tibble(id = seq(10, 20, 1), 
             a = c("d","a", "e","f","k","m","g","i","h", "a", "b"),
             b = c(rep("bar", 7), rep("foo",4)))

# What I am trying to do
dplyr::inner_join(df1, df2, by = "id") %>% select(id, b.x, b.y) %>% filter(b.x!=b.y)

#> # A tibble: 1 x 3
#>      id b.x   b.y  
#>   <dbl> <chr> <chr>
#> 1    18 bar   foo

# creating a function so that I can filter by difference in column if I have more columns
mydiff <- function(filteron, df_1 = df1, df_2 = df2){
  require(dplyr, warn.conflicts = F)
  col_1 = paste0(quo_name(filteron), "x")
  col_2 = paste0(quo_name(filteron), "y")
  my_df<- inner_join(df_1, df_2, by = "id", suffix = c("x", "y"))
  my_df %>% select(id, col_1, col_2) %>% filter(col_1 != col_2)
}

# the filter part is not working as expected. 
# There is no difference whether i pipe filter or leave it out
mydiff("a")

#> # A tibble: 5 x 3
#>      id ax    ay   
#>   <dbl> <chr> <chr>
#> 1    10 d     d    
#> 2    12 e     e    
#> 3    14 f     k    
#> 4    16 g     g    
#> 5    18 h     h

Ответы [ 5 ]

0 голосов
/ 29 сентября 2018

Сначала может быть достаточно найти индекс для col_1 != col_2.

mydiff <- function(filteron, df_1 = df1, df_2 = df2){
  require(dplyr, warn.conflicts = F)
  col_1 <- paste0(quo_name(filteron), "x")
  col_2 <- paste0(quo_name(filteron), "y")
  my_df <-
    inner_join(df_1, df_2, by = "id", suffix = c("x", "y")) %>%
    select(id, col_1, col_2)
  # find indices of different columns
  same <- my_df[, col_1] != my_df[, col_2]
  # return for the rows
  my_df[same, ]
}
my_diff("a")
#> # A tibble: 1 x 3
#>      id ax    ay   
#>   <dbl> <chr> <chr>
#> 1    14 f     k
0 голосов
/ 29 сентября 2018

Совет по использованию base R для простых функций хорош, однако он не масштабируется до более сложных функций обратного хода, и вы теряете переносимость на бэкэнды dplyr, такие как базы данных.Если вы хотите создавать функции вокруг конвейеров, вам нужно немного узнать о R-выражениях и операторе отмены кавычек !!.Я рекомендую пролистать первые разделы https://tidyeval.tidyverse.org, чтобы получить общее представление об используемых здесь понятиях.

Поскольку функция, которую вы хотите создать, принимает пустое имя столбца и не содержит сложныхвыражения (как если бы вы передавали mutate() или summarise()), нам не нужны такие причудливые вещи, как фразы.Мы можем работать с символами.Чтобы создать символ, используйте as.name() или rlang::sym().

as.name("mycolumn")
#> mycolumn

rlang::sym("mycolumn")
#> mycolumn

. Преимущество последнего состоит в том, что он является частью большого семейства функций: ensym(), а варианты множественного числа syms() иensyms().Мы собираемся использовать ensym() для захвата имени столбца, т.е. отложить выполнение столбца, чтобы передать его dplyr после нескольких преобразований.Задержка выполнения называется «цитированием».

Я внес несколько изменений в интерфейс вашей функции:

  • Сначала возьмем кадры данных для согласованности с функциями dplyr

  • Не указывайте значения по умолчанию для фреймов данных.Эти значения по умолчанию основаны на слишком многих допущениях.

  • Настройка by и suffix настраивается пользователем, с разумными значениями по умолчанию.

Воткод с пояснениями:

mydiff <- function(df1, df2, var, by = "id", suffix = c(".x", ".y")) {
  stopifnot(is.character(suffix), length(suffix) == 2)

  # Let's start by the easy task, joining the data frames
  df <- dplyr::inner_join(df1, df2, by = by, suffix = suffix)

  # Now onto dealing with the diff variable. `ensym()` takes a column
  # name and delays its execution:
  var <- rlang::ensym(var)

  # A delayed column name is not a string, it's a symbol. So we need
  # to transform it to a string in order to work with paste() etc.
  # `quo_name()` works in this case but is generally only for
  # providing default names.
  #
  # Better use base::as.character() or rlang::as_string() (the latter
  # works a bit better on Windows with foreign UTF-8 characters):
  var_string <- rlang::as_string(var)

  # Now let's add the suffix to the name:
  col1_string <- paste0(var_string, suffix[[1]])
  col2_string <- paste0(var_string, suffix[[2]])

  # dplyr::select() supports column names as strings but it is an
  # exception in the dplyr API. Generally, dplyr functions take bare
  # column names, i.e. symbols. So let's transform the strings back to
  # symbols:
  col1 <- rlang::sym(col1_string)
  col2 <- rlang::sym(col2_string)

  # The delayed column names now need to be inserted back into the
  # dplyr code. This is accomplished by unquoting with the !!
  # operator:
  df %>%
    dplyr::select(id, !!col1, !!col2) %>%
    dplyr::filter(!!col1 != !!col2)
}

mydiff(df1, df2, b)
#> # A tibble: 1 x 3
#>      id b.x   b.y
#>   <dbl> <chr> <chr>
#> 1    18 bar   foo

mydiff(df1, df2, "a")
#> # A tibble: 1 x 3
#>      id a.x   a.y
#>   <dbl> <chr> <chr>
#> 1    14 f     k

Вы также можете упростить функцию, взяв строки вместо имен столбцов.В этой версии я буду использовать syms() для создания списка символов и !!!, чтобы передать все сразу select():

mydiff2 <- function(df1, df2, var, by = "id", suffix = c(".x", ".y")) {
  stopifnot(
    is.character(suffix), length(suffix) == 2,
    is.character(var), length(var) == 1
  )

  # Create a list of symbols from a character vector:
  cols <- rlang::syms(paste0(var, suffix))

  df <- dplyr::inner_join(df1, df2, by = by, suffix = suffix)

  # Unquote the whole list as once with the big bang !!!
  df %>%
    dplyr::select(id, !!!cols) %>%
    dplyr::filter(!!cols[[1]] != !!cols[[2]])
}

mydiff2(df1, df2, "a")
#> # A tibble: 1 x 3
#>      id a.x   a.y
#>   <dbl> <chr> <chr>
#> 1    14 f     k
0 голосов
/ 28 сентября 2018

С https://dplyr.tidyverse.org/articles/programming.html

Большинство функций dplyr используют нестандартную оценку (NSE).Это универсальный термин, означающий, что они не следуют обычным правилам оценки R.

Это может иногда создавать некоторые проблемы при попытке обернуть их в функции.Вот базовая версия созданной вами функции.

mydiff<- function(filteron, df_1=df1, df_2 = df2){

                 col_1 = paste0(filteron,"x")
                 col_2 = paste0(filteron, "y")

                 my_df <- merge(df1, df2, by="id", suffixes = c("x","y"))

                 my_df[my_df[, col_1] != my_df[, col_2], c("id", col_1, col_2)]  
         }

> mydiff("a")
  id ax ay
3 14  f  k
> mydiff("b")
  id  bx  by
5 18 bar foo

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

0 голосов
/ 29 сентября 2018

Причина, по которой это не сработало в вашей исходной функции, заключалась в том, что col_1 было string, но dplyr::filter() ожидало входную переменную без кавычек для LHS.Таким образом, вам нужно сначала преобразовать col_1 в переменную, используя sym(), а затем заключить ее в кавычки внутри filter, используя !! (bang bang).

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

Смотрите также этот похожий вопрос

library(rlang)
library(dplyr)

# creating a function that can take either string or symbol as input
mydiff <- function(filteron, df_1 = df1, df_2 = df2) {

  col_1 <- paste0(quo_name(enquo(filteron)), "x")
  col_2 <- paste0(quo_name(enquo(filteron)), "y")

  my_df <- inner_join(df_1, df_2, by = "id", suffix = c("x", "y"))

  cat('\nwithout sym and unquote\n')
  qq_show(col_1 != col_2)

  cat('\nwith sym and unquote\n')
  qq_show(!!sym(col_1) != !!sym(col_2))
  cat('\n')

  my_df %>% 
    select(id, col_1, col_2) %>% 
    filter(!!sym(col_1) != !!sym(col_2))
}

### testing: filteron as a string
mydiff("a")
#> 
#> without sym and unquote
#> col_1 != col_2
#> 
#> with sym and unquote
#> ax != ay
#> 
#> # A tibble: 1 x 3
#>      id ax    ay   
#>   <dbl> <chr> <chr>
#> 1    14 f     k

### testing: filteron as a symbol
mydiff(a)
#> 
#> without sym and unquote
#> col_1 != col_2
#> 
#> with sym and unquote
#> ax != ay
#>  
#> # A tibble: 1 x 3
#>      id ax    ay   
#>   <dbl> <chr> <chr>
#> 1    14 f     k

Создан в 2018-09-28 пакетом Представить (v0.2.1.9000)

0 голосов
/ 28 сентября 2018

Кажется, вопрос оценки для меня.Попробуйте эту модифицированную функцию mydiff, используя пакет lazyeval:

mydiff <- function(filteron, df_1 = df1, df_2 = df2){
  require(dplyr, warn.conflicts = F)
  col_1 <- paste0(quo_name(filteron), "x")
  col_2 <- paste0(quo_name(filteron), "y")
  criteria <- lazyeval::interp(~ x != y, .values = list(x = as.name(col_1), y = as.name(col_2)))
  my_df <- inner_join(df_1, df_2, by = "id", suffix = c("x", "y"))
  my_df %>% select(id, col_1, col_2) %>% filter_(criteria)
}

Вы можете взглянуть на главу Функции из книги Хэдли Уикхема Advanced R подробнее об этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...