Использование dplyr-глаголов в функции с метками столбцов в качестве векторов символов

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

Я считаю, что у меня есть рабочий пример того, чем я хочу заниматься. Хотелось бы узнать, есть ли более элегантное решение, или я неправильно думаю об этой проблеме (может, мне не стоит этого делать?). Насколько я могу судить, во избежание проблем с областью видимости переменных мне нужно обернуть имена столбцов в .data [[]] и сделать их выражением, прежде чем удалять кавычки для аккуратных стихотворных глаголов NSE.

Что касается предыдущих вопросов, этот ответ правильный, но я хотите абстрагировать код в функцию. проблема на github спрашивает об этом, но использование rlang :: syms пока не сработает. как я могу судить, потому что комбинация меток столбцов с .data делает его выражением, а не символом. Здесь и здесь решите проблему, но, насколько я могу судить, не учитывать тонкую ошибку, при которой переменные могут просачиваться из среды, если они не существуют в виде меток столбцов в фрейме данных или решения не работают для входных данных, являющихся вектором меток.

# Setup
suppressWarnings(suppressMessages(library("dplyr")))
suppressWarnings(suppressMessages(library("rlang")))

# define iris with and without Sepal.Width column
iris <- tibble::as_tibble(iris)
df_with_missing <- iris %>% select(-Sepal.Width)
# This should not be findable by my function
Sepal.Width <- iris$Sepal.Width * -1

################
# Now lets try a function for which we programmatically define the column labels
programmatic_mutate_y <- function(df, col_names, safe = FALSE) {
  # Add .data[[]] to the col_names to make evalutation safer
  col_exprs <- rlang::parse_exprs(
    purrr::map_chr(
      col_names,
      ~ glue::glue(stringr::str_c('.data[["{.x}"]]'))
    )
  )

  output <- dplyr::mutate(df, product = purrr::pmap_dbl(list(!!!col_exprs), ~ prod(...)))
  output
}
################
# The desired output
testthat::expect_error(programmatic_mutate_y(df_with_missing, c("Sepal.Width", "Sepal.Length")))
programmatic_mutate_y(iris, c("Sepal.Width", "Sepal.Length"))
#> # A tibble: 150 x 6
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species product
#>           <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>
#>  1          5.1         3.5          1.4         0.2 setosa     17.8
#>  2          4.9         3            1.4         0.2 setosa     14.7
#>  3          4.7         3.2          1.3         0.2 setosa     15.0
#>  4          4.6         3.1          1.5         0.2 setosa     14.3
#>  5          5           3.6          1.4         0.2 setosa     18  
#>  6          5.4         3.9          1.7         0.4 setosa     21.1
#>  7          4.6         3.4          1.4         0.3 setosa     15.6
#>  8          5           3.4          1.5         0.2 setosa     17  
#>  9          4.4         2.9          1.4         0.2 setosa     12.8
#> 10          4.9         3.1          1.5         0.1 setosa     15.2
#> # … with 140 more rows

Создано 9 августа 2019 г. пакетом REPEX (v0.3.0)


person leej3    schedule 09.08.2019    source источник


Ответы (2)


Я думаю, вы все усложняете. В варианте _at вы можете использовать строки в качестве аргументов почти во всех dplyr функциях. purrr::pmap_dbl() используется для сопоставления расчета по строкам.

programmatic_mutate_y_v1 <- function(df, col_names, safe = FALSE) {
    df["product"] <- purrr::pmap_dbl(dplyr::select_at(df,col_names),prod)
    return(df)
}

programmatic_mutate_y_v1(iris, c("Sepal.Width", "Sepal.Length"))
# A tibble: 150 x 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species product
          <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>
 1          5.1         3.5          1.4         0.2 setosa     17.8
 2          4.9         3            1.4         0.2 setosa     14.7
 3          4.7         3.2          1.3         0.2 setosa     15.0
 4          4.6         3.1          1.5         0.2 setosa     14.3
 5          5           3.6          1.4         0.2 setosa     18  
 6          5.4         3.9          1.7         0.4 setosa     21.1
 7          4.6         3.4          1.4         0.3 setosa     15.6
 8          5           3.4          1.5         0.2 setosa     17  
 9          4.4         2.9          1.4         0.2 setosa     12.8
10          4.9         3.1          1.5         0.1 setosa     15.2
# ... with 140 more rows
person yusuzech    schedule 09.08.2019
comment
Это, безусловно, более элегантно. Спасибо. Использование варианта '_at' - это хорошо, и я думаю, если кто-то хочет, чтобы он был частью ряда конвейерных выражений, вы могли бы использовать что-то вроде df %>% mutate(product = purrr::pmap_dbl(dplyr::select_at(.,col_names),prod)). Мне также нравится неявная передача «...». Намного аккуратнее. - person leej3; 12.08.2019

Мы можем превратить col_names в одно выражение с parse_expr и paste, а затем убрать кавычки при вычислении в mutate:

library(dplyr)
library(rlang)

programmatic_mutate_y <- function(df, col_names){
  mutate(df, product = !!parse_expr(paste(col_names, collapse = "*")))
}

Вывод:

> parse_expr(paste(c("Sepal.Width", "Sepal.Length"), collapse = "*"))
Sepal.Width * Sepal.Length

> programmatic_mutate_y(df_with_missing, c("Sepal.Width", "Sepal.Length"))
> Error: object 'Sepal.Width' not found 

> programmatic_mutate_y(iris, c("Sepal.Width", "Sepal.Length"))
# A tibble: 150 x 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species product
          <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>
 1          5.1         3.5          1.4         0.2 setosa     17.8
 2          4.9         3            1.4         0.2 setosa     14.7
 3          4.7         3.2          1.3         0.2 setosa     15.0
 4          4.6         3.1          1.5         0.2 setosa     14.3
 5          5           3.6          1.4         0.2 setosa     18  
 6          5.4         3.9          1.7         0.4 setosa     21.1
 7          4.6         3.4          1.4         0.3 setosa     15.6
 8          5           3.4          1.5         0.2 setosa     17  
 9          4.4         2.9          1.4         0.2 setosa     12.8
10          4.9         3.1          1.5         0.1 setosa     15.2
# ... with 140 more rows
person acylam    schedule 09.08.2019
comment
Спасибо за ответ. Хорошее использование parse_expr. Однако в этом ответе есть ошибка, о которой я упоминал. Если вы определяете Sepal. Width как переменную среды (с длиной счетчика строк кадра данных или 1), она используется в вычислениях, и никаких ошибок не возникает. - person leej3; 12.08.2019
comment
Чтобы уточнить, под переменной среды я подразумеваю родительскую среду в стеке вызовов, которую можно найти во время вычисления выражения. - person leej3; 12.08.2019